diff options
author | shadchin <[email protected]> | 2022-02-10 16:44:39 +0300 |
---|---|---|
committer | Daniil Cherednik <[email protected]> | 2022-02-10 16:44:39 +0300 |
commit | e9656aae26e0358d5378e5b63dcac5c8dbe0e4d0 (patch) | |
tree | 64175d5cadab313b3e7039ebaa06c5bc3295e274 /contrib/tools/python3/src/Lib/subprocess.py | |
parent | 2598ef1d0aee359b4b6d5fdd1758916d5907d04f (diff) |
Restoring authorship annotation for <[email protected]>. Commit 2 of 2.
Diffstat (limited to 'contrib/tools/python3/src/Lib/subprocess.py')
-rw-r--r-- | contrib/tools/python3/src/Lib/subprocess.py | 942 |
1 files changed, 471 insertions, 471 deletions
diff --git a/contrib/tools/python3/src/Lib/subprocess.py b/contrib/tools/python3/src/Lib/subprocess.py index bcae14a4113..4effc1d8b3f 100644 --- a/contrib/tools/python3/src/Lib/subprocess.py +++ b/contrib/tools/python3/src/Lib/subprocess.py @@ -41,66 +41,66 @@ getstatusoutput(...): Runs a command in the shell, waits for it to complete, then returns a (exitcode, output) tuple """ -import builtins -import errno +import builtins +import errno import io import os import time import signal -import sys -import threading +import sys +import threading import warnings -import contextlib +import contextlib from time import monotonic as _time -import types - -try: - import pwd -except ImportError: - pwd = None -try: - import grp -except ImportError: - grp = None - -__all__ = ["Popen", "PIPE", "STDOUT", "call", "check_call", "getstatusoutput", - "getoutput", "check_output", "run", "CalledProcessError", "DEVNULL", - "SubprocessError", "TimeoutExpired", "CompletedProcess"] - # NOTE: We intentionally exclude list2cmdline as it is - # considered an internal implementation detail. issue10838. - -try: - import msvcrt - import _winapi - _mswindows = True -except ModuleNotFoundError: - _mswindows = False - import _posixsubprocess - import select - import selectors -else: - from _winapi import (CREATE_NEW_CONSOLE, CREATE_NEW_PROCESS_GROUP, - STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, - STD_ERROR_HANDLE, SW_HIDE, - STARTF_USESTDHANDLES, STARTF_USESHOWWINDOW, - ABOVE_NORMAL_PRIORITY_CLASS, BELOW_NORMAL_PRIORITY_CLASS, - HIGH_PRIORITY_CLASS, IDLE_PRIORITY_CLASS, - NORMAL_PRIORITY_CLASS, REALTIME_PRIORITY_CLASS, - CREATE_NO_WINDOW, DETACHED_PROCESS, - CREATE_DEFAULT_ERROR_MODE, CREATE_BREAKAWAY_FROM_JOB) - - __all__.extend(["CREATE_NEW_CONSOLE", "CREATE_NEW_PROCESS_GROUP", - "STD_INPUT_HANDLE", "STD_OUTPUT_HANDLE", - "STD_ERROR_HANDLE", "SW_HIDE", - "STARTF_USESTDHANDLES", "STARTF_USESHOWWINDOW", - "STARTUPINFO", - "ABOVE_NORMAL_PRIORITY_CLASS", "BELOW_NORMAL_PRIORITY_CLASS", - "HIGH_PRIORITY_CLASS", "IDLE_PRIORITY_CLASS", - "NORMAL_PRIORITY_CLASS", "REALTIME_PRIORITY_CLASS", - "CREATE_NO_WINDOW", "DETACHED_PROCESS", - "CREATE_DEFAULT_ERROR_MODE", "CREATE_BREAKAWAY_FROM_JOB"]) - - +import types + +try: + import pwd +except ImportError: + pwd = None +try: + import grp +except ImportError: + grp = None + +__all__ = ["Popen", "PIPE", "STDOUT", "call", "check_call", "getstatusoutput", + "getoutput", "check_output", "run", "CalledProcessError", "DEVNULL", + "SubprocessError", "TimeoutExpired", "CompletedProcess"] + # NOTE: We intentionally exclude list2cmdline as it is + # considered an internal implementation detail. issue10838. + +try: + import msvcrt + import _winapi + _mswindows = True +except ModuleNotFoundError: + _mswindows = False + import _posixsubprocess + import select + import selectors +else: + from _winapi import (CREATE_NEW_CONSOLE, CREATE_NEW_PROCESS_GROUP, + STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, + STD_ERROR_HANDLE, SW_HIDE, + STARTF_USESTDHANDLES, STARTF_USESHOWWINDOW, + ABOVE_NORMAL_PRIORITY_CLASS, BELOW_NORMAL_PRIORITY_CLASS, + HIGH_PRIORITY_CLASS, IDLE_PRIORITY_CLASS, + NORMAL_PRIORITY_CLASS, REALTIME_PRIORITY_CLASS, + CREATE_NO_WINDOW, DETACHED_PROCESS, + CREATE_DEFAULT_ERROR_MODE, CREATE_BREAKAWAY_FROM_JOB) + + __all__.extend(["CREATE_NEW_CONSOLE", "CREATE_NEW_PROCESS_GROUP", + "STD_INPUT_HANDLE", "STD_OUTPUT_HANDLE", + "STD_ERROR_HANDLE", "SW_HIDE", + "STARTF_USESTDHANDLES", "STARTF_USESHOWWINDOW", + "STARTUPINFO", + "ABOVE_NORMAL_PRIORITY_CLASS", "BELOW_NORMAL_PRIORITY_CLASS", + "HIGH_PRIORITY_CLASS", "IDLE_PRIORITY_CLASS", + "NORMAL_PRIORITY_CLASS", "REALTIME_PRIORITY_CLASS", + "CREATE_NO_WINDOW", "DETACHED_PROCESS", + "CREATE_DEFAULT_ERROR_MODE", "CREATE_BREAKAWAY_FROM_JOB"]) + + # Exception classes used by this module. class SubprocessError(Exception): pass @@ -181,7 +181,7 @@ if _mswindows: self.wShowWindow = wShowWindow self.lpAttributeList = lpAttributeList or {"handle_list": []} - def copy(self): + def copy(self): attr_list = self.lpAttributeList.copy() if 'handle_list' in attr_list: attr_list['handle_list'] = list(attr_list['handle_list']) @@ -212,54 +212,54 @@ if _mswindows: return "%s(%d)" % (self.__class__.__name__, int(self)) __del__ = Close -else: - # When select or poll has indicated that the file is writable, - # we can write up to _PIPE_BUF bytes without risk of blocking. - # POSIX defines PIPE_BUF as >= 512. - _PIPE_BUF = getattr(select, 'PIPE_BUF', 512) - - # poll/select have the advantage of not requiring any extra file - # descriptor, contrarily to epoll/kqueue (also, they require a single - # syscall). - if hasattr(selectors, 'PollSelector'): - _PopenSelector = selectors.PollSelector - else: - _PopenSelector = selectors.SelectSelector - - -if _mswindows: - # On Windows we just need to close `Popen._handle` when we no longer need - # it, so that the kernel can free it. `Popen._handle` gets closed - # implicitly when the `Popen` instance is finalized (see `Handle.__del__`, - # which is calling `CloseHandle` as requested in [1]), so there is nothing - # for `_cleanup` to do. - # - # [1] https://docs.microsoft.com/en-us/windows/desktop/ProcThread/ - # creating-processes - _active = None - - def _cleanup(): - pass -else: - # This lists holds Popen instances for which the underlying process had not - # exited at the time its __del__ method got called: those processes are - # wait()ed for synchronously from _cleanup() when a new Popen object is - # created, to avoid zombie processes. - _active = [] - - def _cleanup(): - if _active is None: - return - for inst in _active[:]: - res = inst._internal_poll(_deadstate=sys.maxsize) - if res is not None: - try: - _active.remove(inst) - except ValueError: - # This can happen if two threads create a new Popen instance. - # It's harmless that it was already removed, so ignore. - pass - +else: + # When select or poll has indicated that the file is writable, + # we can write up to _PIPE_BUF bytes without risk of blocking. + # POSIX defines PIPE_BUF as >= 512. + _PIPE_BUF = getattr(select, 'PIPE_BUF', 512) + + # poll/select have the advantage of not requiring any extra file + # descriptor, contrarily to epoll/kqueue (also, they require a single + # syscall). + if hasattr(selectors, 'PollSelector'): + _PopenSelector = selectors.PollSelector + else: + _PopenSelector = selectors.SelectSelector + + +if _mswindows: + # On Windows we just need to close `Popen._handle` when we no longer need + # it, so that the kernel can free it. `Popen._handle` gets closed + # implicitly when the `Popen` instance is finalized (see `Handle.__del__`, + # which is calling `CloseHandle` as requested in [1]), so there is nothing + # for `_cleanup` to do. + # + # [1] https://docs.microsoft.com/en-us/windows/desktop/ProcThread/ + # creating-processes + _active = None + + def _cleanup(): + pass +else: + # This lists holds Popen instances for which the underlying process had not + # exited at the time its __del__ method got called: those processes are + # wait()ed for synchronously from _cleanup() when a new Popen object is + # created, to avoid zombie processes. + _active = [] + + def _cleanup(): + if _active is None: + return + for inst in _active[:]: + res = inst._internal_poll(_deadstate=sys.maxsize) + if res is not None: + try: + _active.remove(inst) + except ValueError: + # This can happen if two threads create a new Popen instance. + # It's harmless that it was already removed, so ignore. + pass + PIPE = -1 STDOUT = -2 DEVNULL = -3 @@ -326,7 +326,7 @@ def _args_from_interpreter_flags(): if dev_mode: args.extend(('-X', 'dev')) for opt in ('faulthandler', 'tracemalloc', 'importtime', - 'showrefcount', 'utf8', 'oldparser'): + 'showrefcount', 'utf8', 'oldparser'): if opt in xoptions: value = xoptions[opt] if value is True: @@ -404,7 +404,7 @@ def check_output(*popenargs, timeout=None, **kwargs): b'when in the course of barman events\n' By default, all communication is in bytes, and therefore any "input" - should be bytes, and the return value will be bytes. If in text mode, + should be bytes, and the return value will be bytes. If in text mode, any "input" should be a string, and the return value will be a string decoded according to locale encoding, or by "encoding" if set. Text mode is triggered by setting any of text, encoding, errors or universal_newlines. @@ -415,11 +415,11 @@ def check_output(*popenargs, timeout=None, **kwargs): if 'input' in kwargs and kwargs['input'] is None: # Explicitly passing input=None was previously equivalent to passing an # empty string. That is maintained here for backwards compatibility. - if kwargs.get('universal_newlines') or kwargs.get('text'): - empty = '' - else: - empty = b'' - kwargs['input'] = empty + if kwargs.get('universal_newlines') or kwargs.get('text'): + empty = '' + else: + empty = b'' + kwargs['input'] = empty return run(*popenargs, stdout=PIPE, timeout=timeout, check=True, **kwargs).stdout @@ -451,9 +451,9 @@ class CompletedProcess(object): args.append('stderr={!r}'.format(self.stderr)) return "{}({})".format(type(self).__name__, ', '.join(args)) - __class_getitem__ = classmethod(types.GenericAlias) - - + __class_getitem__ = classmethod(types.GenericAlias) + + def check_returncode(self): """Raise CalledProcessError if the exit code is non-zero.""" if self.returncode: @@ -491,12 +491,12 @@ def run(*popenargs, The other arguments are the same as for the Popen constructor. """ if input is not None: - if kwargs.get('stdin') is not None: + if kwargs.get('stdin') is not None: raise ValueError('stdin and input arguments may not both be used.') kwargs['stdin'] = PIPE if capture_output: - if kwargs.get('stdout') is not None or kwargs.get('stderr') is not None: + if kwargs.get('stdout') is not None or kwargs.get('stderr') is not None: raise ValueError('stdout and stderr arguments may not be used ' 'with capture_output.') kwargs['stdout'] = PIPE @@ -505,20 +505,20 @@ def run(*popenargs, with Popen(*popenargs, **kwargs) as process: try: stdout, stderr = process.communicate(input, timeout=timeout) - except TimeoutExpired as exc: + except TimeoutExpired as exc: process.kill() - if _mswindows: - # Windows accumulates the output in a single blocking - # read() call run on child threads, with the timeout - # being done in a join() on those threads. communicate() - # _after_ kill() is required to collect that and add it - # to the exception. - exc.stdout, exc.stderr = process.communicate() - else: - # POSIX _communicate already populated the output so - # far into the TimeoutExpired exception. - process.wait() - raise + if _mswindows: + # Windows accumulates the output in a single blocking + # read() call run on child threads, with the timeout + # being done in a join() on those threads. communicate() + # _after_ kill() is required to collect that and add it + # to the exception. + exc.stdout, exc.stderr = process.communicate() + else: + # POSIX _communicate already populated the output so + # far into the TimeoutExpired exception. + process.wait() + raise except: # Including KeyboardInterrupt, communicate handled that. process.kill() # We don't call process.wait() as .__exit__ does that for us. @@ -562,7 +562,7 @@ def list2cmdline(seq): # "Parsing C++ Command-Line Arguments" result = [] needquote = False - for arg in map(os.fsdecode, seq): + for arg in map(os.fsdecode, seq): bs_buf = [] # Add a space to separate this argument from the others @@ -647,56 +647,56 @@ def getoutput(cmd): return getstatusoutput(cmd)[1] -def _use_posix_spawn(): - """Check if posix_spawn() can be used for subprocess. - - subprocess requires a posix_spawn() implementation that properly reports - errors to the parent process, & sets errno on the following failures: - - * Process attribute actions failed. - * File actions failed. - * exec() failed. - - Prefer an implementation which can use vfork() in some cases for best - performance. - """ - if _mswindows or not hasattr(os, 'posix_spawn'): - # os.posix_spawn() is not available - return False - - if sys.platform == 'darwin': - # posix_spawn() is a syscall on macOS and properly reports errors - return True - - # Check libc name and runtime libc version - try: - ver = os.confstr('CS_GNU_LIBC_VERSION') - # parse 'glibc 2.28' as ('glibc', (2, 28)) - parts = ver.split(maxsplit=1) - if len(parts) != 2: - # reject unknown format - raise ValueError - libc = parts[0] - version = tuple(map(int, parts[1].split('.'))) - - if sys.platform == 'linux' and libc == 'glibc' and version >= (2, 24): - # glibc 2.24 has a new Linux posix_spawn implementation using vfork - # which properly reports errors to the parent process. - return True - # Note: Don't use the implementation in earlier glibc because it doesn't - # use vfork (even if glibc 2.26 added a pipe to properly report errors - # to the parent process). - except (AttributeError, ValueError, OSError): - # os.confstr() or CS_GNU_LIBC_VERSION value not available - pass - - # By default, assume that posix_spawn() does not properly report errors. - return False - - -_USE_POSIX_SPAWN = _use_posix_spawn() - - +def _use_posix_spawn(): + """Check if posix_spawn() can be used for subprocess. + + subprocess requires a posix_spawn() implementation that properly reports + errors to the parent process, & sets errno on the following failures: + + * Process attribute actions failed. + * File actions failed. + * exec() failed. + + Prefer an implementation which can use vfork() in some cases for best + performance. + """ + if _mswindows or not hasattr(os, 'posix_spawn'): + # os.posix_spawn() is not available + return False + + if sys.platform == 'darwin': + # posix_spawn() is a syscall on macOS and properly reports errors + return True + + # Check libc name and runtime libc version + try: + ver = os.confstr('CS_GNU_LIBC_VERSION') + # parse 'glibc 2.28' as ('glibc', (2, 28)) + parts = ver.split(maxsplit=1) + if len(parts) != 2: + # reject unknown format + raise ValueError + libc = parts[0] + version = tuple(map(int, parts[1].split('.'))) + + if sys.platform == 'linux' and libc == 'glibc' and version >= (2, 24): + # glibc 2.24 has a new Linux posix_spawn implementation using vfork + # which properly reports errors to the parent process. + return True + # Note: Don't use the implementation in earlier glibc because it doesn't + # use vfork (even if glibc 2.26 added a pipe to properly report errors + # to the parent process). + except (AttributeError, ValueError, OSError): + # os.confstr() or CS_GNU_LIBC_VERSION value not available + pass + + # By default, assume that posix_spawn() does not properly report errors. + return False + + +_USE_POSIX_SPAWN = _use_posix_spawn() + + class Popen(object): """ Execute a child program in a new process. @@ -735,14 +735,14 @@ class Popen(object): start_new_session (POSIX only) - group (POSIX only) - - extra_groups (POSIX only) - - user (POSIX only) - - umask (POSIX only) - + group (POSIX only) + + extra_groups (POSIX only) + + user (POSIX only) + + umask (POSIX only) + pass_fds (POSIX only) encoding and errors: Text mode encoding and error handling to use for @@ -759,8 +759,8 @@ class Popen(object): shell=False, cwd=None, env=None, universal_newlines=None, startupinfo=None, creationflags=0, restore_signals=True, start_new_session=False, - pass_fds=(), *, user=None, group=None, extra_groups=None, - encoding=None, errors=None, text=None, umask=-1): + pass_fds=(), *, user=None, group=None, extra_groups=None, + encoding=None, errors=None, text=None, umask=-1): """Create new Popen instance.""" _cleanup() # Held while anything is calling waitpid before returncode has been @@ -849,93 +849,93 @@ class Popen(object): self._closed_child_pipe_fds = False - if self.text_mode: - if bufsize == 1: - line_buffering = True - # Use the default buffer size for the underlying binary streams - # since they don't support line buffering. - bufsize = -1 - else: - line_buffering = False - - gid = None - if group is not None: - if not hasattr(os, 'setregid'): - raise ValueError("The 'group' parameter is not supported on the " - "current platform") - - elif isinstance(group, str): - if grp is None: - raise ValueError("The group parameter cannot be a string " - "on systems without the grp module") - - gid = grp.getgrnam(group).gr_gid - elif isinstance(group, int): - gid = group - else: - raise TypeError("Group must be a string or an integer, not {}" - .format(type(group))) - - if gid < 0: - raise ValueError(f"Group ID cannot be negative, got {gid}") - - gids = None - if extra_groups is not None: - if not hasattr(os, 'setgroups'): - raise ValueError("The 'extra_groups' parameter is not " - "supported on the current platform") - - elif isinstance(extra_groups, str): - raise ValueError("Groups must be a list, not a string") - - gids = [] - for extra_group in extra_groups: - if isinstance(extra_group, str): - if grp is None: - raise ValueError("Items in extra_groups cannot be " - "strings on systems without the " - "grp module") - - gids.append(grp.getgrnam(extra_group).gr_gid) - elif isinstance(extra_group, int): - gids.append(extra_group) - else: - raise TypeError("Items in extra_groups must be a string " - "or integer, not {}" - .format(type(extra_group))) - - # make sure that the gids are all positive here so we can do less - # checking in the C code - for gid_check in gids: - if gid_check < 0: - raise ValueError(f"Group ID cannot be negative, got {gid_check}") - - uid = None - if user is not None: - if not hasattr(os, 'setreuid'): - raise ValueError("The 'user' parameter is not supported on " - "the current platform") - - elif isinstance(user, str): - if pwd is None: - raise ValueError("The user parameter cannot be a string " - "on systems without the pwd module") - - uid = pwd.getpwnam(user).pw_uid - elif isinstance(user, int): - uid = user - else: - raise TypeError("User must be a string or an integer") - - if uid < 0: - raise ValueError(f"User ID cannot be negative, got {uid}") - + if self.text_mode: + if bufsize == 1: + line_buffering = True + # Use the default buffer size for the underlying binary streams + # since they don't support line buffering. + bufsize = -1 + else: + line_buffering = False + + gid = None + if group is not None: + if not hasattr(os, 'setregid'): + raise ValueError("The 'group' parameter is not supported on the " + "current platform") + + elif isinstance(group, str): + if grp is None: + raise ValueError("The group parameter cannot be a string " + "on systems without the grp module") + + gid = grp.getgrnam(group).gr_gid + elif isinstance(group, int): + gid = group + else: + raise TypeError("Group must be a string or an integer, not {}" + .format(type(group))) + + if gid < 0: + raise ValueError(f"Group ID cannot be negative, got {gid}") + + gids = None + if extra_groups is not None: + if not hasattr(os, 'setgroups'): + raise ValueError("The 'extra_groups' parameter is not " + "supported on the current platform") + + elif isinstance(extra_groups, str): + raise ValueError("Groups must be a list, not a string") + + gids = [] + for extra_group in extra_groups: + if isinstance(extra_group, str): + if grp is None: + raise ValueError("Items in extra_groups cannot be " + "strings on systems without the " + "grp module") + + gids.append(grp.getgrnam(extra_group).gr_gid) + elif isinstance(extra_group, int): + gids.append(extra_group) + else: + raise TypeError("Items in extra_groups must be a string " + "or integer, not {}" + .format(type(extra_group))) + + # make sure that the gids are all positive here so we can do less + # checking in the C code + for gid_check in gids: + if gid_check < 0: + raise ValueError(f"Group ID cannot be negative, got {gid_check}") + + uid = None + if user is not None: + if not hasattr(os, 'setreuid'): + raise ValueError("The 'user' parameter is not supported on " + "the current platform") + + elif isinstance(user, str): + if pwd is None: + raise ValueError("The user parameter cannot be a string " + "on systems without the pwd module") + + uid = pwd.getpwnam(user).pw_uid + elif isinstance(user, int): + uid = user + else: + raise TypeError("User must be a string or an integer") + + if uid < 0: + raise ValueError(f"User ID cannot be negative, got {uid}") + try: if p2cwrite != -1: self.stdin = io.open(p2cwrite, 'wb', bufsize) if self.text_mode: self.stdin = io.TextIOWrapper(self.stdin, write_through=True, - line_buffering=line_buffering, + line_buffering=line_buffering, encoding=encoding, errors=errors) if c2pread != -1: self.stdout = io.open(c2pread, 'rb', bufsize) @@ -954,9 +954,9 @@ class Popen(object): p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite, - restore_signals, - gid, gids, uid, umask, - start_new_session) + restore_signals, + gid, gids, uid, umask, + start_new_session) except: # Cleanup if the child failed starting. for f in filter(None, (self.stdin, self.stdout, self.stderr)): @@ -986,17 +986,17 @@ class Popen(object): raise - def __repr__(self): - obj_repr = ( - f"<{self.__class__.__name__}: " - f"returncode: {self.returncode} args: {self.args!r}>" - ) - if len(obj_repr) > 80: - obj_repr = obj_repr[:76] + "...>" - return obj_repr - - __class_getitem__ = classmethod(types.GenericAlias) - + def __repr__(self): + obj_repr = ( + f"<{self.__class__.__name__}: " + f"returncode: {self.returncode} args: {self.args!r}>" + ) + if len(obj_repr) > 80: + obj_repr = obj_repr[:76] + "...>" + return obj_repr + + __class_getitem__ = classmethod(types.GenericAlias) + @property def universal_newlines(self): # universal_newlines as retained as an alias of text_mode for API @@ -1169,16 +1169,16 @@ class Popen(object): return endtime - _time() - def _check_timeout(self, endtime, orig_timeout, stdout_seq, stderr_seq, - skip_check_and_raise=False): + def _check_timeout(self, endtime, orig_timeout, stdout_seq, stderr_seq, + skip_check_and_raise=False): """Convenience for checking if a timeout has expired.""" if endtime is None: return - if skip_check_and_raise or _time() > endtime: - raise TimeoutExpired( - self.args, orig_timeout, - output=b''.join(stdout_seq) if stdout_seq else None, - stderr=b''.join(stderr_seq) if stderr_seq else None) + if skip_check_and_raise or _time() > endtime: + raise TimeoutExpired( + self.args, orig_timeout, + output=b''.join(stdout_seq) if stdout_seq else None, + stderr=b''.join(stderr_seq) if stderr_seq else None) def wait(self, timeout=None): @@ -1204,35 +1204,35 @@ class Popen(object): pass raise # resume the KeyboardInterrupt - def _close_pipe_fds(self, - p2cread, p2cwrite, - c2pread, c2pwrite, - errread, errwrite): - # self._devnull is not always defined. - devnull_fd = getattr(self, '_devnull', None) - - with contextlib.ExitStack() as stack: - if _mswindows: - if p2cread != -1: - stack.callback(p2cread.Close) - if c2pwrite != -1: - stack.callback(c2pwrite.Close) - if errwrite != -1: - stack.callback(errwrite.Close) - else: - if p2cread != -1 and p2cwrite != -1 and p2cread != devnull_fd: - stack.callback(os.close, p2cread) - if c2pwrite != -1 and c2pread != -1 and c2pwrite != devnull_fd: - stack.callback(os.close, c2pwrite) - if errwrite != -1 and errread != -1 and errwrite != devnull_fd: - stack.callback(os.close, errwrite) - - if devnull_fd is not None: - stack.callback(os.close, devnull_fd) - - # Prevent a double close of these handles/fds from __init__ on error. - self._closed_child_pipe_fds = True - + def _close_pipe_fds(self, + p2cread, p2cwrite, + c2pread, c2pwrite, + errread, errwrite): + # self._devnull is not always defined. + devnull_fd = getattr(self, '_devnull', None) + + with contextlib.ExitStack() as stack: + if _mswindows: + if p2cread != -1: + stack.callback(p2cread.Close) + if c2pwrite != -1: + stack.callback(c2pwrite.Close) + if errwrite != -1: + stack.callback(errwrite.Close) + else: + if p2cread != -1 and p2cwrite != -1 and p2cread != devnull_fd: + stack.callback(os.close, p2cread) + if c2pwrite != -1 and c2pread != -1 and c2pwrite != devnull_fd: + stack.callback(os.close, c2pwrite) + if errwrite != -1 and errread != -1 and errwrite != devnull_fd: + stack.callback(os.close, errwrite) + + if devnull_fd is not None: + stack.callback(os.close, devnull_fd) + + # Prevent a double close of these handles/fds from __init__ on error. + self._closed_child_pipe_fds = True + if _mswindows: # # Windows methods @@ -1337,38 +1337,38 @@ class Popen(object): p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite, - unused_restore_signals, - unused_gid, unused_gids, unused_uid, - unused_umask, - unused_start_new_session): + unused_restore_signals, + unused_gid, unused_gids, unused_uid, + unused_umask, + unused_start_new_session): """Execute program (MS Windows version)""" assert not pass_fds, "pass_fds not supported on Windows." - if isinstance(args, str): - pass - elif isinstance(args, bytes): - if shell: - raise TypeError('bytes args is not allowed on Windows') - args = list2cmdline([args]) - elif isinstance(args, os.PathLike): - if shell: - raise TypeError('path-like args is not allowed when ' - 'shell is true') - args = list2cmdline([args]) - else: + if isinstance(args, str): + pass + elif isinstance(args, bytes): + if shell: + raise TypeError('bytes args is not allowed on Windows') + args = list2cmdline([args]) + elif isinstance(args, os.PathLike): + if shell: + raise TypeError('path-like args is not allowed when ' + 'shell is true') + args = list2cmdline([args]) + else: args = list2cmdline(args) - if executable is not None: - executable = os.fsdecode(executable) - + if executable is not None: + executable = os.fsdecode(executable) + # Process startup details if startupinfo is None: startupinfo = STARTUPINFO() else: # bpo-34044: Copy STARTUPINFO since it is modified above, # so the caller can reuse it multiple times. - startupinfo = startupinfo.copy() + startupinfo = startupinfo.copy() use_std_handles = -1 not in (p2cread, c2pwrite, errwrite) if use_std_handles: @@ -1410,11 +1410,11 @@ class Popen(object): comspec = os.environ.get("COMSPEC", "cmd.exe") args = '{} /c "{}"'.format (comspec, args) - if cwd is not None: - cwd = os.fsdecode(cwd) - - sys.audit("subprocess.Popen", executable, args, cwd, env) - + if cwd is not None: + cwd = os.fsdecode(cwd) + + sys.audit("subprocess.Popen", executable, args, cwd, env) + # Start the process try: hp, ht, pid, tid = _winapi.CreateProcess(executable, args, @@ -1423,7 +1423,7 @@ class Popen(object): int(not close_fds), creationflags, env, - cwd, + cwd, startupinfo) finally: # Child is launched. Close the parent's copy of those pipe @@ -1432,9 +1432,9 @@ class Popen(object): # output pipe are maintained in this process or else the # pipe will not close when the child process exits and the # ReadFile will hang. - self._close_pipe_fds(p2cread, p2cwrite, - c2pread, c2pwrite, - errread, errwrite) + self._close_pipe_fds(p2cread, p2cwrite, + c2pread, c2pwrite, + errread, errwrite) # Retain the process handle, but close the thread handle self._child_created = True @@ -1525,8 +1525,8 @@ class Popen(object): self.stderr.close() # All data exchanged. Translate lists into strings. - stdout = stdout[0] if stdout else None - stderr = stderr[0] if stderr else None + stdout = stdout[0] if stdout else None + stderr = stderr[0] if stderr else None return (stdout, stderr) @@ -1619,63 +1619,63 @@ class Popen(object): errread, errwrite) - def _posix_spawn(self, args, executable, env, restore_signals, - p2cread, p2cwrite, - c2pread, c2pwrite, - errread, errwrite): - """Execute program using os.posix_spawn().""" - if env is None: - env = os.environ - - kwargs = {} - if restore_signals: - # See _Py_RestoreSignals() in Python/pylifecycle.c - sigset = [] - for signame in ('SIGPIPE', 'SIGXFZ', 'SIGXFSZ'): - signum = getattr(signal, signame, None) - if signum is not None: - sigset.append(signum) - kwargs['setsigdef'] = sigset - - file_actions = [] - for fd in (p2cwrite, c2pread, errread): - if fd != -1: - file_actions.append((os.POSIX_SPAWN_CLOSE, fd)) - for fd, fd2 in ( - (p2cread, 0), - (c2pwrite, 1), - (errwrite, 2), - ): - if fd != -1: - file_actions.append((os.POSIX_SPAWN_DUP2, fd, fd2)) - if file_actions: - kwargs['file_actions'] = file_actions - - self.pid = os.posix_spawn(executable, args, env, **kwargs) - self._child_created = True - - self._close_pipe_fds(p2cread, p2cwrite, - c2pread, c2pwrite, - errread, errwrite) - + def _posix_spawn(self, args, executable, env, restore_signals, + p2cread, p2cwrite, + c2pread, c2pwrite, + errread, errwrite): + """Execute program using os.posix_spawn().""" + if env is None: + env = os.environ + + kwargs = {} + if restore_signals: + # See _Py_RestoreSignals() in Python/pylifecycle.c + sigset = [] + for signame in ('SIGPIPE', 'SIGXFZ', 'SIGXFSZ'): + signum = getattr(signal, signame, None) + if signum is not None: + sigset.append(signum) + kwargs['setsigdef'] = sigset + + file_actions = [] + for fd in (p2cwrite, c2pread, errread): + if fd != -1: + file_actions.append((os.POSIX_SPAWN_CLOSE, fd)) + for fd, fd2 in ( + (p2cread, 0), + (c2pwrite, 1), + (errwrite, 2), + ): + if fd != -1: + file_actions.append((os.POSIX_SPAWN_DUP2, fd, fd2)) + if file_actions: + kwargs['file_actions'] = file_actions + + self.pid = os.posix_spawn(executable, args, env, **kwargs) + self._child_created = True + + self._close_pipe_fds(p2cread, p2cwrite, + c2pread, c2pwrite, + errread, errwrite) + def _execute_child(self, args, executable, preexec_fn, close_fds, pass_fds, cwd, env, startupinfo, creationflags, shell, p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite, - restore_signals, - gid, gids, uid, umask, - start_new_session): + restore_signals, + gid, gids, uid, umask, + start_new_session): """Execute program (POSIX version)""" if isinstance(args, (str, bytes)): args = [args] - elif isinstance(args, os.PathLike): - if shell: - raise TypeError('path-like args is not allowed when ' - 'shell is true') - args = [args] + elif isinstance(args, os.PathLike): + if shell: + raise TypeError('path-like args is not allowed when ' + 'shell is true') + args = [args] else: args = list(args) @@ -1689,29 +1689,29 @@ class Popen(object): if executable is None: executable = args[0] - - sys.audit("subprocess.Popen", executable, args, cwd, env) - - if (_USE_POSIX_SPAWN - and os.path.dirname(executable) - and preexec_fn is None - and not close_fds - and not pass_fds - and cwd is None - and (p2cread == -1 or p2cread > 2) - and (c2pwrite == -1 or c2pwrite > 2) - and (errwrite == -1 or errwrite > 2) - and not start_new_session - and gid is None - and gids is None - and uid is None - and umask < 0): - self._posix_spawn(args, executable, env, restore_signals, - p2cread, p2cwrite, - c2pread, c2pwrite, - errread, errwrite) - return - + + sys.audit("subprocess.Popen", executable, args, cwd, env) + + if (_USE_POSIX_SPAWN + and os.path.dirname(executable) + and preexec_fn is None + and not close_fds + and not pass_fds + and cwd is None + and (p2cread == -1 or p2cread > 2) + and (c2pwrite == -1 or c2pwrite > 2) + and (errwrite == -1 or errwrite > 2) + and not start_new_session + and gid is None + and gids is None + and uid is None + and umask < 0): + self._posix_spawn(args, executable, env, restore_signals, + p2cread, p2cwrite, + c2pread, c2pwrite, + errread, errwrite) + return + orig_executable = executable # For transferring possible exec failure from child to parent. @@ -1758,17 +1758,17 @@ class Popen(object): p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite, errpipe_read, errpipe_write, - restore_signals, start_new_session, - gid, gids, uid, umask, - preexec_fn) + restore_signals, start_new_session, + gid, gids, uid, umask, + preexec_fn) self._child_created = True finally: # be sure the FD is closed no matter what os.close(errpipe_write) - self._close_pipe_fds(p2cread, p2cwrite, - c2pread, c2pwrite, - errread, errwrite) + self._close_pipe_fds(p2cread, p2cwrite, + c2pread, c2pwrite, + errread, errwrite) # Wait for exec to fail or succeed; possibly raising an # exception (limited in size) @@ -1822,17 +1822,17 @@ class Popen(object): raise child_exception_type(err_msg) - def _handle_exitstatus(self, sts, - waitstatus_to_exitcode=os.waitstatus_to_exitcode, - _WIFSTOPPED=os.WIFSTOPPED, - _WSTOPSIG=os.WSTOPSIG): + def _handle_exitstatus(self, sts, + waitstatus_to_exitcode=os.waitstatus_to_exitcode, + _WIFSTOPPED=os.WIFSTOPPED, + _WSTOPSIG=os.WSTOPSIG): """All callers to this function MUST hold self._waitpid_lock.""" # This method is called (indirectly) by __del__, so it cannot # refer to anything outside of its local scope. - if _WIFSTOPPED(sts): + if _WIFSTOPPED(sts): self.returncode = -_WSTOPSIG(sts) else: - self.returncode = waitstatus_to_exitcode(sts) + self.returncode = waitstatus_to_exitcode(sts) def _internal_poll(self, _deadstate=None, _waitpid=os.waitpid, _WNOHANG=os.WNOHANG, _ECHILD=errno.ECHILD): @@ -1961,23 +1961,23 @@ class Popen(object): with _PopenSelector() as selector: if self.stdin and input: selector.register(self.stdin, selectors.EVENT_WRITE) - if self.stdout and not self.stdout.closed: + if self.stdout and not self.stdout.closed: selector.register(self.stdout, selectors.EVENT_READ) - if self.stderr and not self.stderr.closed: + if self.stderr and not self.stderr.closed: selector.register(self.stderr, selectors.EVENT_READ) while selector.get_map(): timeout = self._remaining_time(endtime) if timeout is not None and timeout < 0: - self._check_timeout(endtime, orig_timeout, - stdout, stderr, - skip_check_and_raise=True) - raise RuntimeError( # Impossible :) - '_check_timeout(..., skip_check_and_raise=True) ' - 'failed to raise TimeoutExpired.') + self._check_timeout(endtime, orig_timeout, + stdout, stderr, + skip_check_and_raise=True) + raise RuntimeError( # Impossible :) + '_check_timeout(..., skip_check_and_raise=True) ' + 'failed to raise TimeoutExpired.') ready = selector.select(timeout) - self._check_timeout(endtime, orig_timeout, stdout, stderr) + self._check_timeout(endtime, orig_timeout, stdout, stderr) # XXX Rewrite these to use non-blocking I/O on the file # objects; they are no longer using C stdio! @@ -2039,35 +2039,35 @@ class Popen(object): def send_signal(self, sig): """Send a signal to the process.""" - # bpo-38630: Polling reduces the risk of sending a signal to the - # wrong process if the process completed, the Popen.returncode - # attribute is still None, and the pid has been reassigned - # (recycled) to a new different process. This race condition can - # happens in two cases. - # - # Case 1. Thread A calls Popen.poll(), thread B calls - # Popen.send_signal(). In thread A, waitpid() succeed and returns - # the exit status. Thread B calls kill() because poll() in thread A - # did not set returncode yet. Calling poll() in thread B prevents - # the race condition thanks to Popen._waitpid_lock. - # - # Case 2. waitpid(pid, 0) has been called directly, without - # using Popen methods: returncode is still None is this case. - # Calling Popen.poll() will set returncode to a default value, - # since waitpid() fails with ProcessLookupError. - self.poll() - if self.returncode is not None: - # Skip signalling a process that we know has already died. - return - - # The race condition can still happen if the race condition - # described above happens between the returncode test - # and the kill() call. - try: + # bpo-38630: Polling reduces the risk of sending a signal to the + # wrong process if the process completed, the Popen.returncode + # attribute is still None, and the pid has been reassigned + # (recycled) to a new different process. This race condition can + # happens in two cases. + # + # Case 1. Thread A calls Popen.poll(), thread B calls + # Popen.send_signal(). In thread A, waitpid() succeed and returns + # the exit status. Thread B calls kill() because poll() in thread A + # did not set returncode yet. Calling poll() in thread B prevents + # the race condition thanks to Popen._waitpid_lock. + # + # Case 2. waitpid(pid, 0) has been called directly, without + # using Popen methods: returncode is still None is this case. + # Calling Popen.poll() will set returncode to a default value, + # since waitpid() fails with ProcessLookupError. + self.poll() + if self.returncode is not None: + # Skip signalling a process that we know has already died. + return + + # The race condition can still happen if the race condition + # described above happens between the returncode test + # and the kill() call. + try: os.kill(self.pid, sig) - except ProcessLookupError: - # Supress the race condition error; bpo-40550. - pass + except ProcessLookupError: + # Supress the race condition error; bpo-40550. + pass def terminate(self): """Terminate the process with SIGTERM |