Update changelog for new version
[grml-terminalserver.git] / timeout.c
1 /*++
2  * NAME
3  *      timeout 1
4  * SUMMARY
5  *      run command with bounded time
6  * SYNOPSIS
7  *      \fBtimeout\fR [-\fIsignal\fR] \fItime\fR \fIcommand\fR ...
8  * DESCRIPTION
9  *      \fBtimeout\fR executes a command and imposes an elapsed time limit.
10  *      The command is run in a separate POSIX process group so that the
11  *      right thing happens with commands that spawn child processes.
12  *
13  *      Arguments:
14  * .IP \fI-signal\fR
15  *      Specify an optional signal to send to the controlled process.
16  *      By default, \fBtimeout\fR sends SIGKILL, which cannot be caught
17  *      or ignored.
18  * .IP \fItime\fR
19  *      The elapsed time limit after which the command is terminated.
20  * .IP \fIcommand\fR
21  *      The command to be executed.
22  * DIAGNOSTICS
23  *      The command exit status is the exit status of the command
24  *      (status 1 in case of a usage error).
25  * AUTHOR(S)
26  *      Wietse Venema
27  *      This program is part of SATAN.
28  *--
29  */
30
31 /* System libraries. */
32
33 #include <sys/types.h>
34 #include <sys/wait.h>
35 #include <signal.h>
36 #include <stdlib.h>
37 #include <unistd.h>
38 #include <stdio.h>
39
40 extern int optind;
41
42 /* Application-specific. */
43
44 #define perrorexit(s) { perror(s); exit(1); }
45 #define WRITE(x) write(2, x, strlen(x))
46
47 static int kill_signal = SIGKILL;
48 static char *progname;
49 static char *commandname;
50
51 // fmt_ulong and fmt_long from libowfat {{{
52 unsigned int fmt_ulong(char *dest,unsigned long i) {
53   register unsigned long len,tmp,len2;
54   /* first count the number of bytes needed */
55   for (len=1, tmp=i; tmp>9; ++len) tmp/=10;
56   if (dest)
57     for (tmp=i, dest+=len, len2=len+1; --len2; tmp/=10)
58       *--dest = (tmp%10)+'0';
59   return len;
60 }
61
62 unsigned int fmt_long(char *dest,long int i) {
63   if (i<0) {
64     if (dest) *dest++='-';
65     return fmt_ulong(dest,-i)+1;
66   } else
67     return fmt_ulong(dest,i);
68 }
69 // }}}
70
71 static void usage()
72 {
73     //fprintf(stderr, "usage: %s [-signal] time command...\n", progname);
74     WRITE("usage: ");
75     WRITE(progname);
76     WRITE(" [-signal] time command...\n");
77     exit(1);
78 }
79
80 static void terminate(int sig)
81 {
82     signal(kill_signal, SIG_DFL);
83     //fprintf(stderr, "Timeout: aborting command ``%s'' with signal %d\n",
84     //    commandname, kill_signal);
85     char kill_signal_string[22];
86     fmt_long(kill_signal_string, kill_signal);
87     WRITE("Timeout: aborting command ``");
88     WRITE(commandname);
89     WRITE("'' with signal ");
90     WRITE(kill_signal_string);
91     WRITE("\n");
92     kill(0, kill_signal);
93 }
94
95 int main(int argc, char** argv)
96 {
97     int     time_to_run = 0;
98     pid_t   pid;
99     pid_t   child_pid;
100     int     status;
101
102     progname = argv[0];
103
104     /*
105      * Parse JCL.
106      */
107     while (--argc && *++argv && **argv == '-')
108         if ((kill_signal = atoi(*argv + 1)) <= 0)
109             usage();
110
111     if (argc < 2 || (time_to_run = atoi(argv[0])) <= 0)
112         usage();
113
114     commandname = argv[1];
115
116     /*
117      * Run the command and its watchdog in a separate process group so that
118      * both can be killed off with one signal.
119      */
120     setsid();
121     switch (child_pid = fork()) {
122     case -1:                                    /* error */
123         perrorexit("timeout: fork");
124     case  0:                                    /* run controlled command */
125         execvp(argv[1], argv + 1);
126         perrorexit(argv[1]);
127     default:                                    /* become watchdog */
128         (void) signal(SIGHUP, terminate);
129         (void) signal(SIGINT, terminate);
130         (void) signal(SIGQUIT, terminate);
131         (void) signal(SIGTERM, terminate);
132         (void) signal(SIGALRM, terminate);
133         alarm(time_to_run);
134         while ((pid = wait(&status)) != -1 && pid != child_pid)
135              /* void */ ;
136         return (pid == child_pid ? status : -1);
137     }
138 }
139
140 // vim: foldmethod=marker