]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
kill: use pidfd system calls to implement --timeout option
authorSami Kerola <kerolasa@iki.fi>
Mon, 25 Nov 2019 20:31:20 +0000 (20:31 +0000)
committerSami Kerola <kerolasa@iki.fi>
Mon, 25 Nov 2019 21:25:50 +0000 (21:25 +0000)
At times there is need in scripts to send multiple signals to a process.
Often these cases require some amount of waiting before follow-up signal
should be sent.

One common case is process termination, where first script tries to kill
process gracefully but if that does not work SIGKILL is sent.  Functionality
like that is commonly done by periodically checking if signalled pid exist
or not, and if it does another signal is sent possibly to an unrelated
process that reused pid number.  That means polling a pid is prone to a data
race.  Also if the first signal immediately kills the process one polling
interval is lost in sleep.

Another example when multiple signal need to be sent is various daemon
process control situations, such as Upgrading Executable on the Fly (see
reference).  This happens to be the case that inspired change author to make
sequential signaling a little bit easier.

Reference: http://nginx.org/en/docs/control.html#upgrade
Pull-request: https://github.com/karelzak/util-linux/pull/902
Signed-off-by: Sami Kerola <kerolasa@iki.fi>
configure.ac
include/pidfd-utils.h [new file with mode: 0644]
misc-utils/kill.1
misc-utils/kill.c

index e8a03bf0096d9eac7b353891b25be54ef5df5560..7bc1f656f9d80c1994e110085d90948fb170b9b6 100644 (file)
@@ -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 (file)
index 0000000..cc8aa57
--- /dev/null
@@ -0,0 +1,23 @@
+#ifndef UTIL_LINUX_PIDFD_UTILS
+#define UTIL_LINUX_PIDFD_UTILS
+
+#if defined (__linux__)
+# include <sys/types.h>
+# include <sys/syscall.h>
+# 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
index 24c32a24921fd4e99c9330b379f1402a861ed1a3..e1afc12a0bd06b617234dfa974b027a7801e4c46 100644 (file)
@@ -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))
index 1ac61cf5a696acc61acf219f491cf4df104b8061..1a1715cc40c331ef3034dfeff9ae538bfaadfd3b 100644 (file)
@@ -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 <poll.h>
+# 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 <signal>  send this <signal> instead of SIGTERM\n"), out);
 #ifdef HAVE_SIGQUEUE
        fputs(_(" -q, --queue <value>    use sigqueue(2), not kill(2), and pass <value> as data\n"), out);
+#endif
+#ifdef UL_HAVE_PIDFD
+       fputs(_("     --timeout <milliseconds> <follow-up signal>\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[=<signal>]  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. */