@@ -723,26 +723,24 @@ Raises: Only on an error in the parent process.\n\
723723
724724PyDoc_STRVAR (subprocess_cloexec_pipe_doc ,
725725"cloexec_pipe() -> (read_end, write_end)\n\n\
726- Create a pipe whose ends have the cloexec flag set." );
726+ Create a pipe whose ends have the cloexec flag set; write_end will be >= 3 ." );
727727
728728static PyObject *
729729subprocess_cloexec_pipe (PyObject * self , PyObject * noargs )
730730{
731731 int fds [2 ];
732- int res ;
732+ int res , saved_errno ;
733+ long oldflags ;
733734#ifdef HAVE_PIPE2
734735 Py_BEGIN_ALLOW_THREADS
735736 res = pipe2 (fds , O_CLOEXEC );
736737 Py_END_ALLOW_THREADS
737738 if (res != 0 && errno == ENOSYS )
738739 {
739- {
740740#endif
741741 /* We hold the GIL which offers some protection from other code calling
742742 * fork() before the CLOEXEC flags have been set but we can't guarantee
743743 * anything without pipe2(). */
744- long oldflags ;
745-
746744 res = pipe (fds );
747745
748746 if (res == 0 ) {
@@ -759,9 +757,47 @@ subprocess_cloexec_pipe(PyObject *self, PyObject *noargs)
759757 if (res == 0 )
760758 res = fcntl (fds [1 ], F_SETFD , oldflags | FD_CLOEXEC );
761759#ifdef HAVE_PIPE2
762- }
763760 }
764761#endif
762+ if (res == 0 && fds [1 ] < 3 ) {
763+ /* We always want the write end of the pipe to avoid fds 0, 1 and 2
764+ * as our child may claim those for stdio connections. */
765+ int write_fd = fds [1 ];
766+ int fds_to_close [3 ] = {-1 , -1 , -1 };
767+ int fds_to_close_idx = 0 ;
768+ #ifdef F_DUPFD_CLOEXEC
769+ fds_to_close [fds_to_close_idx ++ ] = write_fd ;
770+ write_fd = fcntl (write_fd , F_DUPFD_CLOEXEC , 3 );
771+ if (write_fd < 0 ) /* We don't support F_DUPFD_CLOEXEC / other error */
772+ #endif
773+ {
774+ /* Use dup a few times until we get a desirable fd. */
775+ for (; fds_to_close_idx < 3 ; ++ fds_to_close_idx ) {
776+ fds_to_close [fds_to_close_idx ] = write_fd ;
777+ write_fd = dup (write_fd );
778+ if (write_fd >= 3 )
779+ break ;
780+ /* We may dup a few extra times if it returns an error but
781+ * that is okay. Repeat calls should return the same error. */
782+ }
783+ if (write_fd < 0 ) res = write_fd ;
784+ if (res == 0 ) {
785+ oldflags = fcntl (write_fd , F_GETFD , 0 );
786+ if (oldflags < 0 ) res = oldflags ;
787+ if (res == 0 )
788+ res = fcntl (write_fd , F_SETFD , oldflags | FD_CLOEXEC );
789+ }
790+ }
791+ saved_errno = errno ;
792+ /* Close fds we tried for the write end that were too low. */
793+ for (fds_to_close_idx = 0 ; fds_to_close_idx < 3 ; ++ fds_to_close_idx ) {
794+ int temp_fd = fds_to_close [fds_to_close_idx ];
795+ while (temp_fd >= 0 && close (temp_fd ) < 0 && errno == EINTR );
796+ }
797+ errno = saved_errno ; /* report dup or fcntl errors, not close. */
798+ fds [1 ] = write_fd ;
799+ } /* end if write fd was too small */
800+
765801 if (res != 0 )
766802 return PyErr_SetFromErrno (PyExc_OSError );
767803 return Py_BuildValue ("(ii)" , fds [0 ], fds [1 ]);
0 commit comments