diff options
author | Pádraig Brady <P@draigBrady.com> | 2008-03-28 11:05:55 +0000 |
---|---|---|
committer | Jim Meyering <meyering@redhat.com> | 2008-06-02 14:40:26 +0200 |
commit | 265c4b83a81c643f3359be926ef8034f28f321ed (patch) | |
tree | 4e8cf6f6938638e99f1e0d848780029480e28216 | |
parent | 4a510cd3997325cd8617c0ae082c9eb7220208d6 (diff) | |
download | coreutils-265c4b83a81c643f3359be926ef8034f28f321ed.tar.xz |
new program: timeout
* 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.
-rw-r--r-- | AUTHORS | 1 | ||||
-rw-r--r-- | NEWS | 4 | ||||
-rw-r--r-- | README | 4 | ||||
-rw-r--r-- | configure.ac | 2 | ||||
-rw-r--r-- | doc/coreutils.texi | 242 | ||||
-rw-r--r-- | man/Makefile.am | 1 | ||||
-rw-r--r-- | man/timeout.x | 6 | ||||
-rw-r--r-- | po/POTFILES.in | 2 | ||||
-rw-r--r-- | src/.gitignore | 1 | ||||
-rw-r--r-- | src/Makefile.am | 5 | ||||
-rw-r--r-- | src/kill.c | 47 | ||||
-rw-r--r-- | src/operand2sig.c | 84 | ||||
-rw-r--r-- | src/operand2sig.h | 18 | ||||
-rw-r--r-- | src/timeout.c | 335 | ||||
-rw-r--r-- | tests/Makefile.am | 2 | ||||
-rwxr-xr-x | tests/misc/help-version | 2 | ||||
-rwxr-xr-x | tests/misc/invalid-opt | 1 | ||||
-rwxr-xr-x | tests/misc/timeout | 44 | ||||
-rwxr-xr-x | tests/misc/timeout-parameters | 55 |
19 files changed, 721 insertions, 135 deletions
@@ -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 @@ -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 @@ -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/configure.ac b/configure.ac index 3c0e100c7..c5a7fcaff 100644 --- a/configure.ac +++ b/configure.ac @@ -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 diff --git a/doc/coreutils.texi b/doc/coreutils.texi index 2a19db1e4..8103ec2e7 100644 --- a/doc/coreutils.texi +++ b/doc/coreutils.texi @@ -113,6 +113,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)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 diff --git a/man/Makefile.am b/man/Makefile.am index 7164b3b23..3fa22609c 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 000000000..19ebed38a --- /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 a8b2e9f2d..2c55552e3 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -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 diff --git a/src/.gitignore b/src/.gitignore index 437dd30d4..cee550bc5 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -87,6 +87,7 @@ tac tail tee test +timeout touch tr true diff --git a/src/Makefile.am b/src/Makefile.am index 14906bc09..97dd33b6d 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) @@ -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 diff --git a/src/kill.c b/src/kill.c index d87c7d0e6..bd5d9b291 100644 --- a/src/kill.c +++ b/src/kill.c @@ -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); } -/* 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; -} - /* 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 index 000000000..228db401c --- /dev/null +++ b/src/operand2sig.c @@ -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 index 000000000..165e6cbb8 --- /dev/null +++ b/src/operand2sig.h @@ -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 index 000000000..19ec8499a --- /dev/null +++ b/src/timeout.c @@ -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: + */ diff --git a/tests/Makefile.am b/tests/Makefile.am index 1b7dcfa64..e5042b96b 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -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 \ diff --git a/tests/misc/help-version b/tests/misc/help-version index 1dcf96f92..ff0133a22 100755 --- a/tests/misc/help-version +++ b/tests/misc/help-version @@ -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). diff --git a/tests/misc/invalid-opt b/tests/misc/invalid-opt index 569a027e7..cbd41cae1 100755 --- a/tests/misc/invalid-opt +++ b/tests/misc/invalid-opt @@ -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 index 000000000..7f52f6e27 --- /dev/null +++ b/tests/misc/timeout @@ -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 index 000000000..091fbd7d0 --- /dev/null +++ b/tests/misc/timeout-parameters @@ -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 |