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