bug-coreutils
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH] Add timeout utility


From: Pádraig Brady
Subject: [PATCH] Add timeout utility
Date: Wed, 2 Apr 2008 00:32:47 +0100
User-agent: Thunderbird 2.0.0.6 (X11/20071008)

attached
>From e2c2d212bfea29540bb433bb8d00e1e9e9fd3ff6 Mon Sep 17 00:00:00 2001
From: =?utf-8?q?P=C3=A1draig=20Brady?= <address@hidden>
Date: Fri, 28 Mar 2008 11:05:55 +0000
Subject: [PATCH] Add new program: timeout

* AUTHORS: Register as the author
* NEWS: Mention this change
* README: Add timeout command to list
* src/truncate.c: New command
* src/Makefile.am: Add timeout command to list to build
* doc/coreutils.texi (timeout invocation): Add timeout info
* man/Makefile.am: Add timeout man page to list to build
* man/truncate.x: Add timeout man page template
* po/POTFILES.in: Add timeout to list to translate
* tests/misc/Makefile.am: Add timeout tests
* tests/misc/help-version: Add support for new timeout command
* tests/misc/timeout: check basic timeout operation
* tests/misc/timeout-parameters: check invalid parameter combinations

Signed-off-by: Pádraig Brady <address@hidden>
---
 AUTHORS                       |    1 +
 NEWS                          |    4 +
 README                        |    4 +-
 doc/coreutils.texi            |  242 +++++++++++++++++----------
 man/Makefile.am               |    1 +
 man/timeout.x                 |    6 +
 po/POTFILES.in                |    1 +
 src/Makefile.am               |    2 +-
 src/timeout.c                 |  370 +++++++++++++++++++++++++++++++++++++++++
 tests/misc/Makefile.am        |    2 +
 tests/misc/help-version       |    2 +
 tests/misc/timeout            |   41 +++++
 tests/misc/timeout-parameters |   58 +++++++
 13 files changed, 646 insertions(+), 88 deletions(-)
 create mode 100644 man/timeout.x
 create mode 100644 src/timeout.c
 create mode 100755 tests/misc/timeout
 create mode 100755 tests/misc/timeout-parameters

diff --git a/AUTHORS b/AUTHORS
index 807857f..87553fd 100644
--- 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: Padraig 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 5250ed8..a326991 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,10 @@ GNU coreutils NEWS                                    -*- 
outline -*-
 
 * Noteworthy changes in release 6.?? (2008-??-??) [stable]
 
+** New programs
+
+  timeout: Run a command with bounded time.
+
 ** Bug fixes
 
   configure --enable-no-install-program=groups now works.
diff --git a/README b/README
index 7a608f4..6159dfd 100644
--- 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.
 
diff --git a/doc/coreutils.texi b/doc/coreutils.texi
index f161c4d..61d0af1 100644
--- a/doc/coreutils.texi
+++ b/doc/coreutils.texi
@@ -111,6 +111,7 @@
 * 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)touch 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.
@@ -190,7 +191,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
@@ -208,6 +209,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
@@ -421,6 +423,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
 
@@ -648,6 +651,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.
@@ -679,8 +683,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},
address@hidden, @command{nohup}, @command{printenv},
address@hidden, @command{su}, @command{test}, @command{tty}.
address@hidden, @command{nohup}, @command{printenv}, @command{sort},
address@hidden, @command{test}, @command{timeout}, @command{tty}.
 
 
 @node Backup options
@@ -929,6 +933,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}.
 
