Index: tools/checkperms/checkperms.py |
diff --git a/tools/checkperms/checkperms.py b/tools/checkperms/checkperms.py |
index c3959f99ce9289fabcfae1a39588947cf89b9797..38bf05e7217dc2d8be4afffe9dc43a2ea87b551c 100755 |
--- a/tools/checkperms/checkperms.py |
+++ b/tools/checkperms/checkperms.py |
@@ -11,79 +11,29 @@ see .cc files in green and get confused. |
To ignore a particular file, add it to WHITELIST_FILES. |
To ignore a particular extension, add it to WHITELIST_EXTENSIONS. |
-To ignore whatever regexps your heart desires, add it WHITELIST_REGEX. |
+To ignore a particular path, add it WHITELIST_PATHS. |
Note that all directory separators must be slashes (Unix-style) and not |
backslashes. All directories should be relative to the source root and all |
file paths should be only lowercase. |
""" |
+import logging |
import optparse |
import os |
-import pipes |
-import re |
import stat |
+import subprocess |
import sys |
#### USER EDITABLE SECTION STARTS HERE #### |
# Files with these extensions are allowed to have executable permissions. |
-WHITELIST_EXTENSIONS = [ |
- 'bash', |
- 'bat', |
- 'dll', |
- 'dylib', |
- 'exe', |
- 'pl', |
- 'py', |
- 'rb', |
- 'sed', |
- 'sh', |
-] |
- |
-# Files that end the following paths are whitelisted too. |
-WHITELIST_FILES = [ |
- '/build/gyp_chromium', |
- '/build/linux/dump_app_syms', |
- '/build/linux/pkg-config-wrapper', |
- '/build/mac/strip_from_xcode', |
- '/build/mac/strip_save_dsym', |
- '/chrome/installer/mac/pkg-dmg', |
- '/chrome/tools/build/linux/chrome-wrapper', |
- '/chrome/tools/build/mac/build_app_dmg', |
- '/chrome/tools/build/mac/clean_up_old_versions', |
- '/chrome/tools/build/mac/dump_product_syms', |
- '/chrome/tools/build/mac/generate_localizer', |
- '/chrome/tools/build/mac/make_sign_sh', |
- '/chrome/tools/build/mac/verify_order', |
- '/o3d/build/gyp_o3d', |
- '/o3d/gypbuild', |
- '/o3d/installer/linux/debian.in/rules', |
- '/third_party/icu/source/runconfigureicu', |
- '/third_party/gold/gold32', |
- '/third_party/gold/gold64', |
- '/third_party/gold/ld', |
- '/third_party/gold/ld.bfd', |
- '/third_party/lcov/bin/gendesc', |
- '/third_party/lcov/bin/genhtml', |
- '/third_party/lcov/bin/geninfo', |
- '/third_party/lcov/bin/genpng', |
- '/third_party/lcov/bin/lcov', |
- '/third_party/lcov/bin/mcov', |
- '/third_party/lcov-1.9/bin/gendesc', |
- '/third_party/lcov-1.9/bin/genhtml', |
- '/third_party/lcov-1.9/bin/geninfo', |
- '/third_party/lcov-1.9/bin/genpng', |
- '/third_party/lcov-1.9/bin/lcov', |
- '/third_party/libxml/linux/xml2-config', |
- '/third_party/lzma_sdk/executable/7za.exe', |
- '/third_party/swig/linux/swig', |
- '/third_party/tcmalloc/chromium/src/pprof', |
- '/tools/deep_memory_profiler/dmprof', |
- '/tools/git/post-checkout', |
- '/tools/git/post-merge', |
- '/tools/ld_bfd/ld', |
-] |
+WHITELIST_EXTENSIONS = ( |
+ 'bat', |
+ 'dll', |
+ 'dylib', |
+ 'exe', |
+) |
# File names that are always whitelisted. (These are all autoconf spew.) |
WHITELIST_FILENAMES = set(( |
@@ -94,251 +44,193 @@ WHITELIST_FILENAMES = set(( |
'install-sh', |
'missing', |
'mkinstalldirs', |
- 'scons', |
'naclsdk', |
+ 'scons', |
)) |
# File paths that contain these regexps will be whitelisted as well. |
-WHITELIST_REGEX = [ |
- re.compile('/third_party/openssl/'), |
- re.compile('/third_party/sqlite/'), |
- re.compile('/third_party/xdg-utils/'), |
- re.compile('/third_party/yasm/source/patched-yasm/config'), |
- re.compile('/third_party/ffmpeg/tools'), |
-] |
+WHITELIST_PATHS = ( |
+ '/third_party/openssl/', |
+ '/third_party/sqlite/', |
+ '/third_party/xdg-utils/', |
+ '/third_party/yasm/source/patched-yasm/config', |
+ '/third_party/ffmpeg/tools', |
+) |
#### USER EDITABLE SECTION ENDS HERE #### |
-WHITELIST_EXTENSIONS_REGEX = re.compile(r'\.(%s)' % |
- '|'.join(WHITELIST_EXTENSIONS)) |
- |
-WHITELIST_FILES_REGEX = re.compile(r'(%s)$' % '|'.join(WHITELIST_FILES)) |
- |
-# Set to true for more output. This is set by the command line options. |
-VERBOSE = False |
- |
-# Using forward slashes as directory separators, ending in a forward slash. |
-# Set by the command line options. |
-BASE_DIRECTORY = '' |
- |
-# The default if BASE_DIRECTORY is not set on the command line. |
-DEFAULT_BASE_DIRECTORY = '../../..' |
- |
-# The directories which contain the sources managed by git. |
-GIT_SOURCE_DIRECTORY = set() |
- |
-# The SVN repository url. |
-SVN_REPO_URL = '' |
- |
-# Whether we are using SVN or GIT. |
-IS_SVN = True |
- |
-# Executable permission mask |
-EXECUTABLE_PERMISSION = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH |
+def is_verbose(): |
+ return logging.getLogger().isEnabledFor(logging.DEBUG) |
-def IsWhiteListed(file_path): |
- """Returns True if file_path is in our whitelist of files to ignore.""" |
- if WHITELIST_EXTENSIONS_REGEX.match(os.path.splitext(file_path)[1]): |
- return True |
- if WHITELIST_FILES_REGEX.search(file_path): |
- return True |
- if os.path.basename(file_path) in WHITELIST_FILENAMES: |
- return True |
- for regex in WHITELIST_REGEX: |
- if regex.search(file_path): |
- return True |
- return False |
+def capture(cmd, cwd): |
+ """Returns the output of a command. |
-def CheckFile(file_path): |
- """Checks file_path's permissions. |
- |
- Args: |
- file_path: The file path to check. |
- |
- Returns: |
- Either a string describing the error if there was one, or None if the file |
- checked out OK. |
+ Ignores the error code or stderr. |
""" |
- if VERBOSE: |
- print 'Checking file: ' + file_path |
- |
- file_path_lower = file_path.lower() |
- if IsWhiteListed(file_path_lower): |
- return None |
- |
- # Not whitelisted, stat the file and check permissions. |
- try: |
- st_mode = os.stat(file_path).st_mode |
- except IOError, e: |
- return 'Failed to stat file: %s' % e |
- except OSError, e: |
- return 'Failed to stat file: %s' % e |
- |
- if EXECUTABLE_PERMISSION & st_mode: |
- # Look if the file starts with #!/ |
+ env = os.environ.copy() |
+ env['LANG'] = 'en_us.UTF-8' |
+ p = subprocess.Popen( |
+ cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd, env=env) |
+ return p.communicate()[0] |
+ |
+ |
+class ApiBase(object): |
+ def __init__(self, root_dir): |
+ self.root_dir = root_dir |
+ |
+ @staticmethod |
+ def is_whitelisted(file_path): |
+ """Returns True if file_path is in our whitelist of files to ignore. |
+ |
+ file_path is a relative directory to the checkout root. |
+ """ |
+ file_path = file_path.lower() |
+ return ( |
+ os.path.splitext(file_path)[1][1:] in WHITELIST_EXTENSIONS or |
+ os.path.basename(file_path) in WHITELIST_FILENAMES or |
+ file_path.startswith(WHITELIST_PATHS)) |
+ |
+ @staticmethod |
+ def has_executable_bit(file_path): |
+ """Returns if any executable bit is set. |
+ |
+ file_path is the absolute path to the file. |
+ """ |
+ permission = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH |
+ return bool(permission & os.stat(file_path).st_mode) |
+ |
+ @staticmethod |
+ def has_shebang(file_path): |
+ """Returns if the file starts with #!/. |
+ |
+ file_path is the absolute path to the file. |
+ """ |
with open(file_path, 'rb') as f: |
- if f.read(3) == '#!/': |
- # That's fine. |
+ return f.read(3) == '#!/' |
+ |
+ def check_file(self, file_path): |
+ """Checks file_path's permissions and returns an error if it is |
+ inconsistent. |
+ """ |
+ if is_verbose(): |
+ print 'Checking file: %s' % file_path |
+ |
+ if self.is_whitelisted(file_path): |
+ return None |
+ |
+ full_path = os.path.join(self.root_dir, file_path) |
+ bit = self.has_executable_bit(full_path) |
+ shebang = self.has_shebang(full_path) |
+ if bit != shebang: |
+ if bit: |
+ return '%s: Has executable bit but not shebang' % file_path |
+ else: |
+ return '%s: Has shebang but not executable bit' % file_path |
+ |
+ def check(self, start_dir): |
+ """Check the files in start_dir, recursively check its subdirectories.""" |
+ errors = [] |
+ for root, dirs, files in os.walk(start_dir): |
+ for f in files: |
+ error = check_file(root_dir, os.path.join(root, f)) |
+ if error: |
+ errors.append(error) |
+ return errors |
+ |
+ |
+class ApiSvn(ApiBase): |
+ @staticmethod |
+ def get_info(dir_path): |
+ """Returns svn meta-data for a svn checkout.""" |
+ if not os.path.isdir(dir_path): |
+ return {} |
+ out = capture(['svn', 'info', '.'], dir_path) |
+ return dict(l.split(': ', 1) for l in out.splitlines() if l) |
+ |
+ @staticmethod |
+ def get_root(dir_path): |
+ """Returns the svn checkout root or None.""" |
+ svn_url = get_svn_info(dir_path).get('Repository Root:') |
+ if not svn_url: |
+ return None |
+ while True: |
+ parent = os.path.dirname(dir_path) |
+ if parent == dir_path: |
return None |
- # TODO(maruel): Check that non-executable file do not start with a shebang. |
- error = 'Contains executable permission' |
- if VERBOSE: |
- return '%s: %06o' % (error, st_mode) |
- return error |
- return None |
- |
- |
-def ShouldCheckDirectory(dir_path): |
- """Determine if we should check the content of dir_path.""" |
- if not IS_SVN: |
- return dir_path in GIT_SOURCE_DIRECTORY |
- repo_url = GetSvnRepositoryRoot(dir_path) |
- if not repo_url: |
- return False |
- return repo_url == SVN_REPO_URL |
- |
- |
-def CheckDirectory(dir_path): |
- """Check the files in dir_path; recursively check its subdirectories.""" |
- # Collect a list of all files and directories to check. |
- files_to_check = [] |
- dirs_to_check = [] |
- success = True |
- contents = os.listdir(dir_path) |
- for cur in contents: |
- full_path = os.path.join(dir_path, cur) |
- if os.path.isdir(full_path) and ShouldCheckDirectory(full_path): |
- dirs_to_check.append(full_path) |
- elif os.path.isfile(full_path): |
- files_to_check.append(full_path) |
- |
- # First check all files in this directory. |
- for cur_file in files_to_check: |
- file_status = CheckFile(cur_file) |
- if file_status is not None: |
- print 'ERROR in %s\n%s' % (cur_file, file_status) |
- success = False |
- |
- # Next recurse into the subdirectories. |
- for cur_dir in dirs_to_check: |
- if not CheckDirectory(cur_dir): |
- success = False |
- return success |
- |
- |
-def GetGitSourceDirectory(root): |
- """Returns a set of the directories to be checked. |
- |
- Args: |
- root: The repository root where a .git directory must exist. |
- |
- Returns: |
- A set of directories which contain sources managed by git. |
- """ |
- git_source_directory = set() |
- popen_out = os.popen('cd %s && git ls-files --full-name .' % |
- pipes.quote(root)) |
- for line in popen_out: |
- dir_path = os.path.join(root, os.path.dirname(line)) |
- git_source_directory.add(dir_path) |
- git_source_directory.add(root) |
- return git_source_directory |
+ if svn_url != get_svn_info(parent).get('Repository Root:'): |
+ return dir_path |
+ dir_path = parent |
+ def check(self, start_dir): |
+ """Like ApiBase.check() excepts that it skips non-versioned directories.""" |
+ return super(ApiSvn, self).check(start_dir) |
-def GetSvnRepositoryRoot(dir_path): |
- """Returns the repository root for a directory. |
- Args: |
- dir_path: A directory where a .svn subdirectory should exist. |
+class ApiGit(ApiBase): |
+ @staticmethod |
+ def get_root(dir_path): |
+ """Returns the git checkout root or None.""" |
+ root = capture(['git', 'rev-parse', '--show-toplevel'], dir_path).strip() |
+ if root: |
+ return root |
+ |
+ def check(self, start_dir): |
+ """Like ApiBase.check() excepts that it skips non-versioned directories.""" |
+ return super(ApiSvn, self).check(start_dir) |
- Returns: |
- The svn repository that contains dir_path or None. |
- """ |
- svn_dir = os.path.join(dir_path, '.svn') |
- if not os.path.isdir(svn_dir): |
- return None |
- popen_out = os.popen('cd %s && svn info' % pipes.quote(dir_path)) |
- for line in popen_out: |
- if line.startswith('Repository Root: '): |
- return line[len('Repository Root: '):].rstrip() |
- return None |
+def get_scm(dir_path): |
+ """Returns a properly configured ApiBase instance.""" |
+ root = ApiSvn.get_root(dir_path) |
+ if root: |
+ return ApiSvn(root) |
+ root = ApiGit.get_root(dir_path) |
+ if root: |
+ return ApiGit(root) |
-def main(argv): |
+ # Returns a non-scm aware checker. |
+ logging.warn('Failed to determine the SCM for %s' % dir_path) |
+ return ApiBase(root) |
+ |
+ |
+def main(): |
usage = """Usage: python %prog [--root <root>] [tocheck] |
tocheck Specifies the directory, relative to root, to check. This defaults |
to "." so it checks everything. |
Examples: |
- python checkperms.py |
- python checkperms.py --root /path/to/source chrome""" |
- |
- option_parser = optparse.OptionParser(usage=usage) |
- option_parser.add_option('--root', dest='base_directory', |
- default=DEFAULT_BASE_DIRECTORY, |
- help='Specifies the repository root. This defaults ' |
- 'to %default relative to the script file, which ' |
- 'will normally be the repository root.') |
- option_parser.add_option('-v', '--verbose', action='store_true', |
- help='Print debug logging') |
- options, args = option_parser.parse_args() |
- |
- global VERBOSE |
- if options.verbose: |
- VERBOSE = True |
- |
- # Optional base directory of the repository. |
- global BASE_DIRECTORY |
- if (not options.base_directory or |
- options.base_directory == DEFAULT_BASE_DIRECTORY): |
- BASE_DIRECTORY = os.path.abspath( |
- os.path.join(os.path.abspath(argv[0]), DEFAULT_BASE_DIRECTORY)) |
- else: |
- BASE_DIRECTORY = os.path.abspath(argv[2]) |
- |
- # Figure out which directory we have to check. |
- if not args: |
- # No directory to check specified, use the repository root. |
- start_dir = BASE_DIRECTORY |
- elif len(args) == 1: |
- # Directory specified. Start here. It's supposed to be relative to the |
- # base directory. |
- start_dir = os.path.abspath(os.path.join(BASE_DIRECTORY, args[0])) |
- else: |
- # More than one argument, we don't handle this. |
- option_parser.print_help() |
- return 1 |
- |
- print 'Using base directory:', BASE_DIRECTORY |
- print 'Checking directory:', start_dir |
- |
- BASE_DIRECTORY = BASE_DIRECTORY.replace('\\', '/') |
- start_dir = start_dir.replace('\\', '/') |
- |
- success = True |
- if os.path.exists(os.path.join(BASE_DIRECTORY, '.svn')): |
- global SVN_REPO_URL |
- SVN_REPO_URL = GetSvnRepositoryRoot(BASE_DIRECTORY) |
- if not SVN_REPO_URL: |
- print 'Cannot determine the SVN repo URL' |
- success = False |
- elif os.path.exists(os.path.join(BASE_DIRECTORY, '.git')): |
- global IS_SVN |
- IS_SVN = False |
- global GIT_SOURCE_DIRECTORY |
- GIT_SOURCE_DIRECTORY = GetGitSourceDirectory(BASE_DIRECTORY) |
- if not GIT_SOURCE_DIRECTORY: |
- print 'Cannot determine the list of GIT directories' |
- success = False |
- else: |
- print 'Cannot determine the SCM used in %s' % BASE_DIRECTORY |
- success = False |
- |
- if success: |
- success = CheckDirectory(start_dir) |
- if not success: |
+ python %prog |
+ python %prog --root /path/to/source chrome""" |
+ |
+ parser = optparse.OptionParser(usage=usage) |
+ parser.add_option( |
+ '--root', |
+ default=get_scm('.')[0], |
+ help='Specifies the repository root. This defaults ' |
+ 'to %default relative to the script file, which ' |
+ 'will normally be the repository root.') |
+ parser.add_option( |
+ '-v', '--verbose', action='store_true', help='Print debug logging') |
+ options, args = parser.parse_args() |
+ |
+ logging.basicConfig( |
+ level=(logging.DEBUG if options.verbose else logging.ERROR)) |
+ |
+ if len(args) > 1: |
+ parser.error('Too many arguments used') |
+ if args: |
+ start_dir = args[0] |
+ |
+ if not options.root: |
+ parser.error('Must specify --root') |
+ options.root = os.path.abspath(options.root) |
+ |
+ # Guess again the SCM used. |
+ api = get_scm(options.root) |
+ |
+ if not api.check(start_dir): |
print '\nFAILED\n' |
return 1 |
print '\nSUCCESS\n' |
@@ -346,4 +238,4 @@ Examples: |
if '__main__' == __name__: |
- sys.exit(main(sys.argv)) |
+ sys.exit(main()) |