chiark / gitweb /
Stamp out trailing whitespace.
[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 <math.h>
54 #include <signal.h>
55 #include <stdio.h>
56 #include <stdlib.h>
57 #include <string.h>
58
59 #include <sys/types.h>
60 #include <sys/time.h>
61 #include <fcntl.h>
62 #include <unistd.h>
63 #include <sys/select.h>
64 #include <sys/wait.h>
65
66 #include <mLib/fdflags.h>
67 #include <mLib/macros.h>
68 #include <mLib/mdwopt.h>
69 #include <mLib/quis.h>
70 #include <mLib/report.h>
71 #include <mLib/sel.h>
72 #include <mLib/sig.h>
73 #include <mLib/tv.h>
74
75 /*----- Static variables --------------------------------------------------*/
76
77 static sel_state sel;
78 static int state;
79
80 enum {
81   ST_WAIT,
82   ST_ABORT,
83   ST_DONE
84 };
85
86 /*----- Argument conversion functions -------------------------------------*/
87
88 /* --- @namesig@ --- *
89  *
90  * Arguments:   @const char *p@ = pointer to string
91  *
92  * Returns:     Signal number, or @-1@ for no match.
93  *
94  * Use:         Converts a signal name into its number.
95  */
96
97 struct namesig {
98   const char *name;
99   int sig;
100 };
101
102 int cmp_namesig(const void *k, const void *v)
103   { const struct namesig *ns = v; return (strcmp(k, ns->name)); }
104
105 static int namesig(const char *p)
106 {
107   const static struct namesig tab[] = {
108   /*
109      ;;; The signal name table is very boring to type.  To make life less
110      ;;; awful, put the signal names in this list and evaluate the code to
111      ;;; get Emacs to regenerate it.  We use @bsearch@ on it, so it's
112      ;;; important that it be sorted: Emacs does this for us.
113
114      (let ((signals '(HUP INT QUIT ILL ABRT FPE KILL SEGV PIPE ALRM TERM
115                       USR1 USR2 CHLD CONT STOP TSTP TTIN TTOU BUS POLL
116                       PROF SYS TRAP URG VTALRM XCPU XFSZ IOT EMT STKFLT
117                       IO CLD PWR INFO LOST WINCH)))
118        (save-excursion
119          (goto-char (point-min))
120          (search-forward (concat "***" "BEGIN siglist" "***"))
121          (beginning-of-line 2)
122          (delete-region (point)
123                         (progn
124                           (search-forward "***END***")
125                           (beginning-of-line)
126                           (point)))
127          (dolist (sig (sort (copy-list signals) #'string<))
128            (insert (format "#ifdef SIG%s\n    { \"%s\", SIG%s },\n#endif\n"
129                            sig sig sig)))))
130   */
131
132     /***BEGIN siglist***/
133 #ifdef SIGABRT
134     { "ABRT", SIGABRT },
135 #endif
136 #ifdef SIGALRM
137     { "ALRM", SIGALRM },
138 #endif
139 #ifdef SIGBUS
140     { "BUS", SIGBUS },
141 #endif
142 #ifdef SIGCHLD
143     { "CHLD", SIGCHLD },
144 #endif
145 #ifdef SIGCLD
146     { "CLD", SIGCLD },
147 #endif
148 #ifdef SIGCONT
149     { "CONT", SIGCONT },
150 #endif
151 #ifdef SIGEMT
152     { "EMT", SIGEMT },
153 #endif
154 #ifdef SIGFPE
155     { "FPE", SIGFPE },
156 #endif
157 #ifdef SIGHUP
158     { "HUP", SIGHUP },
159 #endif
160 #ifdef SIGILL
161     { "ILL", SIGILL },
162 #endif
163 #ifdef SIGINFO
164     { "INFO", SIGINFO },
165 #endif
166 #ifdef SIGINT
167     { "INT", SIGINT },
168 #endif
169 #ifdef SIGIO
170     { "IO", SIGIO },
171 #endif
172 #ifdef SIGIOT
173     { "IOT", SIGIOT },
174 #endif
175 #ifdef SIGKILL
176     { "KILL", SIGKILL },
177 #endif
178 #ifdef SIGLOST
179     { "LOST", SIGLOST },
180 #endif
181 #ifdef SIGPIPE
182     { "PIPE", SIGPIPE },
183 #endif
184 #ifdef SIGPOLL
185     { "POLL", SIGPOLL },
186 #endif
187 #ifdef SIGPROF
188     { "PROF", SIGPROF },
189 #endif
190 #ifdef SIGPWR
191     { "PWR", SIGPWR },
192 #endif
193 #ifdef SIGQUIT
194     { "QUIT", SIGQUIT },
195 #endif
196 #ifdef SIGSEGV
197     { "SEGV", SIGSEGV },
198 #endif
199 #ifdef SIGSTKFLT
200     { "STKFLT", SIGSTKFLT },
201 #endif
202 #ifdef SIGSTOP
203     { "STOP", SIGSTOP },
204 #endif
205 #ifdef SIGSYS
206     { "SYS", SIGSYS },
207 #endif
208 #ifdef SIGTERM
209     { "TERM", SIGTERM },
210 #endif
211 #ifdef SIGTRAP
212     { "TRAP", SIGTRAP },
213 #endif
214 #ifdef SIGTSTP
215     { "TSTP", SIGTSTP },
216 #endif
217 #ifdef SIGTTIN
218     { "TTIN", SIGTTIN },
219 #endif
220 #ifdef SIGTTOU
221     { "TTOU", SIGTTOU },
222 #endif
223 #ifdef SIGURG
224     { "URG", SIGURG },
225 #endif
226 #ifdef SIGUSR1
227     { "USR1", SIGUSR1 },
228 #endif
229 #ifdef SIGUSR2
230     { "USR2", SIGUSR2 },
231 #endif
232 #ifdef SIGVTALRM
233     { "VTALRM", SIGVTALRM },
234 #endif
235 #ifdef SIGWINCH
236     { "WINCH", SIGWINCH },
237 #endif
238 #ifdef SIGXCPU
239     { "XCPU", SIGXCPU },
240 #endif
241 #ifdef SIGXFSZ
242     { "XFSZ", SIGXFSZ },
243 #endif
244     /***END***/
245   };
246
247   const struct namesig *ns = bsearch(p, tab, N(tab), sizeof(tab[0]),
248                                      cmp_namesig);
249   if (ns) return (ns->sig);
250   else if (isdigit((unsigned char)*p)) return (atoi(p));
251   else return (-1);
252 }
253
254 /* --- @strtotime@ --- *
255  *
256  * Arguments:   @const char *p@ = pointer to string
257  *              @struct timeval *tv@ = where to put the result
258  *
259  * Returns:     ---
260  *
261  * Use:         Converts a string representation of a duration into an
262  *              internal version.  Understands various time units.
263  */
264
265 static void strtotime(const char *p, struct timeval *tv)
266 {
267   char *q = (/*unconst*/ char *)p;
268   double t, i, f;
269
270   while (isspace((unsigned char)*q)) q++;
271   t = strtod(q, &q);
272   while (isspace((unsigned char)*q)) q++;
273   switch (*q) {
274     case 'd': case 'D': t *= 24;
275     case 'h': case 'H': t *= 60;
276     case 'm': case 'M': t *= 60;
277     case 's': case 'S':
278       q++;
279       while (isspace((unsigned char)*q)) q++;
280   }
281   if (*q) die(253, "bad time value `%s'", p);
282   f = modf(t, &i);
283   tv->tv_sec = i;
284   tv->tv_usec = f * 1000000;
285 }
286
287 /*----- Help functions ----------------------------------------------------*/
288
289 static void usage(FILE *fp)
290   { pquis(fp, "Usage: $ [-s SIG] SECONDS COMMAND [ARGUMENTS ...]\n"); }
291
292 static void version(FILE *fp)
293   { pquis(fp, "$ (version " VERSION ")\n"); }
294
295 static void help(FILE *fp)
296 {
297   version(fp); fputc('\n', stdout);
298   usage(fp);
299   pquis(fp, "\n\
300 Run COMMAND, giving it the ARGUMENTS.  If it fails to complete within the\n\
301 specified number of SECONDS, kill it.  Otherwise exit with the status it\n\
302 returns.\n                                                              \
303 \n\
304 Options:\n\
305   -h, --help            Show this help text.\n\
306   -v, --version         Show version string.\n\
307   -u, --usage           Show a terse usage summary.\n\
308 \n\
309   -s, --signal=SIG      Send signal SIG to the command.\n\
310 ");
311 }
312
313 /*----- Timeout handling --------------------------------------------------*/
314
315 /* --- @timeout@ --- *
316  *
317  * The timeout sequencing stuff is complicated, so here's a simple machine to
318  * make it work.
319  */
320
321 enum {
322   TA_MOAN,                              /* Report message to user */
323 #define TAARG_MOAN s
324
325   TA_KILL,                              /* Send a signal */
326 #define TAARG_KILL i
327
328   TA_GOTO,                              /* Goto different state */
329 #define TAARG_GOTO i
330
331   TA_WAIT,                              /* Wait for more time */
332 #define TAARG_WAIT tv
333
334   TA_STATE,                             /* Alter the internal state */
335 #define TAARG_STATE i
336 };
337
338 struct tmact {
339   unsigned act;
340   union {
341     const char *s;                      /* String parameter */
342     int i;                              /* Integer parameter */
343     struct timeval tv;                  /* Time parameter */
344   } u;
345 };
346
347 struct timeout {
348   sel_timer t;                          /* Timer intrusion */
349   struct tmact *ta;                     /* Instruction vector */
350   int ip;                               /* Instruction pointer */
351   pid_t kid;
352 };
353
354 static void timeout(struct timeval *now, void *p)
355 {
356   struct timeout *t = p;
357   struct tmact *ta;
358   struct timeval tv;
359
360   for (;;) {
361     ta = &t->ta[t->ip++];
362     switch (ta->act) {
363       case TA_MOAN:
364         moan(ta->u.s);
365         break;
366       case TA_KILL:
367         kill(-t->kid, ta->u.i);
368         break;
369       case TA_GOTO:
370         t->ip = ta->u.i;
371         break;
372       case TA_STATE:
373         state = ta->u.i;
374         return;
375       case TA_WAIT:
376         TV_ADD(&tv, now, &ta->u.tv);
377         sel_addtimer(&sel, &t->t, &tv, timeout, t);
378         return;
379       default:
380         moan("unexpected tmact %u", ta->act);
381         abort();
382     }
383   }
384 }
385
386 /*----- Signal handling ---------------------------------------------------*/
387
388 /* --- @sigchld@ --- *
389  *
390  * If it's our child that died, fetch its exit status and stop.
391  */
392
393 struct sigchld {
394   pid_t kid;
395   int rc;
396 };
397
398 static void sigchld(int sig, void *p)
399 {
400   struct sigchld *s = p;
401   int rc;
402   pid_t pid;
403
404   if ((pid = waitpid(s->kid, &rc, WNOHANG)) < 0)
405     die(254, "waitpid failed: %s", strerror(errno));
406   if (pid == s->kid) {
407     s->rc = rc;
408     state = ST_DONE;
409   }
410 }
411
412 /* --- @sigpropagate@ --- *
413  *
414  * Propagate various signals to the child process group and then maybe act on
415  * them.
416  */
417
418 static void sigpropagate(int sig, void *p)
419 {
420   struct sigchld *s = p;
421
422   kill(-s->kid, sig);
423   switch (sig) {
424     case SIGTSTP: raise(SIGSTOP); break;
425   }
426 }
427
428 #define PROPAGATE_SIGNALS(_)                                            \
429   _(TSTP) _(CONT) _(INT) _(HUP) _(QUIT)
430
431 /*----- Main program ------------------------------------------------------*/
432
433 /* --- @main@ --- */
434
435 int main(int argc, char *const argv[])
436 {
437   pid_t kid;
438   struct timeout to;
439   struct timeval now;
440
441 #define PAIR(x, y) { x, y }
442 #define TACODE(I)                                                       \
443   I(sigwait,    WAIT,   PAIR(0, 0))                                     \
444   I(_a,         MOAN,   "timed out: killing child process")             \
445   I(sig,        KILL,   SIGTERM)                                        \
446   I(killwait,   WAIT,   PAIR(5, 0))                                     \
447   I(_b,         MOAN,   "child hasn't responded: killing harder")       \
448   I(_c,         KILL,   SIGKILL)                                        \
449   I(boredwait,  WAIT,   PAIR(5, 0))                                     \
450   I(_d,         MOAN,   "child still undead: giving up")                \
451   I(_e,         STATE,  ST_ABORT)
452
453   enum {
454 #define TALBL(label, op, arg) taoff_##label,
455     TACODE(TALBL)
456 #undef TALBL
457     taoff_end
458   };
459
460   static struct tmact ta[] = {
461 #define TAASM(label, op, arg) { TA_##op, { .TAARG_##op = arg } },
462     TACODE(TAASM)
463 #undef TAASM
464   };
465
466   struct sigchld sc;
467   sig sig_CHLD;
468 #define DEFSIG(tag) sig sig_##tag;
469   PROPAGATE_SIGNALS(DEFSIG)
470 #undef DEFSIG
471   unsigned f = 0;
472 #define F_BOGUS 1u
473
474   /* --- Option parsing --- */
475
476   ego(argv[0]);
477
478   for (;;) {
479     static const struct option opts[] = {
480       { "help",                 0,              0,      'h' },
481       { "version",              0,              0,      'v' },
482       { "usage",                0,              0,      'u' },
483       { "no-kill",              0,              0,      'K' },
484       { "kill-after",           OPTF_ARGREQ,    0,      'k' },
485       { "bored-after",          OPTF_ARGREQ,    0,      'b' },
486       { "signal",               OPTF_ARGREQ,    0,      's' },
487       { 0,                      0,              0,      0 }
488     };
489
490     int i = mdwopt(argc, argv, "+hvuKb:k:s:", opts, 0, 0, 0);
491     if (i < 0) break;
492     switch (i) {
493       case 'h': help(stdout); exit(0);
494       case 'v': version(stdout); exit(0);
495       case 'u': usage(stdout); exit(0);
496       case 'b':
497         ta[taoff_boredwait].act = TA_WAIT;
498         strtotime(optarg, &ta[taoff_boredwait].u.tv);
499         break;
500       case 'k':
501         ta[taoff_killwait].act = TA_WAIT;
502         strtotime(optarg, &ta[taoff_killwait].u.tv);
503         break;
504       case 'K':
505         ta[taoff_killwait].act = TA_GOTO;
506         ta[taoff_killwait].u.i = taoff_boredwait;
507         break;
508       case 's':
509         if ((ta[taoff_sig].u.i = namesig(optarg)) < 0)
510           die(253, "bad signal spec `%s'", optarg);
511         break;
512       default: f |= F_BOGUS; break;
513     }
514   }
515   argc -= optind; argv += optind;
516   if ((f & F_BOGUS) || argc < 2) { usage(stderr); exit(253); }
517   strtotime(argv[0], &ta[taoff_sigwait].u.tv);
518
519   /* --- Get things set up --- */
520
521   state = ST_WAIT;
522   sel_init(&sel);
523
524   /* --- Set up signal handling --- *
525    *
526    * Doing anything with asynchronous signals is a mug's game, so we bring
527    * them in-band.  Do this before starting the child process, because
528    * otherwise we might miss an immediate @SIGCHLD@.
529    */
530
531   sig_init(&sel);
532   sc.rc = 0;
533   sig_add(&sig_CHLD, SIGCHLD, sigchld, &sc);
534 #define ADDSIG(tag) sig_add(&sig_##tag, SIG##tag, sigpropagate, &sc);
535   PROPAGATE_SIGNALS(ADDSIG)
536 #undef ADDSIG
537
538   /* --- Now start the child process --- */
539
540   if ((kid = fork()) < 0) die(2, "fork failed: %s", strerror(errno));
541   if (!kid) {
542     setpgid(0, 0);
543     execvp(argv[1], argv + 1);
544     die(252, "exec(%s) failed: %s", argv[1], strerror(errno));
545   }
546   sc.kid = kid;
547
548   /* --- Set up the timer --- */
549
550   gettimeofday(&now, 0);
551   to.kid = kid;
552   to.ta = ta;
553   to.ip = 0;
554   timeout(&now, &to);
555
556   /* --- Main @select@ loop */
557
558   while (state == ST_WAIT) {
559     if (sel_select(&sel)) {
560       if (errno == EINTR) continue;
561       die(254, "select failed: %s", strerror(errno));
562     }
563   }
564
565   /* --- Check and translate the exit code --- */
566
567   switch (state) {
568     case ST_ABORT:
569       exit(251);
570     case ST_DONE:
571       if (WIFEXITED(sc.rc))
572         exit(WEXITSTATUS(sc.rc));
573       else if (WIFSIGNALED(sc.rc))
574         exit(128 | WTERMSIG(sc.rc));
575       else
576         exit(128);
577     default:
578       moan("FATAL: unexpected state %d", state);
579       abort();
580   }
581 }
582
583 /*----- That's all, folks -------------------------------------------------*/