8000 bpo-40138: Fix Windows os.waitpid() for large exit code (GH-19637) · python/cpython@9bee32b · GitHub
[go: up one dir, main page]

Skip to content

Commit 9bee32b

Browse files
authored
bpo-40138: Fix Windows os.waitpid() for large exit code (GH-19637)
Fix the Windows implementation of os.waitpid() for exit code larger than "INT_MAX >> 8". The exit status is now interpreted as an unsigned number. os.waitstatus_to_exitcode() now accepts wait status larger than INT_MAX.
1 parent bcc136b commit 9bee32b

File tree

4 files changed

+87
-40
lines changed

4 files changed

+87
-40
lines changed

Lib/test/test_os.py

Lines changed: 51 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2789,40 +2789,66 @@ def test_getppid(self):
27892789
# We are the parent of our subprocess
27902790
self.assertEqual(int(stdout), os.getpid())
27912791

2792+
def check_waitpid(self, code, exitcode, callback=None):
2793+
if sys.platform == 'win32':
2794+
# On Windows, os.spawnv() simply joins arguments with spaces:
2795+
# arguments need to be quoted
2796+
args = [f'"{sys.executable}"', '-c', f'"{code}"']
2797+
else:
2798+
args = [sys.executable, '-c', code]
2799+
pid = os.spawnv(os.P_NOWAIT, sys.executable, args)
2800+
2801+
if callback is not None:
2802+
callback(pid)
2803+
2804+
# don't use support.wait_process() to test directly os.waitpid()
2805+
# and os.waitstatus_to_exitcode()
2806+
pid2, status = os.waitpid(pid, 0)
2807+
self.assertEqual(os.waitstatus_to_exitcode(status), exitcode)
2808+
self.assertEqual(pid2, pid)
2809+
27922810
def test_waitpid(self):
2793-
args = [sys.executable, '-c', 'pass']
2794-
# Add an implicit test for PyUnicode_FSConverter().
2795-
pid = os.spawnv(os.P_NOWAIT, FakePath(args[0]), args)
2796-
support.wait_process(pid, exitcode=0)
2811+
self.check_waitpid(code='pass', exitcode=0)
27972812

27982813
def test_waitstatus_to_exitcode(self):
27992814
exitcode = 23
2800-
filename = support.TESTFN
2801-
self.addCleanup(support.unlink, filename)
2815+
code = f'import sys; sys.exit({exitcode})'
2816+
self.check_waitpid(code, exitcode=exitcode)
28022817

2803-
with open(filename, "w") as fp:
2804-
print(f'import sys; sys.exit({exitcode})', file=fp)
2805-
fp.flush()
2806-
args = [sys.executable, filename]
2807-
pid = os.spawnv(os.P_NOWAIT, args[0], args)
2818+
with self.assertRaises(TypeError):
2819+
os.waitstatus_to_exitcode(0.0)
28082820

2809-
pid2, status = os.waitpid(pid, 0)
2810-
self.assertEqual(os.waitstatus_to_exitcode(status), exitcode)
2811-
self.assertEqual(pid2, pid)
2821+
@unittest.skipUnless(sys.platform == 'win32', 'win32-specific test')
2822+
def test_waitpid_windows(self):
2823+
# bpo-40138: test os.waitpid() and os.waitstatus_to_exitcode()
2824+
# with exit code larger than INT_MAX.
2825+
STATUS_CONTROL_C_EXIT = 0xC000013A
2826+
code = f'import _winapi; _winapi.ExitProcess({STATUS_CONTROL_C_EXIT})'
2827+
self.check_waitpid(code, exitcode=STATUS_CONTROL_C_EXIT)
2828+
2829+
@unittest.skipUnless(sys.platform == 'win32', 'win32-specific test')
2830+
def test_waitstatus_to_exitcode_windows(self):
2831+
max_exitcode = 2 ** 32 - 1
2832+
for exitcode in (0, 1, 5, max_exitcode):
2833+
self.assertEqual(os.waitstatus_to_exitcode(exitcode << 8),
2834+
exitcode)
2835+
2836+
# invalid values
2837+
with self.assertRaises(ValueError):
2838+
os.waitstatus_to_exitcode((max_exitcode + 1) << 8)
2839+
with self.assertRaises(OverflowError):
2840+
os.waitstatus_to_exitcode(-1)
28122841

28132842
# Skip the test on Windows
28142843
@unittest.skipUnless(hasattr(signal, 'SIGKILL'), 'need signal.SIGKILL')
28152844
def test_waitstatus_to_exitcode_kill(self):
2845+
code = f'import time; time.sleep({support.LONG_TIMEOUT})'
28162846
signum = signal.SIGKILL
2817-
args = [sys.executable, '-c',
2818-
f'import time; time.sleep({support.LONG_TIMEOUT})']
2819-
pid = os.spawnv(os.P_NOWAIT, args[0], args)
28202847