address@hidden Signal specifications
address@hidden Signal specifications
address@hidden 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
address@hidden  The case of the letters is ignored. The following signal names
+and numbers are supported on all @acronym{POSIX} compliant systems:
+
address@hidden @samp
address@hidden HUP
+1.  Hangup.
address@hidden INT
+2.  Terminal interrupt.
address@hidden QUIT
+3.  Terminal quit.
address@hidden ABRT
+6.  Process abort.
address@hidden KILL
+9.  Kill (cannot be caught or ignored).
address@hidden ALRM
+14.  Alarm Clock.
address@hidden TERM
+15.  Termination.
address@hidden table
+
address@hidden
+Other supported signal names have system-dependent corresponding
+numbers.  All systems conforming to @acronym{POSIX} 1003.1-2001 also
+support the following signals:
+
address@hidden @samp
address@hidden BUS
+Access to an undefined portion of a memory object.
address@hidden CHLD
+Child process terminated, stopped, or continued.
address@hidden CONT
+Continue executing, if stopped.
address@hidden FPE
+Erroneous arithmetic operation.
address@hidden ILL
+Illegal Instruction.
address@hidden PIPE
+Write on a pipe with no one to read it.
address@hidden SEGV
+Invalid memory reference.
address@hidden STOP
+Stop executing (cannot be caught or ignored).
address@hidden TSTP
+Terminal stop.
address@hidden TTIN
+Background process attempting read.
address@hidden TTOU
+Background process attempting write.
address@hidden URG
+High bandwidth data is available at a socket.
address@hidden USR1
+User-defined signal 1.
address@hidden USR2
+User-defined signal 2.
address@hidden table
+
address@hidden
address@hidden 1003.1-2001 systems that support the @acronym{XSI} extension
+also support the following signals:
+
address@hidden @samp
address@hidden POLL
+Pollable event.
address@hidden PROF
+Profiling timer expired.
address@hidden SYS
+Bad system call.
address@hidden TRAP
+Trace/breakpoint trap.
address@hidden VTALRM
+Virtual timer expired.
address@hidden XCPU
+CPU time limit exceeded.
address@hidden XFSZ
+File size limit exceeded.
address@hidden table
+
address@hidden
address@hidden 1003.1-2001 systems that support the @acronym{XRT} extension
+also support at least eight real-time signals called @samp{RTMIN},
address@hidden, @dots{}, @samp{RTMAX-1}, @samp{RTMAX}.
+
 @node Disambiguating names and IDs
 @section chown and chgrp: Disambiguating user names and IDs
 @cindex user names, disambiguating
@@ -13364,6 +13457,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
 
 
@@ -13867,6 +13961,64 @@ used to supporting the bosses and sysadmins in 
whatever they do, you
 might find this idea strange at first.
 
 
address@hidden timeout invocation
address@hidden @command{timeout}: Run a command with a time limit
+
address@hidden timeout
address@hidden time limit
address@hidden run commands with bounded time
+
address@hidden runs the given @var{command} and kills it if it is
+still running after the specified time interval.  Synopsis:
+
address@hidden
+timeout address@hidden @var{number}[smhd] @var{command} address@hidden@dots{}
address@hidden 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
+
address@hidden 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.
+
address@hidden @samp
address@hidden -s @var{signal}
address@hidden address@hidden
address@hidden -s
address@hidden --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}.
+
address@hidden table
+
address@hidden exit status of @command{timeout}
+Exit status:
+
address@hidden
+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
address@hidden display
+
+
 @node Process control
 @chapter Process control
 
@@ -13945,88 +14097,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
 @address@hidden 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:
