mirror of
https://github.com/python/cpython.git
synced 2025-08-04 00:48:58 +00:00
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.
This commit is contained in:
parent
87010e85cb
commit
b2a6083eb0
8 changed files with 425 additions and 54 deletions
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue