kill: use pidfd system calls to implement --timeout option

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>
This commit is contained in:
Sami Kerola 2019-11-25 20:31:20 +00:00
parent ca27517aae
commit 6e6b9a1d24
No known key found for this signature in database
GPG Key ID: 0D46FEF7E61DBB46
4 changed files with 137 additions and 2 deletions

View 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"])]

23
include/pidfd-utils.h Normal file
View File

@ -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

View 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))

View 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. */