diff --git a/configure.ac b/configure.ac index e8a03bf00..7bc1f656f 100644 --- a/configure.ac +++ b/configure.ac @@ -497,6 +497,8 @@ AC_CHECK_FUNCS([ \ nanosleep \ ntp_gettime \ personality \ + pidfd_open \ + pidfd_send_signal \ posix_fadvise \ prctl \ qsort_r \ @@ -539,6 +541,9 @@ AS_IF([test "x$ul_cv_syscall_setns" = xno], [ have_setns_syscall="no" ]) +UL_CHECK_SYSCALL([pidfd_open]) +UL_CHECK_SYSCALL([pidfd_send_signal]) + AC_CHECK_FUNCS([isnan], [], [AC_CHECK_LIB([m], [isnan], [MATH_LIBS="-lm"])] [AC_CHECK_LIB([m], [__isnan], [MATH_LIBS="-lm"])] diff --git a/include/pidfd-utils.h b/include/pidfd-utils.h new file mode 100644 index 000000000..cc8aa5788 --- /dev/null +++ b/include/pidfd-utils.h @@ -0,0 +1,23 @@ +#ifndef UTIL_LINUX_PIDFD_UTILS +#define UTIL_LINUX_PIDFD_UTILS + +#if defined (__linux__) +# include +# include +# ifndef HAVE_PIDFD_OPEN +static inline int pidfd_send_signal(int pidfd, int sig, siginfo_t *info, + unsigned int flags) +{ + return syscall(SYS_pidfd_send_signal, pidfd, sig, info, flags); +} +# endif +# ifndef HAVE_PIDFD_SEND_SIGNAL +static inline int pidfd_open(pid_t pid, unsigned int flags) +{ + return syscall(SYS_pidfd_open, pid, flags); +} +# endif +# define UL_HAVE_PIDFD 1 +#endif + +#endif diff --git a/misc-utils/kill.1 b/misc-utils/kill.1 index 24c32a249..e1afc12a0 100644 --- a/misc-utils/kill.1 +++ b/misc-utils/kill.1 @@ -1,7 +1,7 @@ .\" Copyright 1994 Salvatore Valente (svalente@mit.edu) .\" Copyright 1992 Rickard E. Faith (faith@cs.unc.edu) .\" May be distributed under the GNU General Public License -.TH KILL 1 "July 2014" "util-linux" "User Commands" +.TH KILL 1 "November 2019" "util-linux" "User Commands" .SH NAME kill \- terminate a process .SH SYNOPSIS @@ -11,6 +11,7 @@ kill \- terminate a process .RB [ \-q .IR value ] .RB [ \-a ] +\fR[\fB\-\-timeout \fImilliseconds signal\fR] .RB [ \-\- ] .IR pid | name ... .br @@ -120,7 +121,26 @@ then it can obtain this data via the field of the .I siginfo_t structure. - +.TP +\fB\-\-timeout\fR \fImilliseconds signal\fR +Send a signal defined the usual way to a process. +.B \-\-timeout +will make +.B kill +to wait for a period defined in +.I milliseconds +before sending follow-up +.I signal +to process. When timeout is speficified multiple times to a list of +timeouts and signals that are sent sequentially. The +.B \-\-timeout +option can be combined with +.B \-\-queue +option. +.IP +Example. Send signal that does nothing twice, and terminate cat(1). +.br +kill --timeout 1000 0 --timeout 1000 TERM --verbose -s 0 cat .SH NOTES Although it is possible to specify the TID (thread ID, see .BR gettid (2)) diff --git a/misc-utils/kill.c b/misc-utils/kill.c index 1ac61cf5a..1a1715cc4 100644 --- a/misc-utils/kill.c +++ b/misc-utils/kill.c @@ -54,6 +54,7 @@ #include "c.h" #include "closestream.h" #include "nls.h" +#include "pidfd-utils.h" #include "procutils.h" #include "signames.h" #include "strutils.h" @@ -68,18 +69,34 @@ enum { KILL_OUTPUT_WIDTH = 72 }; +#ifdef UL_HAVE_PIDFD +# include +# include "list.h" +struct timeouts { + int period; + int sig; + struct list_head follow_ups; +}; +#endif + struct kill_control { char *arg; pid_t pid; int numsig; #ifdef HAVE_SIGQUEUE union sigval sigdata; +#endif +#ifdef UL_HAVE_PIDFD + struct list_head follow_ups; #endif unsigned int check_all:1, do_kill:1, do_pid:1, use_sigval:1, +#ifdef UL_HAVE_PIDFD + timeout:1, +#endif verbose:1; }; @@ -184,6 +201,10 @@ static void __attribute__((__noreturn__)) usage(void) fputs(_(" -s, --signal send this instead of SIGTERM\n"), out); #ifdef HAVE_SIGQUEUE fputs(_(" -q, --queue use sigqueue(2), not kill(2), and pass as data\n"), out); +#endif +#ifdef UL_HAVE_PIDFD + fputs(_(" --timeout \n" + " wait up to timeout and send follow-up signal\n"), out); #endif fputs(_(" -p, --pid print pids without signaling them\n"), out); fputs(_(" -l, --list[=] list signal names, or convert a signal number to a name\n"), out); @@ -286,6 +307,25 @@ static char **parse_arguments(int argc, char **argv, struct kill_control *ctl) ctl->use_sigval = 1; continue; } +#endif +#ifdef UL_HAVE_PIDFD + if (!strcmp(arg, "--timeout")) { + struct timeouts *next; + + ctl->timeout = 1; + if (argc < 2) + errx(EXIT_FAILURE, _("option '%s' requires an argument"), arg); + argc--, argv++; + arg = *argv; + next = xcalloc(1, sizeof(*next)); + next->period = strtos32_or_err(arg, _("argument error")); + argc--, argv++; + arg = *argv; + if ((next->sig = arg_to_signum(arg, 0)) < 0) + err_nosig(arg); + list_add_tail(&next->follow_ups, &ctl->follow_ups); + continue; + } #endif /* 'arg' begins with a dash but is not a known option. * So it's probably something like -HUP, or -1/-n try to @@ -310,6 +350,47 @@ static char **parse_arguments(int argc, char **argv, struct kill_control *ctl) return argv; } +#ifdef UL_HAVE_PIDFD +static int kill_with_timeout(const struct kill_control *ctl) +{ + int pfd, n; + struct pollfd p = { 0 }; + siginfo_t info = { 0 }; + struct list_head *entry; + + info.si_code = SI_QUEUE; + info.si_signo = ctl->numsig; + info.si_uid = getuid(); + info.si_pid = getpid(); + info.si_value.sival_int = + ctl->use_sigval != 0 ? ctl->use_sigval : ctl->numsig; + + if ((pfd = pidfd_open(ctl->pid, 0)) < 0) + err(EXIT_FAILURE, _("pidfd_open() failed: %d"), ctl->pid); + p.fd = pfd; + p.events = POLLIN; + + if (pidfd_send_signal(pfd, ctl->numsig, &info, 0) < 0) + err(EXIT_FAILURE, _("pidfd_send_signal() failed")); + list_for_each(entry, &ctl->follow_ups) { + struct timeouts *timeout; + + timeout = list_entry(entry, struct timeouts, follow_ups); + n = poll(&p, 1, timeout->period); + if (n < 0) + err(EXIT_FAILURE, _("poll() failed")); + if (n == 0) { + info.si_signo = timeout->sig; + if (ctl->verbose) + printf(_("timeout, sending signal %d to pid %d\n"), + timeout->sig, ctl->pid); + if (pidfd_send_signal(pfd, timeout->sig, &info, 0) < 0) + err(EXIT_FAILURE, _("pidfd_send_signal() failed")); + } + } + return 0; +} +#endif static int kill_verbose(const struct kill_control *ctl) { @@ -321,6 +402,11 @@ static int kill_verbose(const struct kill_control *ctl) printf("%ld\n", (long) ctl->pid); return 0; } +#ifdef UL_HAVE_PIDFD + if (ctl->timeout) { + rc = kill_with_timeout(ctl); + } else +#endif #ifdef HAVE_SIGQUEUE if (ctl->use_sigval) rc = sigqueue(ctl->pid, ctl->numsig, ctl->sigdata); @@ -343,6 +429,7 @@ int main(int argc, char **argv) textdomain(PACKAGE); close_stdout_atexit(); + INIT_LIST_HEAD(&ctl.follow_ups); argv = parse_arguments(argc, argv, &ctl); /* The rest of the arguments should be process ids and names. */