summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorPádraig Brady <P@draigBrady.com>2008-03-28 11:05:55 +0000
committerJim Meyering <meyering@redhat.com>2008-06-02 14:40:26 +0200
commit265c4b83a81c643f3359be926ef8034f28f321ed (patch)
tree4e8cf6f6938638e99f1e0d848780029480e28216 /src
parent4a510cd3997325cd8617c0ae082c9eb7220208d6 (diff)
downloadcoreutils-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.
Diffstat (limited to 'src')
-rw-r--r--src/.gitignore1
-rw-r--r--src/Makefile.am5
-rw-r--r--src/kill.c47
-rw-r--r--src/operand2sig.c84
-rw-r--r--src/operand2sig.h18
-rw-r--r--src/timeout.c335
6 files changed, 443 insertions, 47 deletions
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:
+ */