Index: Lib/test/test_subprocess.py =================================================================== --- Lib/test/test_subprocess.py (revision 62996) +++ Lib/test/test_subprocess.py (working copy) @@ -608,6 +608,58 @@ p.terminate() self.assertEqual(p.wait(), -signal.SIGTERM) + def test_remapping_std_fds(self): + # open up some temporary files + temps = (self.mkstemp(), self.mkstemp(), self.mkstemp()) + try: + temp_fds = [ fd for fd, fname in temps ] + + # unlink the files -- we won't need to reopen them + for fd, fname in temps: + os.unlink(fname) + + # write some data to what will become stdin, and rewind + os.write(temp_fds[1], "STDIN") + os.lseek(temp_fds[1], 0, 0) + + # move the standard file descriptors out of the way + std_fds = (0,1,2) + saved_fds = [ os.dup(fd) for fd in std_fds ] + try: + # duplicate the file objects over the standard fd's + for fd in std_fds: + os.dup2(temp_fds[fd], fd) + + # and create corresponding file objects + new_std_files = [ os.fdopen(fd, "rw") for fd in std_fds ] + + # now use those files in the "wrong" order, so that subprocess has to + # rearrange them in the child + p = subprocess.Popen([sys.executable, "-c", + 'import sys; got = sys.stdin.read(); sys.stdout.write("got %s"%got); sys.stderr.write("err")'], + stdin=new_std_files[1], + stdout=new_std_files[2], + stderr=new_std_files[0]) + p.wait() + + finally: + del new_std_files # close file objects at std_fds + + # restore the original fd's underneath sys.stdin, etc. + for saved, std in zip(saved_fds, std_fds): + os.dup2(saved, std) + os.close(saved) + + for fd in temp_fds: + os.lseek(fd, 0, 0) + + self.assertEqual(os.read(temp_fds[0], 1024), "err") + self.assertEqual(os.read(temp_fds[2], 1024), "got STDIN") + + finally: + for fd in temp_fds: + os.close(fd) + # # Windows tests # Index: Lib/subprocess.py =================================================================== --- Lib/subprocess.py (revision 62996) +++ Lib/subprocess.py (working copy) @@ -1035,22 +1035,36 @@ os.close(errread) os.close(errpipe_read) - # Dup fds for child + # Dup fds for child, making sure not to overwrite any + # of the given fd's while we're at it. if p2cread is not None: + if c2pwrite == 0: + c2pwrite = os.dup(c2pwrite) + if errwrite == 0: + errwrite = os.dup(errwrite) os.dup2(p2cread, 0) if c2pwrite is not None: + if errwrite == 1: + errwrite = os.dup(errwrite) + if errpipe_write == 1: + errpipe_write = os.dup(errpipe_write) + self._set_cloexec_flag(errpipe_write) os.dup2(c2pwrite, 1) if errwrite is not None: + if errpipe_write == 2: + errpipe_write = os.dup(errpipe_write) + self._set_cloexec_flag(errpipe_write) os.dup2(errwrite, 2) # Close pipe fds. Make sure we don't close the same # fd more than once, or standard fds. - if p2cread is not None and p2cread not in (0,): - os.close(p2cread) - if c2pwrite is not None and c2pwrite not in (p2cread, 1): - os.close(c2pwrite) - if errwrite is not None and errwrite not in (p2cread, c2pwrite, 2): - os.close(errwrite) + dup_fds = { p2cread : None, + c2pwrite : None, + errwrite : None, + } + for fd in dup_fds: + if fd not in (0, 1, 2, None): + os.close(fd) # Close all other fds, if asked for if close_fds: