chiark / gitweb /
debian/control: Fix architectures for x86-model.
[misc] / mtimeout.c
CommitLineData
e825e5a9
MW
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
76static sel_state sel;
77static int state;
78
79enum {
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
96struct namesig {
97 const char *name;
98 int sig;
99};
100
101int cmp_namesig(const void *k, const void *v)
102 { const struct namesig *ns = v; return (strcmp(k, ns->name)); }
103
104static int namesig(const char *p)
105{
106 const static struct namesig tab[] = {
e33fa755
MW
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.
e825e5a9 112
e33fa755
MW
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***/
e825e5a9
MW
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
e33fa755 243 /***END***/
e825e5a9
MW
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
255static void usage(FILE *fp)
256 { pquis(fp, "Usage: $ [-s SIG] SECONDS COMMAND [ARGUMENTS ...]\n"); }
257
258static void version(FILE *fp)
259 { pquis(fp, "$ (version " VERSION ")\n"); }
260
261static void help(FILE *fp)
262{
263 version(fp); fputc('\n', stdout);
264 usage(fp);
265 pquis(fp, "\n\
266Run COMMAND, giving it the ARGUMENTS. If it fails to complete within the\n\
267specified number of SECONDS, kill it. Otherwise exit with the status it\n\
268returns.\n \
269\n\
270Options:\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
290struct timeout {
291 sel_timer t;
292 int panic;
293 int sig;
294 pid_t kid;
295};
296
297static 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
328struct sigchld {
329 pid_t kid;
330 int rc;
331};
332
333static 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
353static 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
370int 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++;
3ece3fc9 419 if (*p) die(253, "bad time value `%s'", argv[0]);
e825e5a9
MW
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);
3ece3fc9 446 die(252, "exec(%s) failed: %s", argv[1], strerror(errno));
e825e5a9
MW
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 -------------------------------------------------*/