Re: [PATCH] Add misc-utils/timeout utility

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

 



Li Zefan wrote:
>> +/* TODO: merge with kill.c */
>> +int arg_to_signum (char *arg)
>> +{
>> +    int numsig;
>> +    char *ep;
>> +
>> +    if (isdigit(*arg)) {
> 
> This check is unnecessary.

removed. thanks.

> [..snip..]
> 
>> +    } else {
>> +        alarm(timeout);
>> +
>> +        int status;
> 
> Move this declaration above 'alarm(timeout)' ?
> 
>> +        wait(&status);

OK, made source fully ansi compliant. Now compiles with:
gcc -std=c89 -pedantic -Werror -D_GNU_SOURCE timeout.c -o timeout

Previous patch was also missing update to Makefile.am.

Ammended patch is attached.

thanks,
Pádraig.
>From 7631c1188818ffb03e8f65821fe95a6acc0708af Mon Sep 17 00:00:00 2001
From: =?utf-8?q?P=C3=A1draig=20Brady?= <P@xxxxxxxxxxxxxx>
Date: Fri, 7 Mar 2008 10:48:34 +0000
Subject: [PATCH] Add the timeout utility


Signed-off-by: Pádraig Brady <P@xxxxxxxxxxxxxx>
---
 misc-utils/Makefile.am |    4 +-
 misc-utils/timeout.1   |   31 ++++++++
 misc-utils/timeout.c   |  186 ++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 219 insertions(+), 2 deletions(-)
 create mode 100644 misc-utils/timeout.1
 create mode 100644 misc-utils/timeout.c

diff --git a/misc-utils/Makefile.am b/misc-utils/Makefile.am
index b799462..f0e20de 100644
--- a/misc-utils/Makefile.am
+++ b/misc-utils/Makefile.am
@@ -5,7 +5,7 @@ EXTRA_DIST = README.flushb
 bin_PROGRAMS =
 
 usrbinexec_PROGRAMS = cal ddate logger look mcookie \
-	namei script whereis
+	namei script timeout whereis
 EXTRA_DIST += README.cal README.ddate README.namei README.namei2
 
 mcookie_SOURCES = mcookie.c ../lib/md5.c
@@ -15,7 +15,7 @@ usrbinexec_SCRIPTS = chkdupexe scriptreplay
 CLEANFILES = chkdupexe scriptreplay
 
 dist_man_MANS = cal.1 chkdupexe.1 ddate.1 logger.1 look.1 mcookie.1 \
-	namei.1 script.1 whereis.1 scriptreplay.1
+	namei.1 script.1 scriptreplay.1 timeout.1 whereis.1
 
 if HAVE_TINFO
 cal_LDADD = -ltinfo -lncurses
diff --git a/misc-utils/timeout.1 b/misc-utils/timeout.1
new file mode 100644
index 0000000..1a60791
--- /dev/null
+++ b/misc-utils/timeout.1
@@ -0,0 +1,31 @@
+.\" Copyright 2008 Padraig Brady (P@xxxxxxxxxxxxxx)
+.\" May be distributed under the GNU General Public License
+.TH TIMEOUT 1 "7 March 2008" "Linux Utilities" "Linux Programmer's Manual"
+.SH NAME
+timeout \- Start a command, and kill it if the timeout expires
+.SH SYNOPSIS
+.BI "timeout [ \-s " signal " ] " timeout " " command " [ " args... " ]
+.SH DESCRIPTION
+The command
+.B timeout
+starts the command and will kill it after timeout seconds have passed.
+If the command times out, then we exit with status ETIMEDOUT,
+otherwise the normal exit status of the command is returned.
+If no signal is specified, the TERM signal is sent.  The TERM signal
+will kill processes which do not catch this signal.  For other processes,
+it may be necessary to use the KILL (9) signal, since this signal cannot
+be caught.
+.SH OPTIONS
+.TP
+.IR timeout ...
+Specify the timeout in seconds
+.TP
+.BI \-s " signal "
+Specify the signal number to send.
+.SH "SEE ALSO"
+.BR kill (1),
+.SH AUTHOR
+Padraig Brady <P@xxxxxxxxxxxxxx>.
+.SH AVAILABILITY
+The timeout command is part of the util-linux-ng package and is available from
+ftp://ftp.kernel.org/pub/linux/utils/util-linux-ng/.
diff --git a/misc-utils/timeout.c b/misc-utils/timeout.c
new file mode 100644
index 0000000..577f067
--- /dev/null
+++ b/misc-utils/timeout.c
@@ -0,0 +1,186 @@
+/*
+ Copyright © 2008 Pádraig Brady <P@xxxxxxxxxxxxxx>
+
+ 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 2 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+/*
+ 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.
+ If the jobs times out, then we exit with status ETIMEDOUT,
+ otherwise the normal exit status of the job is returned.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include <errno.h>
+#include <ctype.h>
+#include <limits.h>
+
+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 */
+
+/* TODO: merge with kill.c */
+int arg_to_signum (char *arg)
+{
+    int numsig;
+    char *ep;
+
+    numsig = strtol(arg, &ep, 10);
+    if (*ep!='\0' || numsig<0 || numsig>=NSIG)
+        return -1;
+    return numsig;
+}
+
+/* send sig to group but not ourselves.
+ * ??? Is there a better way to achieve this. */
+int send_sig(int where, int sig)
+{
+    sigs_to_ignore[sig]=1;
+    return kill(where, sig);
+}
+
+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);
+    }
+}
+
+void usage(char** argv)
+{
+    fprintf(stderr, "Usage: %s [-s signal number] seconds command\n", argv[0]);
+    fprintf(stderr, "Start a command, and kill it if the timeout expires\n\n");
+
+    fprintf(stderr, "  -s      send signal number to command (default=TERM)\n");
+    fprintf(stderr, "          See `kill -l` for a list of signal numbers\n");
+    exit(EINVAL);
+}
+
+int main(int argc, char** argv)
+{
+    unsigned int timeout;
+    int c;
+    char* ep;
+
+    while ((c = getopt(argc, argv, "+s:")) != -1) {
+        switch (c) {
+            case 's':
+                term_signal = arg_to_signum(optarg);
+                if (term_signal == -1) {
+                    fprintf(stderr, "Invalid signal number: %s\n", optarg);
+                    usage(argv);
+                }
+                break;
+            default:
+                usage(argv);
+                break;
+        }
+    }
+
+    if (argc-optind < 2) {
+        usage(argv);
+    }
+
+    timeout = strtoul(argv[optind++], &ep, 10);
+    if (*ep!='\0' || !timeout || timeout==ULONG_MAX) {
+        usage(argv);
+    }
+
+    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*/
+        enum { EXIT_CANNOT_INVOKE=126, EXIT_ENOENT=127 };
+        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(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;
+        }
+    }
+}
-- 
1.5.3.6


[Index of Archives]     [Netdev]     [Ethernet Bridging]     [Linux Wireless]     [Kernel Newbies]     [Security]     [Linux for Hams]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux RAID]     [Linux Admin]     [Samba]

  Powered by Linux