new program: timeout
authorPádraig Brady <P@draigBrady.com>
Fri, 28 Mar 2008 11:05:55 +0000 (11:05 +0000)
committerJim Meyering <meyering@redhat.com>
Mon, 2 Jun 2008 12:40:26 +0000 (14:40 +0200)
* AUTHORS: Register as the author.
* NEWS: Mention this change.
* README: Add timeout command to list.
* src/timeout.c: New file.
* src/kill.c (operand2sig): Move function to its own file,
now that timeout.c will also use it.
* src/operand2sig.c (operand2sig): New file, extracted from kill.c.
* src/operand2sig.h (operand2sig): Declare.
* src/Makefile.am (EXTRA_PROGRAMS): Add timeout.
* src/.gitignore: Add timeout binary to list to ignore.
* doc/coreutils.texi (timeout invocation): Add timeout info.
(Signal specifications): New section, also referenced by kill.
* man/Makefile.am (timeout.1): Add dependency.
* man/timeout.x: New file.
* po/POTFILES.in: Add timeout.c and operand2sig.c to list to translate.
* tests/Makefile.am (TESTS): Add the two new tests.
* tests/misc/help-version: Add support for new timeout command.
* tests/misc/invalid-opt: Add support for new timeout command.
* tests/misc/timeout: New file: check basic timeout operation.
* tests/misc/timeout-parameters: New file: check invalid parameter
combinations.

19 files changed:
AUTHORS
NEWS
README
configure.ac
doc/coreutils.texi
man/Makefile.am
man/timeout.x [new file with mode: 0644]
po/POTFILES.in
src/.gitignore
src/Makefile.am
src/kill.c
src/operand2sig.c [new file with mode: 0644]
src/operand2sig.h [new file with mode: 0644]
src/timeout.c [new file with mode: 0644]
tests/Makefile.am
tests/misc/help-version
tests/misc/invalid-opt
tests/misc/timeout [new file with mode: 0755]
tests/misc/timeout-parameters [new file with mode: 0755]

diff --git a/AUTHORS b/AUTHORS
index 3e60d754691933c1e8ffe50e298d9efefd1c19e9..36d0bd7871bf61a266c8396343d488715faa9eed 100644 (file)
--- a/AUTHORS
+++ b/AUTHORS
@@ -84,6 +84,7 @@ tac: Jay Lepreau, David MacKenzie
 tail: Paul Rubin, David MacKenzie, Ian Lance Taylor, Jim Meyering
 tee: Mike Parker, Richard M. Stallman, David MacKenzie
 test: Kevin Braunsdorf, Matthew Bradburn
+timeout: Pádraig Brady
 touch: Paul Rubin, Arnold Robbins, Jim Kingdon, David MacKenzie, Randy Smith
 tr: Jim Meyering
 true: Jim Meyering
diff --git a/NEWS b/NEWS
index b12631b1e8e9da3f634be40f83f04ee9b96143cb..5d0b77ef34a1257f459dedf2ec61da0b8f064983 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,10 @@ GNU coreutils NEWS                                    -*- outline -*-
 
 * Noteworthy changes in release 7.0 (????-??-??) [beta]
 
+** New programs
+
+  timeout: Run a command with bounded time.
+
 ** New features
 
   md5sum now accepts the new option, --quiet, to suppress the printing of
diff --git a/README b/README
index 7c1883d48245d9dbe0857a532f1baa5d2904573a..d1a2cec3f5879c7fc3fefdc22b00b50b88369686 100644 (file)
--- a/README
+++ b/README
@@ -13,8 +13,8 @@ The programs that can be built with this package are:
   link ln logname ls md5sum mkdir mkfifo mknod mktemp mv nice nl nohup
   od paste pathchk pinky pr printenv printf ptx pwd readlink rm rmdir
   runcon seq sha1sum sha224sum sha256sum sha384sum sha512sum shred shuf
-  sleep sort split stat stty su sum sync tac tail tee test touch tr true
-  tsort tty uname unexpand uniq unlink uptime users vdir wc who whoami yes
+  sleep sort split stat stty su sum sync tac tail tee test timeout touch tr
+  true tsort tty uname unexpand uniq unlink uptime users vdir wc who whoami yes
 
 See the file NEWS for a list of major changes in the current release.
 
index 3c0e100c7fc8979c5421a6cd20e11a50a692d0c5..c5a7fcafff028f0511a416fe504055f61edd73cb 100644 (file)
@@ -32,7 +32,7 @@ AC_CONFIG_SRCDIR(src/ls.c)
 AC_CONFIG_AUX_DIR(build-aux)
 AC_CONFIG_HEADERS([lib/config.h:lib/config.hin])
 
