Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(528)

Side by Side Diff: tools/checkperms/checkperms.py

Issue 318863006: Add support for JSON output to checkperms.py (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 6 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 5
6 """Makes sure files have the right permissions. 6 """Makes sure files have the right permissions.
7 7
8 Some developers have broken SCM configurations that flip the svn:executable 8 Some developers have broken SCM configurations that flip the svn:executable
9 permission on for no good reason. Unix developers who run ls --color will then 9 permission on for no good reason. Unix developers who run ls --color will then
10 see .cc files in green and get confused. 10 see .cc files in green and get confused.
11 11
12 - For file extensions that must be executable, add it to EXECUTABLE_EXTENSIONS. 12 - For file extensions that must be executable, add it to EXECUTABLE_EXTENSIONS.
13 - For file extensions that must not be executable, add it to 13 - For file extensions that must not be executable, add it to
14 NOT_EXECUTABLE_EXTENSIONS. 14 NOT_EXECUTABLE_EXTENSIONS.
15 - To ignore all the files inside a directory, add it to IGNORED_PATHS. 15 - To ignore all the files inside a directory, add it to IGNORED_PATHS.
16 - For file base name with ambiguous state and that should not be checked for 16 - For file base name with ambiguous state and that should not be checked for
17 shebang, add it to IGNORED_FILENAMES. 17 shebang, add it to IGNORED_FILENAMES.
18 18
19 Any file not matching the above will be opened and looked if it has a shebang 19 Any file not matching the above will be opened and looked if it has a shebang
20 or an ELF header. If this does not match the executable bit on the file, the 20 or an ELF header. If this does not match the executable bit on the file, the
21 file will be flagged. 21 file will be flagged.
22 22
23 Note that all directory separators must be slashes (Unix-style) and not 23 Note that all directory separators must be slashes (Unix-style) and not
24 backslashes. All directories should be relative to the source root and all 24 backslashes. All directories should be relative to the source root and all
25 file paths should be only lowercase. 25 file paths should be only lowercase.
26 """ 26 """
27 27
28 import json
28 import logging 29 import logging
29 import optparse 30 import optparse
30 import os 31 import os
31 import stat 32 import stat
32 import string 33 import string
33 import subprocess 34 import subprocess
34 import sys 35 import sys
35 36
36 #### USER EDITABLE SECTION STARTS HERE #### 37 #### USER EDITABLE SECTION STARTS HERE ####
37 38
(...skipping 253 matching lines...) Expand 10 before | Expand all | Expand 10 after
291 def has_shebang_or_is_elf(full_path): 292 def has_shebang_or_is_elf(full_path):
292 """Returns if the file starts with #!/ or is an ELF binary. 293 """Returns if the file starts with #!/ or is an ELF binary.
293 294
294 full_path is the absolute path to the file. 295 full_path is the absolute path to the file.
295 """ 296 """
296 with open(full_path, 'rb') as f: 297 with open(full_path, 'rb') as f:
297 data = f.read(4) 298 data = f.read(4)
298 return (data[:3] == '#!/', data == '\x7fELF') 299 return (data[:3] == '#!/', data == '\x7fELF')
299 300
300 301
301 def check_file(root_path, rel_path, bare_output): 302 def check_file(root_path, rel_path):
302 """Checks the permissions of the file whose path is root_path + rel_path and 303 """Checks the permissions of the file whose path is root_path + rel_path and
303 returns an error if it is inconsistent. 304 returns an error if it is inconsistent.
M-A Ruel 2014/06/05 15:00:47 It's not clear that it returns None when there's n
Paweł Hajdan Jr. 2014/06/05 15:43:54 Done.
304 305
305 It is assumed that the file is not ignored by is_ignored(). 306 It is assumed that the file is not ignored by is_ignored().
306 307
307 If the file name is matched with must_be_executable() or 308 If the file name is matched with must_be_executable() or
308 must_not_be_executable(), only its executable bit is checked. 309 must_not_be_executable(), only its executable bit is checked.
309 Otherwise, the first few bytes of the file are read to verify if it has a 310 Otherwise, the first few bytes of the file are read to verify if it has a
310 shebang or ELF header and compares this with the executable bit on the file. 311 shebang or ELF header and compares this with the executable bit on the file.
311 """ 312 """
312 full_path = os.path.join(root_path, rel_path) 313 full_path = os.path.join(root_path, rel_path)
314 def result_dict(error):
315 return {
316 'full_path': full_path,
317 'rel_path': rel_path,
M-A Ruel 2014/06/05 15:00:47 sort keys
Paweł Hajdan Jr. 2014/06/05 15:43:54 Done.
318 'error': error,
319 }
313 try: 320 try:
314 bit = has_executable_bit(full_path) 321 bit = has_executable_bit(full_path)
315 except OSError: 322 except OSError:
316 # It's faster to catch exception than call os.path.islink(). Chromium 323 # It's faster to catch exception than call os.path.islink(). Chromium
317 # tree happens to have invalid symlinks under 324 # tree happens to have invalid symlinks under
318 # third_party/openssl/openssl/test/. 325 # third_party/openssl/openssl/test/.
319 return None 326 return None
320 327
321 if must_be_executable(rel_path): 328 if must_be_executable(rel_path):
322 if not bit: 329 if not bit:
323 if bare_output: 330 return result_dict('Must have executable bit set')
324 return full_path
325 return '%s: Must have executable bit set' % full_path
326 return 331 return
327 if must_not_be_executable(rel_path): 332 if must_not_be_executable(rel_path):
328 if bit: 333 if bit:
329 if bare_output: 334 return result_dict('Must not have executable bit set')
330 return full_path
331 return '%s: Must not have executable bit set' % full_path
332 return 335 return
333 336
334 # For the others, it depends on the file header. 337 # For the others, it depends on the file header.
335 (shebang, elf) = has_shebang_or_is_elf(full_path) 338 (shebang, elf) = has_shebang_or_is_elf(full_path)
336 if bit != (shebang or elf): 339 if bit != (shebang or elf):
337 if bare_output:
338 return full_path
339 if bit: 340 if bit:
340 return '%s: Has executable bit but not shebang or ELF header' % full_path 341 return result_dict('Has executable bit but not shebang or ELF header')
341 if shebang: 342 if shebang:
342 return '%s: Has shebang but not executable bit' % full_path 343 return result_dict('Has shebang but not executable bit')
343 return '%s: Has ELF header but not executable bit' % full_path 344 return result_dict('Has ELF header but not executable bit')
344 345
345 346
346 def check_files(root, files, bare_output): 347 def check_files(root, files):
347 errors = [] 348 errors = []
M-A Ruel 2014/06/05 15:00:47 This function should be two lines instead of 10: g
Paweł Hajdan Jr. 2014/06/05 15:43:54 Done.
348 for rel_path in files: 349 for rel_path in files:
349 if is_ignored(rel_path): 350 if is_ignored(rel_path):
350 continue 351 continue
351 352
352 error = check_file(root, rel_path, bare_output) 353 error = check_file(root, rel_path)
353 if error: 354 if error:
354 errors.append(error) 355 errors.append(error)
355 356
356 return errors 357 return errors
357 358
358 359
359 class ApiBase(object): 360 class ApiBase(object):
360 def __init__(self, root_dir, bare_output): 361 def __init__(self, root_dir, bare_output):
361 self.root_dir = root_dir 362 self.root_dir = root_dir
362 self.bare_output = bare_output 363 self.bare_output = bare_output
363 self.count = 0 364 self.count = 0
364 self.count_read_header = 0 365 self.count_read_header = 0
365 366
366 def check_file(self, rel_path): 367 def check_file(self, rel_path):
367 logging.debug('check_file(%s)' % rel_path) 368 logging.debug('check_file(%s)' % rel_path)
368 self.count += 1 369 self.count += 1
369 370
370 if (not must_be_executable(rel_path) and 371 if (not must_be_executable(rel_path) and
371 not must_not_be_executable(rel_path)): 372 not must_not_be_executable(rel_path)):
372 self.count_read_header += 1 373 self.count_read_header += 1
373 374
374 return check_file(self.root_dir, rel_path, self.bare_output) 375 return check_file(self.root_dir, rel_path)
375 376
376 def check_dir(self, rel_path): 377 def check_dir(self, rel_path):
377 return self.check(rel_path) 378 return self.check(rel_path)
378 379
379 def check(self, start_dir): 380 def check(self, start_dir):
380 """Check the files in start_dir, recursively check its subdirectories.""" 381 """Check the files in start_dir, recursively check its subdirectories."""
381 errors = [] 382 errors = []
382 items = self.list_dir(start_dir) 383 items = self.list_dir(start_dir)
383 logging.info('check(%s) -> %d' % (start_dir, len(items))) 384 logging.info('check(%s) -> %d' % (start_dir, len(items)))
384 for item in items: 385 for item in items:
(...skipping 110 matching lines...) Expand 10 before | Expand all | Expand 10 after
495 '-v', '--verbose', action='count', default=0, help='Print debug logging') 496 '-v', '--verbose', action='count', default=0, help='Print debug logging')
496 parser.add_option( 497 parser.add_option(
497 '--bare', 498 '--bare',
498 action='store_true', 499 action='store_true',
499 default=False, 500 default=False,
500 help='Prints the bare filename triggering the checks') 501 help='Prints the bare filename triggering the checks')
501 parser.add_option( 502 parser.add_option(
502 '--file', action='append', dest='files', 503 '--file', action='append', dest='files',
503 help='Specifics a list of files to check the permissions of. Only these ' 504 help='Specifics a list of files to check the permissions of. Only these '
504 'files will be checked') 505 'files will be checked')
506 parser.add_option('--json', help='Path to JSON output file')
505 options, args = parser.parse_args() 507 options, args = parser.parse_args()
506 508
507 levels = [logging.ERROR, logging.INFO, logging.DEBUG] 509 levels = [logging.ERROR, logging.INFO, logging.DEBUG]
508 logging.basicConfig(level=levels[min(len(levels) - 1, options.verbose)]) 510 logging.basicConfig(level=levels[min(len(levels) - 1, options.verbose)])
509 511
510 if len(args) > 1: 512 if len(args) > 1:
511 parser.error('Too many arguments used') 513 parser.error('Too many arguments used')
512 514
513 if options.root: 515 if options.root:
514 options.root = os.path.abspath(options.root) 516 options.root = os.path.abspath(options.root)
515 517
516 if options.files: 518 if options.files:
517 errors = check_files(options.root, options.files, options.bare) 519 # --file implies --bare (for PRESUBMIT.py).
518 print '\n'.join(errors) 520 options.bare = True
519 return bool(errors)
520 521
521 api = get_scm(options.root, options.bare) 522 errors = check_files(options.root, options.files)
522 if args:
523 start_dir = args[0]
524 else: 523 else:
525 start_dir = api.root_dir 524 api = get_scm(options.root, options.bare)
525 if args:
M-A Ruel 2014/06/05 15:00:47 start_dir = args[0] if args else api.root_dir
Paweł Hajdan Jr. 2014/06/05 15:43:54 Done.
526 start_dir = args[0]
527 else:
528 start_dir = api.root_dir
526 529
527 errors = api.check(start_dir) 530 errors = api.check(start_dir)
528 531
529 if not options.bare: 532 if not options.bare:
530 print 'Processed %s files, %d files where tested for shebang/ELF header' % ( 533 print ('Processed %s files, %d files where tested for shebang/ELF '
M-A Ruel 2014/06/05 15:00:47 print(' ... it's a function call now.
Paweł Hajdan Jr. 2014/06/05 15:43:54 Done.
531 api.count, api.count_read_header) 534 'header' % (api.count, api.count_read_header))
535
536 if options.json:
537 with open(options.json, 'w') as f:
M-A Ruel 2014/06/05 15:00:47 'wb' CR have no place in this world.
Paweł Hajdan Jr. 2014/06/05 15:43:54 Keeping 'w' per offline discussion and consistency
538 json.dump(errors, f)
532 539
533 if errors: 540 if errors:
534 if not options.bare: 541 if options.bare:
542 print '\n'.join([e['full_path'] for e in errors])
M-A Ruel 2014/06/05 15:00:47 Remove [], serialization of a generator into a lis
Paweł Hajdan Jr. 2014/06/05 15:43:54 Done.
543 else:
535 print '\nFAILED\n' 544 print '\nFAILED\n'
536 print '\n'.join(errors) 545 print '\n'.join(['%s: %s' % (e['full_path'], e['error']) for e in errors])
M-A Ruel 2014/06/05 15:00:47 Same.
Paweł Hajdan Jr. 2014/06/05 15:43:54 Done.
537 return 1 546 return 1
538 if not options.bare: 547 if not options.bare:
539 print '\nSUCCESS\n' 548 print '\nSUCCESS\n'
540 return 0 549 return 0
541 550
542 551
543 if '__main__' == __name__: 552 if '__main__' == __name__:
544 sys.exit(main()) 553 sys.exit(main())
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698