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