-AM_INIT_AUTOMAKE([1.10.1 dist-lzma])
+AM_INIT_AUTOMAKE([1.10])
 
 AC_PROG_CC_STDC
 AM_PROG_CC_C_O
index 2a19db1e4808d710b2b3bb2db9beca48191fb392..8103ec2e70c00a7a059aa1f225246bda3dc5c1df 100644 (file)
 * tail: (coreutils)tail invocation.             Output the last part of files.
 * tee: (coreutils)tee invocation.               Redirect to multiple files.
 * test: (coreutils)test invocation.             File/string tests.
+* timeout: (coreutils)timeout invocation.       Run with time limit.
 * touch: (coreutils)touch invocation.           Change file timestamps.
 * tr: (coreutils)tr invocation.                 Translate characters.
 * true: (coreutils)true invocation.             Do nothing, successfully.
@@ -192,7 +193,7 @@ Free Documentation License''.
 * Working context::                    pwd stty printenv tty
 * User information::                   id logname whoami groups users who
 * System context::                     date uname hostname hostid
-* Modified command invocation::        chroot env nice nohup su
+* Modified command invocation::        chroot env nice nohup su timeout
 * Process control::                    kill
 * Delaying::                           sleep
 * Numeric operations::                 factor seq
@@ -210,6 +211,7 @@ Common Options
 * Exit status::                 Indicating program success or failure.
 * Backup options::              Backup options
 * Block size::                  Block size
+* Signal specifications::       Specifying signals
 * Disambiguating names and IDs:: chgrp and chown owner and group syntax
 * Random sources::              Sources of random data
 * Target directory::            Target directory
@@ -423,6 +425,7 @@ Modified command invocation
 * nice invocation::              Run a command with modified niceness
 * nohup invocation::             Run a command immune to hangups
 * su invocation::                Run a command with substitute user and group ID
+* timeout invocation::           Run a command with a time limit
 
 Process control
 
@@ -650,6 +653,7 @@ name.
 * Exit status::                 Indicating program success or failure.
 * Backup options::              -b -S, in some programs.
 * Block size::                  BLOCK_SIZE and --block-size, in some programs.
+* Signal specifications::       Specifying signals using the --signal option.
 * Disambiguating names and IDs:: chgrp and chown owner and group syntax
 * Random sources::              --random-source, in some programs.
 * Target directory::            Specifying a target directory, in some programs.
@@ -681,8 +685,8 @@ other exit status values and a few associate different
 meanings with the values @samp{0} and @samp{1}.
 Here are some of the exceptions:
 @command{chroot}, @command{env}, @command{expr},
-@command{nice}, @command{nohup}, @command{printenv},
-@command{sort}, @command{su}, @command{test}, @command{tty}.
+@command{nice}, @command{nohup}, @command{printenv}, @command{sort},
+@command{su}, @command{test}, @command{timeout}, @command{tty}.
 
 
 @node Backup options
@@ -931,6 +935,95 @@ set.  The @option{-h} or @option{--human-readable} option is equivalent to
 @option{--block-size=human-readable}.  The @option{--si} option is
 equivalent to @option{--block-size=si}.
 
