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

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

Issue 10088002: Remove over 100 lines from checkperms.py. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Use more specific filters Created 8 years, 8 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 To ignore a particular file, add it to WHITELIST_FILES. 12 To ignore a particular file, add it to WHITELIST_FILES.
13 To ignore a particular extension, add it to WHITELIST_EXTENSIONS. 13 To ignore a particular extension, add it to WHITELIST_EXTENSIONS.
14 To ignore whatever regexps your heart desires, add it WHITELIST_REGEX. 14 To ignore a particular path, add it WHITELIST_PATHS.
15 15
16 Note that all directory separators must be slashes (Unix-style) and not 16 Note that all directory separators must be slashes (Unix-style) and not
17 backslashes. All directories should be relative to the source root and all 17 backslashes. All directories should be relative to the source root and all
18 file paths should be only lowercase. 18 file paths should be only lowercase.
19 """ 19 """
20 20
21 import logging
21 import optparse 22 import optparse
22 import os 23 import os
23 import pipes
24 import re
25 import stat 24 import stat
25 import subprocess
26 import sys 26 import sys
27 27
28 #### USER EDITABLE SECTION STARTS HERE #### 28 #### USER EDITABLE SECTION STARTS HERE ####
29 29
30 # Files with these extensions are allowed to have executable permissions. 30 # Files with these extensions are allowed to have executable permissions.
31 WHITELIST_EXTENSIONS = [ 31 WHITELIST_EXTENSIONS = (
32 'bash', 32 'bat',
33 'bat', 33 'dll',
34 'dll', 34 'dylib',
35 'dylib', 35 'in',
Lei Zhang 2012/04/24 19:34:01 Why is the '.in' extension whitelisted? Also, 'in'
M-A Ruel 2012/04/25 01:30:11 Replace 'in' with the 2 equivalent exceptions.
36 'exe', 36 'exe',
37 'pl', 37 )
38 'py',
39 'rb',
40 'sed',
41 'sh',
42 ]
43
44 # Files that end the following paths are whitelisted too.
45 WHITELIST_FILES = [
46 '/build/gyp_chromium',
47 '/build/linux/dump_app_syms',
48 '/build/linux/pkg-config-wrapper',
49 '/build/mac/strip_from_xcode',
50 '/build/mac/strip_save_dsym',
51 '/chrome/installer/mac/pkg-dmg',
52 '/chrome/test/data/webrtc/linux/peerconnection_server',
53 '/chrome/tools/build/linux/chrome-wrapper',
54 '/chrome/tools/build/mac/build_app_dmg',
55 '/chrome/tools/build/mac/clean_up_old_versions',
56 '/chrome/tools/build/mac/dump_product_syms',
57 '/chrome/tools/build/mac/generate_localizer',
58 '/chrome/tools/build/mac/make_sign_sh',
59 '/chrome/tools/build/mac/verify_order',
60 '/o3d/build/gyp_o3d',
61 '/o3d/gypbuild',
62 '/o3d/installer/linux/debian.in/rules',
63 '/third_party/icu/source/runconfigureicu',
64 '/third_party/gold/gold32',
65 '/third_party/gold/gold64',
66 '/third_party/gold/ld',
67 '/third_party/gold/ld.bfd',
68 '/third_party/lcov/bin/gendesc',
69 '/third_party/lcov/bin/genhtml',
70 '/third_party/lcov/bin/geninfo',
71 '/third_party/lcov/bin/genpng',
72 '/third_party/lcov/bin/lcov',
73 '/third_party/lcov/bin/mcov',
74 '/third_party/lcov-1.9/bin/gendesc',
75 '/third_party/lcov-1.9/bin/genhtml',
76 '/third_party/lcov-1.9/bin/geninfo',
77 '/third_party/lcov-1.9/bin/genpng',
78 '/third_party/lcov-1.9/bin/lcov',
79 '/third_party/libxml/linux/xml2-config',
80 '/third_party/lzma_sdk/executable/7za.exe',
81 '/third_party/swig/linux/swig',
82 '/third_party/tcmalloc/chromium/src/pprof',
83 '/tools/deep_memory_profiler/dmprof',
84 '/tools/git/post-checkout',
85 '/tools/git/post-merge',
86 '/tools/ld_bfd/ld',
87 ]
88 38
89 # File names that are always whitelisted. (These are all autoconf spew.) 39 # File names that are always whitelisted. (These are all autoconf spew.)
90 WHITELIST_FILENAMES = set(( 40 WHITELIST_FILENAMES = set((
91 'config.guess', 41 'config.guess',
92 'config.sub', 42 'config.sub',
93 'configure', 43 'configure',
94 'depcomp', 44 'depcomp',
95 'install-sh', 45 'install-sh',
96 'missing', 46 'missing',
97 'mkinstalldirs', 47 'mkinstalldirs',
48 'naclsdk',
98 'scons', 49 'scons',
99 'naclsdk',
100 )) 50 ))
101 51
102 # File paths that contain these regexps will be whitelisted as well. 52 # File paths that contain these regexps will be whitelisted as well.
103 WHITELIST_REGEX = [ 53 WHITELIST_PATHS = (
104 re.compile('/third_party/openssl/'), 54 # TODO(maruel): Detect ELF files.
105 re.compile('/third_party/sqlite/'), 55 'chrome/test/data/webrtc/linux/peerconnection_server',
106 re.compile('/third_party/xdg-utils/'), 56 'native_client_sdk/src/build_tools/sdk_tools/third_party/',
107 re.compile('/third_party/yasm/source/patched-yasm/config'), 57 'out/',
108 re.compile('/third_party/ffmpeg/tools'), 58 # TODO(maruel): Fix these.
109 ] 59 'third_party/android_testrunner/',
60 'third_party/closure_linter/',
61 'third_party/devscripts/licensecheck.pl.vanilla',
62 'third_party/hyphen/',
63 'third_party/jemalloc/',
64 'third_party/lcov-1.9/contrib/galaxy/conglomerate_functions.pl',
65 'third_party/lcov-1.9/contrib/galaxy/gen_makefile.sh',
66 'third_party/lcov/contrib/galaxy/conglomerate_functions.pl',
67 'third_party/lcov/contrib/galaxy/gen_makefile.sh',
68 'third_party/libevent/autogen.sh',
69 'third_party/libevent/test/test.sh',
70 'third_party/libxml/linux/xml2-config',
71 'third_party/libxml/src/ltmain.sh',
72 'third_party/mesa/',
73 'third_party/protobuf/',
74 'third_party/python_gflags/gflags.py',
75 'third_party/sqlite/',
76 'third_party/talloc/script/mksyms.sh',
77 'third_party/tcmalloc/',
78 'third_party/tlslite/setup.py',
79 )
110 80
111 #### USER EDITABLE SECTION ENDS HERE #### 81 #### USER EDITABLE SECTION ENDS HERE ####
112 82
113 WHITELIST_EXTENSIONS_REGEX = re.compile(r'\.(%s)' % 83
114 '|'.join(WHITELIST_EXTENSIONS)) 84 def capture(cmd, cwd):
115 85 """Returns the output of a command.
116 WHITELIST_FILES_REGEX = re.compile(r'(%s)$' % '|'.join(WHITELIST_FILES)) 86
117 87 Ignores the error code or stderr.
118 # Set to true for more output. This is set by the command line options.
119 VERBOSE = False
120
121 # Using forward slashes as directory separators, ending in a forward slash.
122 # Set by the command line options.
123 BASE_DIRECTORY = ''
124
125 # The default if BASE_DIRECTORY is not set on the command line.
126 DEFAULT_BASE_DIRECTORY = '../../..'
127
128 # The directories which contain the sources managed by git.
129 GIT_SOURCE_DIRECTORY = set()
130
131 # The SVN repository url.
132 SVN_REPO_URL = ''
133
134 # Whether we are using SVN or GIT.
135 IS_SVN = True
136
137 # Executable permission mask
138 EXECUTABLE_PERMISSION = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
139
140
141 def IsWhiteListed(file_path):
142 """Returns True if file_path is in our whitelist of files to ignore."""
143 if WHITELIST_EXTENSIONS_REGEX.match(os.path.splitext(file_path)[1]):
144 return True
145 if WHITELIST_FILES_REGEX.search(file_path):
146 return True
147 if os.path.basename(file_path) in WHITELIST_FILENAMES:
148 return True
149 for regex in WHITELIST_REGEX:
150 if regex.search(file_path):
151 return True
152 return False
153
154
155 def CheckFile(file_path):
156 """Checks file_path's permissions.
157
158 Args:
159 file_path: The file path to check.
160
161 Returns:
162 Either a string describing the error if there was one, or None if the file
163 checked out OK.
164 """ 88 """
165 if VERBOSE: 89 env = os.environ.copy()
166 print 'Checking file: ' + file_path 90 env['LANG'] = 'en_us.UTF-8'
167 91 p = subprocess.Popen(
168 file_path_lower = file_path.lower() 92 cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd, env=env)
169 if IsWhiteListed(file_path_lower): 93 return p.communicate()[0]
170 return None 94
171 95
172 # Not whitelisted, stat the file and check permissions. 96 class ApiBase(object):
173 try: 97 def __init__(self, root_dir, bare_output):
174 st_mode = os.stat(file_path).st_mode 98 self.root_dir = root_dir
175 except IOError, e: 99 self.bare_output = bare_output
176 return 'Failed to stat file: %s' % e 100
177 except OSError, e: 101 @staticmethod
178 return 'Failed to stat file: %s' % e 102 def is_whitelisted(rel_path):
179 103 """Returns True if rel_path is in our whitelist of files to ignore."""
180 if EXECUTABLE_PERMISSION & st_mode: 104 rel_path = rel_path.lower()
181 # Look if the file starts with #!/ 105 return (
182 with open(file_path, 'rb') as f: 106 os.path.splitext(rel_path)[1][1:] in WHITELIST_EXTENSIONS or
183 if f.read(3) == '#!/': 107 os.path.basename(rel_path) in WHITELIST_FILENAMES or
184 # That's fine. 108 rel_path.startswith(WHITELIST_PATHS))
109
110 @staticmethod
111 def has_executable_bit(full_path):
112 """Returns if any executable bit is set."""
113 permission = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
114 return bool(permission & os.stat(full_path).st_mode)
Lei Zhang 2012/04/24 19:34:01 Are you expecting os.stat() to not throw any excep
M-A Ruel 2012/04/25 01:30:11 Why would a file in the checkout not be readable b
Lei Zhang 2012/04/25 04:01:02 Ideally that would be the case, but the fact that
115
116 @staticmethod
117 def has_shebang(full_path):
118 """Returns if the file starts with #!/.
119
120 file_path is the absolute path to the file.
121 """
122 with open(full_path, 'rb') as f:
123 return f.read(3) == '#!/'
124
125 def check_file(self, rel_path):
126 """Checks file_path's permissions and returns an error if it is
127 inconsistent.
128 """
129 logging.debug('check_file(%s)' % rel_path)
130
131 if self.is_whitelisted(rel_path):
132 return None
133
134 full_path = os.path.join(self.root_dir, rel_path)
135 bit = self.has_executable_bit(full_path)
136 shebang = self.has_shebang(full_path)
Lei Zhang 2012/04/24 19:34:01 I think this is going to slow down checkperms.py s
M-A Ruel 2012/04/25 01:30:11 All the following is on a standard 500gb HD. linu
Lei Zhang 2012/04/25 04:01:02 How about we only check files with the executable
137 if bit != shebang:
138 if self.bare_output:
139 return rel_path
140 if bit:
141 return '%s: Has executable bit but not shebang' % rel_path
142 else:
143 return '%s: Has shebang but not executable bit' % rel_path
144
145 def check(self, start_dir):
146 """Check the files in start_dir, recursively check its subdirectories."""
147 errors = []
148 items = self.list_dir(start_dir)
149 logging.info('check(%s) -> %d' % (start_dir, len(items)))
150 for item in items:
151 full_path = os.path.join(self.root_dir, start_dir, item)
152 rel_path = full_path[len(self.root_dir) + 1:]
153 if os.path.isdir(full_path):
154 if self.is_whitelisted(rel_path):
155 continue
156 # Depth first.
157 errors.extend(self.check(rel_path))
158 else:
159 error = self.check_file(rel_path)
160 if error:
161 errors.append(error)
162 return errors
163
164 def list_dir(self, start_dir):
165 """Lists all the files and directory inside start_dir."""
166 return sorted(
167 x for x in os.listdir(os.path.join(self.root_dir, start_dir))
168 if not x.startswith('.')
169 )
170
171
172 class ApiSvn(ApiBase):
173 @staticmethod
174 def get_info(dir_path):
175 """Returns svn meta-data for a svn checkout."""
176 if not os.path.isdir(dir_path):
177 return {}
178 out = capture(['svn', 'info', '.'], dir_path)
179 return dict(l.split(': ', 1) for l in out.splitlines() if l)
180
181 @classmethod
182 def get_root(cls, dir_path):
183 """Returns the svn checkout root or None."""
184 svn_url = cls.get_info(dir_path).get('Repository Root:')
185 if not svn_url:
186 return None
187 while True:
188 parent = os.path.dirname(dir_path)
189 if parent == dir_path:
185 return None 190 return None
186 # TODO(maruel): Check that non-executable file do not start with a shebang. 191 if svn_url != cls.get_info(parent).get('Repository Root:'):
187 error = 'Contains executable permission' 192 return dir_path
188 if VERBOSE: 193 dir_path = parent
189 return '%s: %06o' % (error, st_mode) 194
190 return error 195 def list_dir(self, start_dir):
191 return None 196 """Lists all the files and directory inside start_dir."""
192 197 return sorted(
193 198 capture(
194 def ShouldCheckDirectory(dir_path): 199 ['svn', 'ls'], os.path.join(self.root_dir, start_dir)).splitlines()
195 """Determine if we should check the content of dir_path.""" 200 )
196 if not IS_SVN: 201
197 return dir_path in GIT_SOURCE_DIRECTORY 202
198 repo_url = GetSvnRepositoryRoot(dir_path) 203 class ApiGit(ApiBase):
199 if not repo_url: 204 _files = None
200 return False 205
201 return repo_url == SVN_REPO_URL 206 @staticmethod
202 207 def get_root(dir_path):
203 208 """Returns the git checkout root or None."""
204 def CheckDirectory(dir_path): 209 root = capture(['git', 'rev-parse', '--show-toplevel'], dir_path).strip()
205 """Check the files in dir_path; recursively check its subdirectories.""" 210 if root:
206 # Collect a list of all files and directories to check. 211 return root
207 files_to_check = [] 212
208 dirs_to_check = [] 213 def list_dir(self, start_dir):
209 success = True 214 """Lists all the files and directory inside start_dir."""
210 contents = os.listdir(dir_path) 215 if self._files is None:
211 for cur in contents: 216 # git has no way to get the list of files in a specific directory without
212 full_path = os.path.join(dir_path, cur) 217 # recursing.
213 if os.path.isdir(full_path) and ShouldCheckDirectory(full_path): 218 self._files = capture(['git', 'ls-files'], self.root_dir).splitlines()
214 dirs_to_check.append(full_path) 219 start_dir = start_dir[len(self.root_dir) + 1:]
215 elif os.path.isfile(full_path): 220 return sorted(
216 files_to_check.append(full_path) 221 x[len(start_dir):] for x in self._files if x.startswith(start_dir)
217 222 )
218 # First check all files in this directory. 223
219 for cur_file in files_to_check: 224
220 file_status = CheckFile(cur_file) 225 def get_scm(dir_path, log, bare):
221 if file_status is not None: 226 """Returns a properly configured ApiBase instance."""
222 print 'ERROR in %s\n%s' % (cur_file, file_status) 227 dir_path = os.path.abspath(dir_path)
223 success = False 228 root = ApiSvn.get_root(dir_path)
224 229 if root:
225 # Next recurse into the subdirectories. 230 if log:
226 for cur_dir in dirs_to_check: 231 logging.info('Found svn at %s' % root)
227 if not CheckDirectory(cur_dir): 232 return ApiSvn(root, bare)
228 success = False 233 root = ApiGit.get_root(dir_path)
229 return success 234 if root:
230 235 if log:
231 236 logging.info('Found git at %s' % root)
232 def GetGitSourceDirectory(root): 237 return ApiGit(root, bare)
233 """Returns a set of the directories to be checked. 238
234 239 # Returns a non-scm aware checker.
235 Args: 240 if log:
236 root: The repository root where a .git directory must exist. 241 logging.warn('Failed to determine the SCM for %s' % dir_path)
237 242 return ApiBase(dir_path, bare)
238 Returns: 243
239 A set of directories which contain sources managed by git. 244
240 """ 245 def main():
241 git_source_directory = set()
242 popen_out = os.popen('cd %s && git ls-files --full-name .' %
243 pipes.quote(root))
244 for line in popen_out:
245 dir_path = os.path.join(root, os.path.dirname(line))
246 git_source_directory.add(dir_path)
247 git_source_directory.add(root)
248 return git_source_directory
249
250
251 def GetSvnRepositoryRoot(dir_path):
252 """Returns the repository root for a directory.
253
254 Args:
255 dir_path: A directory where a .svn subdirectory should exist.
256
257 Returns:
258 The svn repository that contains dir_path or None.
259 """
260 svn_dir = os.path.join(dir_path, '.svn')
261 if not os.path.isdir(svn_dir):
262 return None
263 popen_out = os.popen('cd %s && svn info' % pipes.quote(dir_path))
264 for line in popen_out:
265 if line.startswith('Repository Root: '):
266 return line[len('Repository Root: '):].rstrip()
267 return None
268
269
270 def main(argv):
271 usage = """Usage: python %prog [--root <root>] [tocheck] 246 usage = """Usage: python %prog [--root <root>] [tocheck]
272 tocheck Specifies the directory, relative to root, to check. This defaults 247 tocheck Specifies the directory, relative to root, to check. This defaults
273 to "." so it checks everything. 248 to "." so it checks everything.
274 249
275 Examples: 250 Examples:
276 python checkperms.py 251 python %prog
277 python checkperms.py --root /path/to/source chrome""" 252 python %prog --root /path/to/source chrome"""
278 253
279 option_parser = optparse.OptionParser(usage=usage) 254 parser = optparse.OptionParser(usage=usage)
280 option_parser.add_option('--root', dest='base_directory', 255 parser.add_option(
281 default=DEFAULT_BASE_DIRECTORY, 256 '--root',
282 help='Specifies the repository root. This defaults ' 257 default=get_scm('.', False, False).root_dir,
283 'to %default relative to the script file, which ' 258 help='Specifies the repository root. This defaults '
284 'will normally be the repository root.') 259 'to "%default" relative to the script file, which '
285 option_parser.add_option('-v', '--verbose', action='store_true', 260 'will normally be the repository root.')
286 help='Print debug logging') 261 parser.add_option(
287 options, args = option_parser.parse_args() 262 '-v', '--verbose', action='count', default=0, help='Print debug logging')
288 263 parser.add_option(
289 global VERBOSE 264 '--bare',
290 if options.verbose: 265 action='store_true',
291 VERBOSE = True 266 default=False,
292 267 help='Prints the bare filename triggering the checks')
293 # Optional base directory of the repository. 268 options, args = parser.parse_args()
294 global BASE_DIRECTORY 269
295 if (not options.base_directory or 270 levels = [logging.ERROR, logging.INFO, logging.DEBUG]
296 options.base_directory == DEFAULT_BASE_DIRECTORY): 271 logging.basicConfig(level=levels[min(len(levels) - 1, options.verbose)])
297 BASE_DIRECTORY = os.path.abspath( 272
298 os.path.join(os.path.abspath(argv[0]), DEFAULT_BASE_DIRECTORY)) 273 if len(args) > 1:
274 parser.error('Too many arguments used')
275
276 if not options.root:
277 parser.error('Must specify --root')
278
279 # Guess again the SCM used.
280 api = get_scm(options.root, True, options.bare)
281 if args:
282 start_dir = args[0]
299 else: 283 else:
300 BASE_DIRECTORY = os.path.abspath(argv[2]) 284 start_dir = api.root_dir
301 285
302 # Figure out which directory we have to check. 286 errors = api.check(start_dir)
303 if not args: 287 if errors:
304 # No directory to check specified, use the repository root. 288 if not options.bare:
305 start_dir = BASE_DIRECTORY 289 print '\nFAILED\n'
306 elif len(args) == 1: 290 print '\n'.join(errors)
307 # Directory specified. Start here. It's supposed to be relative to the
308 # base directory.
309 start_dir = os.path.abspath(os.path.join(BASE_DIRECTORY, args[0]))
310 else:
311 # More than one argument, we don't handle this.
312 option_parser.print_help()
313 return 1 291 return 1
314 292 if not options.bare:
315 print 'Using base directory:', BASE_DIRECTORY 293 print '\nSUCCESS\n'
316 print 'Checking directory:', start_dir
317
318 BASE_DIRECTORY = BASE_DIRECTORY.replace('\\', '/')
319 start_dir = start_dir.replace('\\', '/')
320
321 success = True
322 if os.path.exists(os.path.join(BASE_DIRECTORY, '.svn')):
323 global SVN_REPO_URL
324 SVN_REPO_URL = GetSvnRepositoryRoot(BASE_DIRECTORY)
325 if not SVN_REPO_URL:
326 print 'Cannot determine the SVN repo URL'
327 success = False
328 elif os.path.exists(os.path.join(BASE_DIRECTORY, '.git')):
329 global IS_SVN
330 IS_SVN = False
331 global GIT_SOURCE_DIRECTORY
332 GIT_SOURCE_DIRECTORY = GetGitSourceDirectory(BASE_DIRECTORY)
333 if not GIT_SOURCE_DIRECTORY:
334 print 'Cannot determine the list of GIT directories'
335 success = False
336 else:
337 print 'Cannot determine the SCM used in %s' % BASE_DIRECTORY
338 success = False
339
340 if success:
341 success = CheckDirectory(start_dir)
342 if not success:
343 print '\nFAILED\n'
344 return 1
345 print '\nSUCCESS\n'
346 return 0 294 return 0
347 295
348 296
349 if '__main__' == __name__: 297 if '__main__' == __name__:
350 sys.exit(main(sys.argv)) 298 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