2821-
os.kill(pid, signum)
2848+
def kill_process(pid):
2849+
os.kill(pid, signum)
28222850

2823-
pid2, status = os.waitpid(pid, 0)
2824-
self.assertEqual(os.waitstatus_to_exitcode(status), -signum)
2825-
self.assertEqual(pid2, pid)
2851+
self.check_waitpid(code, exitcode=-signum, callback=kill_process)
28262852

28272853

28282854
class SpawnTests(unittest.TestCase):
@@ -2884,6 +2910,10 @@ def test_spawnv(self):
28842910
exitcode = os.spawnv(os.P_WAIT, args[0], args)
28852911
self.assertEqual(exitcode, self.exitcode)
28862912

2913+
# Test for PyUnicode_FSConverter()
2914+
exitcode = os.spawnv(os.P_WAIT, FakePath(args[0]), args)
2915+
self.assertEqual(exitcode, self.exitcode)
2916+
28872917
@requires_os_func('spawnve')
28882918
def test_spawnve(self):
28892919
args = self.create_args(with_env=True)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix the Windows implementation of :func:`os.waitpid` for exit code larger than
2+
``INT_MAX >> 8``. The exit status is now interpreted as an unsigned number.

Modules/clinic/posixmodule.c.h

Lines changed: 5 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Modules/posixmodule.c

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7972,8 +7972,10 @@ os_waitpid_impl(PyObject *module, intptr_t pid, int options)
79727972
if (res < 0)
79737973
return (!async_err) ? posix_error() : NULL;
79747974

7975+
unsigned long long ustatus = (unsigned int)status;
7976+
79757977
/* shift the status left a byte so this is more like the POSIX waitpid */
7976-
return Py_BuildValue(_Py_PARSE_INTPTR "i", res, status << 8);
7978+
return Py_BuildValue(_Py_PARSE_INTPTR "K", res, ustatus << 8);
79777979
}
79787980
#endif
79797981

@@ -13829,7 +13831,7 @@ os__remove_dll_directory_impl(PyObject *module, PyObject *cookie)
1382913831
/*[clinic input]
1383013832
os.waitstatus_to_exitcode
1383113833
13832-
status: int
13834+
status as status_obj: object
1383313835
1383413836
Convert a wait status to an exit code.
1383513837
@@ -13847,10 +13849,20 @@ This function must not be called if WIFSTOPPED(status) is true.
1384713849
[clinic start generated code]*/
1384813850

1384913851
static PyObject *
13850-
os_waitstatus_to_exitcode_impl(PyObject *module, int status)
13851-
/*[clinic end generated code: output=c7c2265731f79b7a input=edfa5ca5006276fb]*/
13852+
os_waitstatus_to_exitcode_impl(PyObject *module, PyObject *status_obj)
13853+
/*[clinic end generated code: output=db50b1b0ba3c7153 input=7fe2d7fdaea3db42]*/
1385213854
{
13855+
if (PyFloat_Check(status_obj)) {
13856+
PyErr_SetString(PyExc_TypeError,
13857+
"integer argument expected, got float" );
13858+
return NULL;
13859+
}
1385313860
#ifndef MS_WINDOWS
13861+
int status = _PyLong_AsInt(status_obj);
13862+
if (status == -1 && PyErr_Occurred()) {
13863+
return NULL;
13864+
}
13865+
1385413866
WAIT_TYPE wait_status;
1385513867
WAIT_STATUS_INT(wait_status) = status;
1385613868
int exitcode;
@@ -13889,8 +13901,19 @@ os_waitstatus_to_exitcode_impl(PyObject *module, int status)
1388913901
#else
1389013902
/* Windows implementation: see os.waitpid() implementation
1389113903
which uses _cwait(). */
13892-
int exitcode = (status >> 8);
13893-
return PyLong_FromLong(exitcode);
13904+
unsigned long long status = PyLong_AsUnsignedLongLong(status_obj);
13905+
if (status == (unsigned long long)-1 && PyErr_Occurred()) {
13906+
return NULL;
13907+
}
13908+
13909+
unsigned long long exitcode = (status >> 8);
13910+
/* ExitProcess() accepts an UINT type:
13911+
reject exit code which doesn't fit in an UINT */
13912+
if (exitcode > UINT_MAX) {
13913+
PyErr_Format(PyExc_ValueError, "invalid exit code: %llu", exitcode);
13914+
return NULL;
13915+
}
13916+
return PyLong_FromUnsignedLong((unsigned long)exitcode);
1389413917
#endif
1389513918
}
1389613919
#endif

0 commit comments

Comments
 (0)
0