3 * Run a command with a timeout.
5 * (c) 2011 Mark Wooding
8 /*----- Licensing notice --------------------------------------------------*
10 * This file is part of the Toys utilties collection.
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.
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.
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.
27 /*----- Basic strategy ----------------------------------------------------*
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
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
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
49 /*----- Header files ------------------------------------------------------*/
59 #include <sys/types.h>
63 #include <sys/select.h>
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>
75 /*----- Static variables --------------------------------------------------*/
86 /*----- Argument conversion functions -------------------------------------*/
88 /* --- @namesig@ --- *
90 * Arguments: @const char *p@ = pointer to string
92 * Returns: Signal number, or @-1@ for no match.
94 * Use: Converts a signal name into its number.
102 int cmp_namesig(const void *k
, const void *v
)
103 { const struct namesig
*ns
= v
; return (strcmp(k
, ns
->name
)); }
105 static int namesig(const char *p
)
107 const static struct namesig tab
[] = {
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.
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)))
119 (goto-char (point-min))
120 (search-forward (concat "***" "BEGIN siglist" "***"))
121 (beginning-of-line 2)
122 (delete-region (point)
124 (search-forward "***END***")
127 (dolist (sig (sort (copy-list signals) #'string<))
128 (insert (format "#ifdef SIG%s\n { \"%s\", SIG%s },\n#endif\n"
132 /***BEGIN siglist***/
200 { "STKFLT", SIGSTKFLT
},
233 { "VTALRM", SIGVTALRM
},
236 { "WINCH", SIGWINCH
},
247 const struct namesig
*ns
= bsearch(p
, tab
, N(tab
), sizeof(tab
[0]),
249 if (ns
) return (ns
->sig
);
250 else if (isdigit((unsigned char)*p
)) return (atoi(p
));
254 /* --- @strtotime@ --- *
256 * Arguments: @const char *p@ = pointer to string
257 * @struct timeval *tv@ = where to put the result
261 * Use: Converts a string representation of a duration into an
262 * internal version. Understands various time units.
265 static void strtotime(const char *p
, struct timeval
*tv
)
267 char *q
= (/*unconst*/ char *)p
;
270 while (isspace((unsigned char)*q
)) q
++;
272 while (isspace((unsigned char)*q
)) q
++;
274 case 'd': case 'D': t
*= 24;
275 case 'h': case 'H': t
*= 60;
276 case 'm': case 'M': t
*= 60;
279 while (isspace((unsigned char)*q
)) q
++;
281 if (*q
) die(253, "bad time value `%s'", p
);
284 tv
->tv_usec
= f
* 1000000;
287 /*----- Help functions ----------------------------------------------------*/
289 static void usage(FILE *fp
)
290 { pquis(fp
, "Usage: $ [-s SIG] SECONDS COMMAND [ARGUMENTS ...]\n"); }
292 static void version(FILE *fp
)
293 { pquis(fp
, "$ (version " VERSION
")\n"); }
295 static void help(FILE *fp
)
297 version(fp
); fputc('\n', stdout
);
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\
305 -h, --help Show this help text.\n\
306 -v, --version Show version string.\n\
307 -u, --usage Show a terse usage summary.\n\
309 -s, --signal=SIG Send signal SIG to the command.\n\
313 /*----- Timeout handling --------------------------------------------------*/
315 /* --- @timeout@ --- *
317 * The timeout sequencing stuff is complicated, so here's a simple machine to
322 TA_MOAN
, /* Report message to user */
325 TA_KILL
, /* Send a signal */
328 TA_GOTO
, /* Goto different state */
331 TA_WAIT
, /* Wait for more time */
332 #define TAARG_WAIT tv
334 TA_STATE
, /* Alter the internal state */
335 #define TAARG_STATE i
341 const char *s
; /* String parameter */
342 int i
; /* Integer parameter */
343 struct timeval tv
; /* Time parameter */
348 sel_timer t
; /* Timer intrusion */
349 struct tmact
*ta
; /* Instruction vector */
350 int ip
; /* Instruction pointer */
354 static void timeout(struct timeval
*now
, void *p
)
356 struct timeout
*t
= p
;
361 ta
= &t
->ta
[t
->ip
++];
367 kill(-t
->kid
, ta
->u
.i
);
376 TV_ADD(&tv
, now
, &ta
->u
.tv
);
377 sel_addtimer(&sel
, &t
->t
, &tv
, timeout
, t
);
380 moan("unexpected tmact %u", ta
->act
);
386 /*----- Signal handling ---------------------------------------------------*/
388 /* --- @sigchld@ --- *
390 * If it's our child that died, fetch its exit status and stop.
398 static void sigchld(int sig
, void *p
)
400 struct sigchld
*s
= p
;
404 if ((pid
= waitpid(s
->kid
, &rc
, WNOHANG
)) < 0)
405 die(254, "waitpid failed: %s", strerror(errno
));
412 /* --- @sigpropagate@ --- *
414 * Propagate various signals to the child process group and then maybe act on
418 static void sigpropagate(int sig
, void *p
)
420 struct sigchld
*s
= p
;
424 case SIGTSTP
: raise(SIGSTOP
); break;
428 #define PROPAGATE_SIGNALS(_) \
429 _(TSTP) _(CONT) _(INT) _(HUP) _(QUIT)
431 /*----- Main program ------------------------------------------------------*/
435 int main(int argc
, char *const argv
[])
441 #define PAIR(x, y) { x, y }
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)
454 #define TALBL(label, op, arg) taoff_##label,
460 static struct tmact ta
[] = {
461 #define TAASM(label, op, arg) { TA_##op, { .TAARG_##op = arg } },
468 #define DEFSIG(tag) sig sig_##tag;
469 PROPAGATE_SIGNALS(DEFSIG
)
474 /* --- Option parsing --- */
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' },
490 int i
= mdwopt(argc
, argv
, "+hvuKb:k:s:", opts
, 0, 0, 0);
493 case 'h': help(stdout
); exit(0);
494 case 'v': version(stdout
); exit(0);
495 case 'u': usage(stdout
); exit(0);
497 ta
[taoff_boredwait
].act
= TA_WAIT
;
498 strtotime(optarg
, &ta
[taoff_boredwait
].u
.tv
);
501 ta
[taoff_killwait
].act
= TA_WAIT
;
502 strtotime(optarg
, &ta
[taoff_killwait
].u
.tv
);
505 ta
[taoff_killwait
].act
= TA_GOTO
;
506 ta
[taoff_killwait
].u
.i
= taoff_boredwait
;
509 if ((ta
[taoff_sig
].u
.i
= namesig(optarg
)) < 0)
510 die(253, "bad signal spec `%s'", optarg
);
512 default: f
|= F_BOGUS
; break;
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
);
519 /* --- Get things set up --- */
524 /* --- Set up signal handling --- *
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@.
533 sig_add(&sig_CHLD
, SIGCHLD
, sigchld
, &sc
);
534 #define ADDSIG(tag) sig_add(&sig_##tag, SIG##tag, sigpropagate, &sc);
535 PROPAGATE_SIGNALS(ADDSIG
)
538 /* --- Now start the child process --- */
540 if ((kid
= fork()) < 0) die(2, "fork failed: %s", strerror(errno
));
543 execvp(argv
[1], argv
+ 1);
544 die(252, "exec(%s) failed: %s", argv
[1], strerror(errno
));
548 /* --- Set up the timer --- */
550 gettimeofday(&now
, 0);
556 /* --- Main @select@ loop */
558 while (state
== ST_WAIT
) {
559 if (sel_select(&sel
)) {
560 if (errno
== EINTR
) continue;
561 die(254, "select failed: %s", strerror(errno
));
565 /* --- Check and translate the exit code --- */
571 if (WIFEXITED(sc
.rc
))
572 exit(WEXITSTATUS(sc
.rc
));
573 else if (WIFSIGNALED(sc
.rc
))
574 exit(128 | WTERMSIG(sc
.rc
));
578 moan("FATAL: unexpected state %d", state
);
583 /*----- That's all, folks -------------------------------------------------*/