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

Side by Side Diff: third_party/google-endpoints/setuptools/sandbox.py

Issue 2666783008: Add google-endpoints to third_party/. (Closed)
Patch Set: Created 3 years, 10 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
OLDNEW
(Empty)
1 import os
2 import sys
3 import tempfile
4 import operator
5 import functools
6 import itertools
7 import re
8 import contextlib
9 import pickle
10
11 import six
12 from six.moves import builtins, map
13
14 import pkg_resources
15
16 if sys.platform.startswith('java'):
17 import org.python.modules.posix.PosixModule as _os
18 else:
19 _os = sys.modules[os.name]
20 try:
21 _file = file
22 except NameError:
23 _file = None
24 _open = open
25 from distutils.errors import DistutilsError
26 from pkg_resources import working_set
27
28 __all__ = [
29 "AbstractSandbox", "DirectorySandbox", "SandboxViolation", "run_setup",
30 ]
31
32
33 def _execfile(filename, globals, locals=None):
34 """
35 Python 3 implementation of execfile.
36 """
37 mode = 'rb'
38 with open(filename, mode) as stream:
39 script = stream.read()
40 # compile() function in Python 2.6 and 3.1 requires LF line endings.
41 if sys.version_info[:2] < (2, 7) or sys.version_info[:2] >= (3, 0) and sys.v ersion_info[:2] < (3, 2):
42 script = script.replace(b'\r\n', b'\n')
43 script = script.replace(b'\r', b'\n')
44 if locals is None:
45 locals = globals
46 code = compile(script, filename, 'exec')
47 exec(code, globals, locals)
48
49
50 @contextlib.contextmanager
51 def save_argv(repl=None):
52 saved = sys.argv[:]
53 if repl is not None:
54 sys.argv[:] = repl
55 try:
56 yield saved
57 finally:
58 sys.argv[:] = saved
59
60
61 @contextlib.contextmanager
62 def save_path():
63 saved = sys.path[:]
64 try:
65 yield saved
66 finally:
67 sys.path[:] = saved
68
69
70 @contextlib.contextmanager
71 def override_temp(replacement):
72 """
73 Monkey-patch tempfile.tempdir with replacement, ensuring it exists
74 """
75 if not os.path.isdir(replacement):
76 os.makedirs(replacement)
77
78 saved = tempfile.tempdir
79
80 tempfile.tempdir = replacement
81
82 try:
83 yield
84 finally:
85 tempfile.tempdir = saved
86
87
88 @contextlib.contextmanager
89 def pushd(target):
90 saved = os.getcwd()
91 os.chdir(target)
92 try:
93 yield saved
94 finally:
95 os.chdir(saved)
96
97
98 class UnpickleableException(Exception):
99 """
100 An exception representing another Exception that could not be pickled.
101 """
102
103 @staticmethod
104 def dump(type, exc):
105 """
106 Always return a dumped (pickled) type and exc. If exc can't be pickled,
107 wrap it in UnpickleableException first.
108 """
109 try:
110 return pickle.dumps(type), pickle.dumps(exc)
111 except Exception:
112 # get UnpickleableException inside the sandbox
113 from setuptools.sandbox import UnpickleableException as cls
114 return cls.dump(cls, cls(repr(exc)))
115
116
117 class ExceptionSaver:
118 """
119 A Context Manager that will save an exception, serialized, and restore it
120 later.
121 """
122
123 def __enter__(self):
124 return self
125
126 def __exit__(self, type, exc, tb):
127 if not exc:
128 return
129
130 # dump the exception
131 self._saved = UnpickleableException.dump(type, exc)
132 self._tb = tb
133
134 # suppress the exception
135 return True
136
137 def resume(self):
138 "restore and re-raise any exception"
139
140 if '_saved' not in vars(self):
141 return
142
143 type, exc = map(pickle.loads, self._saved)
144 six.reraise(type, exc, self._tb)
145
146
147 @contextlib.contextmanager
148 def save_modules():
149 """
150 Context in which imported modules are saved.
151
152 Translates exceptions internal to the context into the equivalent exception
153 outside the context.
154 """
155 saved = sys.modules.copy()
156 with ExceptionSaver() as saved_exc:
157 yield saved
158
159 sys.modules.update(saved)
160 # remove any modules imported since
161 del_modules = (
162 mod_name for mod_name in sys.modules
163 if mod_name not in saved
164 # exclude any encodings modules. See #285
165 and not mod_name.startswith('encodings.')
166 )
167 _clear_modules(del_modules)
168
169 saved_exc.resume()
170
171
172 def _clear_modules(module_names):
173 for mod_name in list(module_names):
174 del sys.modules[mod_name]
175
176
177 @contextlib.contextmanager
178 def save_pkg_resources_state():
179 saved = pkg_resources.__getstate__()
180 try:
181 yield saved
182 finally:
183 pkg_resources.__setstate__(saved)
184
185
186 @contextlib.contextmanager
187 def setup_context(setup_dir):
188 temp_dir = os.path.join(setup_dir, 'temp')
189 with save_pkg_resources_state():
190 with save_modules():
191 hide_setuptools()
192 with save_path():
193 with save_argv():
194 with override_temp(temp_dir):
195 with pushd(setup_dir):
196 # ensure setuptools commands are available
197 __import__('setuptools')
198 yield
199
200
201 def _needs_hiding(mod_name):
202 """
203 >>> _needs_hiding('setuptools')
204 True
205 >>> _needs_hiding('pkg_resources')
206 True
207 >>> _needs_hiding('setuptools_plugin')
208 False
209 >>> _needs_hiding('setuptools.__init__')
210 True
211 >>> _needs_hiding('distutils')
212 True
213 >>> _needs_hiding('os')
214 False
215 >>> _needs_hiding('Cython')
216 True
217 """
218 pattern = re.compile('(setuptools|pkg_resources|distutils|Cython)(\.|$)')
219 return bool(pattern.match(mod_name))
220
221
222 def hide_setuptools():
223 """
224 Remove references to setuptools' modules from sys.modules to allow the
225 invocation to import the most appropriate setuptools. This technique is
226 necessary to avoid issues such as #315 where setuptools upgrading itself
227 would fail to find a function declared in the metadata.
228 """
229 modules = filter(_needs_hiding, sys.modules)
230 _clear_modules(modules)
231
232
233 def run_setup(setup_script, args):
234 """Run a distutils setup script, sandboxed in its directory"""
235 setup_dir = os.path.abspath(os.path.dirname(setup_script))
236 with setup_context(setup_dir):
237 try:
238 sys.argv[:] = [setup_script] + list(args)
239 sys.path.insert(0, setup_dir)
240 # reset to include setup dir, w/clean callback list
241 working_set.__init__()
242 working_set.callbacks.append(lambda dist: dist.activate())
243
244 # __file__ should be a byte string on Python 2 (#712)
245 dunder_file = (
246 setup_script
247 if isinstance(setup_script, str) else
248 setup_script.encode(sys.getfilesystemencoding())
249 )
250
251 def runner():
252 ns = dict(__file__=dunder_file, __name__='__main__')
253 _execfile(setup_script, ns)
254
255 DirectorySandbox(setup_dir).run(runner)
256 except SystemExit as v:
257 if v.args and v.args[0]:
258 raise
259 # Normal exit, just return
260
261
262 class AbstractSandbox:
263 """Wrap 'os' module and 'open()' builtin for virtualizing setup scripts"""
264
265 _active = False
266
267 def __init__(self):
268 self._attrs = [
269 name for name in dir(_os)
270 if not name.startswith('_') and hasattr(self, name)
271 ]
272
273 def _copy(self, source):
274 for name in self._attrs:
275 setattr(os, name, getattr(source, name))
276
277 def run(self, func):
278 """Run 'func' under os sandboxing"""
279 try:
280 self._copy(self)
281 if _file:
282 builtins.file = self._file
283 builtins.open = self._open
284 self._active = True
285 return func()
286 finally:
287 self._active = False
288 if _file:
289 builtins.file = _file
290 builtins.open = _open
291 self._copy(_os)
292
293 def _mk_dual_path_wrapper(name):
294 original = getattr(_os, name)
295
296 def wrap(self, src, dst, *args, **kw):
297 if self._active:
298 src, dst = self._remap_pair(name, src, dst, *args, **kw)
299 return original(src, dst, *args, **kw)
300
301 return wrap
302
303 for name in ["rename", "link", "symlink"]:
304 if hasattr(_os, name):
305 locals()[name] = _mk_dual_path_wrapper(name)
306
307 def _mk_single_path_wrapper(name, original=None):
308 original = original or getattr(_os, name)
309
310 def wrap(self, path, *args, **kw):
311 if self._active:
312 path = self._remap_input(name, path, *args, **kw)
313 return original(path, *args, **kw)
314
315 return wrap
316
317 if _file:
318 _file = _mk_single_path_wrapper('file', _file)
319 _open = _mk_single_path_wrapper('open', _open)
320 for name in [
321 "stat", "listdir", "chdir", "open", "chmod", "chown", "mkdir",
322 "remove", "unlink", "rmdir", "utime", "lchown", "chroot", "lstat",
323 "startfile", "mkfifo", "mknod", "pathconf", "access"
324 ]:
325 if hasattr(_os, name):
326 locals()[name] = _mk_single_path_wrapper(name)
327
328 def _mk_single_with_return(name):
329 original = getattr(_os, name)
330
331 def wrap(self, path, *args, **kw):
332 if self._active:
333 path = self._remap_input(name, path, *args, **kw)
334 return self._remap_output(name, original(path, *args, **kw))
335 return original(path, *args, **kw)
336
337 return wrap
338
339 for name in ['readlink', 'tempnam']:
340 if hasattr(_os, name):
341 locals()[name] = _mk_single_with_return(name)
342
343 def _mk_query(name):
344 original = getattr(_os, name)
345
346 def wrap(self, *args, **kw):
347 retval = original(*args, **kw)
348 if self._active:
349 return self._remap_output(name, retval)
350 return retval
351
352 return wrap
353
354 for name in ['getcwd', 'tmpnam']:
355 if hasattr(_os, name):
356 locals()[name] = _mk_query(name)
357
358 def _validate_path(self, path):
359 """Called to remap or validate any path, whether input or output"""
360 return path
361
362 def _remap_input(self, operation, path, *args, **kw):
363 """Called for path inputs"""
364 return self._validate_path(path)
365
366 def _remap_output(self, operation, path):
367 """Called for path outputs"""
368 return self._validate_path(path)
369
370 def _remap_pair(self, operation, src, dst, *args, **kw):
371 """Called for path pairs like rename, link, and symlink operations"""
372 return (
373 self._remap_input(operation + '-from', src, *args, **kw),
374 self._remap_input(operation + '-to', dst, *args, **kw)
375 )
376
377
378 if hasattr(os, 'devnull'):
379 _EXCEPTIONS = [os.devnull,]
380 else:
381 _EXCEPTIONS = []
382
383
384 class DirectorySandbox(AbstractSandbox):
385 """Restrict operations to a single subdirectory - pseudo-chroot"""
386
387 write_ops = dict.fromkeys([
388 "open", "chmod", "chown", "mkdir", "remove", "unlink", "rmdir",
389 "utime", "lchown", "chroot", "mkfifo", "mknod", "tempnam",
390 ])
391
392 _exception_patterns = [
393 # Allow lib2to3 to attempt to save a pickled grammar object (#121)
394 '.*lib2to3.*\.pickle$',
395 ]
396 "exempt writing to paths that match the pattern"
397
398 def __init__(self, sandbox, exceptions=_EXCEPTIONS):
399 self._sandbox = os.path.normcase(os.path.realpath(sandbox))
400 self._prefix = os.path.join(self._sandbox, '')
401 self._exceptions = [
402 os.path.normcase(os.path.realpath(path))
403 for path in exceptions
404 ]
405 AbstractSandbox.__init__(self)
406
407 def _violation(self, operation, *args, **kw):
408 from setuptools.sandbox import SandboxViolation
409 raise SandboxViolation(operation, args, kw)
410
411 if _file:
412
413 def _file(self, path, mode='r', *args, **kw):
414 if mode not in ('r', 'rt', 'rb', 'rU', 'U') and not self._ok(path):
415 self._violation("file", path, mode, *args, **kw)
416 return _file(path, mode, *args, **kw)
417
418 def _open(self, path, mode='r', *args, **kw):
419 if mode not in ('r', 'rt', 'rb', 'rU', 'U') and not self._ok(path):
420 self._violation("open", path, mode, *args, **kw)
421 return _open(path, mode, *args, **kw)
422
423 def tmpnam(self):
424 self._violation("tmpnam")
425
426 def _ok(self, path):
427 active = self._active
428 try:
429 self._active = False
430 realpath = os.path.normcase(os.path.realpath(path))
431 return (
432 self._exempted(realpath)
433 or realpath == self._sandbox
434 or realpath.startswith(self._prefix)
435 )
436 finally:
437 self._active = active
438
439 def _exempted(self, filepath):
440 start_matches = (
441 filepath.startswith(exception)
442 for exception in self._exceptions
443 )
444 pattern_matches = (
445 re.match(pattern, filepath)
446 for pattern in self._exception_patterns
447 )
448 candidates = itertools.chain(start_matches, pattern_matches)
449 return any(candidates)
450
451 def _remap_input(self, operation, path, *args, **kw):
452 """Called for path inputs"""
453 if operation in self.write_ops and not self._ok(path):
454 self._violation(operation, os.path.realpath(path), *args, **kw)
455 return path
456
457 def _remap_pair(self, operation, src, dst, *args, **kw):
458 """Called for path pairs like rename, link, and symlink operations"""
459 if not self._ok(src) or not self._ok(dst):
460 self._violation(operation, src, dst, *args, **kw)
461 return (src, dst)
462
463 def open(self, file, flags, mode=0o777, *args, **kw):
464 """Called for low-level os.open()"""
465 if flags & WRITE_FLAGS and not self._ok(file):
466 self._violation("os.open", file, flags, mode, *args, **kw)
467 return _os.open(file, flags, mode, *args, **kw)
468
469
470 WRITE_FLAGS = functools.reduce(
471 operator.or_, [getattr(_os, a, 0) for a in
472 "O_WRONLY O_RDWR O_APPEND O_CREAT O_TRUNC O_TEMPORARY".split()]
473 )
474
475
476 class SandboxViolation(DistutilsError):
477 """A setup script attempted to modify the filesystem outside the sandbox"""
478
479 def __str__(self):
480 return """SandboxViolation: %s%r %s
481
482 The package setup script has attempted to modify files on your system
483 that are not within the EasyInstall build area, and has been aborted.
484
485 This package cannot be safely installed by EasyInstall, and may not
486 support alternate installation locations even if you run its setup
487 script by hand. Please inform the package's author and the EasyInstall
488 maintainers to find out if a fix or workaround is available.""" % self.args
489
490
491 #
OLDNEW
« no previous file with comments | « third_party/google-endpoints/setuptools/py36compat.py ('k') | third_party/google-endpoints/setuptools/script.tmpl » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698