1 /* timeout.c - Run command line with a timeout
2 *
3 * Copyright 2013 Rob Landley <[email protected]>
4 *
5 * No standard
6
7 USE_TIMEOUT(NEWTOY(timeout, "<2^(foreground)(preserve-status)vk:s(signal):i", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_ARGFAIL(125)))
8
9 config TIMEOUT
10 bool "timeout"
11 default y
12 help
13 usage: timeout [-iv] [-k DURATION] [-s SIGNAL] DURATION COMMAND...
14
15 Run command line as a child process, sending child a signal if the
16 command doesn't exit soon enough.
17
18 DURATION can be a decimal fraction. An optional suffix can be "m"
19 (minutes), "h" (hours), "d" (days), or "s" (seconds, the default).
20
21 -i Only kill for inactivity (restart timeout when command produces output)
22 -k Send KILL signal if child still running this long after first signal
23 -s Send specified signal (default TERM)
24 -v Verbose
25 --foreground Don't create new process group
26 --preserve-status Exit with the child's exit status
27 */
28
29 #define FOR_timeout
30 #include "toys.h"
31
32 GLOBALS(
33 char *s, *k;
34
35 struct pollfd pfd;
36 sigjmp_buf sj;
37 int fds[2], pid, rc;
38 )
39
handler(int sig,siginfo_t * si)40 static void handler(int sig, siginfo_t *si)
41 {
42 TT.rc = si->si_status + ((si->si_code!=CLD_EXITED)<<7);
43 siglongjmp(TT.sj, 1);
44 }
45
nantomil(struct timespec * ts)46 static long nantomil(struct timespec *ts)
47 {
48 return ts->tv_sec*1000+ts->tv_nsec/1000000;
49 }
50
callback(char * argv[])51 static void callback(char *argv[])
52 {
53 if (!FLAG(foreground)) setpgid(0, 0);
54 }
55
timeout_main(void)56 void timeout_main(void)
57 {
58 int ii, ms, nextsig = SIGTERM;
59 struct timespec tts, kts;
60
61 // Use same ARGFAIL value for any remaining parsing errors
62 toys.exitval = 125;
63 xparsetimespec(*toys.optargs, &tts);
64 if (TT.k) xparsetimespec(TT.k, &kts);
65 if (TT.s && -1==(nextsig = sig_to_num(TT.s))) error_exit("bad -s: '%s'",TT.s);
66
67 toys.exitval = 0;
68 TT.pfd.events = POLLIN;
69 TT.fds[1] = -1;
70 if (sigsetjmp(TT.sj, 1)) goto done;
71 xsignal_flags(SIGCHLD, handler, SA_NOCLDSTOP|SA_SIGINFO);
72
73 TT.pid = xpopen_setup(toys.optargs+1, FLAG(i) ? TT.fds : 0, callback);
74 xsignal(SIGTTIN, SIG_IGN);
75 xsignal(SIGTTOU, SIG_IGN);
76 xsignal(SIGTSTP, SIG_IGN);
77 if (!FLAG(i)) xpipe(TT.fds);
78 TT.pfd.fd = TT.fds[1];
79 ms = nantomil(&tts);
80 for (;;) {
81 if (1 != xpoll(&TT.pfd, 1, ms)) {
82 if (FLAG(v))
83 perror_msg("sending signal %s to command %s", num_to_sig(nextsig),
84 toys.optargs[1]);
85 toys.exitval = (nextsig==9) ? 137 : 124;
86 kill(FLAG(foreground) ? TT.pid : -TT.pid, nextsig);
87 if (!TT.k || nextsig==SIGKILL) break;
88 nextsig = SIGKILL;
89 ms = nantomil(&kts);
90
91 continue;
92 }
93 if (TT.pfd.revents&POLLIN) {
94 errno = 0;
95 if (1>(ii = read(TT.fds[1], toybuf, sizeof(toybuf)))) {
96 if (errno==EINTR) continue;
97 break;
98 }
99 writeall(1, toybuf, ii);
100 }
101 if (TT.pfd.revents&POLLHUP) break;
102 }
103 done:
104 xpclose_both(TT.pid, TT.fds);
105
106 if (FLAG(preserve_status) || !toys.exitval) toys.exitval = TT.rc;
107 }
108