mtimeout.1: Use correct dash for number ranges.
[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 {
291 pquis(fp,
292 "Usage: $ [-K] [-b TIME] [-k TIME] [-s SIG] "
293 "TIME COMMAND [ARGUMENTS ...]\n");
294 }
295
296 static void version(FILE *fp)
297 { pquis(fp, "$ (version " VERSION ")\n"); }
298
299 static void help(FILE *fp)
300 {
301 version(fp); fputc('\n', stdout);
302 usage(fp);
303 pquis(fp, "\n\
304 Run COMMAND, giving it the ARGUMENTS. If it fails to complete within the\n\
305 specified number of TIME, kill it. Otherwise exit with the status it\n\
306 returns.\n\
307 \n\
308 A TIME is a possibly fractional number followed by an optional unit\n\
309 designator, which may be `s', `m', `h', or `d', for seconds, minutes,\n\
310 hours, or days, respectively. The default units are seconds.\n\
311 \n\
312 Options:\n\
313 -h, --help Show this help text.\n\
314 -v, --version Show version string.\n\
315 -u, --usage Show a terse usage summary.\n\
316 \n\
317 -b, --bored-after=TIME Wait for TIME after sending SIGKILL.\n\
318 -k, --kill-after=TIME Wait for TIME after signal before sending SIGKILL.\n\
319 -K, --no-kill Don't send SIGKILL; just give up when bored.\n\
320 -s, --signal=SIG Send signal SIG to the command.\n\
321 ");
322 }
323
324 /*----- Timeout handling --------------------------------------------------*/
325
326 /* --- @timeout@ --- *
327 *
328 * The timeout sequencing stuff is complicated, so here's a simple machine to
329 * make it work.
330 */
331
332 enum {
333 TA_MOAN, /* Report message to user */
334 #define TAARG_MOAN s
335
336 TA_KILL, /* Send a signal */
337 #define TAARG_KILL i
338
339 TA_GOTO, /* Goto different state */
340 #define TAARG_GOTO i
341
342 TA_WAIT, /* Wait for more time */
343 #define TAARG_WAIT tv
344
345 TA_STATE, /* Alter the internal state */
346 #define TAARG_STATE i
347 };
348
349 struct tmact {
350 unsigned act;
351 union {
352 const char *s; /* String parameter */
353 int i; /* Integer parameter */
354 struct timeval tv; /* Time parameter */
355 } u;
356 };
357
358 struct timeout {
359 sel_timer t; /* Timer intrusion */
360 struct tmact *ta; /* Instruction vector */
361 int ip; /* Instruction pointer */
362 pid_t kid;
363 };
364
365 static void timeout(struct timeval *now, void *p)
366 {
367 struct timeout *t = p;
368 struct tmact *ta;
369 struct timeval tv;
370
371 for (;;) {
372 ta = &t->ta[t->ip++];
373 switch (ta->act) {
374 case TA_MOAN:
375 moan("%s", ta->u.s);
376 break;
377 case TA_KILL:
378 kill(-t->kid, ta->u.i);
379 break;
380 case TA_GOTO:
381 t->ip = ta->u.i;
382 break;
383 case TA_STATE:
384 state = ta->u.i;
385 return;
386 case TA_WAIT:
387 TV_ADD(&tv, now, &ta->u.tv);
388 sel_addtimer(&sel, &t->t, &tv, timeout, t);
389 return;
390 default:
391 moan("unexpected tmact %u", ta->act);
392 abort();
393 }
394 }
395 }
396
397 /*----- Signal handling ---------------------------------------------------*/
398
399 /* --- @sigchld@ --- *
400 *
401 * If it's our child that died, fetch its exit status and stop.
402 */
403
404 struct sigchld {
405 pid_t kid;
406 int rc;
407 };
408
409 static void sigchld(int sig, void *p)
410 {
411 struct sigchld *s = p;
412 int rc;
413 pid_t pid;
414
415 if ((pid = waitpid(s->kid, &rc, WNOHANG)) < 0)
416 die(254, "waitpid failed: %s", strerror(errno));
417 if (pid == s->kid) {
418 s->rc = rc;
419 state = ST_DONE;
420 }
421 }
422
423 /* --- @sigpropagate@ --- *
424 *
425 * Propagate various signals to the child process group and then maybe act on
426 * them.
427 */
428
429 static void sigpropagate(int sig, void *p)
430 {
431 struct sigchld *s = p;
432
433 kill(-s->kid, sig);
434 switch (sig) {
435 case SIGTSTP: raise(SIGSTOP); break;
436 }
437 }
438
439 #define PROPAGATE_SIGNALS(_) \
440 _(TSTP) _(CONT) _(INT) _(HUP) _(QUIT)
441
442 /*----- Main program ------------------------------------------------------*/
443
444 /* --- @main@ --- */
445
446 int main(int argc, char *const argv[])
447 {
448 pid_t kid;
449 struct timeout to;
450 struct timeval now;
451
452 #define PAIR(x, y) { x, y }
453 #define TACODE(I) \
454 I(sigwait, WAIT, PAIR(0, 0)) \
455 I(_a, MOAN, "timed out: killing child process") \
456 I(sig, KILL, SIGTERM) \
457 I(killwait, WAIT, PAIR(5, 0)) \
458 I(_b, MOAN, "child hasn't responded: killing harder") \
459 I(_c, KILL, SIGKILL) \
460 I(boredwait, WAIT, PAIR(5, 0)) \
461 I(_d, MOAN, "child still undead: giving up") \
462 I(_e, STATE, ST_ABORT)
463
464 enum {
465 #define TALBL(label, op, arg) taoff_##label,
466 TACODE(TALBL)
467 #undef TALBL
468 taoff_end
469 };
470
471 static struct tmact ta[] = {
472 #define TAASM(label, op, arg) { TA_##op, { .TAARG_##op = arg } },
473 TACODE(TAASM)
474 #undef TAASM
475 };
476
477 struct sigchld sc;
478 sig sig_CHLD;
479 #define DEFSIG(tag) sig sig_##tag;
480 PROPAGATE_SIGNALS(DEFSIG)
481 #undef DEFSIG
482 unsigned f = 0;
483 #define F_BOGUS 1u
484
485 /* --- Option parsing --- */
486
487 ego(argv[0]);
488
489 for (;;) {
490 static const struct option opts[] = {
491 { "help", 0, 0, 'h' },
492 { "version", 0, 0, 'v' },
493 { "usage", 0, 0, 'u' },
494 { "no-kill", 0, 0, 'K' },
495 { "kill-after", OPTF_ARGREQ, 0, 'k' },
496 { "bored-after", OPTF_ARGREQ, 0, 'b' },
497 { "signal", OPTF_ARGREQ, 0, 's' },
498 { 0, 0, 0, 0 }
499 };
500
501 int i = mdwopt(argc, argv, "+hvuKb:k:s:", opts, 0, 0, 0);
502 if (i < 0) break;
503 switch (i) {
504 case 'h': help(stdout); exit(0);
505 case 'v': version(stdout); exit(0);
506 case 'u': usage(stdout); exit(0);
507 case 'b':
508 ta[taoff_boredwait].act = TA_WAIT;
509 strtotime(optarg, &ta[taoff_boredwait].u.tv);
510 break;
511 case 'k':
512 ta[taoff_killwait].act = TA_WAIT;
513 strtotime(optarg, &ta[taoff_killwait].u.tv);
514 break;
515 case 'K':
516 ta[taoff_killwait].act = TA_GOTO;
517 ta[taoff_killwait].u.i = taoff_boredwait;
518 break;
519 case 's':
520 if ((ta[taoff_sig].u.i = namesig(optarg)) < 0)
521 die(253, "bad signal spec `%s'", optarg);
522 break;
523 default: f |= F_BOGUS; break;
524 }
525 }
526 argc -= optind; argv += optind;
527 if ((f & F_BOGUS) || argc < 2) { usage(stderr); exit(253); }
528 strtotime(argv[0], &ta[taoff_sigwait].u.tv);
529
530 /* --- Get things set up --- */
531
532 state = ST_WAIT;
533 sel_init(&sel);
534
535 /* --- Set up signal handling --- *
536 *
537 * Doing anything with asynchronous signals is a mug's game, so we bring
538 * them in-band. Do this before starting the child process, because
539 * otherwise we might miss an immediate @SIGCHLD@.
540 */
541
542 sig_init(&sel);
543 sc.rc = 0;
544 sig_add(&sig_CHLD, SIGCHLD, sigchld, &sc);
545 #define ADDSIG(tag) sig_add(&sig_##tag, SIG##tag, sigpropagate, &sc);
546 PROPAGATE_SIGNALS(ADDSIG)
547 #undef ADDSIG
548
549 /* --- Now start the child process --- */
550
551 if ((kid = fork()) < 0) die(2, "fork failed: %s", strerror(errno));
552 if (!kid) {
553 setpgid(0, 0);
554 execvp(argv[1], argv + 1);
555 die(252, "exec(%s) failed: %s", argv[1], strerror(errno));
556 }
557 sc.kid = kid;
558
559 /* --- Set up the timer --- */
560
561 gettimeofday(&now, 0);
562 to.kid = kid;
563 to.ta = ta;
564 to.ip = 0;
565 timeout(&now, &to);
566
567 /* --- Main @select@ loop */
568
569 while (state == ST_WAIT) {
570 if (sel_select(&sel)) {
571 if (errno == EINTR) continue;
572 die(254, "select failed: %s", strerror(errno));
573 }
574 }
575
576 /* --- Check and translate the exit code --- */
577
578 switch (state) {
579 case ST_ABORT:
580 exit(251);
581 case ST_DONE:
582 if (WIFEXITED(sc.rc))
583 exit(WEXITSTATUS(sc.rc));
584 else if (WIFSIGNALED(sc.rc))
585 exit(128 | WTERMSIG(sc.rc));
586 else
587 exit(128);
588 default:
589 moan("FATAL: unexpected state %d", state);
590 abort();
591 }
592 }
593
594 /*----- That's all, folks -------------------------------------------------*/