Chromium Code Reviews| OLD | NEW |
|---|---|
| 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 Loading... | |
| 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 Loading... | |
| 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()) |
| OLD | NEW |