diff options
author | Victor Stinner <vstinner@python.org> | 2020-03-09 22:45:59 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-03-09 22:45:59 (GMT) |
commit | addaaaa946855ad59c8f5c698aa0891d7e44f018 (patch) | |
tree | 0de7ede6cf1117055d6a45120a73d3c866e3f0bb /Lib/_bootsubprocess.py | |
parent | 9ad58acbe8b90b4d0f2d2e139e38bb5aa32b7fb6 (diff) | |
download | cpython-addaaaa946855ad59c8f5c698aa0891d7e44f018.zip cpython-addaaaa946855ad59c8f5c698aa0891d7e44f018.tar.gz cpython-addaaaa946855ad59c8f5c698aa0891d7e44f018.tar.bz2 |
bpo-39763: Add _bootsubprocess to build Python on AIX (GH-18872)
Add _bootsubprocess module to bootstrap Python: subprocess
implementation which only uses the os module.
On AIX, distutils.util uses _aix_support which calls
subprocess.check_output(), before the _posixsubprocess module is
built. Implement check_output() with os.system() in _bootsubprocess.
Diffstat (limited to 'Lib/_bootsubprocess.py')
-rw-r--r-- | Lib/_bootsubprocess.py | 102 |
1 files changed, 102 insertions, 0 deletions
diff --git a/Lib/_bootsubprocess.py b/Lib/_bootsubprocess.py new file mode 100644 index 0000000..962301a --- /dev/null +++ b/Lib/_bootsubprocess.py @@ -0,0 +1,102 @@ +""" +Basic subprocess implementation for POSIX which only uses os functions. Only +implement features required by setup.py to build C extension modules when +subprocess is unavailable. setup.py is not used on Windows. +""" +import os + + +# distutils.spawn used by distutils.command.build_ext +# calls subprocess.Popen().wait() +class Popen: + def __init__(self, cmd, env=None): + self._cmd = cmd + self._env = env + self.returncode = None + + def wait(self): + pid = os.fork() + if pid == 0: + # Child process + try: + if self._env is not None: + os.execve(self._cmd[0], self._cmd, self._env) + else: + os.execv(self._cmd[0], self._cmd) + finally: + os._exit(1) + else: + # Parent process + pid, status = os.waitpid(pid, 0) + if os.WIFSIGNALED(status): + self.returncode = -os.WTERMSIG(status) + elif os.WIFEXITED(status): + self.returncode = os.WEXITSTATUS(status) + elif os.WIFSTOPPED(status): + self.returncode = -os.WSTOPSIG(status) + else: + raise Exception(f"unknown child process exit status: {status!r}") + + return self.returncode + + +def _check_cmd(cmd): + # Use regex [a-zA-Z0-9./-]+: reject empty string, space, etc. + safe_chars = [] + for first, last in (("a", "z"), ("A", "Z"), ("0", "9")): + for ch in range(ord(first), ord(last) + 1): + safe_chars.append(chr(ch)) + safe_chars.append("./-") + safe_chars = ''.join(safe_chars) + + if isinstance(cmd, (tuple, list)): + check_strs = cmd + elif isinstance(cmd, str): + check_strs = [cmd] + else: + return False + + for arg in check_strs: + if not isinstance(arg, str): + return False + if not arg: + # reject empty string + return False + for ch in arg: + if ch not in safe_chars: + return False + + return True + + +# _aix_support used by distutil.util calls subprocess.check_output() +def check_output(cmd, **kwargs): + if kwargs: + raise NotImplementedError(repr(kwargs)) + + if not _check_cmd(cmd): + raise ValueError(f"unsupported command: {cmd!r}") + + tmp_filename = "check_output.tmp" + if not isinstance(cmd, str): + cmd = " ".join(cmd) + cmd = f"{cmd} >{tmp_filename}" + + try: + # system() spawns a shell + status = os.system(cmd) + if status: + raise ValueError(f"Command {cmd!r} failed with status {status!r}") + + try: + with open(tmp_filename, "rb") as fp: + stdout = fp.read() + except FileNotFoundError: + stdout = b'' + finally: + try: + os.unlink(tmp_filename) + except OSError: + pass + + return stdout |