Path: blob/main/test/lib/python3.9/site-packages/setuptools/sandbox.py
4798 views
import os1import sys2import tempfile3import operator4import functools5import itertools6import re7import contextlib8import pickle9import textwrap10import builtins1112import pkg_resources13from distutils.errors import DistutilsError14from pkg_resources import working_set1516if sys.platform.startswith('java'):17import org.python.modules.posix.PosixModule as _os18else:19_os = sys.modules[os.name]20try:21_file = file22except NameError:23_file = None24_open = open252627__all__ = [28"AbstractSandbox",29"DirectorySandbox",30"SandboxViolation",31"run_setup",32]333435def _execfile(filename, globals, locals=None):36"""37Python 3 implementation of execfile.38"""39mode = 'rb'40with open(filename, mode) as stream:41script = stream.read()42if locals is None:43locals = globals44code = compile(script, filename, 'exec')45exec(code, globals, locals)464748@contextlib.contextmanager49def save_argv(repl=None):50saved = sys.argv[:]51if repl is not None:52sys.argv[:] = repl53try:54yield saved55finally:56sys.argv[:] = saved575859@contextlib.contextmanager60def save_path():61saved = sys.path[:]62try:63yield saved64finally:65sys.path[:] = saved666768@contextlib.contextmanager69def override_temp(replacement):70"""71Monkey-patch tempfile.tempdir with replacement, ensuring it exists72"""73os.makedirs(replacement, exist_ok=True)7475saved = tempfile.tempdir7677tempfile.tempdir = replacement7879try:80yield81finally:82tempfile.tempdir = saved838485@contextlib.contextmanager86def pushd(target):87saved = os.getcwd()88os.chdir(target)89try:90yield saved91finally:92os.chdir(saved)939495class UnpickleableException(Exception):96"""97An exception representing another Exception that could not be pickled.98"""99100@staticmethod101def dump(type, exc):102"""103Always return a dumped (pickled) type and exc. If exc can't be pickled,104wrap it in UnpickleableException first.105"""106try:107return pickle.dumps(type), pickle.dumps(exc)108except Exception:109# get UnpickleableException inside the sandbox110from setuptools.sandbox import UnpickleableException as cls111112return cls.dump(cls, cls(repr(exc)))113114115class ExceptionSaver:116"""117A Context Manager that will save an exception, serialized, and restore it118later.119"""120121def __enter__(self):122return self123124def __exit__(self, type, exc, tb):125if not exc:126return127128# dump the exception129self._saved = UnpickleableException.dump(type, exc)130self._tb = tb131132# suppress the exception133return True134135def resume(self):136"restore and re-raise any exception"137138if '_saved' not in vars(self):139return140141type, exc = map(pickle.loads, self._saved)142raise exc.with_traceback(self._tb)143144145@contextlib.contextmanager146def save_modules():147"""148Context in which imported modules are saved.149150Translates exceptions internal to the context into the equivalent exception151outside the context.152"""153saved = sys.modules.copy()154with ExceptionSaver() as saved_exc:155yield saved156157sys.modules.update(saved)158# remove any modules imported since159del_modules = (160mod_name161for mod_name in sys.modules162if mod_name not in saved163# exclude any encodings modules. See #285164and not mod_name.startswith('encodings.')165)166_clear_modules(del_modules)167168saved_exc.resume()169170171def _clear_modules(module_names):172for mod_name in list(module_names):173del sys.modules[mod_name]174175176@contextlib.contextmanager177def save_pkg_resources_state():178saved = pkg_resources.__getstate__()179try:180yield saved181finally:182pkg_resources.__setstate__(saved)183184185@contextlib.contextmanager186def setup_context(setup_dir):187temp_dir = os.path.join(setup_dir, 'temp')188with save_pkg_resources_state():189with save_modules():190with save_path():191hide_setuptools()192with save_argv():193with override_temp(temp_dir):194with pushd(setup_dir):195# ensure setuptools commands are available196__import__('setuptools')197yield198199200_MODULES_TO_HIDE = {201'setuptools',202'distutils',203'pkg_resources',204'Cython',205'_distutils_hack',206}207208209def _needs_hiding(mod_name):210"""211>>> _needs_hiding('setuptools')212True213>>> _needs_hiding('pkg_resources')214True215>>> _needs_hiding('setuptools_plugin')216False217>>> _needs_hiding('setuptools.__init__')218True219>>> _needs_hiding('distutils')220True221>>> _needs_hiding('os')222False223>>> _needs_hiding('Cython')224True225"""226base_module = mod_name.split('.', 1)[0]227return base_module in _MODULES_TO_HIDE228229230def hide_setuptools():231"""232Remove references to setuptools' modules from sys.modules to allow the233invocation to import the most appropriate setuptools. This technique is234necessary to avoid issues such as #315 where setuptools upgrading itself235would fail to find a function declared in the metadata.236"""237_distutils_hack = sys.modules.get('_distutils_hack', None)238if _distutils_hack is not None:239_distutils_hack.remove_shim()240241modules = filter(_needs_hiding, sys.modules)242_clear_modules(modules)243244245def run_setup(setup_script, args):246"""Run a distutils setup script, sandboxed in its directory"""247setup_dir = os.path.abspath(os.path.dirname(setup_script))248with setup_context(setup_dir):249try:250sys.argv[:] = [setup_script] + list(args)251sys.path.insert(0, setup_dir)252# reset to include setup dir, w/clean callback list253working_set.__init__()254working_set.callbacks.append(lambda dist: dist.activate())255256with DirectorySandbox(setup_dir):257ns = dict(__file__=setup_script, __name__='__main__')258_execfile(setup_script, ns)259except SystemExit as v:260if v.args and v.args[0]:261raise262# Normal exit, just return263264265class AbstractSandbox:266"""Wrap 'os' module and 'open()' builtin for virtualizing setup scripts"""267268_active = False269270def __init__(self):271self._attrs = [272name273for name in dir(_os)274if not name.startswith('_') and hasattr(self, name)275]276277def _copy(self, source):278for name in self._attrs:279setattr(os, name, getattr(source, name))280281def __enter__(self):282self._copy(self)283if _file:284builtins.file = self._file285builtins.open = self._open286self._active = True287288def __exit__(self, exc_type, exc_value, traceback):289self._active = False290if _file:291builtins.file = _file292builtins.open = _open293self._copy(_os)294295def run(self, func):296"""Run 'func' under os sandboxing"""297with self:298return func()299300def _mk_dual_path_wrapper(name):301original = getattr(_os, name)302303def wrap(self, src, dst, *args, **kw):304if self._active:305src, dst = self._remap_pair(name, src, dst, *args, **kw)306return original(src, dst, *args, **kw)307308return wrap309310for name in ["rename", "link", "symlink"]:311if hasattr(_os, name):312locals()[name] = _mk_dual_path_wrapper(name)313314def _mk_single_path_wrapper(name, original=None):315original = original or getattr(_os, name)316317def wrap(self, path, *args, **kw):318if self._active:319path = self._remap_input(name, path, *args, **kw)320return original(path, *args, **kw)321322return wrap323324if _file:325_file = _mk_single_path_wrapper('file', _file)326_open = _mk_single_path_wrapper('open', _open)327for name in [328"stat",329"listdir",330"chdir",331"open",332"chmod",333"chown",334"mkdir",335"remove",336"unlink",337"rmdir",338"utime",339"lchown",340"chroot",341"lstat",342"startfile",343"mkfifo",344"mknod",345"pathconf",346"access",347]:348if hasattr(_os, name):349locals()[name] = _mk_single_path_wrapper(name)350351def _mk_single_with_return(name):352original = getattr(_os, name)353354def wrap(self, path, *args, **kw):355if self._active:356path = self._remap_input(name, path, *args, **kw)357return self._remap_output(name, original(path, *args, **kw))358return original(path, *args, **kw)359360return wrap361362for name in ['readlink', 'tempnam']:363if hasattr(_os, name):364locals()[name] = _mk_single_with_return(name)365366def _mk_query(name):367original = getattr(_os, name)368369def wrap(self, *args, **kw):370retval = original(*args, **kw)371if self._active:372return self._remap_output(name, retval)373return retval374375return wrap376377for name in ['getcwd', 'tmpnam']:378if hasattr(_os, name):379locals()[name] = _mk_query(name)380381def _validate_path(self, path):382"""Called to remap or validate any path, whether input or output"""383return path384385def _remap_input(self, operation, path, *args, **kw):386"""Called for path inputs"""387return self._validate_path(path)388389def _remap_output(self, operation, path):390"""Called for path outputs"""391return self._validate_path(path)392393def _remap_pair(self, operation, src, dst, *args, **kw):394"""Called for path pairs like rename, link, and symlink operations"""395return (396self._remap_input(operation + '-from', src, *args, **kw),397self._remap_input(operation + '-to', dst, *args, **kw),398)399400401if hasattr(os, 'devnull'):402_EXCEPTIONS = [os.devnull]403else:404_EXCEPTIONS = []405406407class DirectorySandbox(AbstractSandbox):408"""Restrict operations to a single subdirectory - pseudo-chroot"""409410write_ops = dict.fromkeys(411[412"open",413"chmod",414"chown",415"mkdir",416"remove",417"unlink",418"rmdir",419"utime",420"lchown",421"chroot",422"mkfifo",423"mknod",424"tempnam",425]426)427428_exception_patterns = []429"exempt writing to paths that match the pattern"430431def __init__(self, sandbox, exceptions=_EXCEPTIONS):432self._sandbox = os.path.normcase(os.path.realpath(sandbox))433self._prefix = os.path.join(self._sandbox, '')434self._exceptions = [435os.path.normcase(os.path.realpath(path)) for path in exceptions436]437AbstractSandbox.__init__(self)438439def _violation(self, operation, *args, **kw):440from setuptools.sandbox import SandboxViolation441442raise SandboxViolation(operation, args, kw)443444if _file:445446def _file(self, path, mode='r', *args, **kw):447if mode not in ('r', 'rt', 'rb', 'rU', 'U') and not self._ok(path):448self._violation("file", path, mode, *args, **kw)449return _file(path, mode, *args, **kw)450451def _open(self, path, mode='r', *args, **kw):452if mode not in ('r', 'rt', 'rb', 'rU', 'U') and not self._ok(path):453self._violation("open", path, mode, *args, **kw)454return _open(path, mode, *args, **kw)455456def tmpnam(self):457self._violation("tmpnam")458459def _ok(self, path):460active = self._active461try:462self._active = False463realpath = os.path.normcase(os.path.realpath(path))464return (465self._exempted(realpath)466or realpath == self._sandbox467or realpath.startswith(self._prefix)468)469finally:470self._active = active471472def _exempted(self, filepath):473start_matches = (474filepath.startswith(exception) for exception in self._exceptions475)476pattern_matches = (477re.match(pattern, filepath) for pattern in self._exception_patterns478)479candidates = itertools.chain(start_matches, pattern_matches)480return any(candidates)481482def _remap_input(self, operation, path, *args, **kw):483"""Called for path inputs"""484if operation in self.write_ops and not self._ok(path):485self._violation(operation, os.path.realpath(path), *args, **kw)486return path487488def _remap_pair(self, operation, src, dst, *args, **kw):489"""Called for path pairs like rename, link, and symlink operations"""490if not self._ok(src) or not self._ok(dst):491self._violation(operation, src, dst, *args, **kw)492return (src, dst)493494def open(self, file, flags, mode=0o777, *args, **kw):495"""Called for low-level os.open()"""496if flags & WRITE_FLAGS and not self._ok(file):497self._violation("os.open", file, flags, mode, *args, **kw)498return _os.open(file, flags, mode, *args, **kw)499500501WRITE_FLAGS = functools.reduce(502operator.or_,503[504getattr(_os, a, 0)505for a in "O_WRONLY O_RDWR O_APPEND O_CREAT O_TRUNC O_TEMPORARY".split()506],507)508509510class SandboxViolation(DistutilsError):511"""A setup script attempted to modify the filesystem outside the sandbox"""512513tmpl = textwrap.dedent(514"""515SandboxViolation: {cmd}{args!r} {kwargs}516517The package setup script has attempted to modify files on your system518that are not within the EasyInstall build area, and has been aborted.519520This package cannot be safely installed by EasyInstall, and may not521support alternate installation locations even if you run its setup522script by hand. Please inform the package's author and the EasyInstall523maintainers to find out if a fix or workaround is available.524"""525).lstrip()526527def __str__(self):528cmd, args, kwargs = self.args529return self.tmpl.format(**locals())530531532