diff --git a/Doc/library/signal.rst b/Doc/library/signal.rst index 8fecc2b7eed..a79fc501352 100644 --- a/Doc/library/signal.rst +++ b/Doc/library/signal.rst @@ -247,6 +247,19 @@ The :mod:`signal` module defines the following functions: .. versionadded:: 3.8 +.. function:: pidfd_send_signal(pidfd, sig, siginfo=None, flags=0) + + Send signal *sig* to the process referred to by file descriptor *pidfd*. + Python does not currently support the *siginfo* parameter; it must be + ``None``. The *flags* argument is provided for future extensions; no flag + values are currently defined. + + See the :manpage:`pidfd_send_signal(2)` man page for more information. + + .. availability:: Linux 5.1+ + .. versionadded:: 3.9 + + .. function:: pthread_kill(thread_id, signalnum) Send the signal *signalnum* to the thread *thread_id*, another thread in the diff --git a/Doc/whatsnew/3.9.rst b/Doc/whatsnew/3.9.rst index ddb295f845c..ce1d3e0e96a 100644 --- a/Doc/whatsnew/3.9.rst +++ b/Doc/whatsnew/3.9.rst @@ -197,6 +197,13 @@ now raises :exc:`ImportError` instead of :exc:`ValueError` for invalid relative import attempts. (Contributed by Ngalim Siregar in :issue:`37444`.) +signal +------ + +Exposed the Linux-specific :func:`signal.pidfd_send_signal` for sending to +signals to a process using a file descriptor instead of a pid. (:issue:`38712`) + + Optimizations ============= diff --git a/Lib/test/test_signal.py b/Lib/test/test_signal.py index d41e94b07f4..119e9e079ac 100644 --- a/Lib/test/test_signal.py +++ b/Lib/test/test_signal.py @@ -1273,6 +1273,25 @@ class RaiseSignalTest(unittest.TestCase): self.assertTrue(is_ok) +class PidfdSignalTest(unittest.TestCase): + + @unittest.skipUnless( + hasattr(signal, "pidfd_send_signal"), + "pidfd support not built in", + ) + def test_pidfd_send_signal(self): + with self.assertRaises(OSError) as cm: + signal.pidfd_send_signal(0, signal.SIGINT) + if cm.exception.errno == errno.ENOSYS: + self.skipTest("kernel does not support pidfds") + self.assertEqual(cm.exception.errno, errno.EBADF) + my_pidfd = os.open(f'/proc/{os.getpid()}', os.O_DIRECTORY) + self.addCleanup(os.close, my_pidfd) + with self.assertRaisesRegexp(TypeError, "^siginfo must be None$"): + signal.pidfd_send_signal(my_pidfd, signal.SIGINT, object(), 0) + with self.assertRaises(KeyboardInterrupt): + signal.pidfd_send_signal(my_pidfd, signal.SIGINT) + def tearDownModule(): support.reap_children() diff --git a/Misc/NEWS.d/next/Library/2019-11-05-21-10-12.bpo-38712.ezJ0TP.rst b/Misc/NEWS.d/next/Library/2019-11-05-21-10-12.bpo-38712.ezJ0TP.rst new file mode 100644 index 00000000000..81d01aa7111 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-11-05-21-10-12.bpo-38712.ezJ0TP.rst @@ -0,0 +1,3 @@ +Add the Linux-specific :func:`signal.pidfd_send_signal` function, which +allows sending a signal to a process identified by a file descriptor rather +than a pid. diff --git a/Modules/clinic/signalmodule.c.h b/Modules/clinic/signalmodule.c.h index 3cb1db14878..7f60e28a3a2 100644 --- a/Modules/clinic/signalmodule.c.h +++ b/Modules/clinic/signalmodule.c.h @@ -611,6 +611,76 @@ exit: #endif /* defined(HAVE_PTHREAD_KILL) */ +#if (defined(__linux__) && defined(__NR_pidfd_send_signal)) + +PyDoc_STRVAR(signal_pidfd_send_signal__doc__, +"pidfd_send_signal($module, pidfd, signalnum, siginfo=None, flags=0, /)\n" +"--\n" +"\n" +"Send a signal to a process referred to by a pid file descriptor."); + +#define SIGNAL_PIDFD_SEND_SIGNAL_METHODDEF \ + {"pidfd_send_signal", (PyCFunction)(void(*)(void))signal_pidfd_send_signal, METH_FASTCALL, signal_pidfd_send_signal__doc__}, + +static PyObject * +signal_pidfd_send_signal_impl(PyObject *module, int pidfd, int signalnum, + PyObject *siginfo, int flags); + +static PyObject * +signal_pidfd_send_signal(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + int pidfd; + int signalnum; + PyObject *siginfo = Py_None; + int flags = 0; + + if (!_PyArg_CheckPositional("pidfd_send_signal", nargs, 2, 4)) { + goto exit; + } + if (PyFloat_Check(args[0])) { + PyErr_SetString(PyExc_TypeError, + "integer argument expected, got float" ); + goto exit; + } + pidfd = _PyLong_AsInt(args[0]); + if (pidfd == -1 && PyErr_Occurred()) { + goto exit; + } + if (PyFloat_Check(args[1])) { + PyErr_SetString(PyExc_TypeError, + "integer argument expected, got float" ); + goto exit; + } + signalnum = _PyLong_AsInt(args[1]); + if (signalnum == -1 && PyErr_Occurred()) { + goto exit; + } + if (nargs < 3) { + goto skip_optional; + } + siginfo = args[2]; + if (nargs < 4) { + goto skip_optional; + } + if (PyFloat_Check(args[3])) { + PyErr_SetString(PyExc_TypeError, + "integer argument expected, got float" ); + goto exit; + } + flags = _PyLong_AsInt(args[3]); + if (flags == -1 && PyErr_Occurred()) { + goto exit; + } +skip_optional: + return_value = signal_pidfd_send_signal_impl(module, pidfd, signalnum, siginfo, flags); + +exit: + return return_value; +} + +#endif /* (defined(__linux__) && defined(__NR_pidfd_send_signal)) */ + #ifndef SIGNAL_ALARM_METHODDEF #define SIGNAL_ALARM_METHODDEF #endif /* !defined(SIGNAL_ALARM_METHODDEF) */ @@ -658,4 +728,8 @@ exit: #ifndef SIGNAL_PTHREAD_KILL_METHODDEF #define SIGNAL_PTHREAD_KILL_METHODDEF #endif /* !defined(SIGNAL_PTHREAD_KILL_METHODDEF) */ -/*[clinic end generated code: output=3320b8f73c20ba60 input=a9049054013a1b77]*/ + +#ifndef SIGNAL_PIDFD_SEND_SIGNAL_METHODDEF + #define SIGNAL_PIDFD_SEND_SIGNAL_METHODDEF +#endif /* !defined(SIGNAL_PIDFD_SEND_SIGNAL_METHODDEF) */ +/*[clinic end generated code: output=b41b4b6bd9ad4da2 input=a9049054013a1b77]*/ diff --git a/Modules/signalmodule.c b/Modules/signalmodule.c index 1d99f877e16..693b90b6c63 100644 --- a/Modules/signalmodule.c +++ b/Modules/signalmodule.c @@ -25,6 +25,9 @@ #ifdef HAVE_SIGNAL_H #include #endif +#ifdef HAVE_SYS_SYSCALL_H +#include +#endif #ifdef HAVE_SYS_STAT_H #include #endif @@ -1250,6 +1253,38 @@ signal_pthread_kill_impl(PyObject *module, unsigned long thread_id, #endif /* #if defined(HAVE_PTHREAD_KILL) */ +#if defined(__linux__) && defined(__NR_pidfd_send_signal) +/*[clinic input] +signal.pidfd_send_signal + + pidfd: int + signalnum: int + siginfo: object = None + flags: int = 0 + / + +Send a signal to a process referred to by a pid file descriptor. +[clinic start generated code]*/ + +static PyObject * +signal_pidfd_send_signal_impl(PyObject *module, int pidfd, int signalnum, + PyObject *siginfo, int flags) +/*[clinic end generated code: output=2d59f04a75d9cbdf input=2a6543a1f4ac2000]*/ + +{ + if (siginfo != Py_None) { + PyErr_SetString(PyExc_TypeError, "siginfo must be None"); + return NULL; + } + if (syscall(__NR_pidfd_send_signal, pidfd, signalnum, NULL, flags) < 0) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + Py_RETURN_NONE; +} +#endif + + /* List of functions defined in the module -- some of the methoddefs are defined to nothing if the corresponding C function is not available. */ @@ -1265,6 +1300,7 @@ static PyMethodDef signal_methods[] = { {"set_wakeup_fd", (PyCFunction)(void(*)(void))signal_set_wakeup_fd, METH_VARARGS | METH_KEYWORDS, set_wakeup_fd_doc}, SIGNAL_SIGINTERRUPT_METHODDEF SIGNAL_PAUSE_METHODDEF + SIGNAL_PIDFD_SEND_SIGNAL_METHODDEF SIGNAL_PTHREAD_KILL_METHODDEF SIGNAL_PTHREAD_SIGMASK_METHODDEF SIGNAL_SIGPENDING_METHODDEF