Index: Doc/library/os.rst =================================================================== --- Doc/library/os.rst (revision 79493) +++ Doc/library/os.rst (working copy) @@ -1715,7 +1715,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 79493) +++ 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: Lib/test/test_os.py =================================================================== --- Lib/test/test_os.py (revision 79493) +++ Lib/test/test_os.py (working copy) @@ -7,6 +7,8 @@ import unittest import warnings import sys +import signal +import subprocess from test import test_support warnings.filterwarnings("ignore", "tempnam", RuntimeWarning, __name__) @@ -649,7 +651,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 +665,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 +672,24 @@ 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 test_main(): test_support.run_unittest( FileTests, @@ -684,7 +702,8 @@ URandomTests, Win32ErrorTests, TestInvalidFD, - PosixUidGidTests + PosixUidGidTests, + Win32KillTests ) if __name__ == "__main__": Index: Lib/unittest/test/test_break.py =================================================================== --- Lib/unittest/test/test_break.py (revision 79493) +++ 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 79493) +++ 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; + 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 79493) +++ 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;