+@node Signal specifications
+@section Signal specifications
+@cindex signals, specifying
+
+A @var{signal} may be a signal name like @samp{HUP}, or a signal
+number like @samp{1}, or an exit status of a process terminated by the
+signal.  A signal name can be given in canonical form or prefixed by
+@samp{SIG}.  The case of the letters is ignored. The following signal names
+and numbers are supported on all @acronym{POSIX} compliant systems:
+
+@table @samp
+@item HUP
+1.  Hangup.
+@item INT
+2.  Terminal interrupt.
+@item QUIT
+3.  Terminal quit.
+@item ABRT
+6.  Process abort.
+@item KILL
+9.  Kill (cannot be caught or ignored).
+@item ALRM
+14.  Alarm Clock.
+@item TERM
+15.  Termination.
+@end table
+
+@noindent
+Other supported signal names have system-dependent corresponding
+numbers.  All systems conforming to @acronym{POSIX} 1003.1-2001 also
+support the following signals:
+
+@table @samp
+@item BUS
+Access to an undefined portion of a memory object.
+@item CHLD
+Child process terminated, stopped, or continued.
+@item CONT
+Continue executing, if stopped.
+@item FPE
+Erroneous arithmetic operation.
+@item ILL
+Illegal Instruction.
+@item PIPE
+Write on a pipe with no one to read it.
+@item SEGV
+Invalid memory reference.
+@item STOP
+Stop executing (cannot be caught or ignored).
+@item TSTP
+Terminal stop.
+@item TTIN
+Background process attempting read.
+@item TTOU
+Background process attempting write.
+@item URG
+High bandwidth data is available at a socket.
+@item USR1
+User-defined signal 1.
+@item USR2
+User-defined signal 2.
+@end table
+
+@noindent
+@acronym{POSIX} 1003.1-2001 systems that support the @acronym{XSI} extension
+also support the following signals:
+
+@table @samp
+@item POLL
+Pollable event.
+@item PROF
+Profiling timer expired.
+@item SYS
+Bad system call.
+@item TRAP
+Trace/breakpoint trap.
+@item VTALRM
+Virtual timer expired.
+@item XCPU
+CPU time limit exceeded.
+@item XFSZ
+File size limit exceeded.
+@end table
+
+@noindent
+@acronym{POSIX} 1003.1-2001 systems that support the @acronym{XRT} extension
+also support at least eight real-time signals called @samp{RTMIN},
+@samp{RTMIN+1}, @dots{}, @samp{RTMAX-1}, @samp{RTMAX}.
+
 @node Disambiguating names and IDs
 @section chown and chgrp: Disambiguating user names and IDs
 @cindex user names, disambiguating
@@ -13379,6 +13472,7 @@ user, etc.
 * nice invocation::             Modify niceness.
 * nohup invocation::            Immunize to hangups.
 * su invocation::               Modify user and group ID.
+* timeout invocation::          Run with time limit.
 @end menu
 
 
@@ -13884,6 +13978,64 @@ used to supporting the bosses and sysadmins in whatever they do, you
 might find this idea strange at first.
 
 
+@node timeout invocation
+@section @command{timeout}: Run a command with a time limit
+
+@pindex timeout
+@cindex time limit
+@cindex run commands with bounded time
+
+@command{timeout} runs the given @var{command} and kills it if it is
+still running after the specified time interval.  Synopsis:
+
+@example
+timeout [@var{option}] @var{number}[smhd] @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}.
+Options must precede operands.
+
+@table @samp
+@item -s @var{signal}
+@itemx --signal=@var{signal}
+@opindex -s
+@opindex --signal
+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 exit status of @command{timeout}
+Exit status:
+
+@display
+110 if @var{command} times out
+125 if @command{timeout} itself fails
+126 if @var{command} is found but cannot be invoked
+127 if @var{command} cannot be found
+the exit status of @var{command} otherwise
+@end display
+
+
 @node Process control
 @chapter Process control
 
@@ -13962,88 +14114,8 @@ number like @samp{1}, or an exit status of a process terminated by the
 signal.  A signal name can be given in canonical form or prefixed by
 @samp{SIG}.  The case of the letters is ignored, except for the
 @option{-@var{signal}} option which must use upper case to avoid
-ambiguity with lower case option letters.  The following signal names
-and numbers are supported on all @acronym{POSIX} compliant systems:
-
-@table @samp
-@item HUP
-1.  Hangup.
-@item INT
-2.  Terminal interrupt.
-@item QUIT
-3.  Terminal quit.
-@item ABRT
-6.  Process abort.
-@item KILL
-9.  Kill (cannot be caught or ignored).
-@item ALRM
-14.  Alarm Clock.
-@item TERM
-15.  Termination.
-@end table
-
-@noindent
-Other supported signal names have system-dependent corresponding
-numbers.  All systems conforming to @acronym{POSIX} 1003.1-2001 also
-support the following signals:
-
-@table @samp
-@item BUS
-Access to an undefined portion of a memory object.
-@item CHLD
-Child process terminated, stopped, or continued.
-@item CONT
-Continue executing, if stopped.
-@item FPE
-Erroneous arithmetic operation.
-@item ILL
-Illegal Instruction.
-@item PIPE
-Write on a pipe with no one to read it.
-@item SEGV
-Invalid memory reference.
-@item STOP
-Stop executing (cannot be caught or ignored).
-@item TSTP
-Terminal stop.
-@item TTIN
-Background process attempting read.
-@item TTOU
-Background process attempting write.
-@item URG
-High bandwidth data is available at a socket.
-@item USR1
-User-defined signal 1.
-@item USR2
-User-defined signal 2.
-@end table
-
-@noindent
-@acronym{POSIX} 1003.1-2001 systems that support the @acronym{XSI} extension
-also support the following signals:
-
-@table @samp
-@item POLL
-Pollable event.
-@item PROF
-Profiling timer expired.
-@item SYS
-Bad system call.
-@item TRAP
-Trace/breakpoint trap.
-@item VTALRM
-Virtual timer expired.
-@item XCPU
-CPU time limit exceeded.
-@item XFSZ
-File size limit exceeded.
-@end table
-
-@noindent
-@acronym{POSIX} 1003.1-2001 systems that support the @acronym{XRT} extension
-also support at least eight real-time signals called @samp{RTMIN},
-@samp{RTMIN+1}, @dots{}, @samp{RTMAX-1}, @samp{RTMAX}.
-
+ambiguity with lower case option letters.  For a list of supported
+signal names and numbers see @xref{Signal specifications}.
 
 @node Delaying
 @chapter Delaying
