diff options
Diffstat (limited to 'Lib/subprocess.py')
| -rw-r--r-- | Lib/subprocess.py | 839 | 
1 files changed, 449 insertions, 390 deletions
| diff --git a/Lib/subprocess.py b/Lib/subprocess.py index 83c79ef..57cc1a4 100644 --- a/Lib/subprocess.py +++ b/Lib/subprocess.py @@ -191,8 +191,10 @@ should prepare for OSErrors.  A ValueError will be raised if Popen is called with invalid arguments. -check_call() and check_output() will raise CalledProcessError, if the -called process returns a non-zero return code. +Exceptions defined within this module inherit from SubprocessError. +check_call() and check_output() will raise CalledProcessError if the +called process returns a non-zero return code.  TimeoutExpired +be raised if a timeout was specified and expired.  Security @@ -340,15 +342,23 @@ mswindows = (sys.platform == "win32")  import io  import os +import time  import traceback  import gc  import signal  import builtins  import warnings  import errno +try: +    from time import monotonic as _time +except ImportError: +    from time import time as _time  # Exception classes used by this module. -class CalledProcessError(Exception): +class SubprocessError(Exception): pass + + +class CalledProcessError(SubprocessError):      """This exception is raised when a process run by check_call() or      check_output() returns a non-zero exit status.      The exit status will be stored in the returncode attribute; @@ -362,10 +372,24 @@ class CalledProcessError(Exception):          return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode) +class TimeoutExpired(SubprocessError): +    """This exception is raised when the timeout expires while waiting for a +    child process. +    """ +    def __init__(self, cmd, timeout, output=None): +        self.cmd = cmd +        self.timeout = timeout +        self.output = output + +    def __str__(self): +        return ("Command '%s' timed out after %s seconds" % +                (self.cmd, self.timeout)) + +  if mswindows:      import threading      import msvcrt -    import _subprocess +    import _winapi      class STARTUPINFO:          dwFlags = 0          hStdInput = None @@ -377,53 +401,49 @@ if mswindows:  else:      import select      _has_poll = hasattr(select, 'poll') -    import fcntl -    import pickle - -    try: -        import _posixsubprocess -    except ImportError: -        _posixsubprocess = None -        warnings.warn("The _posixsubprocess module is not being used. " -                      "Child process reliability may suffer if your " -                      "program uses threads.", RuntimeWarning) +    import _posixsubprocess +    _create_pipe = _posixsubprocess.cloexec_pipe      # 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) -    _FD_CLOEXEC = getattr(fcntl, 'FD_CLOEXEC', 1) - -    def _set_cloexec(fd, cloexec): -        old = fcntl.fcntl(fd, fcntl.F_GETFD) -        if cloexec: -            fcntl.fcntl(fd, fcntl.F_SETFD, old | _FD_CLOEXEC) -        else: -            fcntl.fcntl(fd, fcntl.F_SETFD, old & ~_FD_CLOEXEC) - -    if _posixsubprocess: -        _create_pipe = _posixsubprocess.cloexec_pipe -    else: -        def _create_pipe(): -            fds = os.pipe() -            _set_cloexec(fds[0], True) -            _set_cloexec(fds[1], True) -            return fds  __all__ = ["Popen", "PIPE", "STDOUT", "call", "check_call", "getstatusoutput", -           "getoutput", "check_output", "CalledProcessError"] +           "getoutput", "check_output", "CalledProcessError", "DEVNULL"]  if mswindows: -    from _subprocess import (CREATE_NEW_CONSOLE, CREATE_NEW_PROCESS_GROUP, -                             STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, -                             STD_ERROR_HANDLE, SW_HIDE, -                             STARTF_USESTDHANDLES, STARTF_USESHOWWINDOW) +    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)      __all__.extend(["CREATE_NEW_CONSOLE", "CREATE_NEW_PROCESS_GROUP",                      "STD_INPUT_HANDLE", "STD_OUTPUT_HANDLE",                      "STD_ERROR_HANDLE", "SW_HIDE",                      "STARTF_USESTDHANDLES", "STARTF_USESHOWWINDOW"]) + +    class Handle(int): +        closed = False + +        def Close(self, CloseHandle=_winapi.CloseHandle): +            if not self.closed: +                self.closed = True +                CloseHandle(self) + +        def Detach(self): +            if not self.closed: +                self.closed = True +                return int(self) +            raise ValueError("already closed") + +        def __repr__(self): +            return "Handle(%d)" % int(self) + +        __del__ = Close +        __str__ = __repr__ +  try:      MAXFD = os.sysconf("SC_OPEN_MAX")  except: @@ -448,27 +468,63 @@ def _cleanup():  PIPE = -1  STDOUT = -2 +DEVNULL = -3  def _eintr_retry_call(func, *args):      while True:          try:              return func(*args) -        except (OSError, IOError) as e: -            if e.errno == errno.EINTR: -                continue -            raise - - -def call(*popenargs, **kwargs): -    """Run command with arguments.  Wait for command to complete, then -    return the returncode attribute. +        except InterruptedError: +            continue + + +# XXX This function is only used by multiprocessing and the test suite, +# but it's here so that it can be imported when Python is compiled without +# threads. + +def _args_from_interpreter_flags(): +    """Return a list of command-line arguments reproducing the current +    settings in sys.flags and sys.warnoptions.""" +    flag_opt_map = { +        'debug': 'd', +        # 'inspect': 'i', +        # 'interactive': 'i', +        'optimize': 'O', +        'dont_write_bytecode': 'B', +        'no_user_site': 's', +        'no_site': 'S', +        'ignore_environment': 'E', +        'verbose': 'v', +        'bytes_warning': 'b', +        'quiet': 'q', +        'hash_randomization': 'R', +    } +    args = [] +    for flag, opt in flag_opt_map.items(): +        v = getattr(sys.flags, flag) +        if v > 0: +            args.append('-' + opt * v) +    for opt in sys.warnoptions: +        args.append('-W' + opt) +    return args + + +def call(*popenargs, timeout=None, **kwargs): +    """Run command with arguments.  Wait for command to complete or +    timeout, then return the returncode attribute.      The arguments are the same as for the Popen constructor.  Example:      retcode = call(["ls", "-l"])      """ -    return Popen(*popenargs, **kwargs).wait() +    with Popen(*popenargs, **kwargs) as p: +        try: +            return p.wait(timeout=timeout) +        except: +            p.kill() +            p.wait() +            raise  def check_call(*popenargs, **kwargs): @@ -477,7 +533,7 @@ def check_call(*popenargs, **kwargs):      CalledProcessError.  The CalledProcessError object will have the      return code in the returncode attribute. -    The arguments are the same as for the Popen constructor.  Example: +    The arguments are the same as for the call function.  Example:      check_call(["ls", "-l"])      """ @@ -490,7 +546,7 @@ def check_call(*popenargs, **kwargs):      return 0 -def check_output(*popenargs, **kwargs): +def check_output(*popenargs, timeout=None, **kwargs):      r"""Run command with arguments and return its output as a byte string.      If the exit code was non-zero it raises a CalledProcessError.  The @@ -512,14 +568,20 @@ def check_output(*popenargs, **kwargs):      """      if 'stdout' in kwargs:          raise ValueError('stdout argument not allowed, it will be overridden.') -    process = Popen(*popenargs, stdout=PIPE, **kwargs) -    output, unused_err = process.communicate() -    retcode = process.poll() -    if retcode: -        cmd = kwargs.get("args") -        if cmd is None: -            cmd = popenargs[0] -        raise CalledProcessError(retcode, cmd, output=output) +    with Popen(*popenargs, stdout=PIPE, **kwargs) as process: +        try: +            output, unused_err = process.communicate(timeout=timeout) +        except TimeoutExpired: +            process.kill() +            output, unused_err = process.communicate() +            raise TimeoutExpired(process.args, timeout, output=output) +        except: +            process.kill() +            process.wait() +            raise +        retcode = process.poll() +        if retcode: +            raise CalledProcessError(retcode, process.args, output=output)      return output @@ -614,11 +676,19 @@ def getstatusoutput(cmd):      >>> subprocess.getstatusoutput('/bin/junk')      (256, 'sh: /bin/junk: not found')      """ -    pipe = os.popen('{ ' + cmd + '; } 2>&1', 'r') -    text = pipe.read() -    sts = pipe.close() -    if sts is None: sts = 0 -    if text[-1:] == '\n': text = text[:-1] +    with os.popen('{ ' + cmd + '; } 2>&1', 'r') as pipe: +        try: +            text = pipe.read() +            sts = pipe.close() +        except: +            process = pipe._proc +            process.kill() +            process.wait() +            raise +    if sts is None: +        sts = 0 +    if text[-1:] == '\n': +        text = text[:-1]      return sts, text @@ -650,6 +720,8 @@ class Popen(object):          _cleanup()          self._child_created = False +        self._input = None +        self._communication_started = False          if bufsize is None:              bufsize = 0  # Restore default          if not isinstance(bufsize, int): @@ -684,6 +756,7 @@ class Popen(object):                  raise ValueError("creationflags is only supported on Windows "                                   "platforms") +        self.args = args          self.stdin = None          self.stdout = None          self.stderr = None @@ -724,7 +797,7 @@ class Popen(object):          if p2cwrite != -1:              self.stdin = io.open(p2cwrite, 'wb', bufsize) -            if self.universal_newlines: +            if universal_newlines:                  self.stdin = io.TextIOWrapper(self.stdin, write_through=True)          if c2pread != -1:              self.stdout = io.open(c2pread, 'rb', bufsize) @@ -737,7 +810,7 @@ class Popen(object):          try:              self._execute_child(args, executable, preexec_fn, close_fds, -                                pass_fds, cwd, env, universal_newlines, +                                pass_fds, cwd, env,                                  startupinfo, creationflags, shell,                                  p2cread, p2cwrite,                                  c2pread, c2pwrite, @@ -784,19 +857,28 @@ class Popen(object):              # Child is still running, keep us alive until we can wait on it.              _active.append(self) +    def _get_devnull(self): +        if not hasattr(self, '_devnull'): +            self._devnull = os.open(os.devnull, os.O_RDWR) +        return self._devnull -    def communicate(self, input=None): +    def communicate(self, input=None, timeout=None):          """Interact with process: Send data to stdin.  Read data from          stdout and stderr, until end-of-file is reached.  Wait for -        process to terminate.  The optional input argument should be a -        string to be sent to the child process, or None, if no data +        process to terminate.  The optional input argument should be +        bytes to be sent to the child process, or None, if no data          should be sent to the child.          communicate() returns a tuple (stdout, stderr).""" -        # Optimization: If we are only using one pipe, or no pipe at -        # all, using select() or threads is unnecessary. -        if [self.stdin, self.stdout, self.stderr].count(None) >= 2: +        if self._communication_started and input: +            raise ValueError("Cannot send input after starting communication") + +        # Optimization: If we are not worried about timeouts, we haven't +        # started communicating, and we have one or zero pipes, using select() +        # or threads is unnecessary. +        if (timeout is None and not self._communication_started and +            [self.stdin, self.stdout, self.stderr].count(None) >= 2):              stdout = None              stderr = None              if self.stdin: @@ -814,15 +896,42 @@ class Popen(object):                  stderr = _eintr_retry_call(self.stderr.read)                  self.stderr.close()              self.wait() -            return (stdout, stderr) +        else: +            if timeout is not None: +                endtime = _time() + timeout +            else: +                endtime = None -        return self._communicate(input) +            try: +                stdout, stderr = self._communicate(input, endtime, timeout) +            finally: +                self._communication_started = True + +            sts = self.wait(timeout=self._remaining_time(endtime)) + +        return (stdout, stderr)      def poll(self):          return self._internal_poll() +    def _remaining_time(self, endtime): +        """Convenience for _communicate when computing timeouts.""" +        if endtime is None: +            return None +        else: +            return endtime - _time() + + +    def _check_timeout(self, endtime, orig_timeout): +        """Convenience for checking if a timeout has expired.""" +        if endtime is None: +            return +        if _time() > endtime: +            raise TimeoutExpired(self.args, orig_timeout) + +      if mswindows:          #          # Windows methods @@ -839,11 +948,16 @@ class Popen(object):              errread, errwrite = -1, -1              if stdin is None: -                p2cread = _subprocess.GetStdHandle(_subprocess.STD_INPUT_HANDLE) +                p2cread = _winapi.GetStdHandle(_winapi.STD_INPUT_HANDLE)                  if p2cread is None: -                    p2cread, _ = _subprocess.CreatePipe(None, 0) +                    p2cread, _ = _winapi.CreatePipe(None, 0) +                    p2cread = Handle(p2cread) +                    _winapi.CloseHandle(_)              elif stdin == PIPE: -                p2cread, p2cwrite = _subprocess.CreatePipe(None, 0) +                p2cread, p2cwrite = _winapi.CreatePipe(None, 0) +                p2cread, p2cwrite = Handle(p2cread), Handle(p2cwrite) +            elif stdin == DEVNULL: +                p2cread = msvcrt.get_osfhandle(self._get_devnull())              elif isinstance(stdin, int):                  p2cread = msvcrt.get_osfhandle(stdin)              else: @@ -852,11 +966,16 @@ class Popen(object):              p2cread = self._make_inheritable(p2cread)              if stdout is None: -                c2pwrite = _subprocess.GetStdHandle(_subprocess.STD_OUTPUT_HANDLE) +                c2pwrite = _winapi.GetStdHandle(_winapi.STD_OUTPUT_HANDLE)                  if c2pwrite is None: -                    _, c2pwrite = _subprocess.CreatePipe(None, 0) +                    _, c2pwrite = _winapi.CreatePipe(None, 0) +                    c2pwrite = Handle(c2pwrite) +                    _winapi.CloseHandle(_)              elif stdout == PIPE: -                c2pread, c2pwrite = _subprocess.CreatePipe(None, 0) +                c2pread, c2pwrite = _winapi.CreatePipe(None, 0) +                c2pread, c2pwrite = Handle(c2pread), Handle(c2pwrite) +            elif stdout == DEVNULL: +                c2pwrite = msvcrt.get_osfhandle(self._get_devnull())              elif isinstance(stdout, int):                  c2pwrite = msvcrt.get_osfhandle(stdout)              else: @@ -865,13 +984,18 @@ class Popen(object):              c2pwrite = self._make_inheritable(c2pwrite)              if stderr is None: -                errwrite = _subprocess.GetStdHandle(_subprocess.STD_ERROR_HANDLE) +                errwrite = _winapi.GetStdHandle(_winapi.STD_ERROR_HANDLE)                  if errwrite is None: -                    _, errwrite = _subprocess.CreatePipe(None, 0) +                    _, errwrite = _winapi.CreatePipe(None, 0) +                    errwrite = Handle(errwrite) +                    _winapi.CloseHandle(_)              elif stderr == PIPE: -                errread, errwrite = _subprocess.CreatePipe(None, 0) +                errread, errwrite = _winapi.CreatePipe(None, 0) +                errread, errwrite = Handle(errread), Handle(errwrite)              elif stderr == STDOUT:                  errwrite = c2pwrite +            elif stderr == DEVNULL: +                errwrite = msvcrt.get_osfhandle(self._get_devnull())              elif isinstance(stderr, int):                  errwrite = msvcrt.get_osfhandle(stderr)              else: @@ -886,20 +1010,22 @@ class Popen(object):          def _make_inheritable(self, handle):              """Return a duplicate of handle, which is inheritable""" -            return _subprocess.DuplicateHandle(_subprocess.GetCurrentProcess(), -                                handle, _subprocess.GetCurrentProcess(), 0, 1, -                                _subprocess.DUPLICATE_SAME_ACCESS) +            h = _winapi.DuplicateHandle( +                _winapi.GetCurrentProcess(), handle, +                _winapi.GetCurrentProcess(), 0, 1, +                _winapi.DUPLICATE_SAME_ACCESS) +            return Handle(h)          def _find_w9xpopen(self):              """Find and return absolut path to w9xpopen.exe"""              w9xpopen = os.path.join( -                            os.path.dirname(_subprocess.GetModuleFileName(0)), +                            os.path.dirname(_winapi.GetModuleFileName(0)),                                      "w9xpopen.exe")              if not os.path.exists(w9xpopen):                  # Eeek - file-not-found - possibly an embedding                  # situation - see if we can locate it in sys.exec_prefix -                w9xpopen = os.path.join(os.path.dirname(sys.exec_prefix), +                w9xpopen = os.path.join(os.path.dirname(sys.base_exec_prefix),                                          "w9xpopen.exe")                  if not os.path.exists(w9xpopen):                      raise RuntimeError("Cannot locate w9xpopen.exe, which is " @@ -909,7 +1035,7 @@ class Popen(object):          def _execute_child(self, args, executable, preexec_fn, close_fds, -                           pass_fds, cwd, env, universal_newlines, +                           pass_fds, cwd, env,                             startupinfo, creationflags, shell,                             p2cread, p2cwrite,                             c2pread, c2pwrite, @@ -926,17 +1052,17 @@ class Popen(object):              if startupinfo is None:                  startupinfo = STARTUPINFO()              if -1 not in (p2cread, c2pwrite, errwrite): -                startupinfo.dwFlags |= _subprocess.STARTF_USESTDHANDLES +                startupinfo.dwFlags |= _winapi.STARTF_USESTDHANDLES                  startupinfo.hStdInput = p2cread                  startupinfo.hStdOutput = c2pwrite                  startupinfo.hStdError = errwrite              if shell: -                startupinfo.dwFlags |= _subprocess.STARTF_USESHOWWINDOW -                startupinfo.wShowWindow = _subprocess.SW_HIDE +                startupinfo.dwFlags |= _winapi.STARTF_USESHOWWINDOW +                startupinfo.wShowWindow = _winapi.SW_HIDE                  comspec = os.environ.get("COMSPEC", "cmd.exe")                  args = '{} /c "{}"'.format (comspec, args) -                if (_subprocess.GetVersion() >= 0x80000000 or +                if (_winapi.GetVersion() >= 0x80000000 or                          os.path.basename(comspec).lower() == "command.com"):                      # Win9x, or using command.com on NT. We need to                      # use the w9xpopen intermediate program. For more @@ -950,11 +1076,11 @@ class Popen(object):                      # use at xxx" and a hopeful warning about the                      # stability of your system.  Cost is Ctrl+C won't                      # kill children. -                    creationflags |= _subprocess.CREATE_NEW_CONSOLE +                    creationflags |= _winapi.CREATE_NEW_CONSOLE              # Start the process              try: -                hp, ht, pid, tid = _subprocess.CreateProcess(executable, args, +                hp, ht, pid, tid = _winapi.CreateProcess(executable, args,                                           # no special security                                           None, None,                                           int(not close_fds), @@ -981,17 +1107,19 @@ class Popen(object):                      c2pwrite.Close()                  if errwrite != -1:                      errwrite.Close() +                if hasattr(self, '_devnull'): +                    os.close(self._devnull)              # Retain the process handle, but close the thread handle              self._child_created = True -            self._handle = hp +            self._handle = Handle(hp)              self.pid = pid -            ht.Close() +            _winapi.CloseHandle(ht)          def _internal_poll(self, _deadstate=None, -                _WaitForSingleObject=_subprocess.WaitForSingleObject, -                _WAIT_OBJECT_0=_subprocess.WAIT_OBJECT_0, -                _GetExitCodeProcess=_subprocess.GetExitCodeProcess): +                _WaitForSingleObject=_winapi.WaitForSingleObject, +                _WAIT_OBJECT_0=_winapi.WAIT_OBJECT_0, +                _GetExitCodeProcess=_winapi.GetExitCodeProcess):              """Check if child process has terminated.  Returns returncode              attribute. @@ -1005,13 +1133,21 @@ class Popen(object):              return self.returncode -        def wait(self): +        def wait(self, timeout=None, endtime=None):              """Wait for child process to terminate.  Returns returncode              attribute.""" +            if endtime is not None: +                timeout = self._remaining_time(endtime) +            if timeout is None: +                timeout_millis = _winapi.INFINITE +            else: +                timeout_millis = int(timeout * 1000)              if self.returncode is None: -                _subprocess.WaitForSingleObject(self._handle, -                                                _subprocess.INFINITE) -                self.returncode = _subprocess.GetExitCodeProcess(self._handle) +                result = _winapi.WaitForSingleObject(self._handle, +                                                    timeout_millis) +                if result == _winapi.WAIT_TIMEOUT: +                    raise TimeoutExpired(self.args, timeout) +                self.returncode = _winapi.GetExitCodeProcess(self._handle)              return self.returncode @@ -1020,22 +1156,23 @@ class Popen(object):              fh.close() -        def _communicate(self, input): -            stdout = None # Return -            stderr = None # Return - -            if self.stdout: -                stdout = [] -                stdout_thread = threading.Thread(target=self._readerthread, -                                                 args=(self.stdout, stdout)) -                stdout_thread.daemon = True -                stdout_thread.start() -            if self.stderr: -                stderr = [] -                stderr_thread = threading.Thread(target=self._readerthread, -                                                 args=(self.stderr, stderr)) -                stderr_thread.daemon = True -                stderr_thread.start() +        def _communicate(self, input, endtime, orig_timeout): +            # Start reader threads feeding into a list hanging off of this +            # object, unless they've already been started. +            if self.stdout and not hasattr(self, "_stdout_buff"): +                self._stdout_buff = [] +                self.stdout_thread = \ +                        threading.Thread(target=self._readerthread, +                                         args=(self.stdout, self._stdout_buff)) +                self.stdout_thread.daemon = True +                self.stdout_thread.start() +            if self.stderr and not hasattr(self, "_stderr_buff"): +                self._stderr_buff = [] +                self.stderr_thread = \ +                        threading.Thread(target=self._readerthread, +                                         args=(self.stderr, self._stderr_buff)) +                self.stderr_thread.daemon = True +                self.stderr_thread.start()              if self.stdin:                  if input is not None: @@ -1046,10 +1183,28 @@ class Popen(object):                              raise                  self.stdin.close() +            # Wait for the reader threads, or time out.  If we time out, the +            # threads remain reading and the fds left open in case the user +            # calls communicate again. +            if self.stdout is not None: +                self.stdout_thread.join(self._remaining_time(endtime)) +                if self.stdout_thread.is_alive(): +                    raise TimeoutExpired(self.args, orig_timeout) +            if self.stderr is not None: +                self.stderr_thread.join(self._remaining_time(endtime)) +                if self.stderr_thread.is_alive(): +                    raise TimeoutExpired(self.args, orig_timeout) + +            # Collect the output from and close both pipes, now that we know +            # both have been read successfully. +            stdout = None +            stderr = None              if self.stdout: -                stdout_thread.join() +                stdout = self._stdout_buff +                self.stdout.close()              if self.stderr: -                stderr_thread.join() +                stderr = self._stderr_buff +                self.stderr.close()              # All data exchanged.  Translate lists into strings.              if stdout is not None: @@ -1057,7 +1212,6 @@ class Popen(object):              if stderr is not None:                  stderr = stderr[0] -            self.wait()              return (stdout, stderr)          def send_signal(self, sig): @@ -1076,14 +1230,12 @@ class Popen(object):              """Terminates the process              """              try: -                _subprocess.TerminateProcess(self._handle, 1) -            except OSError as e: +                _winapi.TerminateProcess(self._handle, 1) +            except PermissionError:                  # ERROR_ACCESS_DENIED (winerror 5) is received when the                  # process already died. -                if e.winerror != 5: -                    raise -                rc = _subprocess.GetExitCodeProcess(self._handle) -                if rc == _subprocess.STILL_ACTIVE: +                rc = _winapi.GetExitCodeProcess(self._handle) +                if rc == _winapi.STILL_ACTIVE:                      raise                  self.returncode = rc @@ -1105,6 +1257,8 @@ class Popen(object):                  pass              elif stdin == PIPE:                  p2cread, p2cwrite = _create_pipe() +            elif stdin == DEVNULL: +                p2cread = self._get_devnull()              elif isinstance(stdin, int):                  p2cread = stdin              else: @@ -1115,6 +1269,8 @@ class Popen(object):                  pass              elif stdout == PIPE:                  c2pread, c2pwrite = _create_pipe() +            elif stdout == DEVNULL: +                c2pwrite = self._get_devnull()              elif isinstance(stdout, int):                  c2pwrite = stdout              else: @@ -1127,6 +1283,8 @@ class Popen(object):                  errread, errwrite = _create_pipe()              elif stderr == STDOUT:                  errwrite = c2pwrite +            elif stderr == DEVNULL: +                errwrite = self._get_devnull()              elif isinstance(stderr, int):                  errwrite = stderr              else: @@ -1149,7 +1307,7 @@ class Popen(object):          def _execute_child(self, args, executable, preexec_fn, close_fds, -                           pass_fds, cwd, env, universal_newlines, +                           pass_fds, cwd, env,                             startupinfo, creationflags, shell,                             p2cread, p2cwrite,                             c2pread, c2pwrite, @@ -1157,7 +1315,7 @@ class Popen(object):                             restore_signals, start_new_session):              """Execute program (POSIX version)""" -            if isinstance(args, str): +            if isinstance(args, (str, bytes)):                  args = [args]              else:                  args = list(args) @@ -1177,153 +1335,34 @@ class Popen(object):              errpipe_read, errpipe_write = _create_pipe()              try:                  try: - -                    if _posixsubprocess: -                        # We must avoid complex work that could involve -                        # malloc or free in the child process to avoid -                        # potential deadlocks, thus we do all this here. -                        # and pass it to fork_exec() - -                        if env is not None: -                            env_list = [os.fsencode(k) + b'=' + os.fsencode(v) -                                        for k, v in env.items()] -                        else: -                            env_list = None  # Use execv instead of execve. -                        executable = os.fsencode(executable) -                        if os.path.dirname(executable): -                            executable_list = (executable,) -                        else: -                            # This matches the behavior of os._execvpe(). -                            executable_list = tuple( -                                os.path.join(os.fsencode(dir), executable) -                                for dir in os.get_exec_path(env)) -                        fds_to_keep = set(pass_fds) -                        fds_to_keep.add(errpipe_write) -                        self.pid = _posixsubprocess.fork_exec( -                                args, executable_list, -                                close_fds, sorted(fds_to_keep), cwd, env_list, -                                p2cread, p2cwrite, c2pread, c2pwrite, -                                errread, errwrite, -                                errpipe_read, errpipe_write, -                                restore_signals, start_new_session, preexec_fn) -                        self._child_created = True +                    # We must avoid complex work that could involve +                    # malloc or free in the child process to avoid +                    # potential deadlocks, thus we do all this here. +                    # and pass it to fork_exec() + +                    if env is not None: +                        env_list = [os.fsencode(k) + b'=' + os.fsencode(v) +                                    for k, v in env.items()]                      else: -                        # Pure Python implementation: It is not thread safe. -                        # This implementation may deadlock in the child if your -                        # parent process has any other threads running. - -                        gc_was_enabled = gc.isenabled() -                        # Disable gc to avoid bug where gc -> file_dealloc -> -                        # write to stderr -> hang.  See issue1336 -                        gc.disable() -                        try: -                            self.pid = os.fork() -                        except: -                            if gc_was_enabled: -                                gc.enable() -                            raise -                        self._child_created = True -                        if self.pid == 0: -                            # Child -                            reached_preexec = False -                            try: -                                # Close parent's pipe ends -                                if p2cwrite != -1: -                                    os.close(p2cwrite) -                                if c2pread != -1: -                                    os.close(c2pread) -                                if errread != -1: -                                    os.close(errread) -                                os.close(errpipe_read) - -                                # When duping fds, if there arises a situation -                                # where one of the fds is either 0, 1 or 2, it -                                # is possible that it is overwritten (#12607). -                                if c2pwrite == 0: -                                    c2pwrite = os.dup(c2pwrite) -                                if errwrite == 0 or errwrite == 1: -                                    errwrite = os.dup(errwrite) - -                                # Dup fds for child -                                def _dup2(a, b): -                                    # dup2() removes the CLOEXEC flag but -                                    # we must do it ourselves if dup2() -                                    # would be a no-op (issue #10806). -                                    if a == b: -                                        _set_cloexec(a, False) -                                    elif a != -1: -                                        os.dup2(a, b) -                                _dup2(p2cread, 0) -                                _dup2(c2pwrite, 1) -                                _dup2(errwrite, 2) - -                                # Close pipe fds.  Make sure we don't close the -                                # same fd more than once, or standard fds. -                                closed = set() -                                for fd in [p2cread, c2pwrite, errwrite]: -                                    if fd > 2 and fd not in closed: -                                        os.close(fd) -                                        closed.add(fd) - -                                # Close all other fds, if asked for -                                if close_fds: -                                    fds_to_keep = set(pass_fds) -                                    fds_to_keep.add(errpipe_write) -                                    self._close_fds(fds_to_keep) - - -                                if cwd is not None: -                                    os.chdir(cwd) - -                                # This is a copy of Python/pythonrun.c -                                # _Py_RestoreSignals().  If that were exposed -                                # as a sys._py_restoresignals func it would be -                                # better.. but this pure python implementation -                                # isn't likely to be used much anymore. -                                if restore_signals: -                                    signals = ('SIGPIPE', 'SIGXFZ', 'SIGXFSZ') -                                    for sig in signals: -                                        if hasattr(signal, sig): -                                            signal.signal(getattr(signal, sig), -                                                          signal.SIG_DFL) - -                                if start_new_session and hasattr(os, 'setsid'): -                                    os.setsid() - -                                reached_preexec = True -                                if preexec_fn: -                                    preexec_fn() - -                                if env is None: -                                    os.execvp(executable, args) -                                else: -                                    os.execvpe(executable, args, env) - -                            except: -                                try: -                                    exc_type, exc_value = sys.exc_info()[:2] -                                    if isinstance(exc_value, OSError): -                                        errno_num = exc_value.errno -                                    else: -                                        errno_num = 0 -                                    if not reached_preexec: -                                        exc_value = "noexec" -                                    message = '%s:%x:%s' % (exc_type.__name__, -                                                            errno_num, exc_value) -                                    message = message.encode(errors="surrogatepass") -                                    os.write(errpipe_write, message) -                                except Exception: -                                    # We MUST not allow anything odd happening -                                    # above to prevent us from exiting below. -                                    pass - -                            # This exitcode won't be reported to applications -                            # so it really doesn't matter what we return. -                            os._exit(255) - -                        # Parent -                        if gc_was_enabled: -                            gc.enable() +                        env_list = None  # Use execv instead of execve. +                    executable = os.fsencode(executable) +                    if os.path.dirname(executable): +                        executable_list = (executable,) +                    else: +                        # This matches the behavior of os._execvpe(). +                        executable_list = tuple( +                            os.path.join(os.fsencode(dir), executable) +                            for dir in os.get_exec_path(env)) +                    fds_to_keep = set(pass_fds) +                    fds_to_keep.add(errpipe_write) +                    self.pid = _posixsubprocess.fork_exec( +                            args, executable_list, +                            close_fds, sorted(fds_to_keep), cwd, env_list, +                            p2cread, p2cwrite, c2pread, c2pwrite, +                            errread, errwrite, +                            errpipe_read, errpipe_write, +                            restore_signals, start_new_session, preexec_fn) +                    self._child_created = True                  finally:                      # be sure the FD is closed no matter what                      os.close(errpipe_write) @@ -1334,6 +1373,8 @@ class Popen(object):                      os.close(c2pwrite)                  if errwrite != -1 and errread != -1:                      os.close(errwrite) +                if hasattr(self, '_devnull'): +                    os.close(self._devnull)                  # Wait for exec to fail or succeed; possibly raising an                  # exception (limited in size) @@ -1425,25 +1466,57 @@ class Popen(object):              return self.returncode -        def wait(self): +        def _try_wait(self, wait_flags): +            try: +                (pid, sts) = _eintr_retry_call(os.waitpid, self.pid, wait_flags) +            except OSError as e: +                if e.errno != errno.ECHILD: +                    raise +                # This happens if SIGCLD is set to be ignored or waiting +                # for child processes has otherwise been disabled for our +                # process.  This child is dead, we can't get the status. +                pid = self.pid +                sts = 0 +            return (pid, sts) + + +        def wait(self, timeout=None, endtime=None):              """Wait for child process to terminate.  Returns returncode              attribute.""" -            if self.returncode is None: -                try: -                    pid, sts = _eintr_retry_call(os.waitpid, self.pid, 0) -                except OSError as e: -                    if e.errno != errno.ECHILD: -                        raise -                    # This happens if SIGCLD is set to be ignored or waiting -                    # for child processes has otherwise been disabled for our -                    # process.  This child is dead, we can't get the status. -                    sts = 0 +            if self.returncode is not None: +                return self.returncode + +            # endtime is preferred to timeout.  timeout is only used for +            # printing. +            if endtime is not None or timeout is not None: +                if endtime is None: +                    endtime = _time() + timeout +                elif timeout is None: +                    timeout = self._remaining_time(endtime) + +            if endtime is not None: +                # Enter a busy loop if we have a timeout.  This busy loop was +                # cribbed from Lib/threading.py in Thread.wait() at r71065. +                delay = 0.0005 # 500 us -> initial delay of 1 ms +                while True: +                    (pid, sts) = self._try_wait(os.WNOHANG) +                    assert pid == self.pid or pid == 0 +                    if pid == self.pid: +                        self._handle_exitstatus(sts) +                        break +                    remaining = self._remaining_time(endtime) +                    if remaining <= 0: +                        raise TimeoutExpired(self.args, timeout) +                    delay = min(delay * 2, remaining, .05) +                    time.sleep(delay) +            elif self.returncode is None: +                (pid, sts) = self._try_wait(0)                  self._handle_exitstatus(sts)              return self.returncode -        def _communicate(self, input): -            if self.stdin: +        def _communicate(self, input, endtime, orig_timeout): +            if self.stdin and not self._communication_started:                  # Flush stdio buffer.  This might block, if the user has                  # been writing to .stdin in an uncontrolled fashion.                  self.stdin.flush() @@ -1451,9 +1524,13 @@ class Popen(object):                      self.stdin.close()              if _has_poll: -                stdout, stderr = self._communicate_with_poll(input) +                stdout, stderr = self._communicate_with_poll(input, endtime, +                                                             orig_timeout)              else: -                stdout, stderr = self._communicate_with_select(input) +                stdout, stderr = self._communicate_with_select(input, endtime, +                                                               orig_timeout) + +            self.wait(timeout=self._remaining_time(endtime))              # All data exchanged.  Translate lists into strings.              if stdout is not None: @@ -1471,67 +1548,92 @@ class Popen(object):                      stderr = self._translate_newlines(stderr,                                                        self.stderr.encoding) -            self.wait()              return (stdout, stderr) -        def _communicate_with_poll(self, input): +        def _save_input(self, input): +            # This method is called from the _communicate_with_*() methods +            # so that if we time out while communicating, we can continue +            # sending input if we retry. +            if self.stdin and self._input is None: +                self._input_offset = 0 +                self._input = input +                if self.universal_newlines and input is not None: +                    self._input = self._input.encode(self.stdin.encoding) + + +        def _communicate_with_poll(self, input, endtime, orig_timeout):              stdout = None # Return              stderr = None # Return -            fd2file = {} -            fd2output = {} + +            if not self._communication_started: +                self._fd2file = {}              poller = select.poll()              def register_and_append(file_obj, eventmask):                  poller.register(file_obj.fileno(), eventmask) -                fd2file[file_obj.fileno()] = file_obj +                self._fd2file[file_obj.fileno()] = file_obj              def close_unregister_and_remove(fd):                  poller.unregister(fd) -                fd2file[fd].close() -                fd2file.pop(fd) +                self._fd2file[fd].close() +                self._fd2file.pop(fd)              if self.stdin and input:                  register_and_append(self.stdin, select.POLLOUT) +            # Only create this mapping if we haven't already. +            if not self._communication_started: +                self._fd2output = {} +                if self.stdout: +                    self._fd2output[self.stdout.fileno()] = [] +                if self.stderr: +                    self._fd2output[self.stderr.fileno()] = [] +              select_POLLIN_POLLPRI = select.POLLIN | select.POLLPRI              if self.stdout:                  register_and_append(self.stdout, select_POLLIN_POLLPRI) -                fd2output[self.stdout.fileno()] = stdout = [] +                stdout = self._fd2output[self.stdout.fileno()]              if self.stderr:                  register_and_append(self.stderr, select_POLLIN_POLLPRI) -                fd2output[self.stderr.fileno()] = stderr = [] +                stderr = self._fd2output[self.stderr.fileno()] -            input_offset = 0 -            while fd2file: +            self._save_input(input) + +            while self._fd2file: +                timeout = self._remaining_time(endtime) +                if timeout is not None and timeout < 0: +                    raise TimeoutExpired(self.args, orig_timeout)                  try: -                    ready = poller.poll() +                    ready = poller.poll(timeout)                  except select.error as e:                      if e.args[0] == errno.EINTR:                          continue                      raise +                self._check_timeout(endtime, orig_timeout)                  # XXX Rewrite these to use non-blocking I/O on the                  # file objects; they are no longer using C stdio!                  for fd, mode in ready:                      if mode & select.POLLOUT: -                        chunk = input[input_offset : input_offset + _PIPE_BUF] +                        chunk = self._input[self._input_offset : +                                            self._input_offset + _PIPE_BUF]                          try: -                            input_offset += os.write(fd, chunk) +                            self._input_offset += os.write(fd, chunk)                          except OSError as e:                              if e.errno == errno.EPIPE:                                  close_unregister_and_remove(fd)                              else:                                  raise                          else: -                            if input_offset >= len(input): +                            if self._input_offset >= len(self._input):                                  close_unregister_and_remove(fd)                      elif mode & select_POLLIN_POLLPRI:                          data = os.read(fd, 4096)                          if not data:                              close_unregister_and_remove(fd) -                        fd2output[fd].append(data) +                        self._fd2output[fd].append(data)                      else:                          # Ignore hang up or errors.                          close_unregister_and_remove(fd) @@ -1539,61 +1641,83 @@ class Popen(object):              return (stdout, stderr) -        def _communicate_with_select(self, input): -            read_set = [] -            write_set = [] +        def _communicate_with_select(self, input, endtime, orig_timeout): +            if not self._communication_started: +                self._read_set = [] +                self._write_set = [] +                if self.stdin and input: +                    self._write_set.append(self.stdin) +                if self.stdout: +                    self._read_set.append(self.stdout) +                if self.stderr: +                    self._read_set.append(self.stderr) + +            self._save_input(input) +              stdout = None # Return              stderr = None # Return -            if self.stdin and input: -                write_set.append(self.stdin)              if self.stdout: -                read_set.append(self.stdout) -                stdout = [] +                if not self._communication_started: +                    self._stdout_buff = [] +                stdout = self._stdout_buff              if self.stderr: -                read_set.append(self.stderr) -                stderr = [] - -            input_offset = 0 -            while read_set or write_set: +                if not self._communication_started: +                    self._stderr_buff = [] +                stderr = self._stderr_buff + +            while self._read_set or self._write_set: +                timeout = self._remaining_time(endtime) +                if timeout is not None and timeout < 0: +                    raise TimeoutExpired(self.args, orig_timeout)                  try: -                    rlist, wlist, xlist = select.select(read_set, write_set, []) +                    (rlist, wlist, xlist) = \ +                        select.select(self._read_set, self._write_set, [], +                                      timeout)                  except select.error as e:                      if e.args[0] == errno.EINTR:                          continue                      raise +                # According to the docs, returning three empty lists indicates +                # that the timeout expired. +                if not (rlist or wlist or xlist): +                    raise TimeoutExpired(self.args, orig_timeout) +                # We also check what time it is ourselves for good measure. +                self._check_timeout(endtime, orig_timeout) +                  # XXX Rewrite these to use non-blocking I/O on the                  # file objects; they are no longer using C stdio!                  if self.stdin in wlist: -                    chunk = input[input_offset : input_offset + _PIPE_BUF] +                    chunk = self._input[self._input_offset : +                                        self._input_offset + _PIPE_BUF]                      try:                          bytes_written = os.write(self.stdin.fileno(), chunk)                      except OSError as e:                          if e.errno == errno.EPIPE:                              self.stdin.close() -                            write_set.remove(self.stdin) +                            self._write_set.remove(self.stdin)                          else:                              raise                      else: -                        input_offset += bytes_written -                        if input_offset >= len(input): +                        self._input_offset += bytes_written +                        if self._input_offset >= len(self._input):                              self.stdin.close() -                            write_set.remove(self.stdin) +                            self._write_set.remove(self.stdin)                  if self.stdout in rlist:                      data = os.read(self.stdout.fileno(), 1024)                      if not data:                          self.stdout.close() -                        read_set.remove(self.stdout) +                        self._read_set.remove(self.stdout)                      stdout.append(data)                  if self.stderr in rlist:                      data = os.read(self.stderr.fileno(), 1024)                      if not data:                          self.stderr.close() -                        read_set.remove(self.stderr) +                        self._read_set.remove(self.stderr)                      stderr.append(data)              return (stdout, stderr) @@ -1613,68 +1737,3 @@ class Popen(object):              """Kill the process with SIGKILL              """              self.send_signal(signal.SIGKILL) - - -def _demo_posix(): -    # -    # Example 1: Simple redirection: Get process list -    # -    plist = Popen(["ps"], stdout=PIPE).communicate()[0] -    print("Process list:") -    print(plist) - -    # -    # Example 2: Change uid before executing child -    # -    if os.getuid() == 0: -        p = Popen(["id"], preexec_fn=lambda: os.setuid(100)) -        p.wait() - -    # -    # Example 3: Connecting several subprocesses -    # -    print("Looking for 'hda'...") -    p1 = Popen(["dmesg"], stdout=PIPE) -    p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE) -    print(repr(p2.communicate()[0])) - -    # -    # Example 4: Catch execution error -    # -    print() -    print("Trying a weird file...") -    try: -        print(Popen(["/this/path/does/not/exist"]).communicate()) -    except OSError as e: -        if e.errno == errno.ENOENT: -            print("The file didn't exist.  I thought so...") -            print("Child traceback:") -            print(e.child_traceback) -        else: -            print("Error", e.errno) -    else: -        print("Gosh.  No error.", file=sys.stderr) - - -def _demo_windows(): -    # -    # Example 1: Connecting several subprocesses -    # -    print("Looking for 'PROMPT' in set output...") -    p1 = Popen("set", stdout=PIPE, shell=True) -    p2 = Popen('find "PROMPT"', stdin=p1.stdout, stdout=PIPE) -    print(repr(p2.communicate()[0])) - -    # -    # Example 2: Simple execution of program -    # -    print("Executing calc...") -    p = Popen("calc") -    p.wait() - - -if __name__ == "__main__": -    if mswindows: -        _demo_windows() -    else: -        _demo_posix() | 
