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
|
@ -452,17 +452,20 @@ functions.
|
||||||
common use of *preexec_fn* to call os.setsid() in the child.
|
common use of *preexec_fn* to call os.setsid() in the child.
|
||||||
|
|
||||||
If *close_fds* is true, all file descriptors except :const:`0`, :const:`1` and
|
If *close_fds* is true, all file descriptors except :const:`0`, :const:`1` and
|
||||||
:const:`2` will be closed before the child process is executed. (POSIX only).
|
:const:`2` will be closed before the child process is executed.
|
||||||
The default varies by platform: Always true on POSIX. On Windows it is
|
|
||||||
true when *stdin*/*stdout*/*stderr* are :const:`None`, false otherwise.
|
|
||||||
On Windows, if *close_fds* is true then no handles will be inherited by the
|
On Windows, if *close_fds* is true then no handles will be inherited by the
|
||||||
child process. Note that on Windows, you cannot set *close_fds* to true and
|
child process unless explicitly passed in the ``handle_list`` element of
|
||||||
also redirect the standard handles by setting *stdin*, *stdout* or *stderr*.
|
:attr:`STARTUPINFO.lpAttributeList`, or by standard handle redirection.
|
||||||
|
|
||||||
.. versionchanged:: 3.2
|
.. versionchanged:: 3.2
|
||||||
The default for *close_fds* was changed from :const:`False` to
|
The default for *close_fds* was changed from :const:`False` to
|
||||||
what is described above.
|
what is described above.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.7
|
||||||
|
On Windows the default for *close_fds* was changed from :const:`False` to
|
||||||
|
:const:`True` when redirecting the standard handles. It's now possible to
|
||||||
|
set *close_fds* to :const:`True` when redirecting the standard handles.
|
||||||
|
|
||||||
*pass_fds* is an optional sequence of file descriptors to keep open
|
*pass_fds* is an optional sequence of file descriptors to keep open
|
||||||
between the parent and child. Providing any *pass_fds* forces
|
between the parent and child. Providing any *pass_fds* forces
|
||||||
*close_fds* to be :const:`True`. (POSIX only)
|
*close_fds* to be :const:`True`. (POSIX only)
|
||||||
|
@ -764,7 +767,7 @@ The :class:`STARTUPINFO` class and following constants are only available
|
||||||
on Windows.
|
on Windows.
|
||||||
|
|
||||||
.. class:: STARTUPINFO(*, dwFlags=0, hStdInput=None, hStdOutput=None, \
|
.. class:: STARTUPINFO(*, dwFlags=0, hStdInput=None, hStdOutput=None, \
|
||||||
hStdError=None, wShowWindow=0)
|
hStdError=None, wShowWindow=0, lpAttributeList=None)
|
||||||
|
|
||||||
Partial support of the Windows
|
Partial support of the Windows
|
||||||
`STARTUPINFO <https://msdn.microsoft.com/en-us/library/ms686331(v=vs.85).aspx>`__
|
`STARTUPINFO <https://msdn.microsoft.com/en-us/library/ms686331(v=vs.85).aspx>`__
|
||||||
|
@ -814,6 +817,33 @@ on Windows.
|
||||||
:data:`SW_HIDE` is provided for this attribute. It is used when
|
:data:`SW_HIDE` is provided for this attribute. It is used when
|
||||||
:class:`Popen` is called with ``shell=True``.
|
:class:`Popen` is called with ``shell=True``.
|
||||||
|
|
||||||
|
.. attribute:: lpAttributeList
|
||||||
|
|
||||||
|
A dictionary of additional attributes for process creation as given in
|
||||||
|
``STARTUPINFOEX``, see
|
||||||
|
`UpdateProcThreadAttribute <https://msdn.microsoft.com/en-us/library/windows/desktop/ms686880(v=vs.85).aspx>`__.
|
||||||
|
|
||||||
|
Supported attributes:
|
||||||
|
|
||||||
|
**handle_list**
|
||||||
|
Sequence of handles that will be inherited. *close_fds* must be true if
|
||||||
|
non-empty.
|
||||||
|
|
||||||
|
The handles must be temporarily made inheritable by
|
||||||
|
:func:`os.set_handle_inheritable` when passed to the :class:`Popen`
|
||||||
|
constructor, else :class:`OSError` will be raised with Windows error
|
||||||
|
``ERROR_INVALID_PARAMETER`` (87).
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
In a multithreaded process, use caution to avoid leaking handles
|
||||||
|
that are marked inheritable when combining this feature with
|
||||||
|
concurrent calls to other process creation functions that inherit
|
||||||
|
all handles such as :func:`os.system`. This also applies to
|
||||||
|
standard handle redirection, which temporarily creates inheritable
|
||||||
|
handles.
|
||||||
|
|
||||||
|
.. versionadded:: 3.7
|
||||||
|
|
||||||
Windows Constants
|
Windows Constants
|
||||||
^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
@ -437,6 +437,17 @@ string
|
||||||
expression pattern for braced placeholders and non-braced placeholders
|
expression pattern for braced placeholders and non-braced placeholders
|
||||||
separately. (Contributed by Barry Warsaw in :issue:`1198569`.)
|
separately. (Contributed by Barry Warsaw in :issue:`1198569`.)
|
||||||
|
|
||||||
|
subprocess
|
||||||
|
----------
|
||||||
|
|
||||||
|
On Windows the default for *close_fds* was changed from :const:`False` to
|
||||||
|
:const:`True` when redirecting the standard handles. It's now possible to set
|
||||||
|
*close_fds* to :const:`True` when redirecting the standard handles. See
|
||||||
|
:class:`subprocess.Popen`.
|
||||||
|
|
||||||
|
This means that *close_fds* now defaults to :const:`True` on all supported
|
||||||
|
platforms.
|
||||||
|
|
||||||
sys
|
sys
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -883,6 +894,14 @@ Changes in the Python API
|
||||||
|
|
||||||
.. _Unicode Technical Standard #18: https://unicode.org/reports/tr18/
|
.. _Unicode Technical Standard #18: https://unicode.org/reports/tr18/
|
||||||
|
|
||||||
|
* On Windows the default for the *close_fds* argument of
|
||||||
|
:class:`subprocess.Popen` was changed from :const:`False` to :const:`True`
|
||||||
|
when redirecting the standard handles. If you previously depended on handles
|
||||||
|
being inherited when using :class:`subprocess.Popen` with standard io
|
||||||
|
redirection, you will have to pass ``close_fds=False`` to preserve the
|
||||||
|
previous behaviour, or use
|
||||||
|
:attr:`STARTUPINFO.lpAttributeList <subprocess.STARTUPINFO.lpAttributeList>`.
|
||||||
|
|
||||||
|
|
||||||
Changes in the C API
|
Changes in the C API
|
||||||
--------------------
|
--------------------
|
||||||
|
|
|
@ -128,12 +128,13 @@ if _mswindows:
|
||||||
import _winapi
|
import _winapi
|
||||||
class STARTUPINFO:
|
class STARTUPINFO:
|
||||||
def __init__(self, *, dwFlags=0, hStdInput=None, hStdOutput=None,
|
def __init__(self, *, dwFlags=0, hStdInput=None, hStdOutput=None,
|
||||||
hStdError=None, wShowWindow=0):
|
hStdError=None, wShowWindow=0, lpAttributeList=None):
|
||||||
self.dwFlags = dwFlags
|
self.dwFlags = dwFlags
|
||||||
self.hStdInput = hStdInput
|
self.hStdInput = hStdInput
|
||||||
self.hStdOutput = hStdOutput
|
self.hStdOutput = hStdOutput
|
||||||
self.hStdError = hStdError
|
self.hStdError = hStdError
|
||||||
self.wShowWindow = wShowWindow
|
self.wShowWindow = wShowWindow
|
||||||
|
self.lpAttributeList = lpAttributeList or {"handle_list": []}
|
||||||
else:
|
else:
|
||||||
import _posixsubprocess
|
import _posixsubprocess
|
||||||
import select
|
import select
|
||||||
|
@ -577,9 +578,6 @@ def getoutput(cmd):
|
||||||
return getstatusoutput(cmd)[1]
|
return getstatusoutput(cmd)[1]
|
||||||
|
|
||||||
|
|
||||||
_PLATFORM_DEFAULT_CLOSE_FDS = object()
|
|
||||||
|
|
||||||
|
|
||||||
class Popen(object):
|
class Popen(object):
|
||||||
""" Execute a child program in a new process.
|
""" Execute a child program in a new process.
|
||||||
|
|
||||||
|
@ -630,7 +628,7 @@ class Popen(object):
|
||||||
|
|
||||||
def __init__(self, args, bufsize=-1, executable=None,
|
def __init__(self, args, bufsize=-1, executable=None,
|
||||||
stdin=None, stdout=None, stderr=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,
|
shell=False, cwd=None, env=None, universal_newlines=None,
|
||||||
startupinfo=None, creationflags=0,
|
startupinfo=None, creationflags=0,
|
||||||
restore_signals=True, start_new_session=False,
|
restore_signals=True, start_new_session=False,
|
||||||
|
@ -655,21 +653,8 @@ class Popen(object):
|
||||||
if preexec_fn is not None:
|
if preexec_fn is not None:
|
||||||
raise ValueError("preexec_fn is not supported on Windows "
|
raise ValueError("preexec_fn is not supported on Windows "
|
||||||
"platforms")
|
"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:
|
else:
|
||||||
# POSIX
|
# POSIX
|
||||||
if close_fds is _PLATFORM_DEFAULT_CLOSE_FDS:
|
|
||||||
close_fds = True
|
|
||||||
if pass_fds and not close_fds:
|
if pass_fds and not close_fds:
|
||||||
warnings.warn("pass_fds overriding close_fds.", RuntimeWarning)
|
warnings.warn("pass_fds overriding close_fds.", RuntimeWarning)
|
||||||
close_fds = True
|
close_fds = True
|
||||||
|
@ -1019,6 +1004,19 @@ class Popen(object):
|
||||||
return Handle(h)
|
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,
|
def _execute_child(self, args, executable, preexec_fn, close_fds,
|
||||||
pass_fds, cwd, env,
|
pass_fds, cwd, env,
|
||||||
startupinfo, creationflags, shell,
|
startupinfo, creationflags, shell,
|
||||||
|
@ -1036,12 +1034,41 @@ class Popen(object):
|
||||||
# Process startup details
|
# Process startup details
|
||||||
if startupinfo is None:
|
if startupinfo is None:
|
||||||
startupinfo = STARTUPINFO()
|
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.dwFlags |= _winapi.STARTF_USESTDHANDLES
|
||||||
startupinfo.hStdInput = p2cread
|
startupinfo.hStdInput = p2cread
|
||||||
startupinfo.hStdOutput = c2pwrite
|
startupinfo.hStdOutput = c2pwrite
|
||||||
startupinfo.hStdError = errwrite
|
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:
|
if shell:
|
||||||
startupinfo.dwFlags |= _winapi.STARTF_USESHOWWINDOW
|
startupinfo.dwFlags |= _winapi.STARTF_USESHOWWINDOW
|
||||||
startupinfo.wShowWindow = _winapi.SW_HIDE
|
startupinfo.wShowWindow = _winapi.SW_HIDE
|
||||||
|
|
|
@ -2743,11 +2743,6 @@ class Win32ProcessTestCase(BaseTestCase):
|
||||||
[sys.executable, "-c",
|
[sys.executable, "-c",
|
||||||
"import sys; sys.exit(47)"],
|
"import sys; sys.exit(47)"],
|
||||||
preexec_fn=lambda: 1)
|
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
|
@support.cpython_only
|
||||||
def test_issue31471(self):
|
def test_issue31471(self):
|
||||||
|
@ -2765,6 +2760,67 @@ class Win32ProcessTestCase(BaseTestCase):
|
||||||
close_fds=True)
|
close_fds=True)
|
||||||
self.assertEqual(rc, 47)
|
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):
|
def test_shell_sequence(self):
|
||||||
# Run command through the shell (sequence)
|
# Run command through the shell (sequence)
|
||||||
newenv = os.environ.copy()
|
newenv = os.environ.copy()
|
||||||
|
|
|
@ -466,6 +466,7 @@ Carl Feynman
|
||||||
Vincent Fiack
|
Vincent Fiack
|
||||||
Anastasia Filatova
|
Anastasia Filatova
|
||||||
Tomer Filiba
|
Tomer Filiba
|
||||||
|
Segev Finer
|
||||||
Jeffrey Finkelstein
|
Jeffrey Finkelstein
|
||||||
Russell Finn
|
Russell Finn
|
||||||
Dan Finnie
|
Dan Finnie
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Implement support for `subprocess.Popen(close_fds=True)` on Windows. Patch
|
||||||
|
by Segev Finer.
|
|
@ -811,6 +811,165 @@ getenvironment(PyObject* environment)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static LPHANDLE
|
||||||
|
gethandlelist(PyObject *mapping, const char *name, Py_ssize_t *size)
|
||||||
|
{
|
||||||
|
LPHANDLE ret = NULL;
|
||||||
|
PyObject *value_fast = NULL;
|
||||||
|
PyObject *value;
|
||||||
|
Py_ssize_t i;
|
||||||
|
|
||||||
|
value = PyMapping_GetItemString(mapping, name);
|
||||||
|
if (!value) {
|
||||||
|
PyErr_Clear();
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value == Py_None) {
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
value_fast = PySequence_Fast(value, "handle_list must be a sequence or None");
|
||||||
|
if (value_fast == NULL)
|
||||||
|
goto cleanup;
|
||||||
|
|
||||||
|
*size = PySequence_Fast_GET_SIZE(value_fast) * sizeof(HANDLE);
|
||||||
|
|
||||||
|
/* Passing an empty array causes CreateProcess to fail so just don't set it */
|
||||||
|
if (*size == 0) {
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = PyMem_Malloc(*size);
|
||||||
|
if (ret == NULL)
|
||||||
|
goto cleanup;
|
||||||
|
|
||||||
|
for (i = 0; i < PySequence_Fast_GET_SIZE(value_fast); i++) {
|
||||||
|
ret[i] = PYNUM_TO_HANDLE(PySequence_Fast_GET_ITEM(value_fast, i));
|
||||||
|
if (ret[i] == (HANDLE)-1 && PyErr_Occurred()) {
|
||||||
|
PyMem_Free(ret);
|
||||||
|
ret = NULL;
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
Py_DECREF(value);
|
||||||
|
Py_XDECREF(value_fast);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
LPPROC_THREAD_ATTRIBUTE_LIST attribute_list;
|
||||||
|
LPHANDLE handle_list;
|
||||||
|
} AttributeList;
|
||||||
|
|
||||||
|
static void
|
||||||
|
freeattributelist(AttributeList *attribute_list)
|
||||||
|
{
|
||||||
|
if (attribute_list->attribute_list != NULL) {
|
||||||
|
DeleteProcThreadAttributeList(attribute_list->attribute_list);
|
||||||
|
PyMem_Free(attribute_list->attribute_list);
|
||||||
|
}
|
||||||
|
|
||||||
|
PyMem_Free(attribute_list->handle_list);
|
||||||
|
|
||||||
|
memset(attribute_list, 0, sizeof(*attribute_list));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
getattributelist(PyObject *obj, const char *name, AttributeList *attribute_list)
|
||||||
|
{
|
||||||
|
int ret = 0;
|
||||||
|
DWORD err;
|
||||||
|
BOOL result;
|
||||||
|
PyObject *value;
|
||||||
|
Py_ssize_t handle_list_size;
|
||||||
|
DWORD attribute_count = 0;
|
||||||
|
SIZE_T attribute_list_size = 0;
|
||||||
|
|
||||||
|
value = PyObject_GetAttrString(obj, name);
|
||||||
|
if (!value) {
|
||||||
|
PyErr_Clear(); /* FIXME: propagate error? */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value == Py_None) {
|
||||||
|
ret = 0;
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!PyMapping_Check(value)) {
|
||||||
|
ret = -1;
|
||||||
|
PyErr_Format(PyExc_TypeError, "%s must be a mapping or None", name);
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
attribute_list->handle_list = gethandlelist(value, "handle_list", &handle_list_size);
|
||||||
|
if (attribute_list->handle_list == NULL && PyErr_Occurred()) {
|
||||||
|
ret = -1;
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attribute_list->handle_list != NULL)
|
||||||
|
++attribute_count;
|
||||||
|
|
||||||
|
/* Get how many bytes we need for the attribute list */
|
||||||
|
result = InitializeProcThreadAttributeList(NULL, attribute_count, 0, &attribute_list_size);
|
||||||
|
if (result || GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
|
||||||
|
ret = -1;
|
||||||
|
PyErr_SetFromWindowsErr(GetLastError());
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
attribute_list->attribute_list = PyMem_Malloc(attribute_list_size);
|
||||||
|
if (attribute_list->attribute_list == NULL) {
|
||||||
|
ret = -1;
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = InitializeProcThreadAttributeList(
|
||||||
|
attribute_list->attribute_list,
|
||||||
|
attribute_count,
|
||||||
|
0,
|
||||||
|
&attribute_list_size);
|
||||||
|
if (!result) {
|
||||||
|
err = GetLastError();
|
||||||
|
|
||||||
|
/* So that we won't call DeleteProcThreadAttributeList */
|
||||||
|
PyMem_Free(attribute_list->attribute_list);
|
||||||
|
attribute_list->attribute_list = NULL;
|
||||||
|
|
||||||
|
ret = -1;
|
||||||
|
PyErr_SetFromWindowsErr(err);
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attribute_list->handle_list != NULL) {
|
||||||
|
result = UpdateProcThreadAttribute(
|
||||||
|
attribute_list->attribute_list,
|
||||||
|
0,
|
||||||
|
PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
|
||||||
|
attribute_list->handle_list,
|
||||||
|
handle_list_size,
|
||||||
|
NULL,
|
||||||
|
NULL);
|
||||||
|
if (!result) {
|
||||||
|
ret = -1;
|
||||||
|
PyErr_SetFromWindowsErr(GetLastError());
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
Py_DECREF(value);
|
||||||
|
|
||||||
|
if (ret < 0)
|
||||||
|
freeattributelist(attribute_list);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
/*[clinic input]
|
/*[clinic input]
|
||||||
_winapi.CreateProcess
|
_winapi.CreateProcess
|
||||||
|
|
||||||
|
@ -842,34 +1001,35 @@ _winapi_CreateProcess_impl(PyObject *module, Py_UNICODE *application_name,
|
||||||
PyObject *startup_info)
|
PyObject *startup_info)
|
||||||
/*[clinic end generated code: output=4652a33aff4b0ae1 input=4a43b05038d639bb]*/
|
/*[clinic end generated code: output=4652a33aff4b0ae1 input=4a43b05038d639bb]*/
|
||||||
{
|
{
|
||||||
|
PyObject *ret = NULL;
|
||||||
BOOL result;
|
BOOL result;
|
||||||
PROCESS_INFORMATION pi;
|
PROCESS_INFORMATION pi;
|
||||||
STARTUPINFOW si;
|
STARTUPINFOEXW si;
|
||||||
PyObject* environment;
|
PyObject *environment = NULL;
|
||||||
wchar_t *wenvironment;
|
wchar_t *wenvironment;
|
||||||
|
AttributeList attribute_list = {0};
|
||||||
|
|
||||||
ZeroMemory(&si, sizeof(si));
|
ZeroMemory(&si, sizeof(si));
|
||||||
si.cb = sizeof(si);
|
si.StartupInfo.cb = sizeof(si);
|
||||||
|
|
||||||
/* note: we only support a small subset of all SI attributes */
|
/* note: we only support a small subset of all SI attributes */
|
||||||
si.dwFlags = getulong(startup_info, "dwFlags");
|
si.StartupInfo.dwFlags = getulong(startup_info, "dwFlags");
|
||||||
si.wShowWindow = (WORD)getulong(startup_info, "wShowWindow");
|
si.StartupInfo.wShowWindow = (WORD)getulong(startup_info, "wShowWindow");
|
||||||
si.hStdInput = gethandle(startup_info, "hStdInput");
|
si.StartupInfo.hStdInput = gethandle(startup_info, "hStdInput");
|
||||||
si.hStdOutput = gethandle(startup_info, "hStdOutput");
|
si.StartupInfo.hStdOutput = gethandle(startup_info, "hStdOutput");
|
||||||
si.hStdError = gethandle(startup_info, "hStdError");
|
si.StartupInfo.hStdError = gethandle(startup_info, "hStdError");
|
||||||
if (PyErr_Occurred())
|
if (PyErr_Occurred())
|
||||||
return NULL;
|
goto cleanup;
|
||||||
|
|
||||||
if (env_mapping != Py_None) {
|
if (env_mapping != Py_None) {
|
||||||
environment = getenvironment(env_mapping);
|
environment = getenvironment(env_mapping);
|
||||||
if (environment == NULL) {
|
if (environment == NULL) {
|
||||||
return NULL;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
/* contains embedded null characters */
|
/* contains embedded null characters */
|
||||||
wenvironment = PyUnicode_AsUnicode(environment);
|
wenvironment = PyUnicode_AsUnicode(environment);
|
||||||
if (wenvironment == NULL) {
|
if (wenvironment == NULL) {
|
||||||
Py_DECREF(environment);
|
goto cleanup;
|
||||||
return NULL;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -877,29 +1037,41 @@ _winapi_CreateProcess_impl(PyObject *module, Py_UNICODE *application_name,
|
||||||
wenvironment = NULL;
|
wenvironment = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (getattributelist(startup_info, "lpAttributeList", &attribute_list) < 0)
|
||||||
|
goto cleanup;
|
||||||
|
|
||||||
|
si.lpAttributeList = attribute_list.attribute_list;
|
||||||
|
|
||||||
Py_BEGIN_ALLOW_THREADS
|
Py_BEGIN_ALLOW_THREADS
|
||||||
result = CreateProcessW(application_name,
|
result = CreateProcessW(application_name,
|
||||||
command_line,
|
command_line,
|
||||||
NULL,
|
NULL,
|
||||||
NULL,
|
NULL,
|
||||||
inherit_handles,
|
inherit_handles,
|
||||||
creation_flags | CREATE_UNICODE_ENVIRONMENT,
|
creation_flags | EXTENDED_STARTUPINFO_PRESENT |
|
||||||
|
CREATE_UNICODE_ENVIRONMENT,
|
||||||
wenvironment,
|
wenvironment,
|
||||||
current_directory,
|
current_directory,
|
||||||
&si,
|
(LPSTARTUPINFOW)&si,
|
||||||
&pi);
|
&pi);
|
||||||
Py_END_ALLOW_THREADS
|
Py_END_ALLOW_THREADS
|
||||||
|
|
||||||
Py_XDECREF(environment);
|
if (!result) {
|
||||||
|
PyErr_SetFromWindowsErr(GetLastError());
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
if (! result)
|
ret = Py_BuildValue("NNkk",
|
||||||
return PyErr_SetFromWindowsErr(GetLastError());
|
|
||||||
|
|
||||||
return Py_BuildValue("NNkk",
|
|
||||||
HANDLE_TO_PYNUM(pi.hProcess),
|
HANDLE_TO_PYNUM(pi.hProcess),
|
||||||
HANDLE_TO_PYNUM(pi.hThread),
|
HANDLE_TO_PYNUM(pi.hThread),
|
||||||
pi.dwProcessId,
|
pi.dwProcessId,
|
||||||
pi.dwThreadId);
|
pi.dwThreadId);
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
Py_XDECREF(environment);
|
||||||
|
freeattributelist(&attribute_list);
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*[clinic input]
|
/*[clinic input]
|
||||||
|
@ -1489,7 +1661,6 @@ _winapi_WriteFile_impl(PyObject *module, HANDLE handle, PyObject *buffer,
|
||||||
return Py_BuildValue("II", written, err);
|
return Py_BuildValue("II", written, err);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*[clinic input]
|
/*[clinic input]
|
||||||
_winapi.GetACP
|
_winapi.GetACP
|
||||||
|
|
||||||
|
@ -1503,6 +1674,30 @@ _winapi_GetACP_impl(PyObject *module)
|
||||||
return PyLong_FromUnsignedLong(GetACP());
|
return PyLong_FromUnsignedLong(GetACP());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*[clinic input]
|
||||||
|
_winapi.GetFileType -> DWORD
|
||||||
|
|
||||||
|
handle: HANDLE
|
||||||
|
[clinic start generated code]*/
|
||||||
|
|
||||||
|
static DWORD
|
||||||
|
_winapi_GetFileType_impl(PyObject *module, HANDLE handle)
|
||||||
|
/*[clinic end generated code: output=92b8466ac76ecc17 input=0058366bc40bbfbf]*/
|
||||||
|
{
|
||||||
|
DWORD result;
|
||||||
|
|
||||||
|
Py_BEGIN_ALLOW_THREADS
|
||||||
|
result = GetFileType(handle);
|
||||||
|
Py_END_ALLOW_THREADS
|
||||||
|
|
||||||
|
if (result == FILE_TYPE_UNKNOWN && GetLastError() != NO_ERROR) {
|
||||||
|
PyErr_SetFromWindowsErr(0);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static PyMethodDef winapi_functions[] = {
|
static PyMethodDef winapi_functions[] = {
|
||||||
_WINAPI_CLOSEHANDLE_METHODDEF
|
_WINAPI_CLOSEHANDLE_METHODDEF
|
||||||
|
@ -1530,6 +1725,7 @@ static PyMethodDef winapi_functions[] = {
|
||||||
_WINAPI_WAITFORSINGLEOBJECT_METHODDEF
|
_WINAPI_WAITFORSINGLEOBJECT_METHODDEF
|
||||||
_WINAPI_WRITEFILE_METHODDEF
|
_WINAPI_WRITEFILE_METHODDEF
|
||||||
_WINAPI_GETACP_METHODDEF
|
_WINAPI_GETACP_METHODDEF
|
||||||
|
_WINAPI_GETFILETYPE_METHODDEF
|
||||||
{NULL, NULL}
|
{NULL, NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1623,6 +1819,12 @@ PyInit__winapi(void)
|
||||||
WINAPI_CONSTANT(F_DWORD, CREATE_DEFAULT_ERROR_MODE);
|
WINAPI_CONSTANT(F_DWORD, CREATE_DEFAULT_ERROR_MODE);
|
||||||
WINAPI_CONSTANT(F_DWORD, CREATE_BREAKAWAY_FROM_JOB);
|
WINAPI_CONSTANT(F_DWORD, CREATE_BREAKAWAY_FROM_JOB);
|
||||||
|
|
||||||
|
WINAPI_CONSTANT(F_DWORD, FILE_TYPE_UNKNOWN);
|
||||||
|
WINAPI_CONSTANT(F_DWORD, FILE_TYPE_DISK);
|
||||||
|
WINAPI_CONSTANT(F_DWORD, FILE_TYPE_CHAR);
|
||||||
|
WINAPI_CONSTANT(F_DWORD, FILE_TYPE_PIPE);
|
||||||
|
WINAPI_CONSTANT(F_DWORD, FILE_TYPE_REMOTE);
|
||||||
|
|
||||||
WINAPI_CONSTANT("i", NULL);
|
WINAPI_CONSTANT("i", NULL);
|
||||||
|
|
||||||
return m;
|
return m;
|
||||||
|
|
|
@ -907,4 +907,38 @@ _winapi_GetACP(PyObject *module, PyObject *Py_UNUSED(ignored))
|
||||||
{
|
{
|
||||||
return _winapi_GetACP_impl(module);
|
return _winapi_GetACP_impl(module);
|
||||||
}
|
}
|
||||||
/*[clinic end generated code: output=b9c00c323c9f4944 input=a9049054013a1b77]*/
|
|
||||||
|
PyDoc_STRVAR(_winapi_GetFileType__doc__,
|
||||||
|
"GetFileType($module, /, handle)\n"
|
||||||
|
"--\n"
|
||||||
|
"\n");
|
||||||
|
|
||||||
|
#define _WINAPI_GETFILETYPE_METHODDEF \
|
||||||
|
{"GetFileType", (PyCFunction)_winapi_GetFileType, METH_FASTCALL|METH_KEYWORDS, _winapi_GetFileType__doc__},
|
||||||
|
|
||||||
|
static DWORD
|
||||||
|
_winapi_GetFileType_impl(PyObject *module, HANDLE handle);
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
_winapi_GetFileType(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
|
||||||
|
{
|
||||||
|
PyObject *return_value = NULL;
|
||||||
|
static const char * const _keywords[] = {"handle", NULL};
|
||||||
|
static _PyArg_Parser _parser = {"" F_HANDLE ":GetFileType", _keywords, 0};
|
||||||
|
HANDLE handle;
|
||||||
|
DWORD _return_value;
|
||||||
|
|
||||||
|
if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser,
|
||||||
|
&handle)) {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
_return_value = _winapi_GetFileType_impl(module, handle);
|
||||||
|
if ((_return_value == PY_DWORD_MAX) && PyErr_Occurred()) {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
return_value = Py_BuildValue("k", _return_value);
|
||||||
|
|
||||||
|
exit:
|
||||||
|
return return_value;
|
||||||
|
}
|
||||||
|
/*[clinic end generated code: output=ec7f36eb7efc9d00 input=a9049054013a1b77]*/
|
||||||
|
|
Loading…
Reference in New Issue