Commit 8edd99d0 authored by Gregory P. Smith's avatar Gregory P. Smith

Issue #6559: fix the subprocess.Popen pass_fds implementation. Add a unittest.

Issue #7213: Change the close_fds default on Windows to better match the new
default on POSIX.  True when possible (False if stdin/stdout/stderr are
supplied).

Update the documentation to reflect all of the above.
parent 39f34aa1
...@@ -28,7 +28,7 @@ Using the subprocess Module ...@@ -28,7 +28,7 @@ Using the subprocess Module
This module defines one class called :class:`Popen`: This module defines one class called :class:`Popen`:
.. class:: Popen(args, bufsize=0, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=_PLATFORM_DEFAULT, shell=False, cwd=None, env=None, universal_newlines=False, startupinfo=None, creationflags=0, restore_signals=True, start_new_session=False) .. class:: Popen(args, bufsize=0, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=True, shell=False, cwd=None, env=None, universal_newlines=False, startupinfo=None, creationflags=0, restore_signals=True, start_new_session=False, pass_fds=())
Arguments are: Arguments are:
...@@ -153,14 +153,22 @@ This module defines one class called :class:`Popen`: ...@@ -153,14 +153,22 @@ This module defines one class called :class:`Popen`:
If *close_fds* is true, all file descriptors except :const:`0`, :const:`1` and If *close_fds* is true, all file descriptors except :const:`0`, :const:`1` and
:const:`2` will be closed before the child process is executed. (Unix only). :const:`2` will be closed before the child process is executed. (Unix only).
The default varies by platform: :const:`False` on Windows and :const:`True` The default varies by platform: Always true on Unix. On Windows it is
on POSIX and other platforms. true when *stdin*/*stdout*/*stderr* are :const:`None`, false otherwise.
On Windows, if *close_fds* is true then no handles will be inherited by the On Windows, if *close_fds* is true then no handles will be inherited by the
child process. Note that on Windows, you cannot set *close_fds* to true and child process. Note that on Windows, you cannot set *close_fds* to true and
also redirect the standard handles by setting *stdin*, *stdout* or *stderr*. also redirect the standard handles by setting *stdin*, *stdout* or *stderr*.
.. versionchanged:: 3.2 .. versionchanged:: 3.2
The default was changed to True on non Windows platforms. The default for *close_fds* was changed from :const:`False` to
what is described above.
*pass_fds* is an optional sequence of file descriptors to keep open
between the parent and child. Providing any *pass_fds* forces
*close_fds* to be :const:`True`. (Unix only)
.. versionadded:: 3.2
The *pass_fds* parameter was added.
If *cwd* is not ``None``, the child's current directory will be changed to *cwd* If *cwd* is not ``None``, the child's current directory will be changed to *cwd*
before it is executed. Note that this directory is not considered when before it is executed. Note that this directory is not considered when
......
...@@ -27,10 +27,10 @@ This module defines one class called Popen: ...@@ -27,10 +27,10 @@ This module defines one class called Popen:
class Popen(args, bufsize=0, executable=None, class Popen(args, bufsize=0, executable=None,
stdin=None, stdout=None, stderr=None, stdin=None, stdout=None, stderr=None,
preexec_fn=None, close_fds=_PLATFORM_DEFAULT, shell=False, preexec_fn=None, close_fds=True, shell=False,
cwd=None, env=None, universal_newlines=False, cwd=None, env=None, universal_newlines=False,
startupinfo=None, creationflags=0, startupinfo=None, creationflags=0,
restore_signals=True, start_new_session=False): restore_signals=True, start_new_session=False, pass_fds=()):
Arguments are: Arguments are:
...@@ -81,8 +81,11 @@ is executed. ...@@ -81,8 +81,11 @@ is executed.
If close_fds is true, all file descriptors except 0, 1 and 2 will be If close_fds is true, all file descriptors except 0, 1 and 2 will be
closed before the child process is executed. The default for close_fds closed before the child process is executed. The default for close_fds
varies by platform: False on Windows and True on all other platforms varies by platform: Always true on POSIX. True when stdin/stdout/stderr
such as POSIX. are None on Windows, false otherwise.
pass_fds is an optional sequence of file descriptors to keep open between the
parent and child. Providing any pass_fds implicitly sets close_fds to true.
if shell is true, the specified command will be executed through the if shell is true, the specified command will be executed through the
shell. shell.
...@@ -621,17 +624,14 @@ def getoutput(cmd): ...@@ -621,17 +624,14 @@ def getoutput(cmd):
return getstatusoutput(cmd)[1] return getstatusoutput(cmd)[1]
if mswindows: _PLATFORM_DEFAULT_CLOSE_FDS = object()
_PLATFORM_DEFAULT = False
else:
_PLATFORM_DEFAULT = True
class Popen(object): class Popen(object):
def __init__(self, args, bufsize=0, executable=None, def __init__(self, args, bufsize=0, executable=None,
stdin=None, stdout=None, stderr=None, stdin=None, stdout=None, stderr=None,
preexec_fn=None, close_fds=_PLATFORM_DEFAULT, shell=False, preexec_fn=None, close_fds=_PLATFORM_DEFAULT_CLOSE_FDS,
cwd=None, env=None, universal_newlines=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, restore_signals=True, start_new_session=False,
pass_fds=()): pass_fds=()):
...@@ -648,12 +648,24 @@ class Popen(object): ...@@ -648,12 +648,24 @@ class Popen(object):
if preexec_fn is not None: if preexec_fn is not None:
raise ValueError("preexec_fn is not supported on Windows " raise ValueError("preexec_fn is not supported on Windows "
"platforms") "platforms")
if close_fds and (stdin is not None or stdout is not None or any_stdio_set = (stdin is not None or stdout is not None or
stderr is not None): stderr is not None)
raise ValueError("close_fds is not supported on Windows " if close_fds is _PLATFORM_DEFAULT_CLOSE_FDS:
"platforms if you redirect stdin/stdout/stderr") 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")
else: else:
# POSIX # POSIX
if close_fds is _PLATFORM_DEFAULT_CLOSE_FDS:
close_fds = True
if pass_fds and not close_fds:
warnings.warn("pass_fds overriding close_fds.", RuntimeWarning)
close_fds = True
if startupinfo is not None: if startupinfo is not None:
raise ValueError("startupinfo is only supported on Windows " raise ValueError("startupinfo is only supported on Windows "
"platforms") "platforms")
...@@ -661,9 +673,6 @@ class Popen(object): ...@@ -661,9 +673,6 @@ class Popen(object):
raise ValueError("creationflags is only supported on Windows " raise ValueError("creationflags is only supported on Windows "
"platforms") "platforms")
if pass_fds and not close_fds:
raise ValueError("pass_fds requires close_fds=True.")
self.stdin = None self.stdin = None
self.stdout = None self.stdout = None
self.stderr = None self.stderr = None
...@@ -876,7 +885,7 @@ class Popen(object): ...@@ -876,7 +885,7 @@ class Popen(object):
unused_restore_signals, unused_start_new_session): unused_restore_signals, unused_start_new_session):
"""Execute program (MS Windows version)""" """Execute program (MS Windows version)"""
assert not pass_fds, "pass_fds not yet supported on Windows" assert not pass_fds, "pass_fds not supported on Windows."
if not isinstance(args, str): if not isinstance(args, str):
args = list2cmdline(args) args = list2cmdline(args)
...@@ -1091,7 +1100,7 @@ class Popen(object): ...@@ -1091,7 +1100,7 @@ class Popen(object):
# precondition: fds_to_keep must be sorted and unique # precondition: fds_to_keep must be sorted and unique
start_fd = 3 start_fd = 3
for fd in fds_to_keep: for fd in fds_to_keep:
if fd > start_fd: if fd >= start_fd:
os.closerange(start_fd, fd) os.closerange(start_fd, fd)
start_fd = fd + 1 start_fd = fd + 1
if start_fd <= MAXFD: if start_fd <= MAXFD:
......
...@@ -1042,6 +1042,37 @@ class POSIXProcessTestCase(BaseTestCase): ...@@ -1042,6 +1042,37 @@ class POSIXProcessTestCase(BaseTestCase):
"Some fds were left open") "Some fds were left open")
self.assertIn(1, remaining_fds, "Subprocess failed") self.assertIn(1, remaining_fds, "Subprocess failed")
def test_pass_fds(self):
fd_status = support.findfile("fd_status.py", subdir="subprocessdata")
open_fds = set()
for x in range(5):
fds = os.pipe()
self.addCleanup(os.close, fds[0])
self.addCleanup(os.close, fds[1])
open_fds.update(fds)
for fd in open_fds:
p = subprocess.Popen([sys.executable, fd_status],
stdout=subprocess.PIPE, close_fds=True,
pass_fds=(fd, ))
output, ignored = p.communicate()
remaining_fds = set(map(int, output.split(b',')))
to_be_closed = open_fds - {fd}
self.assertIn(fd, remaining_fds, "fd to be passed not passed")
self.assertFalse(remaining_fds & to_be_closed,
"fd to be closed passed")
# pass_fds overrides close_fds with a warning.
with self.assertWarns(RuntimeWarning) as context:
self.assertFalse(subprocess.call(
[sys.executable, "-c", "import sys; sys.exit(0)"],
close_fds=False, pass_fds=(fd, )))
self.assertIn('overriding close_fds', str(context.warning))
@unittest.skipUnless(mswindows, "Windows specific tests") @unittest.skipUnless(mswindows, "Windows specific tests")
class Win32ProcessTestCase(BaseTestCase): class Win32ProcessTestCase(BaseTestCase):
......
...@@ -23,8 +23,12 @@ Library ...@@ -23,8 +23,12 @@ Library
- Issue #10107: Warn about unsaved files in IDLE on OSX. - Issue #10107: Warn about unsaved files in IDLE on OSX.
- Issue #7213: subprocess.Popen's default for close_fds has been changed. - Issue #7213: subprocess.Popen's default for close_fds has been changed.
It is now platform specific, keeping its default of False on Windows and It is now True in most cases other than on Windows when input, output or
changing the default to True on POSIX and other platforms. error handles are provided.
- Issue #6559: subprocess.Popen has a new pass_fds parameter (actually
added in 3.2beta1) to allow specifying a specific list of file descriptors
to keep open in the child process.
What's New in Python 3.2 Beta 1? What's New in Python 3.2 Beta 1?
......
...@@ -107,7 +107,7 @@ static void child_exec(char *const exec_array[], ...@@ -107,7 +107,7 @@ static void child_exec(char *const exec_array[],
errno = 0; /* We don't want to report an OSError. */ errno = 0; /* We don't want to report an OSError. */
goto error; goto error;
} }
if (keep_fd <= start_fd) if (keep_fd < start_fd)
continue; continue;
for (fd_num = start_fd; fd_num < keep_fd; ++fd_num) { for (fd_num = start_fd; fd_num < keep_fd; ++fd_num) {
close(fd_num); close(fd_num);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment