Index: Doc/library/os.rst =================================================================== --- Doc/library/os.rst (revision 79601) +++ Doc/library/os.rst (working copy) @@ -1719,7 +1719,13 @@ Send signal *sig* to the process *pid*. Constants for the specific signals 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*. .. function:: killpg(pgid, sig) Index: Doc/library/signal.rst =================================================================== --- Doc/library/signal.rst (revision 79601) +++ Doc/library/signal.rst (working copy) @@ -73,8 +73,22 @@ :manpage:`signal(2)`, on others the list is in :manpage:`signal(7)`). Note that not all systems define the same set of signal names; only those names defined by 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 One more than the number of the highest signal number. Index: Doc/library/subprocess.rst =================================================================== --- Doc/library/subprocess.rst (revision 79601) +++ Doc/library/subprocess.rst (working copy) @@ -320,8 +320,9 @@ .. note:: - On Windows only SIGTERM is supported so far. It's an alias for - :meth:`terminate`. + On Windows, SIGTERM is an alias for :meth:`terminate`. CTRL_C_EVENT and + CTRL_BREAK_EVENT can be sent to processes started with a `creationflags` + parameter which includes `CREATE_NEW_PROCESS_GROUP`. .. versionadded:: 2.6 Index: Lib/subprocess.py =================================================================== --- Lib/subprocess.py (revision 79601) +++ Lib/subprocess.py (working copy) @@ -990,6 +990,10 @@ """ if sig == signal.SIGTERM: 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: raise ValueError("Only SIGTERM is supported on Windows") Index: Lib/test/test_os.py =================================================================== --- Lib/test/test_os.py (revision 79601) +++ Lib/test/test_os.py (working copy) @@ -7,6 +7,9 @@ import unittest import warnings import sys +import signal +import subprocess +import time from test import test_support warnings.filterwarnings("ignore", "tempnam", RuntimeWarning, __name__) @@ -649,7 +652,6 @@ def test_setreuid_neg1(self): # Needs to accept -1. We run this in a subprocess to avoid # altering the test runner's process state (issue8045). - import subprocess subprocess.check_call([ sys.executable, '-c', 'import os,sys;os.setreuid(-1,-1);sys.exit(0)']) @@ -664,7 +666,6 @@ def test_setregid_neg1(self): # Needs to accept -1. We run this in a subprocess to avoid # altering the test runner's process state (issue8045). - import subprocess subprocess.check_call([ sys.executable, '-c', 'import os,sys;os.setregid(-1,-1);sys.exit(0)']) @@ -672,6 +673,63 @@ class PosixUidGidTests(unittest.TestCase): 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.1) + 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.1) + 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(): test_support.run_unittest( FileTests, @@ -684,7 +742,8 @@ URandomTests, Win32ErrorTests, TestInvalidFD, - PosixUidGidTests + PosixUidGidTests, + Win32KillTests ) if __name__ == "__main__": Index: Lib/test/win_console_handler.py =================================================================== --- Lib/test/win_console_handler.py (revision 0) +++ Lib/test/win_console_handler.py (revision 0) @@ -0,0 +1,42 @@ +"""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 + input() Index: Lib/unittest/test/test_break.py =================================================================== --- Lib/unittest/test/test_break.py (revision 79601) +++ Lib/unittest/test/test_break.py (working copy) @@ -1,5 +1,6 @@ import gc import os +import sys import signal import weakref @@ -10,6 +11,7 @@ @unittest.skipUnless(hasattr(os, 'kill'), "Test requires os.kill") +@unittest.skipIf(sys.platform =="win32", "Test cannot run on Windows") class TestBreak(unittest.TestCase): def setUp(self): Index: Modules/posixmodule.c =================================================================== --- Modules/posixmodule.c (revision 79601) +++ Modules/posixmodule.c (working copy) @@ -4075,6 +4075,53 @@ } #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_SYS_LOCK_H @@ -8660,6 +8707,7 @@ {"popen3", win32_popen3, METH_VARARGS}, {"popen4", win32_popen4, METH_VARARGS}, {"startfile", win32_startfile, METH_VARARGS, win32_startfile__doc__}, + {"kill", win32_kill, METH_VARARGS, win32_kill__doc__}, #else #if defined(PYOS_OS2) && defined(PYCC_GCC) {"popen2", os2emx_popen2, METH_VARARGS}, Index: Modules/signalmodule.c =================================================================== --- Modules/signalmodule.c (revision 79601) +++ Modules/signalmodule.c (working copy) @@ -7,6 +7,7 @@ #include "intrcheck.h" #ifdef MS_WINDOWS +#include #ifdef HAVE_PROCESS_H #include #endif @@ -793,6 +794,18 @@ PyDict_SetItemString(d, "ItimerError", ItimerError); #endif +#ifdef CTRL_C_EVENT + x = PyInt_FromLong(CTRL_C_EVENT); + PyDict_SetItemString(d, "CTRL_C_EVENT", x); + Py_DECREF(x); +#endif + +#ifdef CTRL_BREAK_EVENT + x = PyInt_FromLong(CTRL_BREAK_EVENT); + PyDict_SetItemString(d, "CTRL_BREAK_EVENT", x); + Py_DECREF(x); +#endif + if (!PyErr_Occurred()) return; Index: PC/_subprocess.c =================================================================== --- PC/_subprocess.c (revision 79601) +++ PC/_subprocess.c (working copy) @@ -586,4 +586,5 @@ defint(d, "INFINITE", INFINITE); defint(d, "WAIT_OBJECT_0", WAIT_OBJECT_0); defint(d, "CREATE_NEW_CONSOLE", CREATE_NEW_CONSOLE); + defint(d, "CREATE_NEW_PROCESS_GROUP", CREATE_NEW_PROCESS_GROUP); }