chiark / gitweb /
debian/control: Fix architectures for x86-model.
[misc] / mtimeout.c
1 /* -*-c-*-
2  *
3  * Run a command with a timeout.
4  *
5  * (c) 2011 Mark Wooding
6  */
7
8 /*----- Licensing notice --------------------------------------------------*
9  *
10  * This file is part of the Toys utilties collection.
11  *
12  * Toys is free software; you can redistribute it and/or modify
13  * it under the terms of the GNU General Public License as published by
14  * the Free Software Foundation; either version 2 of the License, or
15  * (at your option) any later version.
16  *
17  * Toys is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with Toys; if not, write to the Free Software Foundation,
24  * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25  */
26
27 /*----- Basic strategy ----------------------------------------------------*
28  *
29  * There's an obvious and simple way to make a process stop after a certain
30  * amount of time: set an alarm.  Alarms are inherited, so this will take
31  * down the whole subprocess tree.  Unfortunately, processes use alarms for
32  * all sorts of things.  And this relies on the process not fiddling with its
33  * @SIGALRM@ handler.
34  *
35  * The other possibility is to have a separate process which kills our target
36  * program when the time's up.  In order to do this effectively, we need to
37  * trap the whole thing in a process group.  Then we need to wait until the
38  * time is up or the process finished.  We'll assume that the top-level
39  * process finishing is sufficient indication that we don't need to hang on
40  * any longer.
41  *
42  * This isn't perfect.  It won't catch stragglers if the target process
43  * creates its own sub-process groups -- so, in particular, nested timeouts
44  * won't behave in an obvious way.  And, possibly most annoyingly, it
45  * interferes with the way terminal I/O works.  I'm sorry: you can't have
46  * everything.
47  */
48
49 /*----- Header files ------------------------------------------------------*/
50
51 #include <ctype.h>
52 #include <errno.h>
53 #include <signal.h>
54 #include <stdio.h>
55 #include <stdlib.h>
56 #include <string.h>
57
58 #include <sys/types.h>
59 #include <sys/time.h>
60 #include <fcntl.h>
61 #include <unistd.h>
62 #include <sys/select.h>
63 #include <sys/wait.h>
64
65 #include <mLib/fdflags.h>
66 #include <mLib/macros.h>
67 #include <mLib/mdwopt.h>
68 #include <mLib/quis.h>
69 #include <mLib/report.h>
70 #include <mLib/sel.h>
71 #include <mLib/sig.h>
72 #include <mLib/tv.h>
73
74 /*----- Static variables --------------------------------------------------*/
75
76 static sel_state sel;
77 static int state;
78
79 enum {
80   ST_WAIT,
81   ST_ABORT,
82   ST_DONE
83 };
84
85 /*----- Argument conversion functions -------------------------------------*/
86
87 /* --- @namesig@ --- *
88  *
89  * Arguments:   @const char *p@ = pointer to string
90  *
91  * Returns:     Signal number, or @-1@ for no match.
92  *
93  * Use:         Converts a signal name into its number.
94  */
95
96 struct namesig {
97   const char *name;
98   int sig;
99 };
100
101 int cmp_namesig(const void *k, const void *v)
102   { const struct namesig *ns = v; return (strcmp(k, ns->name)); }
103
104 static int namesig(const char *p)
105 {
106   const static struct namesig tab[] = {
107   /*
108      ;;; The signal name table is very boring to type.  To make life less
109      ;;; awful, put the signal names in this list and evaluate the code to
110      ;;; get Emacs to regenerate it.  We use @bsearch@ on it, so it's
111      ;;; important that it be sorted: Emacs does this for us.
112
113      (let ((signals '(HUP INT QUIT ILL ABRT FPE KILL SEGV PIPE ALRM TERM
114                       USR1 USR2 CHLD CONT STOP TSTP TTIN TTOU BUS POLL
115                       PROF SYS TRAP URG VTALRM XCPU XFSZ IOT EMT STKFLT
116                       IO CLD PWR INFO LOST WINCH)))
117        (save-excursion
118          (goto-char (point-min))
119          (search-forward (concat "***" "BEGIN siglist" "***"))
120          (beginning-of-line 2)
121          (delete-region (point)
122                         (progn
123                           (search-forward "***END***")
124                           (beginning-of-line)
125                           (point)))
126          (dolist (sig (sort (copy-list signals) #'string<))
127            (insert (format "#ifdef SIG%s\n    { \"%s\", SIG%s },\n#endif\n"
128                            sig sig sig)))))
129   */
130
131     /***BEGIN siglist***/
132 #ifdef SIGABRT
133     { "ABRT", SIGABRT },
134 #endif
135 #ifdef SIGALRM
136     { "ALRM", SIGALRM },
137 #endif
138 #ifdef SIGBUS
139     { "BUS", SIGBUS },
140 #endif
141 #ifdef SIGCHLD
142     { "CHLD", SIGCHLD },
143 #endif
144 #ifdef SIGCLD
145     { "CLD", SIGCLD },
146 #endif
147 #ifdef SIGCONT
148     { "CONT", SIGCONT },
149 #endif
150 #ifdef SIGEMT
151     { "EMT", SIGEMT },
152 #endif
153 #ifdef SIGFPE
154     { "FPE", SIGFPE },
155 #endif
156 #ifdef SIGHUP
157     { "HUP", SIGHUP },
158 #endif
159 #ifdef SIGILL
160     { "ILL", SIGILL },
161 #endif
162 #ifdef SIGINFO
163     { "INFO", SIGINFO },
164 #endif
165 #ifdef SIGINT
166     { "INT", SIGINT },
167 #endif
168 #ifdef SIGIO
169     { "IO", SIGIO },
170 #endif
171 #ifdef SIGIOT
172     { "IOT", SIGIOT },
173 #endif
174 #ifdef SIGKILL
175     { "KILL", SIGKILL },
176 #endif
177 #ifdef SIGLOST
178     { "LOST", SIGLOST },
179 #endif
180 #ifdef SIGPIPE
181     { "PIPE", SIGPIPE },
182 #endif
183 #ifdef SIGPOLL
184     { "POLL", SIGPOLL },
185 #endif
186 #ifdef SIGPROF
187     { "PROF", SIGPROF },
188 #endif
189 #ifdef SIGPWR
190     { "PWR", SIGPWR },
191 #endif
192 #ifdef SIGQUIT
193     { "QUIT", SIGQUIT },
194 #endif
195 #ifdef SIGSEGV
196     { "SEGV", SIGSEGV },
197 #endif
198 #ifdef SIGSTKFLT
199     { "STKFLT", SIGSTKFLT },
200 #endif
201 #ifdef SIGSTOP
202     { "STOP", SIGSTOP },
203 #endif
204 #ifdef SIGSYS
205     { "SYS", SIGSYS },
206 #endif
207 #ifdef SIGTERM
208     { "TERM", SIGTERM },
209 #endif
210 #ifdef SIGTRAP
211     { "TRAP", SIGTRAP },
212 #endif
213 #ifdef SIGTSTP
214     { "TSTP", SIGTSTP },
215 #endif
216 #ifdef SIGTTIN
217     { "TTIN", SIGTTIN },
218 #endif
219 #ifdef SIGTTOU
220     { "TTOU", SIGTTOU },
221 #endif
222 #ifdef SIGURG
223     { "URG", SIGURG },
224 #endif
225 #ifdef SIGUSR1
226     { "USR1", SIGUSR1 },
227 #endif
228 #ifdef SIGUSR2
229     { "USR2", SIGUSR2 },
230 #endif
231 #ifdef SIGVTALRM
232     { "VTALRM", SIGVTALRM },
233 #endif
234 #ifdef SIGWINCH
235     { "WINCH", SIGWINCH },
236 #endif
237 #ifdef SIGXCPU
238     { "XCPU", SIGXCPU },
239 #endif
240 #ifdef SIGXFSZ
241     { "XFSZ", SIGXFSZ },
242 #endif
243     /***END***/
244   };
245
246   const struct namesig *ns = bsearch(p, tab, N(tab), sizeof(tab[0]),
247                                      cmp_namesig);
248   if (ns) return (ns->sig);
249   else if (isdigit((unsigned char)*p)) return (atoi(p));
250   else return (-1);
251 }
252
253 /*----- Help functions ----------------------------------------------------*/
254
255 static void usage(FILE *fp)
256   { pquis(fp, "Usage: $ [-s SIG] SECONDS COMMAND [ARGUMENTS ...]\n"); }
257
258 static void version(FILE *fp)
259   { pquis(fp, "$ (version " VERSION ")\n"); }
260
261 static void help(FILE *fp)
262 {
263   version(fp); fputc('\n', stdout);
264   usage(fp);
265   pquis(fp, "\n\
266 Run COMMAND, giving it the ARGUMENTS.  If it fails to complete within the\n\
267 specified number of SECONDS, kill it.  Otherwise exit with the status it\n\
268 returns.\n                                                              \
269 \n\
270 Options:\n\
271   -h, --help            Show this help text.\n\
272   -v, --version         Show version string.\n\
273   -u, --usage           Show a terse usage summary.\n\
274 \n\
275   -s, --signal=SIG      Send signal SIG to the command.\n\
276 ");
277 }
278
279 /*----- Timeout handling --------------------------------------------------*/
280
281 /* --- @timeout@ --- *
282  *
283  * The first time, we send the signal requested by the caller.  Then we wait
284  * five seconds for the child to die, and send @SIGKILL@.  If that still
285  * doesn't help, then we just give up.  It's not like there's anything else
286  * we can do which is likely to help.  And it's not like the process is going
287  * to be doing anything else in user mode ever again.
288  */
289
290 struct timeout {
291   sel_timer t;
292   int panic;
293   int sig;
294   pid_t kid;
295 };
296
297 static void timeout(struct timeval *now, void *p)
298 {
299   struct timeout *t = p;
300   struct timeval tv;
301
302   switch (t->panic) {
303     case 0:
304       moan("timed out: killing child process");
305       kill(-t->kid, t->sig);
306       break;
307     case 1:
308       moan("child hasn't responded: killing harder");
309       kill(-t->kid, SIGKILL);
310       break;
311     case 2:
312       moan("child still undead: giving up");
313       state = ST_ABORT;
314       break;
315   }
316   TV_ADDL(&tv, now, 5, 0);
317   sel_addtimer(&sel, &t->t, &tv, timeout, t);
318   t->panic++;
319 }
320
321 /*----- Signal handling ---------------------------------------------------*/
322
323 /* --- @sigchld@ --- *
324  *
325  * If it's our child that died, fetch its exit status and stop.
326  */
327
328 struct sigchld {
329   pid_t kid;
330   int rc;
331 };
332
333 static void sigchld(int sig, void *p)
334 {
335   struct sigchld *s = p;
336   int rc;
337   pid_t pid;
338
339   if ((pid = waitpid(s->kid, &rc, WNOHANG)) < 0)
340     die(254, "waitpid failed: %s", strerror(errno));
341   if (pid == s->kid) {
342     s->rc = rc;
343     state = ST_DONE;
344   }
345 }
346
347 /* --- @sigpropagate@ --- *
348  *
349  * Propagate various signals to the child process group and then maybe act on
350  * them.
351  */
352
353 static void sigpropagate(int sig, void *p)
354 {
355   struct sigchld *s = p;
356
357   kill(-s->kid, sig);
358   switch (sig) {
359     case SIGTSTP: raise(SIGSTOP); break;
360   }
361 }
362
363 #define PROPAGATE_SIGNALS(_)                                            \
364   _(TSTP) _(CONT) _(INT) _(HUP) _(QUIT)
365
366 /*----- Main program ------------------------------------------------------*/
367
368 /* --- @main@ --- */
369
370 int main(int argc, char *const argv[])
371 {
372   char *p;
373   double t;
374   int signo = SIGTERM;
375   pid_t kid;
376   struct timeval tv;
377   struct timeout to;
378   struct sigchld sc;
379   sig sig_CHLD;
380 #define DEFSIG(tag) sig sig_##tag;
381   PROPAGATE_SIGNALS(DEFSIG)
382 #undef DEFSIG
383   unsigned f = 0;
384 #define F_BOGUS 1u
385
386   /* --- Option parsing --- */
387
388   ego(argv[0]);
389
390   for (;;) {
391     static const struct option opts[] = {
392       { "help",                 0,              0,      'h' },
393       { "version",              0,              0,      'v' },
394       { "usage",                0,              0,      'u' },
395       { "signal",               OPTF_ARGREQ,    0,      's' },
396       { 0,                      0,              0,      0 }
397     };
398
399     int i = mdwopt(argc, argv, "+hvus:", opts, 0, 0, 0);
400     if (i < 0) break;
401     switch (i) {
402       case 'h': help(stdout); exit(0);
403       case 'v': version(stdout); exit(0);
404       case 'u': usage(stdout); exit(0);
405       case 's':
406         if ((signo = namesig(optarg)) < 0)
407           die(253, "bad signal spec `%s'", optarg);
408         break;
409       default: f |= F_BOGUS; break;
410     }
411   }
412   argc -= optind; argv += optind;
413   if ((f & F_BOGUS) || argc < 2) { usage(stderr); exit(253); }
414
415   p = argv[0];
416   while (isspace((unsigned char)*p)) p++;
417   t = strtod(argv[0], &p);
418   while (isspace((unsigned char)*p)) p++;
419   if (*p) die(253, "bad time value `%s'", argv[0]);
420
421   /* --- Get things set up --- */
422
423   state = ST_WAIT;
424   sel_init(&sel);
425
426   /* --- Set up signal handling --- *
427    *
428    * Doing anything with asynchronous signals is a mug's game, so we bring
429    * them in-band.  Do this before starting the child process, because
430    * otherwise we might miss an immediate @SIGCHLD@.
431    */
432
433   sig_init(&sel);
434   sc.rc = 0;
435   sig_add(&sig_CHLD, SIGCHLD, sigchld, &sc);
436 #define ADDSIG(tag) sig_add(&sig_##tag, SIG##tag, sigpropagate, &sc);
437   PROPAGATE_SIGNALS(ADDSIG)
438 #undef ADDSIG
439
440   /* --- Now start the child process --- */
441
442   if ((kid = fork()) < 0) die(2, "fork failed: %s", strerror(errno));
443   if (!kid) {
444     setpgid(0, 0);
445     execvp(argv[1], argv + 1);
446     die(252, "exec(%s) failed: %s", argv[1], strerror(errno));
447   }
448   sc.kid = kid;
449
450   /* --- Set up the timer --- */
451
452   to.kid = kid;
453   to.sig = signo;
454   to.panic = 0;
455   gettimeofday(&tv, 0);
456   TV_ADDL(&tv, &tv, (time_t)t, ((long)(t * 1000000))%1000000);
457   sel_addtimer(&sel, &to.t, &tv, timeout, &to);
458
459   /* --- Main @select@ loop */
460
461   while (state == ST_WAIT) {
462     if (sel_select(&sel)) {
463       if (errno == EINTR) continue;
464       die(254, "select failed: %s", strerror(errno));
465     }
466   }
467
468   /* --- Check and translate the exit code --- */
469
470   switch (state) {
471     case ST_ABORT:
472       exit(251);
473     case ST_DONE:
474       if (WIFEXITED(sc.rc))
475         exit(WEXITSTATUS(sc.rc));
476       else if (WIFSIGNALED(sc.rc))
477         exit(128 | WTERMSIG(sc.rc));
478       else
479         exit(128);
480     default:
481       moan("FATAL: unexpected state %d", state);
482       abort();
483   }
484 }
485
486 /*----- That's all, folks -------------------------------------------------*/