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. |
(...skipping 242 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
253 | 253 |
254 | 254 |
255 def has_shebang(full_path): | 255 def has_shebang(full_path): |
256 """Returns if the file starts with #!/. | 256 """Returns if the file starts with #!/. |
257 | 257 |
258 file_path is the absolute path to the file. | 258 file_path is the absolute path to the file. |
259 """ | 259 """ |
260 with open(full_path, 'rb') as f: | 260 with open(full_path, 'rb') as f: |
261 return f.read(3) == '#!/' | 261 return f.read(3) == '#!/' |
262 | 262 |
263 def check_file(full_path, bare_output): | |
264 """Checks file_path's permissions and returns an error if it is | |
265 inconsistent. | |
266 | |
267 It is assumed that the file is not ignored by is_ignored(). | |
268 | |
269 If the file name is matched with must_be_executable() or | |
270 must_not_be_executable(), only its executable bit is checked. | |
271 Otherwise, the 3 first bytes of the file are read to verify if it has a | |
272 shebang and compares this with the executable bit on the file. | |
273 """ | |
274 try: | |
275 bit = has_executable_bit(full_path) | |
276 except OSError: | |
277 # It's faster to catch exception than call os.path.islink(). Chromium | |
278 # tree happens to have invalid symlinks under | |
279 # third_party/openssl/openssl/test/. | |
280 return None | |
281 | |
282 if must_be_executable(full_path): | |
Lei Zhang
2013/11/08 00:15:11
And this is wrong because must_be_executable() tak
| |
283 if not bit: | |
284 if bare_output: | |
285 return full_path | |
286 return '%s: Must have executable bit set' % full_path | |
287 return | |
288 if must_not_be_executable(full_path): | |
289 if bit: | |
290 if bare_output: | |
291 return full_path | |
292 return '%s: Must not have executable bit set' % full_path | |
293 return | |
294 | |
295 # For the others, it depends on the shebang. | |
296 shebang = has_shebang(full_path) | |
297 if bit != shebang: | |
298 if bare_output: | |
299 return full_path | |
300 if bit: | |
301 return '%s: Has executable bit but not shebang' % full_path | |
302 else: | |
303 return '%s: Has shebang but not executable bit' % full_path | |
304 | |
305 | |
306 def check_files(root, files, bare_output): | |
307 errors = [] | |
308 for file_path in files: | |
309 if is_ignored(file_path): | |
310 continue | |
311 | |
312 full_file_path = os.path.join(root, file_path) | |
313 | |
314 error = check_file(full_file_path, bare_output) | |
315 if error: | |
316 errors.append(error) | |
317 | |
318 return errors | |
263 | 319 |
264 class ApiBase(object): | 320 class ApiBase(object): |
265 def __init__(self, root_dir, bare_output): | 321 def __init__(self, root_dir, bare_output): |
266 self.root_dir = root_dir | 322 self.root_dir = root_dir |
267 self.bare_output = bare_output | 323 self.bare_output = bare_output |
268 self.count = 0 | 324 self.count = 0 |
269 self.count_shebang = 0 | 325 self.count_shebang = 0 |
270 | 326 |
271 def check_file(self, rel_path): | 327 def check_file(self, rel_path): |
272 """Checks file_path's permissions and returns an error if it is | |
273 inconsistent. | |
274 | |
275 It is assumed that the file is not ignored by is_ignored(). | |
276 | |
277 If the file name is matched with must_be_executable() or | |
278 must_not_be_executable(), only its executable bit is checked. | |
279 Otherwise, the 3 first bytes of the file are read to verify if it has a | |
280 shebang and compares this with the executable bit on the file. | |
281 """ | |
282 logging.debug('check_file(%s)' % rel_path) | 328 logging.debug('check_file(%s)' % rel_path) |
283 self.count += 1 | 329 self.count += 1 |
284 | 330 |
331 if (not must_be_executable(rel_path) and | |
332 not must_not_be_executable(rel_path)): | |
333 self.count_shebang += 1 | |
334 | |
285 full_path = os.path.join(self.root_dir, rel_path) | 335 full_path = os.path.join(self.root_dir, rel_path) |
286 try: | 336 return check_file(full_path, self.bare_output) |
287 bit = has_executable_bit(full_path) | |
288 except OSError: | |
289 # It's faster to catch exception than call os.path.islink(). Chromium | |
290 # tree happens to have invalid symlinks under | |
291 # third_party/openssl/openssl/test/. | |
292 return None | |
293 | |
294 if must_be_executable(rel_path): | |
295 if not bit: | |
296 if self.bare_output: | |
297 return rel_path | |
298 return '%s: Must have executable bit set' % rel_path | |
299 return | |
300 if must_not_be_executable(rel_path): | |
301 if bit: | |
302 if self.bare_output: | |
303 return rel_path | |
304 return '%s: Must not have executable bit set' % rel_path | |
305 return | |
306 | |
307 # For the others, it depends on the shebang. | |
308 shebang = has_shebang(full_path) | |
309 self.count_shebang += 1 | |
310 if bit != shebang: | |
311 if self.bare_output: | |
312 return rel_path | |
313 if bit: | |
314 return '%s: Has executable bit but not shebang' % rel_path | |
315 else: | |
316 return '%s: Has shebang but not executable bit' % rel_path | |
317 | 337 |
318 def check_dir(self, rel_path): | 338 def check_dir(self, rel_path): |
319 return self.check(rel_path) | 339 return self.check(rel_path) |
320 | 340 |
321 def check(self, start_dir): | 341 def check(self, start_dir): |
322 """Check the files in start_dir, recursively check its subdirectories.""" | 342 """Check the files in start_dir, recursively check its subdirectories.""" |
323 errors = [] | 343 errors = [] |
324 items = self.list_dir(start_dir) | 344 items = self.list_dir(start_dir) |
325 logging.info('check(%s) -> %d' % (start_dir, len(items))) | 345 logging.info('check(%s) -> %d' % (start_dir, len(items))) |
326 for item in items: | 346 for item in items: |
(...skipping 106 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
433 '--root', | 453 '--root', |
434 help='Specifies the repository root. This defaults ' | 454 help='Specifies the repository root. This defaults ' |
435 'to the checkout repository root') | 455 'to the checkout repository root') |
436 parser.add_option( | 456 parser.add_option( |
437 '-v', '--verbose', action='count', default=0, help='Print debug logging') | 457 '-v', '--verbose', action='count', default=0, help='Print debug logging') |
438 parser.add_option( | 458 parser.add_option( |
439 '--bare', | 459 '--bare', |
440 action='store_true', | 460 action='store_true', |
441 default=False, | 461 default=False, |
442 help='Prints the bare filename triggering the checks') | 462 help='Prints the bare filename triggering the checks') |
463 parser.add_option( | |
464 '--file', action='append', dest='files', | |
465 help='Specifics a list of files to check the permissions of. Only these ' | |
466 'files will be checked') | |
443 options, args = parser.parse_args() | 467 options, args = parser.parse_args() |
444 | 468 |
445 levels = [logging.ERROR, logging.INFO, logging.DEBUG] | 469 levels = [logging.ERROR, logging.INFO, logging.DEBUG] |
446 logging.basicConfig(level=levels[min(len(levels) - 1, options.verbose)]) | 470 logging.basicConfig(level=levels[min(len(levels) - 1, options.verbose)]) |
447 | 471 |
448 if len(args) > 1: | 472 if len(args) > 1: |
449 parser.error('Too many arguments used') | 473 parser.error('Too many arguments used') |
450 | 474 |
451 if options.root: | 475 if options.root: |
452 options.root = os.path.abspath(options.root) | 476 options.root = os.path.abspath(options.root) |
453 | 477 |
478 if options.files: | |
479 errors = check_files(options.root, options.files, options.bare) | |
480 print '\n'.join(errors) | |
481 return bool(errors) | |
482 | |
454 api = get_scm(options.root, options.bare) | 483 api = get_scm(options.root, options.bare) |
455 if args: | 484 if args: |
456 start_dir = args[0] | 485 start_dir = args[0] |
457 else: | 486 else: |
458 start_dir = api.root_dir | 487 start_dir = api.root_dir |
459 | 488 |
460 errors = api.check(start_dir) | 489 errors = api.check(start_dir) |
461 | 490 |
462 if not options.bare: | 491 if not options.bare: |
463 print 'Processed %s files, %d files where tested for shebang' % ( | 492 print 'Processed %s files, %d files where tested for shebang' % ( |
464 api.count, api.count_shebang) | 493 api.count, api.count_shebang) |
465 | 494 |
466 if errors: | 495 if errors: |
467 if not options.bare: | 496 if not options.bare: |
468 print '\nFAILED\n' | 497 print '\nFAILED\n' |
469 print '\n'.join(errors) | 498 print '\n'.join(errors) |
470 return 1 | 499 return 1 |
471 if not options.bare: | 500 if not options.bare: |
472 print '\nSUCCESS\n' | 501 print '\nSUCCESS\n' |
473 return 0 | 502 return 0 |
474 | 503 |
475 | 504 |
476 if '__main__' == __name__: | 505 if '__main__' == __name__: |
477 sys.exit(main()) | 506 sys.exit(main()) |
OLD | NEW |