Annotation of src/usr.bin/timeout/timeout.c, Revision 1.24
1.24 ! jmc 1: /* $OpenBSD: timeout.c,v 1.23 2023/01/10 14:19:12 job Exp $ */
1.1 job 2:
1.11 job 3: /*
1.10 job 4: * Copyright (c) 2021 Job Snijders <job@openbsd.org>
1.1 job 5: * Copyright (c) 2014 Baptiste Daroussin <bapt@FreeBSD.org>
6: * Copyright (c) 2014 Vsevolod Stakhov <vsevolod@FreeBSD.org>
7: * All rights reserved.
8: *
9: * Redistribution and use in source and binary forms, with or without
10: * modification, are permitted provided that the following conditions
11: * are met:
12: * 1. Redistributions of source code must retain the above copyright
13: * notice, this list of conditions and the following disclaimer
14: * in this position and unchanged.
15: * 2. Redistributions in binary form must reproduce the above copyright
16: * notice, this list of conditions and the following disclaimer in the
17: * documentation and/or other materials provided with the distribution.
18: *
19: * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
20: * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22: * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
23: * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
24: * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28: * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29: */
30:
1.2 job 31: #include <sys/types.h>
1.1 job 32: #include <sys/time.h>
33: #include <sys/wait.h>
34:
1.21 kn 35: #include <ctype.h>
1.1 job 36: #include <err.h>
37: #include <errno.h>
38: #include <getopt.h>
39: #include <limits.h>
40: #include <signal.h>
41: #include <stdbool.h>
42: #include <stdio.h>
43: #include <stdlib.h>
44: #include <string.h>
45: #include <unistd.h>
46:
47: #define EXIT_TIMEOUT 124
48:
49: static sig_atomic_t sig_chld = 0;
50: static sig_atomic_t sig_term = 0;
51: static sig_atomic_t sig_alrm = 0;
52: static sig_atomic_t sig_ign = 0;
53:
54: static void __dead
55: usage(void)
56: {
1.18 jmc 57: fprintf(stderr,
1.24 ! jmc 58: "usage: timeout [-fp] [-k time] [-s signal] duration command"
1.23 job 59: " [arg ...]\n");
1.2 job 60: exit(1);
1.1 job 61: }
62:
63: static double
64: parse_duration(const char *duration)
65: {
1.20 tb 66: double ret;
67: char *suffix;
1.1 job 68:
1.11 job 69: ret = strtod(duration, &suffix);
70: if (ret == 0 && suffix == duration)
1.19 schwarze 71: errx(1, "duration is not a number");
1.11 job 72: if (ret < 0 || ret >= 100000000UL)
1.19 schwarze 73: errx(1, "duration out of range");
1.1 job 74:
1.11 job 75: if (suffix == NULL || *suffix == '\0')
1.1 job 76: return (ret);
77:
1.19 schwarze 78: if (suffix[1] != '\0')
79: errx(1, "duration unit suffix too long");
1.1 job 80:
1.11 job 81: switch (*suffix) {
1.1 job 82: case 's':
83: break;
84: case 'm':
85: ret *= 60;
86: break;
87: case 'h':
88: ret *= 60 * 60;
89: break;
90: case 'd':
91: ret *= 60 * 60 * 24;
92: break;
93: default:
1.19 schwarze 94: errx(1, "duration unit suffix is invalid");
1.1 job 95: }
96:
97: return (ret);
98: }
99:
100: static int
101: parse_signal(const char *str)
102: {
1.9 deraadt 103: long long sig;
1.4 job 104: const char *errstr;
1.1 job 105:
1.21 kn 106: if (isalpha((unsigned char)*str)) {
1.9 deraadt 107: int i;
108:
1.21 kn 109: if (strncasecmp(str, "SIG", 3) == 0)
110: str += 3;
1.1 job 111: for (i = 1; i < NSIG; i++) {
112: if (strcasecmp(str, sys_signame[i]) == 0)
113: return (i);
114: }
1.15 deraadt 115: errx(1, "invalid signal name");
1.1 job 116: }
117:
1.8 deraadt 118: sig = strtonum(str, 1, NSIG, &errstr);
1.4 job 119: if (errstr != NULL)
1.15 deraadt 120: errx(1, "signal %s %s", str, errstr);
1.1 job 121:
122: return (int)sig;
123: }
124:
125: static void
126: sig_handler(int signo)
127: {
128: if (sig_ign != 0 && signo == sig_ign) {
129: sig_ign = 0;
130: return;
131: }
132:
1.7 job 133: switch (signo) {
1.1 job 134: case SIGINT:
135: case SIGHUP:
136: case SIGQUIT:
137: case SIGTERM:
138: sig_term = signo;
139: break;
140: case SIGCHLD:
141: sig_chld = 1;
142: break;
143: case SIGALRM:
144: sig_alrm = 1;
145: break;
146: }
147: }
148:
149: static void
150: set_interval(double iv)
151: {
152: struct itimerval tim;
153:
154: memset(&tim, 0, sizeof(tim));
155: tim.it_value.tv_sec = (time_t)iv;
156: iv -= (double)tim.it_value.tv_sec;
157: tim.it_value.tv_usec = (suseconds_t)(iv * 1000000UL);
158:
159: if (setitimer(ITIMER_REAL, &tim, NULL) == -1)
1.10 job 160: err(1, "setitimer");
1.1 job 161: }
162:
163: int
164: main(int argc, char **argv)
165: {
166: int ch;
1.20 tb 167: unsigned long i;
168: int foreground = 0, preserve = 0;
169: int pstat, status;
170: int killsig = SIGTERM;
171: pid_t pgid = 0, pid, cpid = 0;
172: double first_kill;
173: double second_kill = 0;
174: bool timedout = false;
175: bool do_second_kill = false;
176: struct sigaction signals;
177: int signums[] = {-1, SIGTERM, SIGINT, SIGHUP, SIGCHLD,
1.5 job 178: SIGALRM, SIGQUIT};
1.1 job 179:
180: const struct option longopts[] = {
1.23 job 181: { "preserve-status", no_argument, NULL, 'p'},
182: { "foreground", no_argument, NULL, 'f'},
1.1 job 183: { "kill-after", required_argument, NULL, 'k'},
184: { "signal", required_argument, NULL, 's'},
185: { "help", no_argument, NULL, 'h'},
186: { NULL, 0, NULL, 0 }
187: };
188:
1.3 job 189: if (pledge("stdio proc exec", NULL) == -1)
190: err(1, "pledge");
191:
1.23 job 192: while ((ch = getopt_long(argc, argv, "+fk:ps:h", longopts, NULL))
193: != -1) {
1.1 job 194: switch (ch) {
1.23 job 195: case 'f':
196: foreground = 1;
197: break;
1.1 job 198: case 'k':
199: do_second_kill = true;
200: second_kill = parse_duration(optarg);
1.23 job 201: break;
202: case 'p':
203: preserve = 1;
1.1 job 204: break;
205: case 's':
206: killsig = parse_signal(optarg);
207: break;
208: case 0:
209: break;
210: default:
211: usage();
212: break;
213: }
214: }
215:
216: argc -= optind;
217: argv += optind;
218:
219: if (argc < 2)
220: usage();
221:
222: first_kill = parse_duration(argv[0]);
223: argc--;
224: argv++;
225:
226: if (!foreground) {
1.7 job 227: pgid = setpgid(0, 0);
1.1 job 228:
229: if (pgid == -1)
1.10 job 230: err(1, "setpgid");
1.1 job 231: }
232:
233: memset(&signals, 0, sizeof(signals));
234: sigemptyset(&signals.sa_mask);
235:
236: if (killsig != SIGKILL && killsig != SIGSTOP)
237: signums[0] = killsig;
238:
1.7 job 239: for (i = 0; i < sizeof(signums) / sizeof(signums[0]); i++)
1.1 job 240: sigaddset(&signals.sa_mask, signums[i]);
241:
242: signals.sa_handler = sig_handler;
243: signals.sa_flags = SA_RESTART;
244:
1.7 job 245: for (i = 0; i < sizeof(signums) / sizeof(signums[0]); i++) {
1.1 job 246: if (signums[i] != -1 && signums[i] != 0 &&
247: sigaction(signums[i], &signals, NULL) == -1)
1.10 job 248: err(1, "sigaction");
1.1 job 249: }
250:
251: signal(SIGTTIN, SIG_IGN);
252: signal(SIGTTOU, SIG_IGN);
253:
254: pid = fork();
255: if (pid == -1)
1.10 job 256: err(1, "fork");
1.1 job 257: else if (pid == 0) {
258: /* child process */
259: signal(SIGTTIN, SIG_DFL);
260: signal(SIGTTOU, SIG_DFL);
261:
1.17 semarie 262: execvp(argv[0], argv);
1.19 schwarze 263: err(1, "%s", argv[0]);
1.1 job 264: }
1.3 job 265:
1.16 deraadt 266: /* parent continues here */
267:
1.13 deraadt 268: if (pledge("stdio proc", NULL) == -1)
1.3 job 269: err(1, "pledge");
1.1 job 270:
271: if (sigprocmask(SIG_BLOCK, &signals.sa_mask, NULL) == -1)
1.10 job 272: err(1, "sigprocmask");
1.1 job 273:
274: set_interval(first_kill);
275:
276: for (;;) {
277: sigemptyset(&signals.sa_mask);
278: sigsuspend(&signals.sa_mask);
279:
280: if (sig_chld) {
281: sig_chld = 0;
282: while (((cpid = wait(&status)) < 0) && errno == EINTR)
283: continue;
284:
285: if (cpid == pid) {
286: pstat = status;
287: break;
288: }
289: } else if (sig_alrm) {
290: sig_alrm = 0;
291:
292: timedout = true;
293: if (!foreground)
294: killpg(pgid, killsig);
295: else
296: kill(pid, killsig);
297:
298: if (do_second_kill) {
299: set_interval(second_kill);
300: second_kill = 0;
301: sig_ign = killsig;
302: killsig = SIGKILL;
303: } else
304: break;
305:
306: } else if (sig_term) {
307: if (!foreground)
308: killpg(pgid, killsig);
309: else
310: kill(pid, (int)sig_term);
311:
312: if (do_second_kill) {
313: set_interval(second_kill);
314: second_kill = 0;
315: sig_ign = killsig;
316: killsig = SIGKILL;
317: } else
318: break;
319: }
320: }
321:
1.7 job 322: while (cpid != pid && wait(&pstat) == -1) {
1.1 job 323: if (errno != EINTR)
1.10 job 324: err(1, "wait");
1.1 job 325: }
326:
327: if (WEXITSTATUS(pstat))
328: pstat = WEXITSTATUS(pstat);
1.7 job 329: else if (WIFSIGNALED(pstat))
1.1 job 330: pstat = 128 + WTERMSIG(pstat);
331:
332: if (timedout && !preserve)
333: pstat = EXIT_TIMEOUT;
334:
335: return (pstat);
336: }