diff --git a/Lib/test/test_cmd_line.py b/Lib/test/test_cmd_line.py --- a/Lib/test/test_cmd_line.py +++ b/Lib/test/test_cmd_line.py @@ -291,6 +291,90 @@ class CmdLineTest(unittest.TestCase): rc, out, err = assert_python_ok('-c', code) self.assertEqual(b'', err) + @unittest.skipIf(os.name == 'nt', "test needs POSIX semantics") + def test_no_stdin(self): + # Issue #7111: Python should work without an stdin + code = """if 1: + import os, sys + if sys.stdin is not None: + os._exit(1) + os._exit(42)""" + def preexec(): + os.close(0) + p = subprocess.Popen( + [sys.executable, "-E", "-c", code], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + preexec_fn=preexec) + out, err = p.communicate() + self.assertEqual(test.support.strip_python_stderr(err), b'') + self.assertEqual(p.returncode, 42) + + @unittest.skipIf(os.name == 'nt', "test needs POSIX semantics") + def test_no_stdout(self): + # Issue #7111: Python should work without an stdout + code = """if 1: + import os, sys + if sys.stdout is not None: + os._exit(1) + os._exit(42)""" + def preexec(): + os.close(1) + p = subprocess.Popen( + [sys.executable, "-E", "-c", code], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + preexec_fn=preexec) + out, err = p.communicate() + self.assertEqual(test.support.strip_python_stderr(err), b'') + self.assertEqual(p.returncode, 42) + + @unittest.skipIf(os.name == 'nt', "test needs POSIX semantics") + def test_no_stderr(self): + # Issue #7111: Python should work without an stderr + code = """if 1: + import os, sys + if sys.stderr is not None: + os._exit(1) + os._exit(42)""" + def preexec(): + os.close(2) + p = subprocess.Popen( + [sys.executable, "-E", "-c", code], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + preexec_fn=preexec) + out, err = p.communicate() + self.assertEqual(p.returncode, 42) + + @unittest.skipIf(os.name == 'nt', "test needs POSIX semantics") + def test_no_std_streams(self): + # Issue #7111: Python should work with no standard streams at all + code = """if 1: + import os, sys + if sys.stdin is not None: + os._exit(1) + if sys.stdout is not None: + os._exit(2) + if sys.stderr is not None: + os._exit(3) + os._exit(42)""" + def preexec(): + os.close(0) + os.close(1) + os.close(2) + p = subprocess.Popen( + [sys.executable, "-E", "-c", code], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + preexec_fn=preexec) + out, err = p.communicate() + self.assertEqual(p.returncode, 42) + def test_main(): test.support.run_unittest(CmdLineTest) diff --git a/Python/pythonrun.c b/Python/pythonrun.c --- a/Python/pythonrun.c +++ b/Python/pythonrun.c @@ -892,6 +892,19 @@ error: return NULL; } +static int +is_valid_fd(int fd) +{ + int dummy_fd; + if (fd < 0 || !_PyVerify_fd(fd)) + return 0; + dummy_fd = dup(fd); + if (dummy_fd < 0) + return 0; + close(dummy_fd); + return 1; +} + /* Initialize sys.stdin, stdout, stderr and builtins.open */ static int initstdio(void) @@ -951,13 +964,9 @@ initstdio(void) * and fileno() may point to an invalid file descriptor. For example * GUI apps don't have valid standard streams by default. */ - if (fd < 0) { -#ifdef MS_WINDOWS + if (!is_valid_fd(fd)) { std = Py_None; Py_INCREF(std); -#else - goto error; -#endif } else { std = create_stdio(iomod, fd, 0, "", encoding, errors); @@ -970,13 +979,9 @@ initstdio(void) /* Set sys.stdout */ fd = fileno(stdout); - if (fd < 0) { -#ifdef MS_WINDOWS + if (!is_valid_fd(fd)) { std = Py_None; Py_INCREF(std); -#else - goto error; -#endif } else { std = create_stdio(iomod, fd, 1, "", encoding, errors); @@ -990,13 +995,9 @@ initstdio(void) #if 1 /* Disable this if you have trouble debugging bootstrap stuff */ /* Set sys.stderr, replaces the preliminary stderr */ fd = fileno(stderr); - if (fd < 0) { -#ifdef MS_WINDOWS + if (!is_valid_fd(fd)) { std = Py_None; Py_INCREF(std); -#else - goto error; -#endif } else { std = create_stdio(iomod, fd, 1, "", encoding, "backslashreplace");