-
address@hidden @samp
address@hidden HUP
-1.  Hangup.
address@hidden INT
-2.  Terminal interrupt.
address@hidden QUIT
-3.  Terminal quit.
address@hidden ABRT
-6.  Process abort.
address@hidden KILL
-9.  Kill (cannot be caught or ignored).
address@hidden ALRM
-14.  Alarm Clock.
address@hidden TERM
-15.  Termination.
address@hidden table
-
address@hidden
-Other supported signal names have system-dependent corresponding
-numbers.  All systems conforming to @acronym{POSIX} 1003.1-2001 also
-support the following signals:
-
address@hidden @samp
address@hidden BUS
-Access to an undefined portion of a memory object.
address@hidden CHLD
-Child process terminated, stopped, or continued.
address@hidden CONT
-Continue executing, if stopped.
address@hidden FPE
-Erroneous arithmetic operation.
address@hidden ILL
-Illegal Instruction.
address@hidden PIPE
-Write on a pipe with no one to read it.
address@hidden SEGV
-Invalid memory reference.
address@hidden STOP
-Stop executing (cannot be caught or ignored).
address@hidden TSTP
-Terminal stop.
address@hidden TTIN
-Background process attempting read.
address@hidden TTOU
-Background process attempting write.
address@hidden URG
-High bandwidth data is available at a socket.
address@hidden USR1
-User-defined signal 1.
address@hidden USR2
-User-defined signal 2.
address@hidden table
-
address@hidden
address@hidden 1003.1-2001 systems that support the @acronym{XSI} extension
-also support the following signals:
-
address@hidden @samp
address@hidden POLL
-Pollable event.
address@hidden PROF
-Profiling timer expired.
address@hidden SYS
-Bad system call.
address@hidden TRAP
-Trace/breakpoint trap.
address@hidden VTALRM
-Virtual timer expired.
address@hidden XCPU
-CPU time limit exceeded.
address@hidden XFSZ
-File size limit exceeded.
address@hidden table
-
address@hidden
address@hidden 1003.1-2001 systems that support the @acronym{XRT} extension
-also support at least eight real-time signals called @samp{RTMIN},
address@hidden, @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
diff --git a/man/Makefile.am b/man/Makefile.am
index 7164b3b..3fa2260 100644
--- a/man/Makefile.am
+++ b/man/Makefile.am
@@ -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
index 0000000..19ebed3
--- /dev/null
+++ b/man/timeout.x
@@ -0,0 +1,6 @@
+[NAME]
+timeout \- run a command with a time limit
+[DESCRIPTION]
+.\" Add any additional description here
+[SEE ALSO]
+kill(1)
diff --git a/po/POTFILES.in b/po/POTFILES.in
index e975109..ddbeb4b 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -114,6 +114,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
diff --git a/src/Makefile.am b/src/Makefile.am
index 44d802e..6a7def8 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -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)
diff --git a/src/timeout.c b/src/timeout.c
new file mode 100644
index 0000000..7c15f1d
--- /dev/null
+++ b/src/timeout.c
@@ -0,0 +1,370 @@
+/* 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/wait.h>
+#include <sys/types.h>
+#include <signal.h>
+
+#include "system.h"
+#include "xstrtol.h"
+#include "sig2str.h"
+#include "cloexec.h"
+#include "error.h"
+#include "long-options.h"
+#include "quote.h"
+
+#define PROGRAM_NAME "timeout"
+
+#define AUTHORS "Padraig Brady"
+
+/* Internal failure.  */
+#ifndef ECANCELED
+#define ECANCELED 125
+#endif
+
+#ifndef WIFSIGNALED
+# define WIFSIGNALED(s) (((s) & 0xFFFF) - 1 < (unsigned int) 0xFF)
+#endif
+#ifndef WTERMSIG
+# define WTERMSIG(s) ((s) & 0x7F)
+#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.
+ * ??? 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);
+}
+
+/* Convert OPERAND to a signal number with printable representation SIGNAME.
+   Return the signal number, or -1 if unsuccessful.
+   Copied from kill.c  */
+
+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;
+}
+
+/* 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_suffix (unsigned int *x, char suffix_char)
+{
+  int multiplier;
+
+  switch (suffix_char)
+    {
+    case 0:
+    case 's':
+      multiplier = 1;
+      break;
+    case 'm':
+      multiplier = 60;
+      break;
+    case 'h':
+      multiplier = 60 * 60;
+      break;
+    case 'd':
+      multiplier = 60 * 60 * 24;
+      break;
+    default:
+      return false;
+    }
+
+  if (*x > UINT_MAX / multiplier)
+      return false;
+
+  *x *= multiplier;
+
+  return true;
+}
+
+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. ??? should we allow 0.  */
+      || (timeout == 0 || 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_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.  */
+  signal (SIGALRM, cleanup);    /* our timeout.  */
+  signal (SIGINT, cleanup);     /* Ctrl-C at terminal for example.  */
+  signal (SIGQUIT, cleanup);    /* Ctrl-\ at terminal for example.  */
+  signal (SIGTERM, cleanup);    /* if we're killed, stop monitored process.  */
+  signal (SIGHUP, cleanup);     /* terminal closed for example.  */
+  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);   /* ??? should we just 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:
+ */
diff --git a/tests/misc/Makefile.am b/tests/misc/Makefile.am
index f3ed132..536daef 100644
--- a/tests/misc/Makefile.am
+++ b/tests/misc/Makefile.am
@@ -114,6 +114,8 @@ TESTS = \
   tee \
   tee-dash \
   test-diag \
+  timeout-parameters \
+  timeout \
   tsort \
   tty-eof \
   unexpand
diff --git a/tests/misc/help-version b/tests/misc/help-version
index b54a7d8..22ba417 100755
--- a/tests/misc/help-version
+++ b/tests/misc/help-version
@@ -29,6 +29,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
@@ -148,6 +149,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).
diff --git a/tests/misc/timeout b/tests/misc/timeout
new file mode 100755
index 0000000..14fe2ee
--- /dev/null
+++ b/tests/misc/timeout
@@ -0,0 +1,41 @@
+#!/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
+timeout 1d true || fail=1
+
+# exit status propagation
+timeout 1 false && fail=1
+
+# timeout
+timeout 1 sleep 2
+[ $? -eq 110 ] || fail=1
+
+(exit $fail); exit $fail
diff --git a/tests/misc/timeout-parameters b/tests/misc/timeout-parameters
new file mode 100755
index 0000000..b4abbff
--- /dev/null
+++ b/tests/misc/timeout-parameters
@@ -0,0 +1,58 @@
+#!/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
+
+# timeout of 0 is not valid
+timeout 0 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
-- 
1.5.3.6


reply via email to

[Prev in Thread] Current Thread [Next in Thread]