summaryrefslogtreecommitdiffstats
path: root/Lib/subprocess.py
diff options
context:
space:
mode:
authorSegev Finer <segev208@gmail.com>2017-12-18 09:28:19 (GMT)
committerVictor Stinner <victor.stinner@gmail.com>2017-12-18 09:28:19 (GMT)
commitb2a6083eb0384f38839d3f1ed32262a3852026fa (patch)
treed95a4dd911ebc05549fe54dee0b76c67fe5c727a /Lib/subprocess.py
parent87010e85cb37192d63b1a30e5fabba307ad5a3f5 (diff)
downloadcpython-b2a6083eb0384f38839d3f1ed32262a3852026fa.zip
cpython-b2a6083eb0384f38839d3f1ed32262a3852026fa.tar.gz
cpython-b2a6083eb0384f38839d3f1ed32262a3852026fa.tar.bz2
bpo-19764: Implemented support for subprocess.Popen(close_fds=True) on Windows (#1218)
Even though Python marks any handles it opens as non-inheritable there is still a race when using `subprocess.Popen` since creating a process with redirected stdio requires temporarily creating inheritable handles. By implementing support for `subprocess.Popen(close_fds=True)` we fix this race. In order to implement this we use PROC_THREAD_ATTRIBUTE_HANDLE_LIST which is available since Windows Vista. Which allows to pass an explicit list of handles to inherit when creating a process. This commit also adds `STARTUPINFO.lpAttributeList["handle_list"]` which can be used to control PROC_THREAD_ATTRIBUTE_HANDLE_LIST directly.
Diffstat (limited to 'Lib/subprocess.py')
-rw-r--r--Lib/subprocess.py65
1 files changed, 46 insertions, 19 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