xref: /openbsd-src/usr.bin/timeout/timeout.c (revision 25c4e8bd056e974b28f4a0ffd39d76c190a56013)
1 /* $OpenBSD: timeout.c,v 1.21 2022/07/02 19:00:35 kn 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 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 {
57 	fprintf(stderr,
58 	    "usage: timeout [-k time] [-s sig] [--foreground]"
59 	    " [--preserve-status] duration\n"
60 	    "               command [args]\n");
61 
62 	exit(1);
63 }
64 
65 static double
66 parse_duration(const char *duration)
67 {
68 	double	 ret;
69 	char	*suffix;
70 
71 	ret = strtod(duration, &suffix);
72 	if (ret == 0 && suffix == duration)
73 		errx(1, "duration is not a number");
74 	if (ret < 0 || ret >= 100000000UL)
75 		errx(1, "duration out of range");
76 
77 	if (suffix == NULL || *suffix == '\0')
78 		return (ret);
79 
80 	if (suffix[1] != '\0')
81 		errx(1, "duration unit suffix too long");
82 
83 	switch (*suffix) {
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:
96 		errx(1, "duration unit suffix is invalid");
97 	}
98 
99 	return (ret);
100 }
101 
102 static int
103 parse_signal(const char *str)
104 {
105 	long long	 sig;
106 	const char	*errstr;
107 
108 	if (isalpha((unsigned char)*str)) {
109 		int i;
110 
111 		if (strncasecmp(str, "SIG", 3) == 0)
112 			str += 3;
113 		for (i = 1; i < NSIG; i++) {
114 			if (strcasecmp(str, sys_signame[i]) == 0)
115 				return (i);
116 		}
117 		errx(1, "invalid signal name");
118 	}
119 
120 	sig = strtonum(str, 1, NSIG, &errstr);
121 	if (errstr != NULL)
122 		errx(1, "signal %s %s", str, errstr);
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 
135 	switch (signo) {
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)
162 		err(1, "setitimer");
163 }
164 
165 int
166 main(int argc, char **argv)
167 {
168 	int		ch;
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,
180 			    SIGALRM, SIGQUIT};
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 
191 	if (pledge("stdio proc exec", NULL) == -1)
192 		err(1, "pledge");
193 
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) {
222 		pgid = setpgid(0, 0);
223 
224 		if (pgid == -1)
225 			err(1, "setpgid");
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 
234 	for (i = 0; i < sizeof(signums) / sizeof(signums[0]); i++)
235 		sigaddset(&signals.sa_mask, signums[i]);
236 
237 	signals.sa_handler = sig_handler;
238 	signals.sa_flags = SA_RESTART;
239 
240 	for (i = 0; i < sizeof(signums) / sizeof(signums[0]); i++) {
241 		if (signums[i] != -1 && signums[i] != 0 &&
242 		    sigaction(signums[i], &signals, NULL) == -1)
243 			err(1, "sigaction");
244 	}
245 
246 	signal(SIGTTIN, SIG_IGN);
247 	signal(SIGTTOU, SIG_IGN);
248 
249 	pid = fork();
250 	if (pid == -1)
251 		err(1, "fork");
252 	else if (pid == 0) {
253 		/* child process */
254 		signal(SIGTTIN, SIG_DFL);
255 		signal(SIGTTOU, SIG_DFL);
256 
257 		execvp(argv[0], argv);
258 		err(1, "%s", argv[0]);
259 	}
260 
261 	/* parent continues here */
262 
263 	if (pledge("stdio proc", NULL) == -1)
264 		err(1, "pledge");
265 
266 	if (sigprocmask(SIG_BLOCK, &signals.sa_mask, NULL) == -1)
267 		err(1, "sigprocmask");
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 
317 	while (cpid != pid && wait(&pstat) == -1) {
318 		if (errno != EINTR)
319 			err(1, "wait");
320 	}
321 
322 	if (WEXITSTATUS(pstat))
323 		pstat = WEXITSTATUS(pstat);
324 	else if (WIFSIGNALED(pstat))
325 		pstat = 128 + WTERMSIG(pstat);
326 
327 	if (timedout && !preserve)
328 		pstat = EXIT_TIMEOUT;
329 
330 	return (pstat);
331 }
332