diff options
-rw-r--r-- | Lib/test/test_subprocess.py | 50 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Library/2018-02-28-13-08-00.bpo-32844.u8tnAe.rst | 2 | ||||
-rw-r--r-- | Modules/_posixsubprocess.c | 2 |
3 files changed, 53 insertions, 1 deletions
diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py index ddee3b9..2a766d7 100644 --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -6,6 +6,7 @@ import sys import platform import signal import io +import itertools import os import errno import tempfile @@ -2134,6 +2135,55 @@ class POSIXProcessTestCase(BaseTestCase): self.check_swap_fds(2, 0, 1) self.check_swap_fds(2, 1, 0) + def _check_swap_std_fds_with_one_closed(self, from_fds, to_fds): + saved_fds = self._save_fds(range(3)) + try: + for from_fd in from_fds: + with tempfile.TemporaryFile() as f: + os.dup2(f.fileno(), from_fd) + + fd_to_close = (set(range(3)) - set(from_fds)).pop() + os.close(fd_to_close) + + arg_names = ['stdin', 'stdout', 'stderr'] + kwargs = {} + for from_fd, to_fd in zip(from_fds, to_fds): + kwargs[arg_names[to_fd]] = from_fd + + code = textwrap.dedent(r''' + import os, sys + skipped_fd = int(sys.argv[1]) + for fd in range(3): + if fd != skipped_fd: + os.write(fd, str(fd).encode('ascii')) + ''') + + skipped_fd = (set(range(3)) - set(to_fds)).pop() + + rc = subprocess.call([sys.executable, '-c', code, str(skipped_fd)], + **kwargs) + self.assertEqual(rc, 0) + + for from_fd, to_fd in zip(from_fds, to_fds): + os.lseek(from_fd, 0, os.SEEK_SET) + read_bytes = os.read(from_fd, 1024) + read_fds = list(map(int, read_bytes.decode('ascii'))) + msg = textwrap.dedent(f""" + When testing {from_fds} to {to_fds} redirection, + parent descriptor {from_fd} got redirected + to descriptor(s) {read_fds} instead of descriptor {to_fd}. + """) + self.assertEqual([to_fd], read_fds, msg) + finally: + self._restore_fds(saved_fds) + + # Check that subprocess can remap std fds correctly even + # if one of them is closed (#32844). + def test_swap_std_fds_with_one_closed(self): + for from_fds in itertools.combinations(range(3), 2): + for to_fds in itertools.permutations(range(3), 2): + self._check_swap_std_fds_with_one_closed(from_fds, to_fds) + def test_surrogates_error_message(self): def prepare(): raise ValueError("surrogate:\uDCff") diff --git a/Misc/NEWS.d/next/Library/2018-02-28-13-08-00.bpo-32844.u8tnAe.rst b/Misc/NEWS.d/next/Library/2018-02-28-13-08-00.bpo-32844.u8tnAe.rst new file mode 100644 index 0000000..67412fe --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-02-28-13-08-00.bpo-32844.u8tnAe.rst @@ -0,0 +1,2 @@ +Fix wrong redirection of a low descriptor (0 or 1) to stderr in subprocess +if another low descriptor is closed. diff --git a/Modules/_posixsubprocess.c b/Modules/_posixsubprocess.c index dc43ffc..0150fcb 100644 --- a/Modules/_posixsubprocess.c +++ b/Modules/_posixsubprocess.c @@ -424,7 +424,7 @@ child_exec(char *const exec_array[], either 0, 1 or 2, it is possible that it is overwritten (#12607). */ if (c2pwrite == 0) POSIX_CALL(c2pwrite = dup(c2pwrite)); - if (errwrite == 0 || errwrite == 1) + while (errwrite == 0 || errwrite == 1) POSIX_CALL(errwrite = dup(errwrite)); /* Dup fds for child. |