xref: /aosp_15_r20/external/toybox/toys/other/timeout.c (revision cf5a6c84e2b8763fc1a7db14496fd4742913b199)
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