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:
parent
ca27517aae
commit
6e6b9a1d24
|
@ -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"])]
|
||||
|
|
|
@ -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
|
|
@ -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))
|
||||
|
|
|
@ -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. */
|
||||
|
|
Loading…
Reference in New Issue