index 7164b3b2374db8299d2b88e63ca0778b96b7a3cc..3fa22609c6a98179a0bf743b3885375a50d138bb 100644 (file)
@@ -113,6 +113,7 @@ tac.1:              $(common_dep)   $(srcdir)/tac.x         ../src/tac.c
 tail.1:                $(common_dep)   $(srcdir)/tail.x        ../src/tail.c
 tee.1:         $(common_dep)   $(srcdir)/tee.x         ../src/tee.c
 test.1:                $(common_dep)   $(srcdir)/test.x        ../src/test.c
+timeout.1:     $(common_dep)   $(srcdir)/timeout.x     ../src/timeout.c
 touch.1:       $(common_dep)   $(srcdir)/touch.x       ../src/touch.c
 tr.1:          $(common_dep)   $(srcdir)/tr.x          ../src/tr.c
 true.1:                $(common_dep)   $(srcdir)/true.x        ../src/true.c
diff --git a/man/timeout.x b/man/timeout.x
new file mode 100644 (file)
index 0000000..19ebed3
--- /dev/null
@@ -0,0 +1,6 @@
+[NAME]
+timeout \- run a command with a time limit
+[DESCRIPTION]
+.\" Add any additional description here
+[SEE ALSO]
+kill(1)
index a8b2e9f2d3337c49829885daff5d776f89879e85..2c55552e38136020714d82caa22ca80c5aa01834 100644 (file)
@@ -84,6 +84,7 @@ src/nice.c
 src/nl.c
 src/nohup.c
 src/od.c
+src/operand2sig.c
 src/paste.c
 src/pathchk.c
 src/pinky.c
@@ -115,6 +116,7 @@ src/tac.c
 src/tail.c
 src/tee.c
 src/test.c
+src/timeout.c
 src/touch.c
 src/tr.c
 src/true.c
index 437dd30d41ef2396b154d75e7fede8998daa2f9a..cee550bc5e2ab4cd772e371e5a3953741656b85e 100644 (file)
@@ -87,6 +87,7 @@ tac
 tail
 tee
 test
+timeout
 touch
 tr
 true
index 14906bc09e4e39f66a963f35640b0b0309ea0edd..97dd33b6d00cfb554716a313d92f8ee0038f5835 100644 (file)
@@ -39,7 +39,7 @@ EXTRA_PROGRAMS = \
   basename date dirname echo env expr factor false \
   id kill logname pathchk printenv printf pwd \
   runcon seq sleep tee \
-  test true tty whoami yes \
+  test timeout true tty whoami yes \
   base64
 
 bin_PROGRAMS = $(OPTIONAL_BIN_PROGS)
@@ -54,6 +54,7 @@ noinst_HEADERS = \
   fs.h \
   group-list.h \
   ls.h \
+  operand2sig.h \
   prog-fprintf.h \
   remove.h \
   system.h \
@@ -233,6 +234,8 @@ ln_SOURCES = ln.c
 ls_SOURCES = ls.c ls-ls.c
 chown_SOURCES = chown.c chown-core.c
 chgrp_SOURCES = chgrp.c chown-core.c
+kill_SOURCES = kill.c operand2sig.c
+timeout_SOURCES = timeout.c operand2sig.c
 
 mv_SOURCES = mv.c remove.c $(copy_sources)
 rm_SOURCES = rm.c remove.c
index d87c7d0e6c05ce32025ff795351e67bdd96af3a2..bd5d9b291bdc98e7e429213ef560c4bedf0558d9 100644 (file)
@@ -35,6 +35,7 @@
 #include "system.h"
 #include "error.h"
 #include "sig2str.h"
+#include "operand2sig.h"
 
 /* The official name of this program (e.g., no `g' prefix).  */
 #define PROGRAM_NAME "kill"
