summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Lib/subprocess.py3
-rw-r--r--Lib/test/test_subprocess.py38
-rw-r--r--Misc/NEWS3
3 files changed, 41 insertions, 3 deletions
diff --git a/Lib/subprocess.py b/Lib/subprocess.py
index dad7b16..d3d90ca 100644
--- a/Lib/subprocess.py
+++ b/Lib/subprocess.py
@@ -1419,9 +1419,6 @@ class Popen(object):
child_exception_type = getattr(
builtins, exception_name.decode('ascii'),
RuntimeError)
- for fd in (p2cwrite, c2pread, errread):
- if fd != -1:
- os.close(fd)
err_msg = err_msg.decode(errors="surrogatepass")
if issubclass(child_exception_type, OSError) and hex_errno:
errno_num = int(hex_errno, 16)
diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py
index 2d77554..da7045c 100644
--- a/Lib/test/test_subprocess.py
+++ b/Lib/test/test_subprocess.py
@@ -1189,6 +1189,44 @@ class POSIXProcessTestCase(BaseTestCase):
self.fail("Exception raised by preexec_fn did not make it "
"to the parent process.")
+ @unittest.skipIf(not os.path.exists("/dev/zero"), "/dev/zero required.")
+ def test_preexec_errpipe_does_not_double_close_pipes(self):
+ """Issue16140: Don't double close pipes on preexec error."""
+ class SafeConstructorPopen(subprocess.Popen):
+ def __init__(self):
+ pass # Do nothing so we can modify the instance for testing.
+ def RealPopen(self, *args, **kwargs):
+ subprocess.Popen.__init__(self, *args, **kwargs)
+ def raise_it():
+ raise RuntimeError("force the _execute_child() errpipe_data path.")
+
+ p = SafeConstructorPopen()
+
+ def _test_fds_execute_child_wrapper(*args, **kwargs):
+ try:
+ subprocess.Popen._execute_child(p, *args, **kwargs)
+ finally:
+ # Open a bunch of file descriptors and verify that
+ # none of them are the same as the ones the Popen
+ # instance is using for stdin/stdout/stderr.
+ devzero_fds = [os.open("/dev/zero", os.O_RDONLY)
+ for _ in range(8)]
+ try:
+ for fd in devzero_fds:
+ self.assertNotIn(fd, (
+ p.stdin.fileno(), p.stdout.fileno(),
+ p.stderr.fileno()),
+ msg="At least one fd was closed early.")
+ finally:
+ map(os.close, devzero_fds)
+
+ p._execute_child = _test_fds_execute_child_wrapper
+
+ with self.assertRaises(RuntimeError):
+ p.RealPopen([sys.executable, "-c", "pass"],
+ stdin=subprocess.PIPE, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE, preexec_fn=raise_it)
+
def test_preexec_gc_module_failure(self):
# This tests the code that disables garbage collection if the child
# process will execute any Python.
diff --git a/Misc/NEWS b/Misc/NEWS
index cd923b3..3e945b1 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -83,6 +83,9 @@ Core and Builtins
Library
-------
+- Issue #16140: The subprocess module no longer double closes its child
+ subprocess.PIPE parent file descriptors on child error prior to exec().
+
- Remove a bare print to stdout from the subprocess module that could have
happened if the child process wrote garbage to its pre-exec error pipe.