diff options
author | Gregory P. Smith <greg@krypto.org> | 2013-12-02 01:27:40 (GMT) |
---|---|---|
committer | Gregory P. Smith <greg@krypto.org> | 2013-12-02 01:27:40 (GMT) |
commit | 708a3182c931ff1b6e8a420f87929d143822de28 (patch) | |
tree | 7788ae8571bf4b061d425fc41988e958d34276d5 | |
parent | 5c1c3b4f197c57952760be37d77d73669284a607 (diff) | |
download | cpython-708a3182c931ff1b6e8a420f87929d143822de28.zip cpython-708a3182c931ff1b6e8a420f87929d143822de28.tar.gz cpython-708a3182c931ff1b6e8a420f87929d143822de28.tar.bz2 |
Fixes issue #15798: subprocess.Popen() no longer fails if file
descriptor 0, 1 or 2 is closed.
The errpipe_write fd will always be >= 3.
-rw-r--r-- | Lib/test/test_subprocess.py | 21 | ||||
-rw-r--r-- | Misc/NEWS | 3 | ||||
-rw-r--r-- | Modules/_posixsubprocess.c | 48 |
3 files changed, 66 insertions, 6 deletions
diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py index 77f1ba3..f5a70a3 100644 --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -1559,6 +1559,27 @@ class POSIXProcessTestCase(BaseTestCase): # all standard fds closed. self.check_close_std_fds([0, 1, 2]) + def test_small_errpipe_write_fd(self): + """Issue #15798: Popen should work when stdio fds are available.""" + new_stdin = os.dup(0) + new_stdout = os.dup(1) + try: + os.close(0) + os.close(1) + + # Side test: if errpipe_write fails to have its CLOEXEC + # flag set this should cause the parent to think the exec + # failed. Extremely unlikely: everyone supports CLOEXEC. + subprocess.Popen([ + sys.executable, "-c", + "print('AssertionError:0:CLOEXEC failure.')"]).wait() + finally: + # Restore original stdin and stdout + os.dup2(new_stdin, 0) + os.dup2(new_stdout, 1) + os.close(new_stdin) + os.close(new_stdout) + def test_remapping_std_fds(self): # open up some temporary files temps = [mkstemp() for i in range(3)] @@ -18,6 +18,9 @@ Core and Builtins Library ------- +- Issue #15798: Fixed subprocess.Popen() to no longer fail if file + descriptor 0, 1 or 2 is closed. + - Issue #19088: Fixed incorrect caching of the copyreg module in object.__reduce__() and object.__reduce_ex__(). diff --git a/Modules/_posixsubprocess.c b/Modules/_posixsubprocess.c index 8d65530..0196ca2 100644 --- a/Modules/_posixsubprocess.c +++ b/Modules/_posixsubprocess.c @@ -723,26 +723,24 @@ Raises: Only on an error in the parent process.\n\ PyDoc_STRVAR(subprocess_cloexec_pipe_doc, "cloexec_pipe() -> (read_end, write_end)\n\n\ -Create a pipe whose ends have the cloexec flag set."); +Create a pipe whose ends have the cloexec flag set; write_end will be >= 3."); static PyObject * subprocess_cloexec_pipe(PyObject *self, PyObject *noargs) { int fds[2]; - int res; + int res, saved_errno; + long oldflags; #ifdef HAVE_PIPE2 Py_BEGIN_ALLOW_THREADS res = pipe2(fds, O_CLOEXEC); Py_END_ALLOW_THREADS if (res != 0 && errno == ENOSYS) { - { #endif /* We hold the GIL which offers some protection from other code calling * fork() before the CLOEXEC flags have been set but we can't guarantee * anything without pipe2(). */ - long oldflags; - res = pipe(fds); if (res == 0) { @@ -759,9 +757,47 @@ subprocess_cloexec_pipe(PyObject *self, PyObject *noargs) if (res == 0) res = fcntl(fds[1], F_SETFD, oldflags | FD_CLOEXEC); #ifdef HAVE_PIPE2 - } } #endif + if (res == 0 && fds[1] < 3) { + /* We always want the write end of the pipe to avoid fds 0, 1 and 2 + * as our child may claim those for stdio connections. */ + int write_fd = fds[1]; + int fds_to_close[3] = {-1, -1, -1}; + int fds_to_close_idx = 0; +#ifdef F_DUPFD_CLOEXEC + fds_to_close[fds_to_close_idx++] = write_fd; + write_fd = fcntl(write_fd, F_DUPFD_CLOEXEC, 3); + if (write_fd < 0) /* We don't support F_DUPFD_CLOEXEC / other error */ +#endif + { + /* Use dup a few times until we get a desirable fd. */ + for (; fds_to_close_idx < 3; ++fds_to_close_idx) { + fds_to_close[fds_to_close_idx] = write_fd; + write_fd = dup(write_fd); + if (write_fd >= 3) + break; + /* We may dup a few extra times if it returns an error but + * that is okay. Repeat calls should return the same error. */ + } + if (write_fd < 0) res = write_fd; + if (res == 0) { + oldflags = fcntl(write_fd, F_GETFD, 0); + if (oldflags < 0) res = oldflags; + if (res == 0) + res = fcntl(write_fd, F_SETFD, oldflags | FD_CLOEXEC); + } + } + saved_errno = errno; + /* Close fds we tried for the write end that were too low. */ + for (fds_to_close_idx=0; fds_to_close_idx < 3; ++fds_to_close_idx) { + int temp_fd = fds_to_close[fds_to_close_idx]; + while (temp_fd >= 0 && close(temp_fd) < 0 && errno == EINTR); + } + errno = saved_errno; /* report dup or fcntl errors, not close. */ + fds[1] = write_fd; + } /* end if write fd was too small */ + if (res != 0) return PyErr_SetFromErrno(PyExc_OSError); return Py_BuildValue("(ii)", fds[0], fds[1]); |