I've added a --kill-delay option in the attached,
so you should get the operation you require like:

  timeout -k1 1h command

That gives a 1 second grace period after a TERM signal is sent
(either from the timer or from explicitly terminating the timeout command)
before the command is killed.

cheers,
Pádraig.
>From 33398b0c563db9de27ac99d47a714dde98f61e80 Mon Sep 17 00:00:00 2001
From: =?utf-8?q?P=C3=A1draig=20Brady?= <p...@draigbrady.com>
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 [...@var{option}] @var{number}[smhd] @var{command} [...@var{arg}]@dots{}
+timeout [...@var{option}] @var{duration} @var{command} [...@var{arg}]@dots{}
 @end example
 
-...@cindex time units
-...@var{number} is an integer followed by an optional unit; the default
-is seconds.  The units are:
-
-...@table @samp
-...@item s
-seconds
-...@item m
-minutes
-...@item h
-hours
-...@item d
-days
-...@end 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
+...@item -k @var{duration}
+...@itemx --kill-del...@var{duration}
+...@opindex -k
+...@opindex --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 --sign...@var{signal}
 @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
 
+...@cindex time units
+...@var{duration} is an integer followed by an optional unit:
+...@display
+...@samp{s} for seconds (the default)
+...@samp{m} for minutes
+...@samp{h} for hours
+...@samp{d} for days
+...@end 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

Reply via email to