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 ------------------------------------------------------*/
58 #include <sys/types.h>
62 #include <sys/select.h>
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>
74 /*----- Static variables --------------------------------------------------*/
85 /*----- Argument conversion functions -------------------------------------*/
87 /* --- @namesig@ --- *
89 * Arguments: @const char *p@ = pointer to string
91 * Returns: Signal number, or @-1@ for no match.
93 * Use: Converts a signal name into its number.
101 int cmp_namesig(const void *k, const void *v)
102 { const struct namesig *ns = v; return (strcmp(k, ns->name)); }
104 static int namesig(const char *p)
106 const static struct namesig tab[] = {
108 ;;; The signal name table is very boring to type. To make life less awful,
109 ;;; put the signal names in this list and evaluate the code to get Emacs to
110 ;;; regenerate it. We use @bsearch@ on it, so it's important that it be
111 ;;; sorted: Emacs does this for us.
112 (let ((signals '(HUP INT QUIT ILL ABRT FPE KILL SEGV PIPE ALRM TERM
113 USR1 USR2 CHLD CONT STOP TSTP TTIN TTOU BUS POLL
114 PROF SYS TRAP URG VTALRM XCPU XFSZ IOT EMT STKFLT
115 IO CLD PWR INFO LOST WINCH)))
117 (goto-char (point-min))
118 (let ((start (search-forward (concat "/" "* SIGLIST *" "/\n")))
119 (end (search-forward (concat "/" "* END *" "/\n"))))
120 (delete-region start end))
121 (dolist (sig (sort (copy-list signals) #'string<))
122 (insert (format "#ifdef SIG%s\n { \"%s\", SIG%s },\n#endif\n"
124 (insert (concat "/" "* END *" "/\n"))))
195 { "STKFLT", SIGSTKFLT },
228 { "VTALRM", SIGVTALRM },
231 { "WINCH", SIGWINCH },
242 const struct namesig *ns = bsearch(p, tab, N(tab), sizeof(tab[0]),
244 if (ns) return (ns->sig);
245 else if (isdigit((unsigned char)*p)) return (atoi(p));
249 /*----- Help functions ----------------------------------------------------*/
251 static void usage(FILE *fp)
252 { pquis(fp, "Usage: $ [-s SIG] SECONDS COMMAND [ARGUMENTS ...]\n"); }
254 static void version(FILE *fp)
255 { pquis(fp, "$ (version " VERSION ")\n"); }
257 static void help(FILE *fp)
259 version(fp); fputc('\n', stdout);
262 Run COMMAND, giving it the ARGUMENTS. If it fails to complete within the\n\
263 specified number of SECONDS, kill it. Otherwise exit with the status it\n\
267 -h, --help Show this help text.\n\
268 -v, --version Show version string.\n\
269 -u, --usage Show a terse usage summary.\n\
271 -s, --signal=SIG Send signal SIG to the command.\n\
275 /*----- Timeout handling --------------------------------------------------*/
277 /* --- @timeout@ --- *
279 * The first time, we send the signal requested by the caller. Then we wait
280 * five seconds for the child to die, and send @SIGKILL@. If that still
281 * doesn't help, then we just give up. It's not like there's anything else
282 * we can do which is likely to help. And it's not like the process is going
283 * to be doing anything else in user mode ever again.
293 static void timeout(struct timeval *now, void *p)
295 struct timeout *t = p;
300 moan("timed out: killing child process");
301 kill(-t->kid, t->sig);
304 moan("child hasn't responded: killing harder");
305 kill(-t->kid, SIGKILL);
308 moan("child still undead: giving up");
312 TV_ADDL(&tv, now, 5, 0);
313 sel_addtimer(&sel, &t->t, &tv, timeout, t);
317 /*----- Signal handling ---------------------------------------------------*/
319 /* --- @sigchld@ --- *
321 * If it's our child that died, fetch its exit status and stop.
329 static void sigchld(int sig, void *p)
331 struct sigchld *s = p;
335 if ((pid = waitpid(s->kid, &rc, WNOHANG)) < 0)
336 die(254, "waitpid failed: %s", strerror(errno));
343 /* --- @sigpropagate@ --- *
345 * Propagate various signals to the child process group and then maybe act on
349 static void sigpropagate(int sig, void *p)
351 struct sigchld *s = p;
355 case SIGTSTP: raise(SIGSTOP); break;
359 #define PROPAGATE_SIGNALS(_) \
360 _(TSTP) _(CONT) _(INT) _(HUP) _(QUIT)
362 /*----- Main program ------------------------------------------------------*/
366 int main(int argc, char *const argv[])
376 #define DEFSIG(tag) sig sig_##tag;
377 PROPAGATE_SIGNALS(DEFSIG)
382 /* --- Option parsing --- */
387 static const struct option opts[] = {
388 { "help", 0, 0, 'h' },
389 { "version", 0, 0, 'v' },
390 { "usage", 0, 0, 'u' },
391 { "signal", OPTF_ARGREQ, 0, 's' },
395 int i = mdwopt(argc, argv, "+hvus:", opts, 0, 0, 0);
398 case 'h': help(stdout); exit(0);
399 case 'v': version(stdout); exit(0);
400 case 'u': usage(stdout); exit(0);
402 if ((signo = namesig(optarg)) < 0)
403 die(253, "bad signal spec `%s'", optarg);
405 default: f |= F_BOGUS; break;
408 argc -= optind; argv += optind;
409 if ((f & F_BOGUS) || argc < 2) { usage(stderr); exit(253); }
412 while (isspace((unsigned char)*p)) p++;
413 t = strtod(argv[0], &p);
414 while (isspace((unsigned char)*p)) p++;
415 if (*p) die(253, "bad time value `%s'", argv[0]);
417 /* --- Get things set up --- */
422 /* --- Set up signal handling --- *
424 * Doing anything with asynchronous signals is a mug's game, so we bring
425 * them in-band. Do this before starting the child process, because
426 * otherwise we might miss an immediate @SIGCHLD@.
431 sig_add(&sig_CHLD, SIGCHLD, sigchld, &sc);
432 #define ADDSIG(tag) sig_add(&sig_##tag, SIG##tag, sigpropagate, &sc);
433 PROPAGATE_SIGNALS(ADDSIG)
436 /* --- Now start the child process --- */
438 if ((kid = fork()) < 0) die(2, "fork failed: %s", strerror(errno));
441 execvp(argv[1], argv + 1);
442 die(252, "exec(%s) failed: %s", argv[1], strerror(errno));
446 /* --- Set up the timer --- */
451 gettimeofday(&tv, 0);
452 TV_ADDL(&tv, &tv, (time_t)t, ((long)(t * 1000000))%1000000);
453 sel_addtimer(&sel, &to.t, &tv, timeout, &to);
455 /* --- Main @select@ loop */
457 while (state == ST_WAIT) {
458 if (sel_select(&sel)) {
459 if (errno == EINTR) continue;
460 die(254, "select failed: %s", strerror(errno));
464 /* --- Check and translate the exit code --- */
470 if (WIFEXITED(sc.rc))
471 exit(WEXITSTATUS(sc.rc));
472 else if (WIFSIGNALED(sc.rc))
473 exit(128 | WTERMSIG(sc.rc));
477 moan("FATAL: unexpected state %d", state);
482 /*----- That's all, folks -------------------------------------------------*/