Annotation of src/usr.bin/timeout/timeout.c, Revision 1.22
1.22 ! kn 1: /* $OpenBSD: timeout.c,v 1.21 2022/07/02 19:00:35 kn 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,
58: "usage: timeout [-k time] [-s sig] [--foreground]"
59: " [--preserve-status] duration\n"
1.22 ! kn 60: " command [arg ...]\n");
1.1 job 61:
1.2 job 62: exit(1);
1.1 job 63: }
64:
65: static double
66: parse_duration(const char *duration)
67: {
1.20 tb 68: double ret;
69: char *suffix;
1.1 job 70:
1.11 job 71: ret = strtod(duration, &suffix);
72: if (ret == 0 && suffix == duration)
1.19 schwarze 73: errx(1, "duration is not a number");
1.11 job 74: if (ret < 0 || ret >= 100000000UL)
1.19 schwarze 75: errx(1, "duration out of range");
1.1 job 76:
1.11 job 77: if (suffix == NULL || *suffix == '\0')
1.1 job 78: return (ret);
79:
1.19 schwarze 80: if (suffix[1] != '\0')
81: errx(1, "duration unit suffix too long");
1.1 job 82:
1.11 job 83: switch (*suffix) {
1.1 job 84: case 's':
85: break;
86: case 'm':
87: ret *= 60;
88: break;
89: case 'h':
90: ret *= 60 * 60;
91: break;
92: case 'd':
93: ret *= 60 * 60 * 24;
94: break;
95: default:
1.19 schwarze 96: errx(1, "duration unit suffix is invalid");
1.1 job 97: }
98:
99: return (ret);
100: }
101:
102: static int
103: parse_signal(const char *str)
104: {
1.9 deraadt 105: long long sig;
1.4 job 106: const char *errstr;
1.1 job 107:
1.21 kn 108: if (isalpha((unsigned char)*str)) {
1.9 deraadt 109: int i;
110:
1.21 kn 111: if (strncasecmp(str, "SIG", 3) == 0)
112: str += 3;
1.1 job 113: for (i = 1; i < NSIG; i++) {
114: if (strcasecmp(str, sys_signame[i]) == 0)
115: return (i);
116: }
1.15 deraadt 117: errx(1, "invalid signal name");
1.1 job 118: }
119:
1.8 deraadt 120: sig = strtonum(str, 1, NSIG, &errstr);
1.4 job 121: if (errstr != NULL)
1.15 deraadt 122: errx(1, "signal %s %s", str, errstr);
1.1 job 123:
124: return (int)sig;
125: }
126:
127: static void
128: sig_handler(int signo)
129: {
130: if (sig_ign != 0 && signo == sig_ign) {
131: sig_ign = 0;
132: return;
133: }
134:
1.7 job 135: switch (signo) {
1.1 job 136: case SIGINT:
137: case SIGHUP:
138: case SIGQUIT:
139: case SIGTERM:
140: sig_term = signo;
141: break;
142: case SIGCHLD:
143: sig_chld = 1;
144: break;
145: case SIGALRM:
146: sig_alrm = 1;
147: break;
148: }
149: }
150:
151: static void
152: set_interval(double iv)
153: {
154: struct itimerval tim;
155:
156: memset(&tim, 0, sizeof(tim));
157: tim.it_value.tv_sec = (time_t)iv;
158: iv -= (double)tim.it_value.tv_sec;
159: tim.it_value.tv_usec = (suseconds_t)(iv * 1000000UL);
160:
161: if (setitimer(ITIMER_REAL, &tim, NULL) == -1)
1.10 job 162: err(1, "setitimer");
1.1 job 163: }
164:
165: int
166: main(int argc, char **argv)
167: {
168: int ch;
1.20 tb 169: unsigned long i;
170: int foreground = 0, preserve = 0;
171: int pstat, status;
172: int killsig = SIGTERM;
173: pid_t pgid = 0, pid, cpid = 0;
174: double first_kill;
175: double second_kill = 0;
176: bool timedout = false;
177: bool do_second_kill = false;
178: struct sigaction signals;
179: int signums[] = {-1, SIGTERM, SIGINT, SIGHUP, SIGCHLD,
1.5 job 180: SIGALRM, SIGQUIT};
1.1 job 181:
182: const struct option longopts[] = {
183: { "preserve-status", no_argument, &preserve, 1 },
184: { "foreground", no_argument, &foreground, 1 },
185: { "kill-after", required_argument, NULL, 'k'},
186: { "signal", required_argument, NULL, 's'},
187: { "help", no_argument, NULL, 'h'},
188: { NULL, 0, NULL, 0 }
189: };
190:
1.3 job 191: if (pledge("stdio proc exec", NULL) == -1)
192: err(1, "pledge");
193:
1.1 job 194: while ((ch = getopt_long(argc, argv, "+k:s:h", longopts, NULL)) != -1) {
195: switch (ch) {
196: case 'k':
197: do_second_kill = true;
198: second_kill = parse_duration(optarg);
199: break;
200: case 's':
201: killsig = parse_signal(optarg);
202: break;
203: case 0:
204: break;
205: default:
206: usage();
207: break;
208: }
209: }
210:
211: argc -= optind;
212: argv += optind;
213:
214: if (argc < 2)
215: usage();
216:
217: first_kill = parse_duration(argv[0]);
218: argc--;
219: argv++;
220:
221: if (!foreground) {
1.7 job 222: pgid = setpgid(0, 0);
1.1 job 223:
224: if (pgid == -1)
1.10 job 225: err(1, "setpgid");
1.1 job 226: }
227:
228: memset(&signals, 0, sizeof(signals));
229: sigemptyset(&signals.sa_mask);
230:
231: if (killsig != SIGKILL && killsig != SIGSTOP)
232: signums[0] = killsig;
233:
1.7 job 234: for (i = 0; i < sizeof(signums) / sizeof(signums[0]); i++)
1.1 job 235: sigaddset(&signals.sa_mask, signums[i]);
236:
237: signals.sa_handler = sig_handler;
238: signals.sa_flags = SA_RESTART;
239:
1.7 job 240: for (i = 0; i < sizeof(signums) / sizeof(signums[0]); i++) {
1.1 job 241: if (signums[i] != -1 && signums[i] != 0 &&
242: sigaction(signums[i], &signals, NULL) == -1)
1.10 job 243: err(1, "sigaction");
1.1 job 244: }
245:
246: signal(SIGTTIN, SIG_IGN);
247: signal(SIGTTOU, SIG_IGN);
248:
249: pid = fork();
250: if (pid == -1)
1.10 job 251: err(1, "fork");
1.1 job 252: else if (pid == 0) {
253: /* child process */
254: signal(SIGTTIN, SIG_DFL);
255: signal(SIGTTOU, SIG_DFL);
256:
1.17 semarie 257: execvp(argv[0], argv);
1.19 schwarze 258: err(1, "%s", argv[0]);
1.1 job 259: }
1.3 job 260:
1.16 deraadt 261: /* parent continues here */
262:
1.13 deraadt 263: if (pledge("stdio proc", NULL) == -1)
1.3 job 264: err(1, "pledge");
1.1 job 265:
266: if (sigprocmask(SIG_BLOCK, &signals.sa_mask, NULL) == -1)
1.10 job 267: err(1, "sigprocmask");
1.1 job 268:
269: set_interval(first_kill);
270:
271: for (;;) {
272: sigemptyset(&signals.sa_mask);
273: sigsuspend(&signals.sa_mask);
274:
275: if (sig_chld) {
276: sig_chld = 0;
277: while (((cpid = wait(&status)) < 0) && errno == EINTR)
278: continue;
279:
280: if (cpid == pid) {
281: pstat = status;
282: break;
283: }
284: } else if (sig_alrm) {
285: sig_alrm = 0;
286:
287: timedout = true;
288: if (!foreground)
289: killpg(pgid, killsig);
290: else
291: kill(pid, killsig);
292:
293: if (do_second_kill) {
294: set_interval(second_kill);
295: second_kill = 0;
296: sig_ign = killsig;
297: killsig = SIGKILL;
298: } else
299: break;
300:
301: } else if (sig_term) {
302: if (!foreground)
303: killpg(pgid, killsig);
304: else
305: kill(pid, (int)sig_term);
306:
307: if (do_second_kill) {
308: set_interval(second_kill);
309: second_kill = 0;
310: sig_ign = killsig;
311: killsig = SIGKILL;
312: } else
313: break;
314: }
315: }
316:
1.7 job 317: while (cpid != pid && wait(&pstat) == -1) {
1.1 job 318: if (errno != EINTR)
1.10 job 319: err(1, "wait");
1.1 job 320: }
321:
322: if (WEXITSTATUS(pstat))
323: pstat = WEXITSTATUS(pstat);
1.7 job 324: else if (WIFSIGNALED(pstat))
1.1 job 325: pstat = 128 + WTERMSIG(pstat);
326:
327: if (timedout && !preserve)
328: pstat = EXIT_TIMEOUT;
329:
330: return (pstat);
331: }