Mercurial > repos > shellac > sam_consensus_v3
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()) |
