xref: /openbsd/usr.bin/timeout/timeout.c (revision 5a38ef86)
1 /* $OpenBSD: timeout.c,v 1.19 2021/09/04 11:49:11 schwarze Exp $ */
2 
3 /*
4  * Copyright (c) 2021 Job Snijders <job@openbsd.org>
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 
31 #include <sys/types.h>
32 #include <sys/time.h>
33 #include <sys/wait.h>
34 
35 #include <err.h>
36 #include <errno.h>
37 #include <getopt.h>
38 #include <limits.h>
39 #include <signal.h>
40 #include <stdbool.h>
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <unistd.h>
45 
46 #define EXIT_TIMEOUT 124
47 
48 static sig_atomic_t sig_chld = 0;
49 static sig_atomic_t sig_term = 0;
50 static sig_atomic_t sig_alrm = 0;
51 static sig_atomic_t sig_ign = 0;
52 
53 static void __dead
54 usage(void)
55 {
56 	fprintf(stderr,
57 	    "usage: timeout [-k time] [-s sig] [--foreground]"
58 	    " [--preserve-status] duration\n"
59 	    "               command [args]\n");
60 
61 	exit(1);
62 }
63 
64 static double
65 parse_duration(const char *duration)
66 {
67 	double 	 ret;
68 	char 	*suffix;
69 
70 	ret = strtod(duration, &suffix);
71 	if (ret == 0 && suffix == duration)
72 		errx(1, "duration is not a number");
73 	if (ret < 0 || ret >= 100000000UL)
74 		errx(1, "duration out of range");
75 
76 	if (suffix == NULL || *suffix == '\0')
77 		return (ret);
78 
79 	if (suffix[1] != '\0')
80 		errx(1, "duration unit suffix too long");
81 
82 	switch (*suffix) {
83 	case 's':
84 		break;
85 	case 'm':
86 		ret *= 60;
87 		break;
88 	case 'h':
89 		ret *= 60 * 60;
90 		break;
91 	case 'd':
92 		ret *= 60 * 60 * 24;
93 		break;
94 	default:
95 		errx(1, "duration unit suffix is invalid");
96 	}
97 
98 	return (ret);
99 }
100 
101 static int
102 parse_signal(const char *str)
103 {
104 	long long	 sig;
105 	const char	*errstr;
106 
107 	if (strncasecmp(str, "SIG", 3) == 0) {
108 		int i;
109 
110 		str += 3;
111 		for (i = 1; i < NSIG; i++) {
112 			if (strcasecmp(str, sys_signame[i]) == 0)
113 				return (i);
114 		}
115 		errx(1, "invalid signal name");
116 	}
117 
118 	sig = strtonum(str, 1, NSIG, &errstr);
119 	if (errstr != NULL)
120 		errx(1, "signal %s %s", str, errstr);
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 
133 	switch (signo) {
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)
160 		err(1, "setitimer");
161 }
162 
163 int
164 main(int argc, char **argv)
165 {
166 	int		ch;
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,
178 			    SIGALRM, SIGQUIT};
179 
180 	const struct option longopts[] = {
181 		{ "preserve-status", no_argument,       &preserve,    1 },
182 		{ "foreground",      no_argument,       &foreground,  1 },
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 
189 	if (pledge("stdio proc exec", NULL) == -1)
190 		err(1, "pledge");
191 
192 	while ((ch = getopt_long(argc, argv, "+k:s:h", longopts, NULL)) != -1) {
193 		switch (ch) {
194 		case 'k':
195 			do_second_kill = true;
196 			second_kill = parse_duration(optarg);
197 			break;
198 		case 's':
199 			killsig = parse_signal(optarg);
200 			break;
201 		case 0:
202 			break;
203 		default:
204 			usage();
205 			break;
206 		}
207 	}
208 
209 	argc -= optind;
210 	argv += optind;
211 
212 	if (argc < 2)
213 		usage();
214 
215 	first_kill = parse_duration(argv[0]);
216 	argc--;
217 	argv++;
218 
219 	if (!foreground) {
220 		pgid = setpgid(0, 0);
221 
222 		if (pgid == -1)
223 			err(1, "setpgid");
224 	}
225 
226 	memset(&signals, 0, sizeof(signals));
227 	sigemptyset(&signals.sa_mask);
228 
229 	if (killsig != SIGKILL && killsig != SIGSTOP)
230 		signums[0] = killsig;
231 
232 	for (i = 0; i < sizeof(signums) / sizeof(signums[0]); i++)
233 		sigaddset(&signals.sa_mask, signums[i]);
234 
235 	signals.sa_handler = sig_handler;
236 	signals.sa_flags = SA_RESTART;
237 
238 	for (i = 0; i < sizeof(signums) / sizeof(signums[0]); i++) {
239 		if (signums[i] != -1 && signums[i] != 0 &&
240 		    sigaction(signums[i], &signals, NULL) == -1)
241 			err(1, "sigaction");
242 	}
243 
244 	signal(SIGTTIN, SIG_IGN);
245 	signal(SIGTTOU, SIG_IGN);
246 
247 	pid = fork();
248 	if (pid == -1)
249 		err(1, "fork");
250 	else if (pid == 0) {
251 		/* child process */
252 		signal(SIGTTIN, SIG_DFL);
253 		signal(SIGTTOU, SIG_DFL);
254 
255 		execvp(argv[0], argv);
256 		err(1, "%s", argv[0]);
257 	}
258 
259 	/* parent continues here */
260 
261 	if (pledge("stdio proc", NULL) == -1)
262 		err(1, "pledge");
263 
264 	if (sigprocmask(SIG_BLOCK, &signals.sa_mask, NULL) == -1)
265 		err(1, "sigprocmask");
266 
267 	set_interval(first_kill);
268 
269 	for (;;) {
270 		sigemptyset(&signals.sa_mask);
271 		sigsuspend(&signals.sa_mask);
272 
273 		if (sig_chld) {
274 			sig_chld = 0;
275 			while (((cpid = wait(&status)) < 0) && errno == EINTR)
276 				continue;
277 
278 			if (cpid == pid) {
279 				pstat = status;
280 				break;
281 			}
282 		} else if (sig_alrm) {
283 			sig_alrm = 0;
284 
285 			timedout = true;
286 			if (!foreground)
287 				killpg(pgid, killsig);
288 			else
289 				kill(pid, killsig);
290 
291 			if (do_second_kill) {
292 				set_interval(second_kill);
293 				second_kill = 0;
294 				sig_ign = killsig;
295 				killsig = SIGKILL;
296 			} else
297 				break;
298 
299 		} else if (sig_term) {
300 			if (!foreground)
301 				killpg(pgid, killsig);
302 			else
303 				kill(pid, (int)sig_term);
304 
305 			if (do_second_kill) {
306 				set_interval(second_kill);
307 				second_kill = 0;
308 				sig_ign = killsig;
309 				killsig = SIGKILL;
310 			} else
311 				break;
312 		}
313 	}
314 
315 	while (cpid != pid && wait(&pstat) == -1) {
316 		if (errno != EINTR)
317 			err(1, "wait");
318 	}
319 
320 	if (WEXITSTATUS(pstat))
321 		pstat = WEXITSTATUS(pstat);
322 	else if (WIFSIGNALED(pstat))
323 		pstat = 128 + WTERMSIG(pstat);
324 
325 	if (timedout && !preserve)
326 		pstat = EXIT_TIMEOUT;
327 
328 	return (pstat);
329 }
330