xref: /openbsd-src/usr.bin/timeout/timeout.c (revision 17e3ddc9933714bd053ce70afd6d575a774aee60)
1 /* $OpenBSD: timeout.c,v 1.26 2023/11/03 19:16:31 cheloha 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 <ctype.h>
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 volatile sig_atomic_t sig_chld = 0;
50 static volatile sig_atomic_t sig_term = 0;
51 static volatile sig_atomic_t sig_alrm = 0;
52 static volatile sig_atomic_t sig_ign = 0;
53 
54 static void __dead
usage(void)55 usage(void)
56 {
57 	fprintf(stderr,
58 	    "usage: timeout [-fp] [-k time] [-s signal] duration command"
59 	    " [arg ...]\n");
60 	exit(1);
61 }
62 
63 static double
parse_duration(const char * duration)64 parse_duration(const char *duration)
65 {
66 	double	 ret;
67 	char	*suffix;
68 
69 	ret = strtod(duration, &suffix);
70 	if (ret == 0 && suffix == duration)
71 		errx(1, "duration is not a number");
72 	if (ret < 0 || ret >= 100000000UL)
73 		errx(1, "duration out of range");
74 
75 	if (suffix == NULL || *suffix == '\0')
76 		return (ret);
77 
78 	if (suffix[1] != '\0')
79 		errx(1, "duration unit suffix too long");
80 
81 	switch (*suffix) {
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:
94 		errx(1, "duration unit suffix is invalid");
95 	}
96 
97 	return (ret);
98 }
99 
100 static int
parse_signal(const char * str)101 parse_signal(const char *str)
102 {
103 	long long	 sig;
104 	const char	*errstr;
105 
106 	if (isalpha((unsigned char)*str)) {
107 		int i;
108 
109 		if (strncasecmp(str, "SIG", 3) == 0)
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
sig_handler(int signo)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
set_interval(double iv)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
main(int argc,char ** argv)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,       NULL,        'p'},
182 		{ "foreground",      no_argument,       NULL,        'f'},
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, "+fk:ps:h", longopts, NULL))
193 	    != -1) {
194 		switch (ch) {
195 		case 'f':
196 			foreground = 1;
197 			break;
198 		case 'k':
199 			do_second_kill = true;
200 			second_kill = parse_duration(optarg);
201 			break;
202 		case 'p':
203 			preserve = 1;
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) {
227 		pgid = setpgid(0, 0);
228 
229 		if (pgid == -1)
230 			err(1, "setpgid");
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 
239 	for (i = 0; i < sizeof(signums) / sizeof(signums[0]); i++)
240 		sigaddset(&signals.sa_mask, signums[i]);
241 
242 	signals.sa_handler = sig_handler;
243 	signals.sa_flags = SA_RESTART;
244 
245 	for (i = 0; i < sizeof(signums) / sizeof(signums[0]); i++) {
246 		if (signums[i] != -1 && signums[i] != 0 &&
247 		    sigaction(signums[i], &signals, NULL) == -1)
248 			err(1, "sigaction");
249 	}
250 
251 	signal(SIGTTIN, SIG_IGN);
252 	signal(SIGTTOU, SIG_IGN);
253 
254 	pid = fork();
255 	if (pid == -1)
256 		err(1, "fork");
257 	else if (pid == 0) {
258 		/* child process */
259 		signal(SIGTTIN, SIG_DFL);
260 		signal(SIGTTOU, SIG_DFL);
261 
262 		execvp(argv[0], argv);
263 		warn("%s", argv[0]);
264 		_exit(errno == ENOENT ? 127 : 126);
265 	}
266 
267 	/* parent continues here */
268 
269 	if (pledge("stdio proc", NULL) == -1)
270 		err(1, "pledge");
271 
272 	if (sigprocmask(SIG_BLOCK, &signals.sa_mask, NULL) == -1)
273 		err(1, "sigprocmask");
274 
275 	set_interval(first_kill);
276 
277 	for (;;) {
278 		sigemptyset(&signals.sa_mask);
279 		sigsuspend(&signals.sa_mask);
280 
281 		if (sig_chld) {
282 			sig_chld = 0;
283 			while (((cpid = wait(&status)) < 0) && errno == EINTR)
284 				continue;
285 
286 			if (cpid == pid) {
287 				pstat = status;
288 				break;
289 			}
290 		} else if (sig_alrm) {
291 			sig_alrm = 0;
292 
293 			timedout = true;
294 			if (!foreground)
295 				killpg(pgid, killsig);
296 			else
297 				kill(pid, killsig);
298 
299 			if (do_second_kill) {
300 				set_interval(second_kill);
301 				second_kill = 0;
302 				sig_ign = killsig;
303 				killsig = SIGKILL;
304 			} else
305 				break;
306 
307 		} else if (sig_term) {
308 			if (!foreground)
309 				killpg(pgid, killsig);
310 			else
311 				kill(pid, (int)sig_term);
312 
313 			if (do_second_kill) {
314 				set_interval(second_kill);
315 				second_kill = 0;
316 				sig_ign = killsig;
317 				killsig = SIGKILL;
318 			} else
319 				break;
320 		}
321 	}
322 
323 	while (cpid != pid && wait(&pstat) == -1) {
324 		if (errno != EINTR)
325 			err(1, "wait");
326 	}
327 
328 	if (WEXITSTATUS(pstat))
329 		pstat = WEXITSTATUS(pstat);
330 	else if (WIFSIGNALED(pstat))
331 		pstat = 128 + WTERMSIG(pstat);
332 
333 	if (timedout && !preserve)
334 		pstat = EXIT_TIMEOUT;
335 
336 	return (pstat);
337 }
338