summaryrefslogtreecommitdiffstats
path: root/Lib/subprocess.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/subprocess.py')
-rw-r--r--Lib/subprocess.py305
1 files changed, 204 insertions, 101 deletions
diff --git a/Lib/subprocess.py b/Lib/subprocess.py
index 63ca956..b33601a 100644
--- a/Lib/subprocess.py
+++ b/Lib/subprocess.py
@@ -29,7 +29,8 @@ class Popen(args, bufsize=0, executable=None,
stdin=None, stdout=None, stderr=None,
preexec_fn=None, close_fds=False, shell=False,
cwd=None, env=None, universal_newlines=False,
- startupinfo=None, creationflags=0):
+ startupinfo=None, creationflags=0,
+ restore_signals=True, start_new_session=False):
Arguments are:
@@ -72,8 +73,11 @@ parent. Additionally, stderr can be STDOUT, which indicates that the
stderr data from the applications should be captured into the same
file handle as for stdout.
-If preexec_fn is set to a callable object, this object will be called
-in the child process just before the child is executed.
+On UNIX, if preexec_fn is set to a callable object, this object will be
+called in the child process just before the child is executed. The use
+of preexec_fn is not thread safe, using it in the presence of threads
+could lead to a deadlock in the child process before the new executable
+is executed.
If close_fds is true, all file descriptors except 0, 1 and 2 will be
closed before the child process is executed.
@@ -84,6 +88,14 @@ shell.
If cwd is not None, the current directory will be changed to cwd
before the child is executed.
+On UNIX, if restore_signals is True all signals that Python sets to
+SIG_IGN are restored to SIG_DFL in the child process before the exec.
+Currently this includes the SIGPIPE, SIGXFZ and SIGXFSZ signals. This
+parameter does nothing on Windows.
+
+On UNIX, if start_new_session is True, the setsid() system call will be made
+in the child process prior to executing the command.
+
If env is not None, it defines the environment variables for the new
process.
@@ -326,6 +338,7 @@ import os
import traceback
import gc
import signal
+import builtins
# Exception classes used by this module.
class CalledProcessError(Exception):
@@ -375,6 +388,15 @@ else:
import fcntl
import pickle
+ try:
+ import _posixsubprocess
+ except ImportError:
+ _posixsubprocess = None
+ import warnings
+ warnings.warn("The _posixsubprocess module is not being used. "
+ "Child process reliability may suffer if your "
+ "program uses threads.", RuntimeWarning)
+
# 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.
@@ -596,7 +618,8 @@ class Popen(object):
stdin=None, stdout=None, stderr=None,
preexec_fn=None, close_fds=False, shell=False,
cwd=None, env=None, universal_newlines=False,
- startupinfo=None, creationflags=0):
+ startupinfo=None, creationflags=0,
+ restore_signals=True, start_new_session=False):
"""Create new Popen instance."""
_cleanup()
@@ -642,7 +665,7 @@ class Popen(object):
# 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 None when not using PIPEs. The child objects are None
+ # are -1 when not using PIPEs. The child objects are -1
# when not redirecting.
(p2cread, p2cwrite,
@@ -654,7 +677,8 @@ class Popen(object):
startupinfo, creationflags, shell,
p2cread, p2cwrite,
c2pread, c2pwrite,
- errread, errwrite)
+ errread, errwrite,
+ restore_signals, start_new_session)
if mswindows:
if p2cwrite is not None:
@@ -666,15 +690,15 @@ class Popen(object):
if bufsize == 0:
bufsize = 1 # Nearly unbuffered (XXX for now)
- if p2cwrite is not None:
+ if p2cwrite != -1:
self.stdin = io.open(p2cwrite, 'wb', bufsize)
if self.universal_newlines:
self.stdin = io.TextIOWrapper(self.stdin)
- if c2pread is not None:
+ if c2pread != -1:
self.stdout = io.open(c2pread, 'rb', bufsize)
if universal_newlines:
self.stdout = io.TextIOWrapper(self.stdout)
- if errread is not None:
+ if errread != -1:
self.stderr = io.open(errread, 'rb', bufsize)
if universal_newlines:
self.stderr = io.TextIOWrapper(self.stderr)
@@ -739,11 +763,11 @@ class Popen(object):
p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite
"""
if stdin is None and stdout is None and stderr is None:
- return (None, None, None, None, None, None)
+ return (-1, -1, -1, -1, -1, -1)
- p2cread, p2cwrite = None, None
- c2pread, c2pwrite = None, None
- errread, errwrite = None, None
+ p2cread, p2cwrite = -1, -1
+ c2pread, c2pwrite = -1, -1
+ errread, errwrite = -1, -1
if stdin is None:
p2cread = GetStdHandle(STD_INPUT_HANDLE)
@@ -819,7 +843,8 @@ class Popen(object):
startupinfo, creationflags, shell,
p2cread, p2cwrite,
c2pread, c2pwrite,
- errread, errwrite):
+ errread, errwrite,
+ unused_restore_signals, unused_start_new_session):
"""Execute program (MS Windows version)"""
if not isinstance(args, str):
@@ -973,9 +998,9 @@ class Popen(object):
"""Construct and return tuple with IO objects:
p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite
"""
- p2cread, p2cwrite = None, None
- c2pread, c2pwrite = None, None
- errread, errwrite = None, None
+ p2cread, p2cwrite = -1, -1
+ c2pread, c2pwrite = -1, -1
+ errread, errwrite = -1, -1
if stdin is None:
pass
@@ -1034,7 +1059,8 @@ class Popen(object):
startupinfo, creationflags, shell,
p2cread, p2cwrite,
c2pread, c2pwrite,
- errread, errwrite):
+ errread, errwrite,
+ restore_signals, start_new_session):
"""Execute program (POSIX version)"""
if isinstance(args, str):
@@ -1048,113 +1074,190 @@ class Popen(object):
if executable is None:
executable = args[0]
- # For transferring possible exec failure from child to parent
- # The first char specifies the exception type: 0 means
- # OSError, 1 means some other error.
+ # For transferring possible exec failure from child to parent.
+ # Data format: "exception name:hex errno:description"
+ # Pickle is not used; it is complex and involves memory allocation.
errpipe_read, errpipe_write = os.pipe()
try:
try:
self._set_cloexec_flag(errpipe_write)
- 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 = os.fork()
- except:
- if gc_was_enabled:
- gc.enable()
- raise
- self._child_created = True
- if self.pid == 0:
- # Child
+ if _posixsubprocess:
+ fs_encoding = sys.getfilesystemencoding()
+ def fs_encode(s):
+ """Encode s for use in the env, fs or cmdline."""
+ return s.encode(fs_encoding, 'surrogateescape')
+
+ # 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:
+ env_list = [fs_encode(k) + b'=' + fs_encode(v)
+ for k, v in env.items()]
+ else:
+ env_list = None # Use execv instead of execve.
+ if os.path.dirname(executable):
+ executable_list = (fs_encode(executable),)
+ else:
+ # This matches the behavior of os._execvpe().
+ path_list = os.get_exec_path(env)
+ executable_list = (os.path.join(dir, executable)
+ for dir in path_list)
+ executable_list = tuple(fs_encode(exe)
+ for exe in executable_list)
+ self.pid = _posixsubprocess.fork_exec(
+ args, executable_list,
+ close_fds, cwd, env_list,
+ p2cread, p2cwrite, c2pread, c2pwrite,
+ errread, errwrite,
+ errpipe_read, errpipe_write,
+ restore_signals, start_new_session, preexec_fn)
+ 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:
- # Close parent's pipe ends
- if p2cwrite is not None:
- os.close(p2cwrite)
- if c2pread is not None:
- os.close(c2pread)
- if errread is not None:
- os.close(errread)
- os.close(errpipe_read)
-
- # Dup fds for child
- if p2cread is not None:
- os.dup2(p2cread, 0)
- if c2pwrite is not None:
- os.dup2(c2pwrite, 1)
- if errwrite is not None:
- os.dup2(errwrite, 2)
-
- # Close pipe fds. Make sure we don't close the
- # same fd more than once, or standard fds.
- if p2cread is not None and p2cread not in (0,):
- os.close(p2cread)
- if c2pwrite is not None and \
- c2pwrite not in (p2cread, 1):
- os.close(c2pwrite)
- if (errwrite is not None and
- errwrite not in (p2cread, c2pwrite, 2)):
- os.close(errwrite)
-
- # Close all other fds, if asked for
- if close_fds:
- self._close_fds(but=errpipe_write)
-
- if cwd is not None:
- os.chdir(cwd)
-
- if preexec_fn:
- preexec_fn()
-
- if env is None:
- os.execvp(executable, args)
- else:
- os.execvpe(executable, args, env)
-
+ self.pid = os.fork()
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))
-
- # 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()
+ if gc_was_enabled:
+ gc.enable()
+ raise
+ self._child_created = True
+ if self.pid == 0:
+ # Child
+ 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)
+
+ # Dup fds for child
+ if p2cread != -1:
+ os.dup2(p2cread, 0)
+ if c2pwrite != -1:
+ os.dup2(c2pwrite, 1)
+ if errwrite != -1:
+ os.dup2(errwrite, 2)
+
+ # Close pipe fds. Make sure we don't close the
+ # same fd more than once, or standard fds.
+ if p2cread != -1 and p2cread not in (0,):
+ os.close(p2cread)
+ if (c2pwrite != -1 and
+ c2pwrite not in (p2cread, 1)):
+ os.close(c2pwrite)
+ if (errwrite != -1 and
+ errwrite not in (p2cread, c2pwrite, 2)):
+ os.close(errwrite)
+
+ # Close all other fds, if asked for
+ if close_fds:
+ self._close_fds(but=errpipe_write)
+
+ 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()
+
+ 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 = exc_value.errno
+ else:
+ errno = 0
+ message = '%s:%x:%s' % (exc_type.__name__,
+ errno, exc_value)
+ os.write(errpipe_write, message.encode())
+ except:
+ # 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()
finally:
# be sure the FD is closed no matter what
os.close(errpipe_write)
- if p2cread is not None and p2cwrite is not None:
+ if p2cread != -1 and p2cwrite != -1:
os.close(p2cread)
- if c2pwrite is not None and c2pread is not None:
+ if c2pwrite != -1 and c2pread != -1:
os.close(c2pwrite)
- if errwrite is not None and errread is not None:
+ if errwrite != -1 and errread != -1:
os.close(errwrite)
# Wait for exec to fail or succeed; possibly raising an
- # exception (limited to 1 MB)
- data = _eintr_retry_call(os.read, errpipe_read, 1048576)
+ # exception (limited in size)
+ data = bytearray()
+ while True:
+ part = _eintr_retry_call(os.read, errpipe_read, 50000)
+ data += part
+ if not part or len(data) > 50000:
+ break
finally:
# be sure the FD is closed no matter what
os.close(errpipe_read)
if data:
_eintr_retry_call(os.waitpid, self.pid, 0)
- child_exception = pickle.loads(data)
+ try:
+ exception_name, hex_errno, err_msg = data.split(b':', 2)
+ except ValueError:
+ print('Bad exception data:', repr(data))
+ exception_name = b'RuntimeError'
+ hex_errno = b'0'
+ err_msg = b'Unknown'
+ child_exception_type = getattr(
+ builtins, exception_name.decode('ascii'),
+ RuntimeError)
for fd in (p2cwrite, c2pread, errread):
- if fd is not None:
+ if fd != -1:
os.close(fd)
- raise child_exception
+ err_msg = err_msg.decode()
+ if issubclass(child_exception_type, OSError) and hex_errno:
+ errno = int(hex_errno, 16)
+ if errno != 0:
+ err_msg = os.strerror(errno)
+ raise child_exception_type(errno, err_msg)
+ raise child_exception_type(err_msg)
def _handle_exitstatus(self, sts):