>From 33398b0c563db9de27ac99d47a714dde98f61e80 Mon Sep 17 00:00:00 2001 From: =?utf-8?q?P=C3=A1draig=20Brady?= Date: Mon, 15 Mar 2010 23:03:30 +0000 Subject: [PATCH] timeout: add the --kill-delay option Based on a report from Kim Hansen who wanted to send a KILL signal to the monitored command when `timeout` itself received a termination signal. Rather than changing such a signal into a KILL, we provide the more general mechanism of sending the KILL after the specified grace period. * src/timeout.c (cleanup): If a non zero kill delay is specified, (re)set the alarm to that delay, after which a KILL signal will be sent to the process group. (usage): Mention the new option. Separate the description of DURATION since it's now specified in 2 places. Clarify that the duration is an integer. (parse_duration): A new function refactored from main(), since this logic is now called for two parameters. (main): Parse the -k option. * doc/coreutils.texi (timeout invocation): Describe the new --kill-delay option and use @display rather than @table to show the duration suffixes. Clarify that a duration of 0 disables the associated timeout. * tests/misc/timeout-parameters: Check invalid --kill-delay. * tests/misc/timeout: Check a valid --kill-delay works. * NEWS: Mention the new feature. --- NEWS | 8 ++-- doc/coreutils.texi | 37 +++++++++++---------- src/timeout.c | 70 +++++++++++++++++++++++++--------------- tests/misc/timeout | 4 ++ tests/misc/timeout-parameters | 4 ++ 5 files changed, 75 insertions(+), 48 deletions(-) diff --git a/NEWS b/NEWS index af6953e..707d0ca 100644 --- a/NEWS +++ b/NEWS @@ -17,6 +17,10 @@ GNU coreutils NEWS -*- outline -*- join now accepts the --header option, to treat the first line of each file as a header line to be joined and printed unconditionally. + timeout now accepts the --kill-delay option which sends a kill + signal to the monitored command if it's still running the specified + duration after the initial signal was sent. + who: the "+/-" --mesg (-T) indicator of whether a user/tty is accepting messages could be incorrectly listed as "+", when in fact, the user was not accepting messages (mesg no). Before, who would examine only the @@ -35,10 +39,6 @@ GNU coreutils NEWS -*- outline -*- join -t '' no longer emits an error and instead operates on each line as a whole (even if they contain NUL characters). - timeout now reduces the timeout to 1 second upon forwarding a SIGTERM - if the user has specified SIGKILL as the termination signal, so as - to ensure a timely exit. - * Noteworthy changes in release 8.4 (2010-01-13) [stable] ** Bug fixes diff --git a/doc/coreutils.texi b/doc/coreutils.texi index 34ccf5a..d341a5d 100644 --- a/doc/coreutils.texi +++ b/doc/coreutils.texi @@ -15221,31 +15221,23 @@ might find this idea strange at first. still running after the specified time interval. Synopsis: @example -timeout address@hidden @var{number}[smhd] @var{command} address@hidden@dots{} +timeout address@hidden @var{duration} @var{command} address@hidden@dots{} @end example address@hidden time units address@hidden is an integer followed by an optional unit; the default -is seconds. The units are: - address@hidden @samp address@hidden s -seconds address@hidden m -minutes address@hidden h -hours address@hidden d -days address@hidden table - @var{command} must not be a special built-in utility (@pxref{Special built-in utilities}). -The program accepts the following option. Also see @ref{Common options}. +The program accepts the following options. Also see @ref{Common options}. Options must precede operands. @table @samp address@hidden -k @var{duration} address@hidden address@hidden address@hidden -k address@hidden --kill-delay +Ensure the monitored @var{command} is killed by also sending a @samp{KILL} +signal, after the specified @var{duration}. + @item -s @var{signal} @itemx address@hidden @opindex -s @@ -15253,9 +15245,18 @@ Options must precede operands. Send this @var{signal} to @var{command} on timeout, rather than the default @samp{TERM} signal. @var{signal} may be a name like @samp{HUP} or a number. Also see @xref{Signal specifications}. - @end table address@hidden time units address@hidden is an integer followed by an optional unit: address@hidden address@hidden for seconds (the default) address@hidden for minutes address@hidden for hours address@hidden for days address@hidden display +A duration of 0 disables the associated timeout. + @cindex exit status of @command{timeout} Exit status: diff --git a/src/timeout.c b/src/timeout.c index 8e47327..11709d7 100644 --- a/src/timeout.c +++ b/src/timeout.c @@ -77,9 +77,11 @@ static int timed_out; static int term_signal = SIGTERM; /* same default as kill command. */ static int monitored_pid; static int sigs_to_ignore[NSIG]; /* so monitor can ignore sigs it resends. */ +static unsigned long kill_delay; static struct option const long_options[] = { + {"kill-delay", required_argument, NULL, 'k'}, {"signal", required_argument, NULL, 's'}, {NULL, 0, NULL, 0} }; @@ -108,12 +110,11 @@ cleanup (int sig) sigs_to_ignore[sig] = 0; return; } - if (sig == SIGTERM && term_signal == SIGKILL) + if (kill_delay) { - /* If the user has specified SIGKILL, then in the case - where the monitored doesn't respond to the TERM, don't - wait for the original timeout before sending the KILL. */ - alarm(1); /* XXX: Hopefully the process can cleanup in time. */ + /* Start a new timeout after which we'll send SIGKILL. */ + term_signal = SIGKILL; + alarm(kill_delay); } send_sig (0, sig); if (sig != SIGKILL && sig != SIGCONT) @@ -132,20 +133,19 @@ usage (int status) else { printf (_("\ -Usage: %s [OPTION] NUMBER[SUFFIX] COMMAND [ARG]...\n\ +Usage: %s [OPTION] DURATION COMMAND [ARG]...\n\ or: %s [OPTION]\n"), program_name, program_name); fputs (_("\ -Start COMMAND, and kill it if still running after NUMBER seconds.\n\ -SUFFIX may be `s' for seconds (the default), `m' for minutes,\n\ -`h' for hours or `d' for days.\n\ +Start COMMAND, and kill it if still running after DURATION.\n\ \n\ -"), stdout); - - fputs (_("\ Mandatory arguments to long options are mandatory for short options too.\n\ "), stdout); + fputs (_("\ + -k, --kill-delay=DURATION\n\ + Also send a KILL signal if COMMAND is still running\n\ + this long after the initial signal was sent.\n\ -s, --signal=SIGNAL\n\ specify the signal to be sent on timeout.\n\ SIGNAL may be a name like `HUP' or a number.\n\ @@ -153,6 +153,12 @@ Mandatory arguments to long options are mandatory for short options too.\n\ fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); + + fputs (_("\n\ +DURATION is an integer which may be optionally followed by a suffix:\n\ +`s' for seconds(the default), `m' for minutes, `h' for hours or `d' for days.\n\ +"), stdout); + fputs (_("\n\ If the command times out, then exit with status 124. Otherwise, exit\n\ with the status of COMMAND. If no signal is specified, send the TERM\n\ @@ -202,6 +208,27 @@ apply_time_suffix (unsigned long *x, char suffix_char) return true; } +static unsigned long +parse_duration (const char* str) +{ + unsigned long duration; + char *ep; + + if (xstrtoul (str, &ep, 10, &duration, NULL) + /* Invalid interval. Note 0 disables timeout */ + || (duration > UINT_MAX) + /* Extra chars after the number and an optional s,m,h,d char. */ + || (*ep && *(ep + 1)) + /* Check any suffix char and update timeout based on the suffix. */ + || !apply_time_suffix (&duration, *ep)) + { + error (0, 0, _("invalid time interval %s"), quote (str)); + usage (EXIT_CANCELED); + } + + return duration; +} + static void install_signal_handlers (int sigterm) { @@ -224,7 +251,6 @@ main (int argc, char **argv) unsigned long timeout; char signame[SIG2STR_MAX]; int c; - char *ep; initialize_main (&argc, &argv); set_program_name (argv[0]); @@ -238,10 +264,13 @@ main (int argc, char **argv) parse_long_options (argc, argv, PROGRAM_NAME, PACKAGE_NAME, Version, usage, AUTHORS, (char const *) NULL); - while ((c = getopt_long (argc, argv, "+s:", long_options, NULL)) != -1) + while ((c = getopt_long (argc, argv, "+k:s:", long_options, NULL)) != -1) { switch (c) { + case 'k': + kill_delay = parse_duration (optarg); + break; case 's': term_signal = operand2sig (optarg, signame); if (term_signal == -1) @@ -256,18 +285,7 @@ main (int argc, char **argv) if (argc - optind < 2) usage (EXIT_CANCELED); - if (xstrtoul (argv[optind], &ep, 10, &timeout, NULL) - /* Invalid interval. Note 0 disables timeout */ - || (timeout > UINT_MAX) - /* Extra chars after the number and an optional s,m,h,d char. */ - || (*ep && *(ep + 1)) - /* Check any suffix char and update timeout based on the suffix. */ - || !apply_time_suffix (&timeout, *ep)) - { - error (0, 0, _("invalid time interval %s"), quote (argv[optind])); - usage (EXIT_CANCELED); - } - optind++; + timeout = parse_duration (argv[optind++]); argv += optind; diff --git a/tests/misc/timeout b/tests/misc/timeout index 3bd3af3..1e027d6 100755 --- a/tests/misc/timeout +++ b/tests/misc/timeout @@ -40,6 +40,10 @@ test $? = 2 || fail=1 timeout 1 sleep 10 test $? = 124 || fail=1 +# kill delay +timeout -s0 -k1 1 sleep 10 +test $? = 124 && fail=1 + # Ensure `timeout` is immune to parent's SIGCHLD handler # Use a subshell and an exec to work around a bug in FreeBSD 5.0 /bin/sh. ( diff --git a/tests/misc/timeout-parameters b/tests/misc/timeout-parameters index a55b2d2..8ab17b5 100755 --- a/tests/misc/timeout-parameters +++ b/tests/misc/timeout-parameters @@ -35,6 +35,10 @@ test $? = 125 || fail=1 timeout invalid sleep 0 test $? = 125 || fail=1 +# invalid kill delay +timeout --kill-delay=invalid 1 sleep 0 +test $? = 125 || fail=1 + # invalid timeout suffix timeout 42D sleep 0 test $? = 125 || fail=1 -- 1.6.2.5