comparison env/lib/python3.9/site-packages/setuptools/sandbox.py @ 0:4f3585e2f14b draft default tip

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