Commit eb24d749 authored by Brian Curtin's avatar Brian Curtin

Port #1220212 (os.kill for Win32) to py3k.

parent b2416e54
...@@ -1491,7 +1491,14 @@ written in Python, such as a mail server's external command delivery program. ...@@ -1491,7 +1491,14 @@ written in Python, such as a mail server's external command delivery program.
Send signal *sig* to the process *pid*. Constants for the specific signals Send signal *sig* to the process *pid*. Constants for the specific signals
available on the host platform are defined in the :mod:`signal` module. available on the host platform are defined in the :mod:`signal` module.
Availability: Unix.
Windows: The :data:`signal.CTRL_C_EVENT` and
:data:`signal.CTRL_BREAK_EVENT` signals are special signals which can
only be sent to console processes which share a common console window,
e.g., some subprocesses. Any other value for *sig* will cause the process
to be unconditionally killed by the TerminateProcess API, and the exit code
will be set to *sig*. The Windows version of :func:`kill` additionally takes
process handles to be killed.
.. function:: killpg(pgid, sig) .. function:: killpg(pgid, sig)
......
...@@ -74,6 +74,20 @@ The variables defined in the :mod:`signal` module are: ...@@ -74,6 +74,20 @@ The variables defined in the :mod:`signal` module are:
the system are defined by this module. the system are defined by this module.
.. data:: CTRL_C_EVENT
The signal corresponding to the CTRL+C keystroke event.
Availability: Windows.
.. data:: CTRL_BREAK_EVENT
The signal corresponding to the CTRL+BREAK keystroke event.
Availability: Windows.
.. data:: NSIG .. data:: NSIG
One more than the number of the highest signal number. One more than the number of the highest signal number.
......
...@@ -373,8 +373,9 @@ Instances of the :class:`Popen` class have the following methods: ...@@ -373,8 +373,9 @@ Instances of the :class:`Popen` class have the following methods:
.. note:: .. note::
On Windows only SIGTERM is supported so far. It's an alias for On Windows, SIGTERM is an alias for :meth:`terminate`. CTRL_C_EVENT and
:meth:`terminate`. CTRL_BREAK_EVENT can be sent to processes started with a `creationflags`
parameter which includes `CREATE_NEW_PROCESS_GROUP`.
.. method:: Popen.terminate() .. method:: Popen.terminate()
......
...@@ -980,6 +980,10 @@ class Popen(object): ...@@ -980,6 +980,10 @@ class Popen(object):
""" """
if sig == signal.SIGTERM: if sig == signal.SIGTERM:
self.terminate() self.terminate()
elif sig == signal.CTRL_C_EVENT:
os.kill(self.pid, signal.CTRL_C_EVENT)
elif sig == signal.CTRL_BREAK_EVENT:
os.kill(self.pid, signal.CTRL_BREAK_EVENT)
else: else:
raise ValueError("Only SIGTERM is supported on Windows") raise ValueError("Only SIGTERM is supported on Windows")
......
...@@ -7,9 +7,13 @@ import errno ...@@ -7,9 +7,13 @@ import errno
import unittest import unittest
import warnings import warnings
import sys import sys
import signal
import subprocess
import time
import shutil import shutil
from test import support from test import support
# Tests creating TESTFN # Tests creating TESTFN
class FileTests(unittest.TestCase): class FileTests(unittest.TestCase):
def setUp(self): def setUp(self):
...@@ -739,7 +743,6 @@ if sys.platform != 'win32': ...@@ -739,7 +743,6 @@ if sys.platform != 'win32':
def test_setreuid_neg1(self): def test_setreuid_neg1(self):
# Needs to accept -1. We run this in a subprocess to avoid # Needs to accept -1. We run this in a subprocess to avoid
# altering the test runner's process state (issue8045). # altering the test runner's process state (issue8045).
import subprocess
subprocess.check_call([ subprocess.check_call([
sys.executable, '-c', sys.executable, '-c',
'import os,sys;os.setreuid(-1,-1);sys.exit(0)']) 'import os,sys;os.setreuid(-1,-1);sys.exit(0)'])
...@@ -754,7 +757,6 @@ if sys.platform != 'win32': ...@@ -754,7 +757,6 @@ if sys.platform != 'win32':
def test_setregid_neg1(self): def test_setregid_neg1(self):
# Needs to accept -1. We run this in a subprocess to avoid # Needs to accept -1. We run this in a subprocess to avoid
# altering the test runner's process state (issue8045). # altering the test runner's process state (issue8045).
import subprocess
subprocess.check_call([ subprocess.check_call([
sys.executable, '-c', sys.executable, '-c',
'import os,sys;os.setregid(-1,-1);sys.exit(0)']) 'import os,sys;os.setregid(-1,-1);sys.exit(0)'])
...@@ -798,6 +800,63 @@ else: ...@@ -798,6 +800,63 @@ else:
class Pep383Tests(unittest.TestCase): class Pep383Tests(unittest.TestCase):
pass pass
@unittest.skipUnless(sys.platform == "win32", "Win32 specific tests")
class Win32KillTests(unittest.TestCase):
def _kill(self, sig, *args):
# Send a subprocess a signal (or in some cases, just an int to be
# the return value)
proc = subprocess.Popen(*args)
os.kill(proc.pid, sig)
self.assertEqual(proc.wait(), sig)
def test_kill_sigterm(self):
# SIGTERM doesn't mean anything special, but make sure it works
self._kill(signal.SIGTERM, [sys.executable])
def test_kill_int(self):
# os.kill on Windows can take an int which gets set as the exit code
self._kill(100, [sys.executable])
def _kill_with_event(self, event, name):
# Run a script which has console control handling enabled.
proc = subprocess.Popen([sys.executable,
os.path.join(os.path.dirname(__file__),
"win_console_handler.py")],
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP)
# Let the interpreter startup before we send signals. See #3137.
time.sleep(0.5)
os.kill(proc.pid, event)
# proc.send_signal(event) could also be done here.
# Allow time for the signal to be passed and the process to exit.
time.sleep(0.5)
if not proc.poll():
# Forcefully kill the process if we weren't able to signal it.
os.kill(proc.pid, signal.SIGINT)
self.fail("subprocess did not stop on {}".format(name))
@unittest.skip("subprocesses aren't inheriting CTRL+C property")
def test_CTRL_C_EVENT(self):
from ctypes import wintypes
import ctypes
# Make a NULL value by creating a pointer with no argument.
NULL = ctypes.POINTER(ctypes.c_int)()
SetConsoleCtrlHandler = ctypes.windll.kernel32.SetConsoleCtrlHandler
SetConsoleCtrlHandler.argtypes = (ctypes.POINTER(ctypes.c_int),
wintypes.BOOL)
SetConsoleCtrlHandler.restype = wintypes.BOOL
# Calling this with NULL and FALSE causes the calling process to
# handle CTRL+C, rather than ignore it. This property is inherited
# by subprocesses.
SetConsoleCtrlHandler(NULL, 0)
self._kill_with_event(signal.CTRL_C_EVENT, "CTRL_C_EVENT")
def test_CTRL_BREAK_EVENT(self):
self._kill_with_event(signal.CTRL_BREAK_EVENT, "CTRL_BREAK_EVENT")
def test_main(): def test_main():
support.run_unittest( support.run_unittest(
ArgTests, ArgTests,
...@@ -812,7 +871,8 @@ def test_main(): ...@@ -812,7 +871,8 @@ def test_main():
Win32ErrorTests, Win32ErrorTests,
TestInvalidFD, TestInvalidFD,
PosixUidGidTests, PosixUidGidTests,
Pep383Tests Pep383Tests,
Win32KillTests
) )
if __name__ == "__main__": if __name__ == "__main__":
......
"""Script used to test os.kill on Windows, for issue #1220212
This script is started as a subprocess in test_os and is used to test the
CTRL_C_EVENT and CTRL_BREAK_EVENT signals, which requires a custom handler
to be written into the kill target.
See http://msdn.microsoft.com/en-us/library/ms685049%28v=VS.85%29.aspx for a
similar example in C.
"""
from ctypes import wintypes
import signal
import ctypes
# Function prototype for the handler function. Returns BOOL, takes a DWORD.
HandlerRoutine = wintypes.WINFUNCTYPE(wintypes.BOOL, wintypes.DWORD)
def _ctrl_handler(sig):
"""Handle a sig event and return 0 to terminate the process"""
if sig == signal.CTRL_C_EVENT:
pass
elif sig == signal.CTRL_BREAK_EVENT:
pass
else:
print("UNKNOWN EVENT")
return 0
ctrl_handler = HandlerRoutine(_ctrl_handler)
SetConsoleCtrlHandler = ctypes.windll.kernel32.SetConsoleCtrlHandler
SetConsoleCtrlHandler.argtypes = (HandlerRoutine, wintypes.BOOL)
SetConsoleCtrlHandler.restype = wintypes.BOOL
if __name__ == "__main__":
# Add our console control handling function with value 1
if not SetConsoleCtrlHandler(ctrl_handler, 1):
print("Unable to add SetConsoleCtrlHandler")
exit(-1)
# Do nothing but wait for the signal
while True:
pass
import gc import gc
import io import io
import os import os
import sys
import signal import signal
import weakref import weakref
...@@ -8,6 +9,7 @@ import unittest ...@@ -8,6 +9,7 @@ import unittest
@unittest.skipUnless(hasattr(os, 'kill'), "Test requires os.kill") @unittest.skipUnless(hasattr(os, 'kill'), "Test requires os.kill")
@unittest.skipIf(sys.platform =="win32", "Test cannot run on Windows")
class TestBreak(unittest.TestCase): class TestBreak(unittest.TestCase):
def setUp(self): def setUp(self):
......
...@@ -4171,6 +4171,53 @@ posix_killpg(PyObject *self, PyObject *args) ...@@ -4171,6 +4171,53 @@ posix_killpg(PyObject *self, PyObject *args)
} }
#endif #endif
#ifdef MS_WINDOWS
PyDoc_STRVAR(win32_kill__doc__,
"kill(pid, sig)\n\n\
Kill a process with a signal.");
static PyObject *
win32_kill(PyObject *self, PyObject *args)
{
PyObject *result, handle_obj;
DWORD pid, sig, err;
HANDLE handle;
if (!PyArg_ParseTuple(args, "kk:kill", &pid, &sig))
return NULL;
/* Console processes which share a common console can be sent CTRL+C or
CTRL+BREAK events, provided they handle said events. */
if (sig == CTRL_C_EVENT || sig == CTRL_BREAK_EVENT) {
if (GenerateConsoleCtrlEvent(sig, pid) == 0) {
err = GetLastError();
PyErr_SetFromWindowsErr(err);
}
else
Py_RETURN_NONE;
}
/* If the signal is outside of what GenerateConsoleCtrlEvent can use,
attempt to open and terminate the process. */
handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
if (handle == NULL) {
err = GetLastError();
return PyErr_SetFromWindowsErr(err);
}
if (TerminateProcess(handle, sig) == 0) {
err = GetLastError();
result = PyErr_SetFromWindowsErr(err);
} else {
Py_INCREF(Py_None);
result = Py_None;
}
CloseHandle(handle);
return result;
}
#endif /* MS_WINDOWS */
#ifdef HAVE_PLOCK #ifdef HAVE_PLOCK
#ifdef HAVE_SYS_LOCK_H #ifdef HAVE_SYS_LOCK_H
...@@ -7200,6 +7247,7 @@ static PyMethodDef posix_methods[] = { ...@@ -7200,6 +7247,7 @@ static PyMethodDef posix_methods[] = {
#endif /* HAVE_PLOCK */ #endif /* HAVE_PLOCK */
#ifdef MS_WINDOWS #ifdef MS_WINDOWS
{"startfile", win32_startfile, METH_VARARGS, win32_startfile__doc__}, {"startfile", win32_startfile, METH_VARARGS, win32_startfile__doc__},
{"kill", win32_kill, METH_VARARGS, win32_kill__doc__},
#endif #endif
#ifdef HAVE_SETUID #ifdef HAVE_SETUID
{"setuid", posix_setuid, METH_VARARGS, posix_setuid__doc__}, {"setuid", posix_setuid, METH_VARARGS, posix_setuid__doc__},
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include "intrcheck.h" #include "intrcheck.h"
#ifdef MS_WINDOWS #ifdef MS_WINDOWS
#include <Windows.h>
#ifdef HAVE_PROCESS_H #ifdef HAVE_PROCESS_H
#include <process.h> #include <process.h>
#endif #endif
...@@ -805,6 +806,18 @@ PyInit_signal(void) ...@@ -805,6 +806,18 @@ PyInit_signal(void)
PyDict_SetItemString(d, "ItimerError", ItimerError); PyDict_SetItemString(d, "ItimerError", ItimerError);
#endif #endif
#ifdef CTRL_C_EVENT
x = PyLong_FromLong(CTRL_C_EVENT);
PyDict_SetItemString(d, "CTRL_C_EVENT", x);
Py_DECREF(x);
#endif
#ifdef CTRL_BREAK_EVENT
x = PyLong_FromLong(CTRL_BREAK_EVENT);
PyDict_SetItemString(d, "CTRL_BREAK_EVENT", x);
Py_DECREF(x);
#endif
if (PyErr_Occurred()) { if (PyErr_Occurred()) {
Py_DECREF(m); Py_DECREF(m);
m = NULL; m = NULL;
......
...@@ -599,5 +599,7 @@ PyInit__subprocess() ...@@ -599,5 +599,7 @@ PyInit__subprocess()
defint(d, "INFINITE", INFINITE); defint(d, "INFINITE", INFINITE);
defint(d, "WAIT_OBJECT_0", WAIT_OBJECT_0); defint(d, "WAIT_OBJECT_0", WAIT_OBJECT_0);
defint(d, "CREATE_NEW_CONSOLE", CREATE_NEW_CONSOLE); defint(d, "CREATE_NEW_CONSOLE", CREATE_NEW_CONSOLE);
defint(d, "CREATE_NEW_PROCESS_GROUP", CREATE_NEW_PROCESS_GROUP);
return m; return m;
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment