summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
Diffstat (limited to 'Lib')
-rw-r--r--Lib/subprocess.py65
-rw-r--r--Lib/test/test_subprocess.py66
2 files changed, 107 insertions, 24 deletions
diff --git a/Lib/subprocess.py b/Lib/subprocess.py
index 65b4086..db6342f 100644
--- a/Lib/subprocess.py
+++ b/Lib/subprocess.py
@@ -128,12 +128,13 @@ if _mswindows:
import _winapi
class STARTUPINFO:
def __init__(self, *, dwFlags=0, hStdInput=None, hStdOutput=None,
- hStdError=None, wShowWindow=0):
+ hStdError=None, wShowWindow=0, lpAttributeList=None):
self.dwFlags = dwFlags
self.hStdInput = hStdInput
self.hStdOutput = hStdOutput
self.hStdError = hStdError
self.wShowWindow = wShowWindow
+ self.lpAttributeList = lpAttributeList or {"handle_list": []}
else:
import _posixsubprocess
import select
@@ -577,9 +578,6 @@ def getoutput(cmd):
return getstatusoutput(cmd)[1]
-_PLATFORM_DEFAULT_CLOSE_FDS = object()
-
-
class Popen(object):
""" Execute a child program in a new process.
@@ -630,7 +628,7 @@ class Popen(object):
def __init__(self, args, bufsize=-1, executable=None,
stdin=None, stdout=None, stderr=None,
- preexec_fn=None, close_fds=_PLATFORM_DEFAULT_CLOSE_FDS,
+ preexec_fn=None, close_fds=True,
shell=False, cwd=None, env=None, universal_newlines=None,
startupinfo=None, creationflags=0,
restore_signals=True, start_new_session=False,
@@ -655,21 +653,8 @@ class Popen(object):
if preexec_fn is not None:
raise ValueError("preexec_fn is not supported on Windows "
"platforms")
- any_stdio_set = (stdin is not None or stdout is not None or
- stderr is not None)
- if close_fds is _PLATFORM_DEFAULT_CLOSE_FDS:
- if any_stdio_set:
- close_fds = False
- else:
- close_fds = True
- elif close_fds and any_stdio_set:
- raise ValueError(
- "close_fds is not supported on Windows platforms"
- " if you redirect stdin/stdout/stderr")
else:
# POSIX
- if close_fds is _PLATFORM_DEFAULT_CLOSE_FDS:
- close_fds = True
if pass_fds and not close_fds:
warnings.warn("pass_fds overriding close_fds.", RuntimeWarning)
close_fds = True
@@ -1019,6 +1004,19 @@ class Popen(object):
return Handle(h)
+ def _filter_handle_list(self, handle_list):
+ """Filter out console handles that can't be used
+ in lpAttributeList["handle_list"] and make sure the list
+ isn't empty. This also removes duplicate handles."""
+ # An handle with it's lowest two bits set might be a special console
+ # handle that if passed in lpAttributeList["handle_list"], will
+ # cause it to fail.
+ return list({handle for handle in handle_list
+ if handle & 0x3 != 0x3
+ or _winapi.GetFileType(handle) !=
+ _winapi.FILE_TYPE_CHAR})
+
+
def _execute_child(self, args, executable, preexec_fn, close_fds,
pass_fds, cwd, env,
startupinfo, creationflags, shell,
@@ -1036,12 +1034,41 @@ class Popen(object):
# Process startup details
if startupinfo is None:
startupinfo = STARTUPINFO()
- if -1 not in (p2cread, c2pwrite, errwrite):
+
+ use_std_handles = -1 not in (p2cread, c2pwrite, errwrite)
+ if use_std_handles:
startupinfo.dwFlags |= _winapi.STARTF_USESTDHANDLES
startupinfo.hStdInput = p2cread
startupinfo.hStdOutput = c2pwrite
startupinfo.hStdError = errwrite
+ attribute_list = startupinfo.lpAttributeList
+ have_handle_list = bool(attribute_list and
+ "handle_list" in attribute_list and
+ attribute_list["handle_list"])
+
+ # If we were given an handle_list or need to create one
+ if have_handle_list or (use_std_handles and close_fds):
+ if attribute_list is None:
+ attribute_list = startupinfo.lpAttributeList = {}
+ handle_list = attribute_list["handle_list"] = \
+ list(attribute_list.get("handle_list", []))
+
+ if use_std_handles:
+ handle_list += [int(p2cread), int(c2pwrite), int(errwrite)]
+
+ handle_list[:] = self._filter_handle_list(handle_list)
+
+ if handle_list:
+ if not close_fds:
+ warnings.warn("startupinfo.lpAttributeList['handle_list'] "
+ "overriding close_fds", RuntimeWarning)
+
+ # When using the handle_list we always request to inherit
+ # handles but the only handles that will be inherited are
+ # the ones in the handle_list
+ close_fds = False
+
if shell:
startupinfo.dwFlags |= _winapi.STARTF_USESHOWWINDOW
startupinfo.wShowWindow = _winapi.SW_HIDE
diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py
index fff1b0d..bd3b9b4 100644
--- a/Lib/test/test_subprocess.py
+++ b/Lib/test/test_subprocess.py
@@ -2743,11 +2743,6 @@ class Win32ProcessTestCase(BaseTestCase):
[sys.executable, "-c",
"import sys; sys.exit(47)"],
preexec_fn=lambda: 1)
- self.assertRaises(ValueError, subprocess.call,
- [sys.executable, "-c",
- "import sys; sys.exit(47)"],
- stdout=subprocess.PIPE,
- close_fds=True)
@support.cpython_only
def test_issue31471(self):
@@ -2765,6 +2760,67 @@ class Win32ProcessTestCase(BaseTestCase):
close_fds=True)
self.assertEqual(rc, 47)
+ def test_close_fds_with_stdio(self):
+ import msvcrt
+
+ fds = os.pipe()
+ self.addCleanup(os.close, fds[0])
+ self.addCleanup(os.close, fds[1])
+
+ handles = []
+ for fd in fds:
+ os.set_inheritable(fd, True)
+ handles.append(msvcrt.get_osfhandle(fd))
+
+ p = subprocess.Popen([sys.executable, "-c",
+ "import msvcrt; print(msvcrt.open_osfhandle({}, 0))".format(handles[0])],
+ stdout=subprocess.PIPE, close_fds=False)
+ stdout, stderr = p.communicate()
+ self.assertEqual(p.returncode, 0)
+ int(stdout.strip()) # Check that stdout is an integer
+
+ p = subprocess.Popen([sys.executable, "-c",
+ "import msvcrt; print(msvcrt.open_osfhandle({}, 0))".format(handles[0])],
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True)
+ stdout, stderr = p.communicate()
+ self.assertEqual(p.returncode, 1)
+ self.assertIn(b"OSError", stderr)
+
+ # The same as the previous call, but with an empty handle_list
+ handle_list = []
+ startupinfo = subprocess.STARTUPINFO()
+ startupinfo.lpAttributeList = {"handle_list": handle_list}
+ p = subprocess.Popen([sys.executable, "-c",
+ "import msvcrt; print(msvcrt.open_osfhandle({}, 0))".format(handles[0])],
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+ startupinfo=startupinfo, close_fds=True)
+ stdout, stderr = p.communicate()
+ self.assertEqual(p.returncode, 1)
+ self.assertIn(b"OSError", stderr)
+
+ # Check for a warning due to using handle_list and close_fds=False
+ with support.check_warnings((".*overriding close_fds", RuntimeWarning)):
+ startupinfo = subprocess.STARTUPINFO()
+ startupinfo.lpAttributeList = {"handle_list": handles[:]}
+ p = subprocess.Popen([sys.executable, "-c",
+ "import msvcrt; print(msvcrt.open_osfhandle({}, 0))".format(handles[0])],
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+ startupinfo=startupinfo, close_fds=False)
+ stdout, stderr = p.communicate()
+ self.assertEqual(p.returncode, 0)
+
+ def test_empty_attribute_list(self):
+ startupinfo = subprocess.STARTUPINFO()
+ startupinfo.lpAttributeList = {}
+ subprocess.call([sys.executable, "-c", "import sys; sys.exit(0)"],
+ startupinfo=startupinfo)
+
+ def test_empty_handle_list(self):
+ startupinfo = subprocess.STARTUPINFO()
+ startupinfo.lpAttributeList = {"handle_list": []}
+ subprocess.call([sys.executable, "-c", "import sys; sys.exit(0)"],
+ startupinfo=startupinfo)
+
def test_shell_sequence(self):
# Run command through the shell (sequence)
newenv = os.environ.copy()