@@ -118,52 +119,6 @@ PID is an integer; if negative it identifies a process group.\n\
   exit (status);
 }
 \f
-/* Convert OPERAND to a signal number with printable representation SIGNAME.
-   Return the signal number, or -1 if unsuccessful.  */
-
-static int
-operand2sig (char const *operand, char *signame)
-{
-  int signum;
-
-  if (ISDIGIT (*operand))
-    {
-      char *endp;
-      long int l = (errno = 0, strtol (operand, &endp, 10));
-      int i = l;
-      signum = (operand == endp || *endp || errno || i != l ? -1
-               : WIFSIGNALED (i) ? WTERMSIG (i)
-               : i);
-    }
-  else
-    {
-      /* Convert signal to upper case in the C locale, not in the
-        current locale.  Don't assume ASCII; it might be EBCDIC.  */
-      char *upcased = xstrdup (operand);
-      char *p;
-      for (p = upcased; *p; p++)
-       if (strchr ("abcdefghijklmnopqrstuvwxyz", *p))
-         *p += 'A' - 'a';
-
-      /* Look for the signal name, possibly prefixed by "SIG",
-        and possibly lowercased.  */
-      if (! (str2sig (upcased, &signum) == 0
-            || (upcased[0] == 'S' && upcased[1] == 'I' && upcased[2] == 'G'
-                && str2sig (upcased + 3, &signum) == 0)))
-       signum = -1;
-
-      free (upcased);
-    }
-
-  if (signum < 0 || sig2str (signum, signame) != 0)
-    {
-      error (0, 0, _("%s: invalid signal"), operand);
-      return -1;
-    }
-
-  return signum;
-}
-\f
 /* Print a row of `kill -t' output.  NUM_WIDTH is the maximum signal
    number width, and SIGNUM is the signal number to print.  The
    maximum name width is NAME_WIDTH, and SIGNAME is the name to print.  */
diff --git a/src/operand2sig.c b/src/operand2sig.c
new file mode 100644 (file)
index 0000000..228db40
--- /dev/null
@@ -0,0 +1,84 @@
+/* operand2sig.c -- common function for parsing signal specifications
+   Copyright (C) 2008 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+/* Extracted from kill.c/timeout.c by Pádraig Brady.
+   FIXME: Move this to gnulib/str2sig.c */
+
+
+/* Convert OPERAND to a signal number with printable representation SIGNAME.
+   Return the signal number, or -1 if unsuccessful.  */
+
+#include <config.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <signal.h>
+
+#if HAVE_SYS_WAIT_H
+# include <sys/wait.h>
+#endif
+#ifndef WIFSIGNALED
+# define WIFSIGNALED(s) (((s) & 0xFFFF) - 1 < (unsigned int) 0xFF)
+#endif
+#ifndef WTERMSIG
+# define WTERMSIG(s) ((s) & 0x7F)
+#endif
+
+#include "system.h"
+#include "error.h"
+#include "sig2str.h"
+#include "operand2sig.h"
+
+extern int
+operand2sig (char const *operand, char *signame)
+{
+  int signum;
+
+  if (ISDIGIT (*operand))
+    {
+      char *endp;
+      long int l = (errno = 0, strtol (operand, &endp, 10));
+      int i = l;
+      signum = (operand == endp || *endp || errno || i != l ? -1
+                : WIFSIGNALED (i) ? WTERMSIG (i) : i);
+    }
+  else
+    {
+      /* Convert signal to upper case in the C locale, not in the
+         current locale.  Don't assume ASCII; it might be EBCDIC.  */
+      char *upcased = xstrdup (operand);
+      char *p;
+      for (p = upcased; *p; p++)
+        if (strchr ("abcdefghijklmnopqrstuvwxyz", *p))
+          *p += 'A' - 'a';
+
+      /* Look for the signal name, possibly prefixed by "SIG",
+         and possibly lowercased.  */
+      if (!(str2sig (upcased, &signum) == 0
+            || (upcased[0] == 'S' && upcased[1] == 'I' && upcased[2] == 'G'
+                && str2sig (upcased + 3, &signum) == 0)))
+        signum = -1;
+
+      free (upcased);
+    }
+
+  if (signum < 0 || sig2str (signum, signame) != 0)
+    {
+      error (0, 0, _("%s: invalid signal"), operand);
+      return -1;
+    }
+
+  return signum;
+}
diff --git a/src/operand2sig.h b/src/operand2sig.h
new file mode 100644 (file)
index 0000000..165e6cb
--- /dev/null
@@ -0,0 +1,18 @@
+/* operand2sig.h -- prototype for signal specification function
+
+   Copyright (C) 2008 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+extern int operand2sig (char const *operand, char *signame);
diff --git a/src/timeout.c b/src/timeout.c
new file mode 100644 (file)
index 0000000..19ec849
--- /dev/null
@@ -0,0 +1,335 @@
+/* timeout -- run a command with bounded time
+   Copyright (C) 2008 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+
+/* timeout - Start a command, and kill it if the specified timeout expires
+
+   We try to behave like a shell starting a single (foreground) job,
+   and will kill the job if we receive the alarm signal we setup.
+   The exit status of the job is returned, or one of these errors:
+     ETIMEDOUT          110      job timed out
+     ECANCELED          125      internal error
+     EXIT_CANNOT_INVOKE 126      error executing job
+     EXIT_ENOENT        127      couldn't find job to exec
+
+   Caveats:
+     If user specifies the KILL (9) signal is to be sent on timeout,
+     the monitor is killed and so exits with 128+9 rather than ETIMEDOUT.
+
+     If you start a command in the background, which reads from the tty
+     and so is immediately sent SIGTTIN to stop, then the timeout
+     process will ignore this so it can timeout the command as expected.
+     This can be seen with `timeout 10 dd&` for example.
+     However if one brings this group to the foreground with the `fg`
+     command before the timer expires, the command will remain
+     in the sTop state as the shell doesn't send a SIGCONT
+     because the timeout process (group leader) is already running.
+     To get the command running again one can Ctrl-Z, and do fg again.
+     Note one can Ctrl-C the whole job when in this state.
+     I think this could be fixed but I'm not sure the extra
+     complication is justified for this scenario.
+
+   Written by Pádraig Brady.  */
+
+#include <config.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <signal.h>
+
+#if HAVE_SYS_WAIT_H
+# include <sys/wait.h>
+#endif
+#ifndef WIFSIGNALED
+# define WIFSIGNALED(s) (((s) & 0xFFFF) - 1 < (unsigned int) 0xFF)
+#endif
+#ifndef WTERMSIG
+# define WTERMSIG(s) ((s) & 0x7F)
+#endif
+
+#include "system.h"
+#include "xstrtol.h"
+#include "sig2str.h"
+#include "operand2sig.h"
+#include "cloexec.h"
+#include "error.h"
+#include "long-options.h"
+#include "quote.h"
+
+#define PROGRAM_NAME "timeout"
+
+#define AUTHORS proper_name_utf8 ("Padraig Brady", "P\303\241draig Brady")
+
+/* Internal failure.  */
+#ifndef ECANCELED
+#define ECANCELED 125
+#endif
+
+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 char *program_name;
+
+static struct option const long_options[] = {
+  {"signal", required_argument, NULL, 's'},
+  {NULL, 0, NULL, 0}
+};
+
+/* send sig to group but not ourselves.
+ * FIXME: Is there a better way to achieve this?  */
+static int
+send_sig (int where, int sig)
+{
+  sigs_to_ignore[sig] = 1;
+  return kill (where, sig);
+}
+
+static void
+cleanup (int sig)
+{
+  if (sig == SIGALRM)
+    {
+      timed_out = 1;
+      sig = term_signal;
+    }
+  if (monitored_pid)
+    {
+      if (sigs_to_ignore[sig])
+        {
+          sigs_to_ignore[sig] = 0;
+          return;
+        }
+      send_sig (0, sig);
+      if (sig != SIGKILL && sig != SIGCONT)
+        send_sig (0, SIGCONT);
+    }
+  else /* we're the child or the child is not exec'd yet.  */
+    _exit (128 + sig);
+}
+
+static void
+usage (int status)
+{
+  if (status != EXIT_SUCCESS)
+    fprintf (stderr, _("Try `%s --help' for more information.\n"),
+             program_name);
+  else
+    {
+      printf (_("\
+Usage: %s [OPTION] NUMBER[SUFFIX] 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\
+\n\
+"), stdout);
+
+      fputs (_("\
+Mandatory arguments to long options are mandatory for short options too.\n\
+"), stdout);
+      fputs (_("\
+  -s, --signal=SIGNAL\n\
+                   specify the signal to be sent on timeout.\n\
+                   SIGNAL may be a name like `HUP' or a number.\n\
+                   See `kill -l` for a list of signals\n"), stdout);
+
+      fputs (HELP_OPTION_DESCRIPTION, stdout);
+      fputs (VERSION_OPTION_DESCRIPTION, stdout);
+      fputs (_("\n\
+If the command times out, then we exit with status ETIMEDOUT,\n\
+otherwise the normal exit status of the command is returned.\n\
+If no signal is specified, the TERM signal is sent. The TERM signal\n\
+will kill processes which do not catch this signal. For other processes,\n\
+it may be necessary to use the KILL (9) signal, since this signal cannot\n\
+be caught.\n"), stdout);
+      emit_bug_reporting_address ();
+    }
+  exit (status);
+}
+
+/* Given an integer value *X, and a suffix character, SUFFIX_CHAR,
+   scale *X by the multiplier implied by SUFFIX_CHAR.  SUFFIX_CHAR may
+   be the NUL byte or `s' to denote seconds, `m' for minutes, `h' for
+   hours, or `d' for days.  If SUFFIX_CHAR is invalid, don't modify *X
+   and return false.  If *X would overflow, don't modify *X and return false.
+   Otherwise return true.  */
+
+static bool
+apply_time_suffix (unsigned int *x, char suffix_char)
+{
+  int multiplier=1;
+
+  switch (suffix_char)
+    {
+    case 0:
+    case 's':
+      return true;
+    case 'd':
+      multiplier *= 24;
+    case 'h':
+      multiplier *= 60;
+    case 'm':
+      multiplier *= 60;
+      break;
+    default:
+      return false;
+    }
+
+  if (*x > UINT_MAX / multiplier)
+      return false;
+
+  *x *= multiplier;
+
+  return true;
+}
+
+static void
+install_signal_handlers (void)
+{
+  struct sigaction sa;
+  sigemptyset(&sa.sa_mask);  /* Allow concurrent calls to handler */
+  sa.sa_handler = cleanup;
+  sa.sa_flags = SA_RESTART;  /* restart syscalls (like wait() below) */
+
+  sigaction (SIGALRM, &sa, NULL); /* our timeout.  */
+  sigaction (SIGINT, &sa, NULL);  /* Ctrl-C at terminal for example.  */
+  sigaction (SIGQUIT, &sa, NULL); /* Ctrl-\ at terminal for example.  */
+  sigaction (SIGTERM, &sa, NULL); /* if we're killed, stop monitored proc.  */
+  sigaction (SIGHUP, &sa, NULL);  /* terminal closed for example.  */
+}
+
+int
+main (int argc, char **argv)
+{
+  unsigned long timeout;
+  char signame[SIG2STR_MAX];
+  int c;
+  char *ep;
+
+  initialize_main (&argc, &argv);
+  program_name = argv[0];
+  setlocale (LC_ALL, "");
+  bindtextdomain (PACKAGE, LOCALEDIR);
+  textdomain (PACKAGE);
+
+  initialize_exit_failure (ECANCELED);
+  atexit (close_stdout);
+
+  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)
+    {
+      switch (c)
+        {
+        case 's':
+          term_signal = operand2sig (optarg, signame);
+          if (term_signal == -1)
+            usage (ECANCELED);
+          break;
+        default:
+          usage (ECANCELED);
+          break;
+        }
+    }
+
+  if (argc - optind < 2)
+    usage (ECANCELED);
+
+  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 ((unsigned int *) &timeout, *ep))
+    {
+      error (0, 0, _("invalid time interval %s"), quote (argv[optind]));
+      usage (ECANCELED);
+    }
+  optind++;
+
+  argc -= optind;
+  argv += optind;
+
+  /* Ensure we're in our own group so all subprocesses can be killed.
+   * Note we don't put the just child in a separate group as
+   * then we would need to worry about foreground and background groups
+   * and propagating signals between them.  */
+  setpgid (0, 0);
+
+  /* Setup handlers before fork() so that we
+   * handle any signals caused by child, without races.  */
+  install_signal_handlers ();
+  signal (SIGTTIN, SIG_IGN);    /* don't sTop if background child needs tty.  */
+  signal (SIGTTOU, SIG_IGN);    /* don't sTop if background child needs tty.  */
+
+  monitored_pid = fork ();
+  if (monitored_pid == -1)
+    {
+      perror ("fork");
+      return errno;
+    }
+  else if (monitored_pid == 0)
+    {                           /* child */
+      int exit_status;
+
+      /* exec doesn't reset SIG_IGN -> SIG_DFL.  */
+      signal (SIGTTIN, SIG_DFL);
+      signal (SIGTTOU, SIG_DFL);
+
+      execvp (argv[0], argv);   /* FIXME: should we use "sh -c" ... here?  */
+
+      /* exit like sh, env, nohup, ...  */
+      exit_status = (errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE);
+      perror (argv[0]);
+      return exit_status;
+    }
+  else
+    {
+      int status;
+
+      alarm ((unsigned int) timeout);
+
+      /* We're just waiting for a single process here, so wait() suffices.
+       * Note the signal() calls above on linux and BSD at least, essentially
+       * call the lower level sigaction() with the SA_RESTART flag set, which
+       * ensures the following wait call will only return if the child exits,
+       * not on this process receiving a signal. Also we're not passing
+       * WUNTRACED | WCONTINUED to a waitpid() call and so will not get
+       * indication that the child has stopped or continued.  */
+      wait (&status);
+
+      if (WIFEXITED (status))
+        status = WEXITSTATUS (status);
+      else if (WIFSIGNALED (status))
+        status = WTERMSIG (status) + 128;     /* what sh does at least.  */
+
+      if (timed_out)
+        return ETIMEDOUT;
+      else
+        return status;
+    }
+}
+
+/*
+ * Local variables:
+ *  indent-tabs-mode: nil
+ * End:
+ */
index 1b7dcfa64ea914c9237d28130b99cc68746c5b58..e5042b96bd0c8973160d75ac66c0b174eee8a7a5 100644 (file)
@@ -209,6 +209,8 @@ TESTS =                                             \
   misc/tee                                     \
   misc/tee-dash                                        \
   misc/test-diag                               \
