diff -r d38509db3616 Lib/subprocess.py --- a/Lib/subprocess.py Fri Aug 19 18:43:43 2011 +0200 +++ b/Lib/subprocess.py Sat Aug 20 00:31:11 2011 +0200 @@ -1035,7 +1035,7 @@ if stdin is None: pass elif stdin == PIPE: - p2cread, p2cwrite = os.pipe() + p2cread, p2cwrite = self._pipe2() elif isinstance(stdin, int): p2cread = stdin else: @@ -1045,7 +1045,7 @@ if stdout is None: pass elif stdout == PIPE: - c2pread, c2pwrite = os.pipe() + c2pread, c2pwrite = self._pipe2() elif isinstance(stdout, int): c2pwrite = stdout else: @@ -1055,7 +1055,7 @@ if stderr is None: pass elif stderr == PIPE: - errread, errwrite = os.pipe() + errread, errwrite = self._pipe2() elif stderr == STDOUT: errwrite = c2pwrite elif isinstance(stderr, int): @@ -1082,6 +1082,16 @@ fcntl.fcntl(fd, fcntl.F_SETFD, old & ~cloexec_flag) + def _pipe2(self, cloexec=True): + """Ad-hoc pipe2() implementation.""" + # This is not atomic, we would need pipe2() syscall for that. + r, w = os.pipe() + if cloexec: + self._set_cloexec_flag(r) + self._set_cloexec_flag(w) + return r, w + + def _close_fds(self, but): if hasattr(os, 'closerange'): os.closerange(3, but) @@ -1120,11 +1130,9 @@ # 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 = os.pipe() + errpipe_read, errpipe_write = self._pipe2() 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 diff -r d38509db3616 Lib/test/test_subprocess.py --- a/Lib/test/test_subprocess.py Fri Aug 19 18:43:43 2011 +0200 +++ b/Lib/test/test_subprocess.py Sat Aug 20 00:31:11 2011 +0200 @@ -995,6 +995,37 @@ self.assertRaises(OSError, os.waitpid, pid, 0) self.assertNotIn(ident, [id(o) for o in subprocess._active]) + def test_pipe_cloexec(self): + # Issue 12786: check that the parent-end FDs of the communication pipes + # are set CLOEXEC, and not inherited by another child process. + p1 = subprocess.Popen([sys.executable, "-c", + 'import os;' + 'os.read(0)' + ], + stdin=subprocess.PIPE, stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + + p2 = subprocess.Popen([sys.executable, "-c", """if True: + import os, errno, sys + for fd in %r: + try: + os.close(fd) + except OSError as e: + if e.errno != errno.EBADF: + raise + else: + sys.exit(1) + sys.exit(0) + """ % [f.fileno() for f in (p1.stdin, p1.stdout, + p1.stderr)] + ], + stdin=subprocess.PIPE, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, close_fds=False) + p1.communicate('foo') + _, stderr = p2.communicate() + + self.assertEqual(p2.returncode, 0, "Unexpected error: " + repr(stderr)) + @unittest.skipUnless(mswindows, "Windows specific tests") class Win32ProcessTestCase(BaseTestCase):