aboutsummaryrefslogtreecommitdiffstats
path: root/python/gevent/subprocess.py
diff options
context:
space:
mode:
Diffstat (limited to 'python/gevent/subprocess.py')
-rw-r--r--python/gevent/subprocess.py1672
1 files changed, 0 insertions, 1672 deletions
diff --git a/python/gevent/subprocess.py b/python/gevent/subprocess.py
deleted file mode 100644
index e1e28ef..0000000
--- a/python/gevent/subprocess.py
+++ /dev/null
@@ -1,1672 +0,0 @@
-"""
-Cooperative ``subprocess`` module.
-
-.. caution:: On POSIX platforms, this module is not usable from native
- threads other than the main thread; attempting to do so will raise
- a :exc:`TypeError`. This module depends on libev's fork watchers.
- On POSIX systems, fork watchers are implemented using signals, and
- the thread to which process-directed signals are delivered `is not
- defined`_. Because each native thread has its own gevent/libev
- loop, this means that a fork watcher registered with one loop
- (thread) may never see the signal about a child it spawned if the
- signal is sent to a different thread.
-
-.. note:: The interface of this module is intended to match that of
- the standard library :mod:`subprocess` module (with many backwards
- compatible extensions from Python 3 backported to Python 2). There
- are some small differences between the Python 2 and Python 3
- versions of that module (the Python 2 ``TimeoutExpired`` exception,
- notably, extends ``Timeout`` and there is no ``SubprocessError``) and between the
- POSIX and Windows versions. The HTML documentation here can only
- describe one version; for definitive documentation, see the
- standard library or the source code.
-
-.. _is not defined: http://www.linuxprogrammingblog.com/all-about-linux-signals?page=11
-"""
-from __future__ import absolute_import, print_function
-# Can we split this up to make it cleaner? See https://github.com/gevent/gevent/issues/748
-# pylint: disable=too-many-lines
-# Import magic
-# pylint: disable=undefined-all-variable,undefined-variable
-# Most of this we inherit from the standard lib
-# pylint: disable=bare-except,too-many-locals,too-many-statements,attribute-defined-outside-init
-# pylint: disable=too-many-branches,too-many-instance-attributes
-# Most of this is cross-platform
-# pylint: disable=no-member,expression-not-assigned,unused-argument,unused-variable
-import errno
-import gc
-import os
-import signal
-import sys
-import traceback
-from gevent.event import AsyncResult
-from gevent.hub import _get_hub_noargs as get_hub
-from gevent.hub import linkproxy
-from gevent.hub import sleep
-from gevent.hub import getcurrent
-from gevent._compat import integer_types, string_types, xrange
-from gevent._compat import PY3
-from gevent._compat import reraise
-from gevent._compat import fspath
-from gevent._compat import fsencode
-from gevent._util import _NONE
-from gevent._util import copy_globals
-from gevent.fileobject import FileObject
-from gevent.greenlet import Greenlet, joinall
-spawn = Greenlet.spawn
-import subprocess as __subprocess__
-
-
-# Standard functions and classes that this module re-implements in a gevent-aware way.
-__implements__ = [
- 'Popen',
- 'call',
- 'check_call',
- 'check_output',
-]
-if PY3 and not sys.platform.startswith('win32'):
- __implements__.append("_posixsubprocess")
- _posixsubprocess = None
-
-# Some symbols we define that we expect to export;
-# useful for static analysis
-PIPE = "PIPE should be imported"
-
-# Standard functions and classes that this module re-imports.
-__imports__ = [
- 'PIPE',
- 'STDOUT',
- 'CalledProcessError',
- # Windows:
- 'CREATE_NEW_CONSOLE',
- 'CREATE_NEW_PROCESS_GROUP',
- 'STD_INPUT_HANDLE',
- 'STD_OUTPUT_HANDLE',
- 'STD_ERROR_HANDLE',
- 'SW_HIDE',
- 'STARTF_USESTDHANDLES',
- 'STARTF_USESHOWWINDOW',
-]
-
-
-__extra__ = [
- 'MAXFD',
- '_eintr_retry_call',
- 'STARTUPINFO',
- 'pywintypes',
- 'list2cmdline',
- '_subprocess',
- '_winapi',
- # Python 2.5 does not have _subprocess, so we don't use it
- # XXX We don't run on Py 2.5 anymore; can/could/should we use _subprocess?
- # It's only used on mswindows
- 'WAIT_OBJECT_0',
- 'WaitForSingleObject',
- 'GetExitCodeProcess',
- 'GetStdHandle',
- 'CreatePipe',
- 'DuplicateHandle',
- 'GetCurrentProcess',
- 'DUPLICATE_SAME_ACCESS',
- 'GetModuleFileName',
- 'GetVersion',
- 'CreateProcess',
- 'INFINITE',
- 'TerminateProcess',
- 'STILL_ACTIVE',
-
- # These were added for 3.5, but we make them available everywhere.
- 'run',
- 'CompletedProcess',
-]
-
-if sys.version_info[:2] >= (3, 3):
- __imports__ += [
- 'DEVNULL',
- 'getstatusoutput',
- 'getoutput',
- 'SubprocessError',
- 'TimeoutExpired',
- ]
-else:
- __extra__.append("TimeoutExpired")
-
-
-if sys.version_info[:2] >= (3, 5):
- __extra__.remove('run')
- __extra__.remove('CompletedProcess')
- __implements__.append('run')
- __implements__.append('CompletedProcess')
-
- # Removed in Python 3.5; this is the exact code that was removed:
- # https://hg.python.org/cpython/rev/f98b0a5e5ef5
- __extra__.remove('MAXFD')
- try:
- MAXFD = os.sysconf("SC_OPEN_MAX")
- except:
- MAXFD = 256
-
-if sys.version_info[:2] >= (3, 6):
- # This was added to __all__ for windows in 3.6
- __extra__.remove('STARTUPINFO')
- __imports__.append('STARTUPINFO')
-
-if sys.version_info[:2] >= (3, 7):
- __imports__.extend([
- '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'
- ])
-
-actually_imported = copy_globals(__subprocess__, globals(),
- only_names=__imports__,
- ignore_missing_names=True)
-# anything we couldn't import from here we may need to find
-# elsewhere
-__extra__.extend(set(__imports__).difference(set(actually_imported)))
-__imports__ = actually_imported
-del actually_imported
-
-
-# In Python 3 on Windows, a lot of the functions previously
-# in _subprocess moved to _winapi
-_subprocess = getattr(__subprocess__, '_subprocess', _NONE)
-_winapi = getattr(__subprocess__, '_winapi', _NONE)
-
-_attr_resolution_order = [__subprocess__, _subprocess, _winapi]
-
-for name in list(__extra__):
- if name in globals():
- continue
- value = _NONE
- for place in _attr_resolution_order:
- value = getattr(place, name, _NONE)
- if value is not _NONE:
- break
-
- if value is _NONE:
- __extra__.remove(name)
- else:
- globals()[name] = value
-
-del _attr_resolution_order
-__all__ = __implements__ + __imports__
-# Some other things we want to document
-for _x in ('run', 'CompletedProcess', 'TimeoutExpired'):
- if _x not in __all__:
- __all__.append(_x)
-
-
-mswindows = sys.platform == 'win32'
-if mswindows:
- import msvcrt # pylint: disable=import-error
- if PY3:
- class Handle(int):
- closed = False
-
- def Close(self):
- if not self.closed:
- self.closed = True
- _winapi.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__
-else:
- import fcntl
- import pickle
- from gevent import monkey
- fork = monkey.get_original('os', 'fork')
- from gevent.os import fork_and_watch
-
-def call(*popenargs, **kwargs):
- """
- call(args, *, stdin=None, stdout=None, stderr=None, shell=False, timeout=None) -> returncode
-
- 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"])
-
- .. versionchanged:: 1.2a1
- The ``timeout`` keyword argument is now accepted on all supported
- versions of Python (not just Python 3) and if it expires will raise a
- :exc:`TimeoutExpired` exception (under Python 2 this is a subclass of :exc:`~.Timeout`).
- """
- timeout = kwargs.pop('timeout', None)
- with Popen(*popenargs, **kwargs) as p:
- try:
- return p.wait(timeout=timeout, _raise_exc=True)
- except:
- p.kill()
- p.wait()
- raise
-
-def check_call(*popenargs, **kwargs):
- """
- check_call(args, *, stdin=None, stdout=None, stderr=None, shell=False, timeout=None) -> 0
-
- Run command with arguments. Wait for command to complete. If
- the exit code was zero then return, otherwise raise
- :exc:`CalledProcessError`. The ``CalledProcessError`` object will have the
- return code in the returncode attribute.
-
- The arguments are the same as for the Popen constructor. Example::
-
- retcode = check_call(["ls", "-l"])
- """
- retcode = call(*popenargs, **kwargs)
- if retcode:
- cmd = kwargs.get("args")
- if cmd is None:
- cmd = popenargs[0]
- raise CalledProcessError(retcode, cmd)
- return 0
-
-def check_output(*popenargs, **kwargs):
- r"""
- check_output(args, *, input=None, stdin=None, stderr=None, shell=False, universal_newlines=False, timeout=None) -> output
-
- Run command with arguments and return its output.
-
- If the exit code was non-zero it raises a :exc:`CalledProcessError`. The
- ``CalledProcessError`` object will have the return code in the returncode
- attribute and output in the output attribute.
-
-
- The arguments are the same as for the Popen constructor. Example::
-
- >>> check_output(["ls", "-1", "/dev/null"])
- '/dev/null\n'
-
- The ``stdout`` argument is not allowed as it is used internally.
-
- To capture standard error in the result, use ``stderr=STDOUT``::
-
- >>> check_output(["/bin/sh", "-c",
- ... "ls -l non_existent_file ; exit 0"],
- ... stderr=STDOUT)
- 'ls: non_existent_file: No such file or directory\n'
-
- There is an additional optional argument, "input", allowing you to
- pass a string to the subprocess's stdin. If you use this argument
- you may not also use the Popen constructor's "stdin" argument, as
- it too will be used internally. Example::
-
- >>> check_output(["sed", "-e", "s/foo/bar/"],
- ... input=b"when in the course of fooman events\n")
- 'when in the course of barman events\n'
-
- If ``universal_newlines=True`` is passed, the return value will be a
- string rather than bytes.
-
- .. versionchanged:: 1.2a1
- The ``timeout`` keyword argument is now accepted on all supported
- versions of Python (not just Python 3) and if it expires will raise a
- :exc:`TimeoutExpired` exception (under Python 2 this is a subclass of :exc:`~.Timeout`).
- .. versionchanged:: 1.2a1
- The ``input`` keyword argument is now accepted on all supported
- versions of Python, not just Python 3
- """
- timeout = kwargs.pop('timeout', None)
- if 'stdout' in kwargs:
- raise ValueError('stdout argument not allowed, it will be overridden.')
- if 'input' in kwargs:
- if 'stdin' in kwargs:
- raise ValueError('stdin and input arguments may not both be used.')
- inputdata = kwargs['input']
- del kwargs['input']
- kwargs['stdin'] = PIPE
- else:
- inputdata = None
- with Popen(*popenargs, stdout=PIPE, **kwargs) as process:
- try:
- output, unused_err = process.communicate(inputdata, 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
-
-_PLATFORM_DEFAULT_CLOSE_FDS = object()
-
-if 'TimeoutExpired' not in globals():
- # Python 2
-
- # Make TimeoutExpired inherit from _Timeout so it can be caught
- # the way we used to throw things (except Timeout), but make sure it doesn't
- # init a timer. Note that we can't have a fake 'SubprocessError' that inherits
- # from exception, because we need TimeoutExpired to just be a BaseException for
- # bwc.
- from gevent.timeout import Timeout as _Timeout
-
- class TimeoutExpired(_Timeout):
- """
- This exception is raised when the timeout expires while waiting for
- a child process in `communicate`.
-
- Under Python 2, this is a gevent extension with the same name as the
- Python 3 class for source-code forward compatibility. However, it extends
- :class:`gevent.timeout.Timeout` for backwards compatibility (because
- we used to just raise a plain ``Timeout``); note that ``Timeout`` is a
- ``BaseException``, *not* an ``Exception``.
-
- .. versionadded:: 1.2a1
- """
-
- def __init__(self, cmd, timeout, output=None):
- _Timeout.__init__(self, None)
- self.cmd = cmd
- self.seconds = timeout
- self.output = output
-
- @property
- def timeout(self):
- return self.seconds
-
- def __str__(self):
- return ("Command '%s' timed out after %s seconds" %
- (self.cmd, self.timeout))
-
-
-if hasattr(os, 'set_inheritable'):
- _set_inheritable = os.set_inheritable
-else:
- _set_inheritable = lambda i, v: True
-
-
-class Popen(object):
- """
- The underlying process creation and management in this module is
- handled by the Popen class. It offers a lot of flexibility so that
- developers are able to handle the less common cases not covered by
- the convenience functions.
-
- .. seealso:: :class:`subprocess.Popen`
- This class should have the same interface as the standard library class.
-
- .. versionchanged:: 1.2a1
- Instances can now be used as context managers under Python 2.7. Previously
- this was restricted to Python 3.
-
- .. versionchanged:: 1.2a1
- Instances now save the ``args`` attribute under Python 2.7. Previously this was
- restricted to Python 3.
-
- .. versionchanged:: 1.2b1
- Add the ``encoding`` and ``errors`` parameters for Python 3.
-
- .. versionchanged:: 1.3a1
- Accept "path-like" objects for the *cwd* parameter on all platforms.
- This was added to Python 3.6. Previously with gevent, it only worked
- on POSIX platforms on 3.6.
-
- .. versionchanged:: 1.3a1
- Add the ``text`` argument as a synonym for ``universal_newlines``,
- as added on Python 3.7.
-
- .. versionchanged:: 1.3a2
- Allow the same keyword arguments under Python 2 as Python 3:
- ``pass_fds``, ``start_new_session``, ``restore_signals``, ``encoding``
- and ``errors``. Under Python 2, ``encoding`` and ``errors`` are ignored
- because native handling of universal newlines is used.
-
- .. versionchanged:: 1.3a2
- Under Python 2, ``restore_signals`` defaults to ``False``. Previously it
- defaulted to ``True``, the same as it did in Python 3.
- """
-
- # The value returned from communicate() when there was nothing to read.
- # Changes if we're in text mode or universal newlines mode.
- _communicate_empty_value = b''
-
- def __init__(self, args,
- bufsize=-1 if PY3 else 0,
- executable=None,
- stdin=None, stdout=None, stderr=None,
- preexec_fn=None, close_fds=_PLATFORM_DEFAULT_CLOSE_FDS, shell=False,
- cwd=None, env=None, universal_newlines=None,
- startupinfo=None, creationflags=0,
- restore_signals=PY3, start_new_session=False,
- pass_fds=(),
- # Added in 3.6. These are kept as ivars
- encoding=None, errors=None,
- # Added in 3.7. Not an ivar directly.
- text=None,
- # gevent additions
- threadpool=None):
-
- self.encoding = encoding
- self.errors = errors
-
- hub = get_hub()
-
- if bufsize is None:
- # Python 2 doesn't allow None at all, but Python 3 treats
- # it the same as the default. We do as well.
- bufsize = -1 if PY3 else 0
- if not isinstance(bufsize, integer_types):
- raise TypeError("bufsize must be an integer")
-
- if mswindows:
- if preexec_fn is not None:
- raise ValueError("preexec_fn is not supported on Windows "
- "platforms")
- if sys.version_info[:2] >= (3, 7):
- if close_fds is _PLATFORM_DEFAULT_CLOSE_FDS:
- close_fds = True
- else:
- any_stdio_set = (stdin is not None or stdout is not None or
- stderr is not None)
- if close_fds is _PLATFORM_DEFAULT_CLOSE_FDS:
- if any_stdio_set:
- close_fds = False
- else:
- close_fds = True
- elif close_fds and any_stdio_set:
- raise ValueError("close_fds is not supported on Windows "
- "platforms if you redirect stdin/stdout/stderr")
- if threadpool is None:
- threadpool = hub.threadpool
- self.threadpool = threadpool
- self._waiting = False
- else:
- # POSIX
- if close_fds is _PLATFORM_DEFAULT_CLOSE_FDS:
- # close_fds has different defaults on Py3/Py2
- if PY3: # pylint: disable=simplifiable-if-statement
- close_fds = True
- else:
- close_fds = False
-
- if pass_fds and not close_fds:
- import warnings
- warnings.warn("pass_fds overriding close_fds.", RuntimeWarning)
- close_fds = True
- if startupinfo is not None:
- raise ValueError("startupinfo is only supported on Windows "
- "platforms")
- if creationflags != 0:
- raise ValueError("creationflags is only supported on Windows "
- "platforms")
- assert threadpool is None
- self._loop = hub.loop
-
- # Validate the combinations of text and universal_newlines
- if (text is not None and universal_newlines is not None
- and bool(universal_newlines) != bool(text)):
- raise SubprocessError('Cannot disambiguate when both text '
- 'and universal_newlines are supplied but '
- 'different. Pass one or the other.')
-
- self.args = args # Previously this was Py3 only.
- self.stdin = None
- self.stdout = None
- self.stderr = None
- self.pid = None
- self.returncode = None
- self.universal_newlines = universal_newlines
- self.result = AsyncResult()
-
- # Input and output objects. The general principle is like
- # this:
- #
- # Parent Child
- # ------ -----
- # p2cwrite ---stdin---> p2cread
- # c2pread <--stdout--- c2pwrite
- # errread <--stderr--- errwrite
- #
- # On POSIX, the child objects are file descriptors. On
- # Windows, these are Windows file handles. The parent objects
- # are file descriptors on both platforms. The parent objects
- # are -1 when not using PIPEs. The child objects are -1
- # when not redirecting.
-
- (p2cread, p2cwrite,
- c2pread, c2pwrite,
- errread, errwrite) = self._get_handles(stdin, stdout, stderr)
-
- # We wrap OS handles *before* launching the child, otherwise a
- # quickly terminating child could make our fds unwrappable
- # (see #8458).
- if mswindows:
- if p2cwrite != -1:
- p2cwrite = msvcrt.open_osfhandle(p2cwrite.Detach(), 0)
- if c2pread != -1:
- c2pread = msvcrt.open_osfhandle(c2pread.Detach(), 0)
- if errread != -1:
- errread = msvcrt.open_osfhandle(errread.Detach(), 0)
-
- text_mode = PY3 and (self.encoding or self.errors or universal_newlines or text)
- if text_mode or universal_newlines:
- # Always a native str in universal_newlines mode, even when that
- # str type is bytes. Additionally, text_mode is only true under
- # Python 3, so it's actually a unicode str
- self._communicate_empty_value = ''
-
- if p2cwrite != -1:
- if PY3 and text_mode:
- # Under Python 3, if we left on the 'b' we'd get different results
- # depending on whether we used FileObjectPosix or FileObjectThread
- self.stdin = FileObject(p2cwrite, 'wb', bufsize)
- self.stdin.translate_newlines(None,
- write_through=True,
- line_buffering=(bufsize == 1),
- encoding=self.encoding, errors=self.errors)
- else:
- self.stdin = FileObject(p2cwrite, 'wb', bufsize)
- if c2pread != -1:
- if universal_newlines or text_mode:
- if PY3:
- # FileObjectThread doesn't support the 'U' qualifier
- # with a bufsize of 0
- self.stdout = FileObject(c2pread, 'rb', bufsize)
- # NOTE: Universal Newlines are broken on Windows/Py3, at least
- # in some cases. This is true in the stdlib subprocess module
- # as well; the following line would fix the test cases in
- # test__subprocess.py that depend on python_universal_newlines,
- # but would be inconsistent with the stdlib:
- #msvcrt.setmode(self.stdout.fileno(), os.O_TEXT)
- self.stdout.translate_newlines('r', encoding=self.encoding, errors=self.errors)
- else:
- self.stdout = FileObject(c2pread, 'rU', bufsize)
- else:
- self.stdout = FileObject(c2pread, 'rb', bufsize)
- if errread != -1:
- if universal_newlines or text_mode:
- if PY3:
- self.stderr = FileObject(errread, 'rb', bufsize)
- self.stderr.translate_newlines(None, encoding=encoding, errors=errors)
- else:
- self.stderr = FileObject(errread, 'rU', bufsize)
- else:
- self.stderr = FileObject(errread, 'rb', bufsize)
-
- self._closed_child_pipe_fds = False
- # Convert here for the sake of all platforms. os.chdir accepts
- # path-like objects natively under 3.6, but CreateProcess
- # doesn't.
- cwd = fspath(cwd) if cwd is not None else None
- try:
- self._execute_child(args, executable, preexec_fn, close_fds,
- pass_fds, cwd, env, universal_newlines,
- startupinfo, creationflags, shell,
- p2cread, p2cwrite,
- c2pread, c2pwrite,
- errread, errwrite,
- restore_signals, start_new_session)
- except:
- # Cleanup if the child failed starting.
- # (gevent: New in python3, but reported as gevent bug in #347.
- # Note that under Py2, any error raised below will replace the
- # original error so we have to use reraise)
- if not PY3:
- exc_info = sys.exc_info()
- for f in filter(None, (self.stdin, self.stdout, self.stderr)):
- try:
- f.close()
- except (OSError, IOError):
- pass # Ignore EBADF or other errors.
-
- if not self._closed_child_pipe_fds:
- to_close = []
- if stdin == PIPE:
- to_close.append(p2cread)
- if stdout == PIPE:
- to_close.append(c2pwrite)
- if stderr == PIPE:
- to_close.append(errwrite)
- if hasattr(self, '_devnull'):
- to_close.append(self._devnull)
- for fd in to_close:
- try:
- os.close(fd)
- except (OSError, IOError):
- pass
- if not PY3:
- try:
- reraise(*exc_info)
- finally:
- del exc_info
- raise
-
- def __repr__(self):
- return '<%s at 0x%x pid=%r returncode=%r>' % (self.__class__.__name__, id(self), self.pid, self.returncode)
-
- def _on_child(self, watcher):
- watcher.stop()
- status = watcher.rstatus
- if os.WIFSIGNALED(status):
- self.returncode = -os.WTERMSIG(status)
- else:
- self.returncode = os.WEXITSTATUS(status)
- self.result.set(self.returncode)
-
- def _get_devnull(self):
- if not hasattr(self, '_devnull'):
- self._devnull = os.open(os.devnull, os.O_RDWR)
- return self._devnull
-
- _stdout_buffer = None
- _stderr_buffer = 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
- should be sent to the child.
-
- communicate() returns a tuple (stdout, stderr).
-
- :keyword timeout: Under Python 2, this is a gevent extension; if
- given and it expires, we will raise :exc:`TimeoutExpired`, which
- extends :exc:`gevent.timeout.Timeout` (note that this only extends :exc:`BaseException`,
- *not* :exc:`Exception`)
- Under Python 3, this raises the standard :exc:`TimeoutExpired` exception.
-
- .. versionchanged:: 1.1a2
- Under Python 2, if the *timeout* elapses, raise the :exc:`gevent.timeout.Timeout`
- exception. Previously, we silently returned.
- .. versionchanged:: 1.1b5
- Honor a *timeout* even if there's no way to communicate with the child
- (stdin, stdout, and stderr are not pipes).
- """
- greenlets = []
- if self.stdin:
- greenlets.append(spawn(write_and_close, self.stdin, input))
-
- # If the timeout parameter is used, and the caller calls back after
- # getting a TimeoutExpired exception, we can wind up with multiple
- # greenlets trying to run and read from and close stdout/stderr.
- # That's bad because it can lead to 'RuntimeError: reentrant call in io.BufferedReader'.
- # We can't just kill the previous greenlets when a timeout happens,
- # though, because we risk losing the output collected by that greenlet
- # (and Python 3, where timeout is an official parameter, explicitly says
- # that no output should be lost in the event of a timeout.) Instead, we're
- # watching for the exception and ignoring it. It's not elegant,
- # but it works
- def _make_pipe_reader(pipe_name):
- pipe = getattr(self, pipe_name)
- buf_name = '_' + pipe_name + '_buffer'
-
- def _read():
- try:
- data = pipe.read()
- except RuntimeError:
- return
- if not data:
- return
- the_buffer = getattr(self, buf_name)
- if the_buffer:
- the_buffer.append(data)
- else:
- setattr(self, buf_name, [data])
- return _read
-
- if self.stdout:
- _read_out = _make_pipe_reader('stdout')
- stdout = spawn(_read_out)
- greenlets.append(stdout)
- else:
- stdout = None
-
- if self.stderr:
- _read_err = _make_pipe_reader('stderr')
- stderr = spawn(_read_err)
- greenlets.append(stderr)
- else:
- stderr = None
-
- # If we were given stdin=stdout=stderr=None, we have no way to
- # communicate with the child, and thus no greenlets to wait
- # on. This is a nonsense case, but it comes up in the test
- # case for Python 3.5 (test_subprocess.py
- # RunFuncTestCase.test_timeout). Instead, we go directly to
- # self.wait
- if not greenlets and timeout is not None:
- self.wait(timeout=timeout, _raise_exc=True)
-
- done = joinall(greenlets, timeout=timeout)
- if timeout is not None and len(done) != len(greenlets):
- raise TimeoutExpired(self.args, timeout)
-
- for pipe in (self.stdout, self.stderr):
- if pipe:
- try:
- pipe.close()
- except RuntimeError:
- pass
-
- self.wait()
-
- def _get_output_value(pipe_name):
- buf_name = '_' + pipe_name + '_buffer'
- buf_value = getattr(self, buf_name)
- setattr(self, buf_name, None)
- if buf_value:
- buf_value = self._communicate_empty_value.join(buf_value)
- else:
- buf_value = self._communicate_empty_value
- return buf_value
-
- stdout_value = _get_output_value('stdout')
- stderr_value = _get_output_value('stderr')
-
- return (None if stdout is None else stdout_value,
- None if stderr is None else stderr_value)
-
- def poll(self):
- """Check if child process has terminated. Set and return :attr:`returncode` attribute."""
- return self._internal_poll()
-
- def __enter__(self):
- return self
-
- def __exit__(self, t, v, tb):
- if self.stdout:
- self.stdout.close()
- if self.stderr:
- self.stderr.close()
- try: # Flushing a BufferedWriter may raise an error
- if self.stdin:
- self.stdin.close()
- finally:
- # Wait for the process to terminate, to avoid zombies.
- # JAM: gevent: If the process never terminates, this
- # blocks forever.
- self.wait()
-
- def _gevent_result_wait(self, timeout=None, raise_exc=PY3):
- result = self.result.wait(timeout=timeout)
- if raise_exc and timeout is not None and not self.result.ready():
- raise TimeoutExpired(self.args, timeout)
- return result
-
-
- if mswindows:
- #
- # Windows methods
- #
- def _get_handles(self, stdin, stdout, stderr):
- """Construct and return tuple with IO objects:
- p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite
- """
- if stdin is None and stdout is None and stderr is None:
- return (-1, -1, -1, -1, -1, -1)
-
- p2cread, p2cwrite = -1, -1
- c2pread, c2pwrite = -1, -1
- errread, errwrite = -1, -1
-
- try:
- DEVNULL
- except NameError:
- _devnull = object()
- else:
- _devnull = DEVNULL
-
- if stdin is None:
- p2cread = GetStdHandle(STD_INPUT_HANDLE)
- if p2cread is None:
- p2cread, _ = CreatePipe(None, 0)
- if PY3:
- p2cread = Handle(p2cread)
- _winapi.CloseHandle(_)
- elif stdin == PIPE:
- p2cread, p2cwrite = CreatePipe(None, 0)
- if PY3:
- 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:
- # Assuming file-like object
- p2cread = msvcrt.get_osfhandle(stdin.fileno())
- p2cread = self._make_inheritable(p2cread)
-
- if stdout is None:
- c2pwrite = GetStdHandle(STD_OUTPUT_HANDLE)
- if c2pwrite is None:
- _, c2pwrite = CreatePipe(None, 0)
- if PY3:
- c2pwrite = Handle(c2pwrite)
- _winapi.CloseHandle(_)
- elif stdout == PIPE:
- c2pread, c2pwrite = CreatePipe(None, 0)
- if PY3:
- 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:
- # Assuming file-like object
- c2pwrite = msvcrt.get_osfhandle(stdout.fileno())
- c2pwrite = self._make_inheritable(c2pwrite)
-
- if stderr is None:
- errwrite = GetStdHandle(STD_ERROR_HANDLE)
- if errwrite is None:
- _, errwrite = CreatePipe(None, 0)
- if PY3:
- errwrite = Handle(errwrite)
- _winapi.CloseHandle(_)
- elif stderr == PIPE:
- errread, errwrite = CreatePipe(None, 0)
- if PY3:
- 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:
- # Assuming file-like object
- errwrite = msvcrt.get_osfhandle(stderr.fileno())
- errwrite = self._make_inheritable(errwrite)
-
- return (p2cread, p2cwrite,
- c2pread, c2pwrite,
- errread, errwrite)
-
- def _make_inheritable(self, handle):
- """Return a duplicate of handle, which is inheritable"""
- return DuplicateHandle(GetCurrentProcess(),
- handle, GetCurrentProcess(), 0, 1,
- DUPLICATE_SAME_ACCESS)
-
- def _find_w9xpopen(self):
- """Find and return absolute path to w9xpopen.exe"""
- w9xpopen = os.path.join(os.path.dirname(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.exe")
- if not os.path.exists(w9xpopen):
- raise RuntimeError("Cannot locate w9xpopen.exe, which is "
- "needed for Popen to work with your "
- "shell or platform.")
- return w9xpopen
-
-
- def _filter_handle_list(self, handle_list):
- """Filter out console handles that can't be used
- in lpAttributeList["handle_list"] and make sure the list
- isn't empty. This also removes duplicate handles."""
- # An handle with it's lowest two bits set might be a special console
- # handle that if passed in lpAttributeList["handle_list"], will
- # cause it to fail.
- # Only works on 3.7+
- return list({handle for handle in handle_list
- if handle & 0x3 != 0x3
- or _winapi.GetFileType(handle) !=
- _winapi.FILE_TYPE_CHAR})
-
-
- def _execute_child(self, args, executable, preexec_fn, close_fds,
- pass_fds, cwd, env, universal_newlines,
- startupinfo, creationflags, shell,
- p2cread, p2cwrite,
- c2pread, c2pwrite,
- errread, errwrite,
- unused_restore_signals, unused_start_new_session):
- """Execute program (MS Windows version)"""
-
- assert not pass_fds, "pass_fds not supported on Windows."
-
- if not isinstance(args, string_types):
- args = list2cmdline(args)
-
- # Process startup details
- if startupinfo is None:
- startupinfo = STARTUPINFO()
- use_std_handles = -1 not in (p2cread, c2pwrite, errwrite)
- if use_std_handles:
- startupinfo.dwFlags |= STARTF_USESTDHANDLES
- startupinfo.hStdInput = p2cread
- startupinfo.hStdOutput = c2pwrite
- startupinfo.hStdError = errwrite
-
- if hasattr(startupinfo, 'lpAttributeList'):
- # Support for Python >= 3.7
-
- attribute_list = startupinfo.lpAttributeList
- have_handle_list = bool(attribute_list and
- "handle_list" in attribute_list and
- attribute_list["handle_list"])
-
- # If we were given an handle_list or need to create one
- if have_handle_list or (use_std_handles and close_fds):
- if attribute_list is None:
- attribute_list = startupinfo.lpAttributeList = {}
- handle_list = attribute_list["handle_list"] = \
- list(attribute_list.get("handle_list", []))
-
- if use_std_handles:
- handle_list += [int(p2cread), int(c2pwrite), int(errwrite)]
-
- handle_list[:] = self._filter_handle_list(handle_list)
-
- if handle_list:
- if not close_fds:
- import warnings
- warnings.warn("startupinfo.lpAttributeList['handle_list'] "
- "overriding close_fds", RuntimeWarning)
-
- # When using the handle_list we always request to inherit
- # handles but the only handles that will be inherited are
- # the ones in the handle_list
- close_fds = False
-
- if shell:
- startupinfo.dwFlags |= STARTF_USESHOWWINDOW
- startupinfo.wShowWindow = SW_HIDE
- comspec = os.environ.get("COMSPEC", "cmd.exe")
- args = '{} /c "{}"'.format(comspec, args)
- if 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
- # information, see KB Q150956
- # (http://web.archive.org/web/20011105084002/http://support.microsoft.com/support/kb/articles/Q150/9/56.asp)
- w9xpopen = self._find_w9xpopen()
- args = '"%s" %s' % (w9xpopen, args)
- # Not passing CREATE_NEW_CONSOLE has been known to
- # cause random failures on win9x. Specifically a
- # dialog: "Your program accessed mem currently in
- # use at xxx" and a hopeful warning about the
- # stability of your system. Cost is Ctrl+C wont
- # kill children.
- creationflags |= CREATE_NEW_CONSOLE
-
- # Start the process
- try:
- hp, ht, pid, tid = CreateProcess(executable, args,
- # no special security
- None, None,
- int(not close_fds),
- creationflags,
- env,
- cwd,
- startupinfo)
- except IOError as e: # From 2.6 on, pywintypes.error was defined as IOError
- # Translate pywintypes.error to WindowsError, which is
- # a subclass of OSError. FIXME: We should really
- # translate errno using _sys_errlist (or similar), but
- # how can this be done from Python?
- if PY3:
- raise # don't remap here
- raise WindowsError(*e.args)
- finally:
- # Child is launched. Close the parent's copy of those pipe
- # handles that only the child should have open. You need
- # to make sure that no handles to the write end of the
- # output pipe are maintained in this process or else the
- # pipe will not close when the child process exits and the
- # ReadFile will hang.
- def _close(x):
- if x is not None and x != -1:
- if hasattr(x, 'Close'):
- x.Close()
- else:
- _winapi.CloseHandle(x)
-
- _close(p2cread)
- _close(c2pwrite)
- _close(errwrite)
- if hasattr(self, '_devnull'):
- os.close(self._devnull)
-
- # Retain the process handle, but close the thread handle
- self._child_created = True
- self._handle = Handle(hp) if not hasattr(hp, 'Close') else hp
- self.pid = pid
- _winapi.CloseHandle(ht) if not hasattr(ht, 'Close') else ht.Close()
-
- def _internal_poll(self):
- """Check if child process has terminated. Returns returncode
- attribute.
- """
- if self.returncode is None:
- if WaitForSingleObject(self._handle, 0) == WAIT_OBJECT_0:
- self.returncode = GetExitCodeProcess(self._handle)
- self.result.set(self.returncode)
- return self.returncode
-
- def rawlink(self, callback):
- if not self.result.ready() and not self._waiting:
- self._waiting = True
- Greenlet.spawn(self._wait)
- self.result.rawlink(linkproxy(callback, self))
- # XXX unlink
-
- def _blocking_wait(self):
- WaitForSingleObject(self._handle, INFINITE)
- self.returncode = GetExitCodeProcess(self._handle)
- return self.returncode
-
- def _wait(self):
- self.threadpool.spawn(self._blocking_wait).rawlink(self.result)
-
- def wait(self, timeout=None, _raise_exc=PY3):
- """Wait for child process to terminate. Returns returncode
- attribute."""
- if self.returncode is None:
- if not self._waiting:
- self._waiting = True
- self._wait()
- return self._gevent_result_wait(timeout, _raise_exc)
-
- def send_signal(self, sig):
- """Send a signal to the process
- """
- if sig == signal.SIGTERM:
- self.terminate()
- elif sig == signal.CTRL_C_EVENT:
- os.kill(self.pid, signal.CTRL_C_EVENT)
- elif sig == signal.CTRL_BREAK_EVENT:
- os.kill(self.pid, signal.CTRL_BREAK_EVENT)
- else:
- raise ValueError("Unsupported signal: {}".format(sig))
-
- def terminate(self):
- """Terminates the process
- """
- # Don't terminate a process that we know has already died.
- if self.returncode is not None:
- return
- try:
- TerminateProcess(self._handle, 1)
- except OSError as e:
- # ERROR_ACCESS_DENIED (winerror 5) is received when the
- # process already died.
- if e.winerror != 5:
- raise
- rc = GetExitCodeProcess(self._handle)
- if rc == STILL_ACTIVE:
- raise
- self.returncode = rc
- self.result.set(self.returncode)
-
- kill = terminate
-
- else:
- #
- # POSIX methods
- #
-
- def rawlink(self, callback):
- # Not public documented, part of the link protocol
- self.result.rawlink(linkproxy(callback, self))
- # XXX unlink
-
- def _get_handles(self, stdin, stdout, stderr):
- """Construct and return tuple with IO objects:
- p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite
- """
- p2cread, p2cwrite = -1, -1
- c2pread, c2pwrite = -1, -1
- errread, errwrite = -1, -1
-
- try:
- DEVNULL
- except NameError:
- _devnull = object()
- else:
- _devnull = DEVNULL
-
- if stdin is None:
- pass
- elif stdin == PIPE:
- p2cread, p2cwrite = self.pipe_cloexec()
- elif stdin == _devnull:
- p2cread = self._get_devnull()
- elif isinstance(stdin, int):
- p2cread = stdin
- else:
- # Assuming file-like object
- p2cread = stdin.fileno()
-
- if stdout is None:
- pass
- elif stdout == PIPE:
- c2pread, c2pwrite = self.pipe_cloexec()
- elif stdout == _devnull:
- c2pwrite = self._get_devnull()
- elif isinstance(stdout, int):
- c2pwrite = stdout
- else:
- # Assuming file-like object
- c2pwrite = stdout.fileno()
-
- if stderr is None:
- pass
- elif stderr == PIPE:
- errread, errwrite = self.pipe_cloexec()
- elif stderr == STDOUT:
- if c2pwrite != -1:
- errwrite = c2pwrite
- else: # child's stdout is not set, use parent's stdout
- errwrite = sys.__stdout__.fileno()
- elif stderr == _devnull:
- errwrite = self._get_devnull()
- elif isinstance(stderr, int):
- errwrite = stderr
- else:
- # Assuming file-like object
- errwrite = stderr.fileno()
-
- return (p2cread, p2cwrite,
- c2pread, c2pwrite,
- errread, errwrite)
-
- def _set_cloexec_flag(self, fd, cloexec=True):
- try:
- cloexec_flag = fcntl.FD_CLOEXEC
- except AttributeError:
- cloexec_flag = 1
-
- old = fcntl.fcntl(fd, fcntl.F_GETFD)
- if cloexec:
- fcntl.fcntl(fd, fcntl.F_SETFD, old | cloexec_flag)
- else:
- fcntl.fcntl(fd, fcntl.F_SETFD, old & ~cloexec_flag)
-
- def _remove_nonblock_flag(self, fd):
- flags = fcntl.fcntl(fd, fcntl.F_GETFL) & (~os.O_NONBLOCK)
- fcntl.fcntl(fd, fcntl.F_SETFL, flags)
-
- def pipe_cloexec(self):
- """Create a pipe with FDs set CLOEXEC."""
- # Pipes' FDs are set CLOEXEC by default because we don't want them
- # to be inherited by other subprocesses: the CLOEXEC flag is removed
- # from the child's FDs by _dup2(), between fork() and exec().
- # This is not atomic: we would need the pipe2() syscall for that.
- r, w = os.pipe()
- self._set_cloexec_flag(r)
- self._set_cloexec_flag(w)
- return r, w
-
- _POSSIBLE_FD_DIRS = (
- '/proc/self/fd', # Linux
- '/dev/fd', # BSD, including macOS
- )
-
- @classmethod
- def _close_fds(cls, keep, errpipe_write):
- # From the C code:
- # errpipe_write is part of keep. It must be closed at
- # exec(), but kept open in the child process until exec() is
- # called.
- for path in cls._POSSIBLE_FD_DIRS:
- if os.path.isdir(path):
- return cls._close_fds_from_path(path, keep, errpipe_write)
- return cls._close_fds_brute_force(keep, errpipe_write)
-
- @classmethod
- def _close_fds_from_path(cls, path, keep, errpipe_write):
- # path names a directory whose only entries have
- # names that are ascii strings of integers in base10,
- # corresponding to the fds the current process has open
- try:
- fds = [int(fname) for fname in os.listdir(path)]
- except (ValueError, OSError):
- cls._close_fds_brute_force(keep, errpipe_write)
- else:
- for i in keep:
- if i == errpipe_write:
- continue
- _set_inheritable(i, True)
-
- for fd in fds:
- if fd in keep or fd < 3:
- continue
- try:
- os.close(fd)
- except:
- pass
-
- @classmethod
- def _close_fds_brute_force(cls, keep, errpipe_write):
- # `keep` is a set of fds, so we
- # use os.closerange from 3 to min(keep)
- # and then from max(keep + 1) to MAXFD and
- # loop through filling in the gaps.
-
- # Under new python versions, we need to explicitly set
- # passed fds to be inheritable or they will go away on exec
-
- # XXX: Bug: We implicitly rely on errpipe_write being the largest open
- # FD so that we don't change its cloexec flag.
-
- assert hasattr(os, 'closerange') # Added in 2.7
- keep = sorted(keep)
- min_keep = min(keep)
- max_keep = max(keep)
- os.closerange(3, min_keep)
- os.closerange(max_keep + 1, MAXFD)
-
- for i in xrange(min_keep, max_keep):
- if i in keep:
- _set_inheritable(i, True)
- continue
-
- try:
- os.close(i)
- except:
- pass
-
- def _execute_child(self, args, executable, preexec_fn, close_fds,
- pass_fds, cwd, env, universal_newlines,
- startupinfo, creationflags, shell,
- p2cread, p2cwrite,
- c2pread, c2pwrite,
- errread, errwrite,
- restore_signals, start_new_session):
- """Execute program (POSIX version)"""
-
- if PY3 and isinstance(args, (str, bytes)):
- args = [args]
- elif not PY3 and isinstance(args, string_types):
- args = [args]
- else:
- try:
- args = list(args)
- except TypeError: # os.PathLike instead of a sequence?
- args = [fsencode(args)] # os.PathLike -> [str]
-
- if shell:
- args = ["/bin/sh", "-c"] + args
- if executable:
- args[0] = executable
-
- if executable is None:
- executable = args[0]
-
- self._loop.install_sigchld()
-
- # For transferring possible exec failure from child to parent
- # The first char specifies the exception type: 0 means
- # OSError, 1 means some other error.
- errpipe_read, errpipe_write = self.pipe_cloexec()
- # errpipe_write must not be in the standard io 0, 1, or 2 fd range.
- low_fds_to_close = []
- while errpipe_write < 3:
- low_fds_to_close.append(errpipe_write)
- errpipe_write = os.dup(errpipe_write)
- for low_fd in low_fds_to_close:
- os.close(low_fd)
- try:
- try:
- gc_was_enabled = gc.isenabled()
- # Disable gc to avoid bug where gc -> file_dealloc ->
- # write to stderr -> hang. http://bugs.python.org/issue1336
- gc.disable()
- try:
- self.pid = fork_and_watch(self._on_child, self._loop, True, fork)
- except:
- if gc_was_enabled:
- gc.enable()
- raise
- if self.pid == 0:
- # Child
-
- # XXX: Technically we're doing a lot of stuff here that
- # may not be safe to do before a exec(), depending on the OS.
- # CPython 3 goes to great lengths to precompute a lot
- # of this info before the fork and pass it all to C functions that
- # try hard not to call things like malloc(). (Of course,
- # CPython 2 pretty much did what we're doing.)
- 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)
- while errwrite in (0, 1):
- errwrite = os.dup(errwrite)
-
- # Dup fds for child
- def _dup2(existing, desired):
- # dup2() removes the CLOEXEC flag but
- # we must do it ourselves if dup2()
- # would be a no-op (issue #10806).
- if existing == desired:
- self._set_cloexec_flag(existing, False)
- elif existing != -1:
- os.dup2(existing, desired)
- try:
- self._remove_nonblock_flag(desired)
- except OSError:
- # Ignore EBADF, it may not actually be
- # open yet.
- # Tested beginning in 3.7.0b3 test_subprocess.py
- pass
- _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([None])
- for fd in [p2cread, c2pwrite, errwrite]:
- if fd not in closed and fd > 2:
- os.close(fd)
- closed.add(fd)
-
- if cwd is not None:
- try:
- os.chdir(cwd)
- except OSError as e:
- e._failed_chdir = True
- raise
-
- if preexec_fn:
- preexec_fn()
-
- # Close all other fds, if asked for. This must be done
- # after preexec_fn runs.
- if close_fds:
- fds_to_keep = set(pass_fds)
- fds_to_keep.add(errpipe_write)
- self._close_fds(fds_to_keep, errpipe_write)
-
- if restore_signals:
- # restore the documented signals back to sig_dfl;
- # not all will be defined on every platform
- for sig in 'SIGPIPE', 'SIGXFZ', 'SIGXFSZ':
- sig = getattr(signal, sig, None)
- if sig is not None:
- signal.signal(sig, signal.SIG_DFL)
-
- if start_new_session:
- os.setsid()
-
- if env is None:
- os.execvp(executable, args)
- else:
- if PY3:
- # Python 3.6 started testing for
- # bytes values in the env; it also
- # started encoding strs using
- # fsencode and using a lower-level
- # API that takes a list of keys
- # and values. We don't have access
- # to that API, so we go the reverse direction.
- env = {os.fsdecode(k) if isinstance(k, bytes) else k:
- os.fsdecode(v) if isinstance(v, bytes) else v
- for k, v in env.items()}
- os.execvpe(executable, args, env)
-
- except:
- exc_type, exc_value, tb = sys.exc_info()
- # Save the traceback and attach it to the exception object
- exc_lines = traceback.format_exception(exc_type,
- exc_value,
- tb)
- exc_value.child_traceback = ''.join(exc_lines)
- os.write(errpipe_write, pickle.dumps(exc_value))
-
- finally:
- # Make sure that the process exits no matter what.
- # The return code does not matter much as it won't be
- # reported to the application
- os._exit(1)
-
- # Parent
- self._child_created = True
- if gc_was_enabled:
- gc.enable()
- finally:
- # be sure the FD is closed no matter what
- os.close(errpipe_write)
-
- # self._devnull is not always defined.
- devnull_fd = getattr(self, '_devnull', None)
- if p2cread != -1 and p2cwrite != -1 and p2cread != devnull_fd:
- os.close(p2cread)
- if c2pwrite != -1 and c2pread != -1 and c2pwrite != devnull_fd:
- os.close(c2pwrite)
- if errwrite != -1 and errread != -1 and errwrite != devnull_fd:
- os.close(errwrite)
- if devnull_fd is not None:
- os.close(devnull_fd)
- # Prevent a double close of these fds from __init__ on error.
- self._closed_child_pipe_fds = True
-
- # Wait for exec to fail or succeed; possibly raising exception
- errpipe_read = FileObject(errpipe_read, 'rb')
- data = errpipe_read.read()
- finally:
- if hasattr(errpipe_read, 'close'):
- errpipe_read.close()
- else:
- os.close(errpipe_read)
-
- if data != b"":
- self.wait()
- child_exception = pickle.loads(data)
- for fd in (p2cwrite, c2pread, errread):
- if fd is not None and fd != -1:
- os.close(fd)
- if isinstance(child_exception, OSError):
- child_exception.filename = executable
- if hasattr(child_exception, '_failed_chdir'):
- child_exception.filename = cwd
- raise child_exception
-
- def _handle_exitstatus(self, sts):
- if os.WIFSIGNALED(sts):
- self.returncode = -os.WTERMSIG(sts)
- elif os.WIFEXITED(sts):
- self.returncode = os.WEXITSTATUS(sts)
- else:
- # Should never happen
- raise RuntimeError("Unknown child exit status!")
-
- def _internal_poll(self):
- """Check if child process has terminated. Returns returncode
- attribute.
- """
- if self.returncode is None:
- if get_hub() is not getcurrent():
- sig_pending = getattr(self._loop, 'sig_pending', True)
- if sig_pending:
- sleep(0.00001)
- return self.returncode
-
- def wait(self, timeout=None, _raise_exc=PY3):
- """
- Wait for child process to terminate. Returns :attr:`returncode`
- attribute.
-
- :keyword timeout: The floating point number of seconds to
- wait. Under Python 2, this is a gevent extension, and
- we simply return if it expires. Under Python 3, if
- this time elapses without finishing the process,
- :exc:`TimeoutExpired` is raised.
- """
- return self._gevent_result_wait(timeout, _raise_exc)
-
- def send_signal(self, sig):
- """Send a signal to the process
- """
- # Skip signalling a process that we know has already died.
- if self.returncode is None:
- os.kill(self.pid, sig)
-
- def terminate(self):
- """Terminate the process with SIGTERM
- """
- self.send_signal(signal.SIGTERM)
-
- def kill(self):
- """Kill the process with SIGKILL
- """
- self.send_signal(signal.SIGKILL)
-
-
-def write_and_close(fobj, data):
- try:
- if data:
- fobj.write(data)
- if hasattr(fobj, 'flush'):
- # 3.6 started expecting flush to be called.
- fobj.flush()
- except (OSError, IOError) as ex:
- if ex.errno != errno.EPIPE and ex.errno != errno.EINVAL:
- raise
- finally:
- try:
- fobj.close()
- except EnvironmentError:
- pass
-
-def _with_stdout_stderr(exc, stderr):
- # Prior to Python 3.5, most exceptions didn't have stdout
- # and stderr attributes and can't take the stderr attribute in their
- # constructor
- exc.stdout = exc.output
- exc.stderr = stderr
- return exc
-
-class CompletedProcess(object):
- """
- A process that has finished running.
-
- This is returned by run().
-
- Attributes:
- - args: The list or str args passed to run().
- - returncode: The exit code of the process, negative for signals.
- - stdout: The standard output (None if not captured).
- - stderr: The standard error (None if not captured).
-
- .. versionadded:: 1.2a1
- This first appeared in Python 3.5 and is available to all
- Python versions in gevent.
- """
- def __init__(self, args, returncode, stdout=None, stderr=None):
- self.args = args
- self.returncode = returncode
- self.stdout = stdout
- self.stderr = stderr
-
- def __repr__(self):
- args = ['args={!r}'.format(self.args),
- 'returncode={!r}'.format(self.returncode)]
- if self.stdout is not None:
- args.append('stdout={!r}'.format(self.stdout))
- if self.stderr is not None:
- args.append('stderr={!r}'.format(self.stderr))
- return "{}({})".format(type(self).__name__, ', '.join(args))
-
- def check_returncode(self):
- """Raise CalledProcessError if the exit code is non-zero."""
- if self.returncode:
- raise _with_stdout_stderr(CalledProcessError(self.returncode, self.args, self.stdout), self.stderr)
-
-
-def run(*popenargs, **kwargs):
- """
- run(args, *, stdin=None, input=None, stdout=None, stderr=None, shell=False, timeout=None, check=False) -> CompletedProcess
-
- Run command with arguments and return a CompletedProcess instance.
-
- The returned instance will have attributes args, returncode, stdout and
- stderr. By default, stdout and stderr are not captured, and those attributes
- will be None. Pass stdout=PIPE and/or stderr=PIPE in order to capture them.
- If check is True and the exit code was non-zero, it raises a
- CalledProcessError. The CalledProcessError object will have the return code
- in the returncode attribute, and output & stderr attributes if those streams
- were captured.
-
- If timeout is given, and the process takes too long, a TimeoutExpired
- exception will be raised.
-
- There is an optional argument "input", allowing you to
- pass a string to the subprocess's stdin. If you use this argument
- you may not also use the Popen constructor's "stdin" argument, as
- it will be used internally.
- The other arguments are the same as for the Popen constructor.
- If universal_newlines=True is passed, the "input" argument must be a
- string and stdout/stderr in the returned object will be strings rather than
- bytes.
-
- .. versionadded:: 1.2a1
- This function first appeared in Python 3.5. It is available on all Python
- versions gevent supports.
-
- .. versionchanged:: 1.3a2
- Add the ``capture_output`` argument from Python 3.7. It automatically sets
- ``stdout`` and ``stderr`` to ``PIPE``. It is an error to pass either
- of those arguments along with ``capture_output``.
- """
- input = kwargs.pop('input', None)
- timeout = kwargs.pop('timeout', None)
- check = kwargs.pop('check', False)
- capture_output = kwargs.pop('capture_output', False)
-
- if input is not None:
- if 'stdin' in kwargs:
- raise ValueError('stdin and input arguments may not both be used.')
- kwargs['stdin'] = PIPE
-
- if capture_output:
- if ('stdout' in kwargs) or ('stderr' in kwargs):
- raise ValueError('stdout and stderr arguments may not be used '
- 'with capture_output.')
- kwargs['stdout'] = PIPE
- kwargs['stderr'] = PIPE
-
- with Popen(*popenargs, **kwargs) as process:
- try:
- stdout, stderr = process.communicate(input, timeout=timeout)
- except TimeoutExpired:
- process.kill()
- stdout, stderr = process.communicate()
- raise _with_stdout_stderr(TimeoutExpired(process.args, timeout, output=stdout), stderr)
- except:
- process.kill()
- process.wait()
- raise
- retcode = process.poll()
- if check and retcode:
- raise _with_stdout_stderr(CalledProcessError(retcode, process.args, stdout), stderr)
-
- return CompletedProcess(process.args, retcode, stdout, stderr)