+  misc/timeout                                 \
+  misc/timeout-parameters                      \
   misc/tr                                      \
   misc/tsort                                   \
   misc/tty-eof                                 \
index 1dcf96f92b94641c0bc14609280507b81df5e7b6..ff0133a223b1595f09cfe73af9faaa7ca8af6a85 100755 (executable)
@@ -28,6 +28,7 @@ export SHELL
 . $srcdir/test-lib.sh
 
 expected_failure_status_nohup=127
+expected_failure_status_timeout=125
 expected_failure_status_printenv=2
 expected_failure_status_tty=3
 expected_failure_status_sort=2
@@ -146,6 +147,7 @@ printf_args=foo
 seq_args=10
 sleep_args=0
 su_args=--version
+timeout_args=--version
 
 # I'd rather not run sync, since it spins up disks that I've
 # deliberately caused to spin down (but not unmounted).
index 569a027e740317e6ae15e2727281b0af28b33730..cbd41cae11196a241d85688403d0843f5b2ff4cd 100755 (executable)
@@ -33,6 +33,7 @@ my %exit_status =
     nohup => 127,
     sort => 2,
     test => 0,
+    timeout => 125,
     true => 0,
     tty => 2,
     printf => 0,
diff --git a/tests/misc/timeout b/tests/misc/timeout
new file mode 100755 (executable)
index 0000000..7f52f6e
--- /dev/null
@@ -0,0 +1,44 @@
+#!/bin/sh
+# Validate timeout basic operation
+
+# Copyright (C) 2008 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+if test "$VERBOSE" = yes; then
+  set -x
+  timeout --version
+fi
+
+. $srcdir/test-lib.sh
+
+fail=0
+
+# no timeout
+timeout 1 true || fail=1
+
+# no timeout (suffix check)
+timeout 1d true || fail=1
+
+# disabled timeout
+timeout 0 true || fail=1
+
+# exit status propagation
+timeout 1 false && fail=1
+
+# timeout
+timeout 1 sleep 2
+test $? = 110 || fail=1
+
+(exit $fail); exit $fail
diff --git a/tests/misc/timeout-parameters b/tests/misc/timeout-parameters
new file mode 100755 (executable)
index 0000000..091fbd7
--- /dev/null
@@ -0,0 +1,55 @@
+#!/bin/sh
+# Validate timeout parameter combinations
+
+# Copyright (C) 2008 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+if test "$VERBOSE" = yes; then
+  set -x
+  timeout --version
+fi
+
+. $srcdir/test-lib.sh
+
+fail=0
+
+# --help and --version must be specified alone
+timeout --help --version && fail=1
+
+# invalid timeout
+timeout invalid sleep 0 && fail=1
+
+# invalid timeout suffix
+timeout 42D sleep 0 && fail=1
+
+# timeout overflow
+timeout 4294967296 sleep 0 && fail=1
+
+# timeout overflow
+timeout 49711d sleep 0 && fail=1
+
+# invalid signal spec
+timeout --signal=invalid sleep 0 && fail=1
+
+# invalid signal number
+timeout --signal=128 sleep 0 && fail=1
+
+# invalid command
+timeout 1 . && fail=1
+
+# non existant command
+timeout 1 ... && fail=1
+
+(exit $fail); exit $fail