xref: /netbsd/usr.bin/timeout/timeout.c (revision 65f114bc)
1 /* $NetBSD: timeout.c,v 1.5 2022/12/13 13:25:36 kre Exp $ */
2 
3 /*-
4  * Copyright (c) 2014 Baptiste Daroussin <bapt@FreeBSD.org>
5  * Copyright (c) 2014 Vsevolod Stakhov <vsevolod@FreeBSD.org>
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer
13  *    in this position and unchanged.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
19  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21  * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
22  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29 
30 #include <sys/cdefs.h>
31 #if !defined(lint)
32 #if 0
33 __FBSDID("$FreeBSD: head/usr.bin/timeout/timeout.c 268763 2014-07-16 13:52:05Z bapt $");
34 #else
35 __RCSID("$NetBSD: timeout.c,v 1.5 2022/12/13 13:25:36 kre Exp $");
36 #endif
37 #endif /* not lint */
38 
39 #include <sys/time.h>
40 #include <sys/wait.h>
41 
42 #include <err.h>
43 #include <errno.h>
44 #include <getopt.h>
45 #include <limits.h>
46 #include <signal.h>
47 #include <stdbool.h>
48 #include <stdio.h>
49 #include <stdlib.h>
50 #include <string.h>
51 #include <sysexits.h>
52 #include <unistd.h>
53 
54 #define EXIT_TIMEOUT 124
55 
56 static sig_atomic_t sig_chld = 0;
57 static sig_atomic_t sig_term = 0;
58 static sig_atomic_t sig_alrm = 0;
59 static sig_atomic_t sig_ign = 0;
60 
61 static void __dead
usage(void)62 usage(void)
63 {
64 
65 	fprintf(stderr, "Usage: %s [--signal sig | -s sig] [--preserve-status]"
66 	    " [--kill-after time | -k time] [--foreground] <duration> <command>"
67 	    " <arg ...>\n", getprogname());
68 
69 	exit(EX_USAGE);
70 }
71 
72 static double
parse_duration(const char * duration)73 parse_duration(const char *duration)
74 {
75 	double ret;
76 	char *end;
77 
78 	ret = strtod(duration, &end);
79 	if (ret == 0 && end == duration)
80 		errx(EXIT_FAILURE, "invalid duration");
81 
82 	if (end == NULL || *end == '\0')
83 		return (ret);
84 
85 	if (end != NULL && *(end + 1) != '\0')
86 		errx(EX_USAGE, "invalid duration");
87 
88 	switch (*end) {
89 	case 's':
90 		break;
91 	case 'm':
92 		ret *= 60;
93 		break;
94 	case 'h':
95 		ret *= 60 * 60;
96 		break;
97 	case 'd':
98 		ret *= 60 * 60 * 24;
99 		break;
100 	default:
101 		errx(EX_USAGE, "invalid duration");
102 	}
103 
104 	if (ret < 0 || ret >= 100000000UL)
105 		errx(EX_USAGE, "invalid duration");
106 
107 	return (ret);
108 }
109 
110 static int
parse_signal(const char * str)111 parse_signal(const char *str)
112 {
113 	long sig;
114 	int i;
115 	char *ep;
116 
117 	if (strncasecmp(str, "SIG", 3) == 0) {
118 		str += 3;
119 
120 		for (i = 1; i < sys_nsig; i++) {
121 			if (strcasecmp(str, sys_signame[i]) == 0)
122 				return (i);
123 		}
124 
125 		goto err;
126 	}
127 
128 	errno = 0;
129 	sig = strtol(str, &ep, 10);
130 
131 	if (str[0] == '\0' || *ep != '\0')
132 		goto err;
133 	if (errno == ERANGE && (sig == LONG_MAX || sig == LONG_MIN))
134 		goto err;
135 	if (sig >= sys_nsig || sig < 0)
136 		goto err;
137 
138 	return (int)sig;
139 
140 err:
141 	errx(EX_USAGE, "invalid signal");
142 }
143 
144 static void
sig_handler(int signo)145 sig_handler(int signo)
146 {
147 	if (sig_ign != 0 && signo == sig_ign) {
148 		sig_ign = 0;
149 		return;
150 	}
151 
152 	switch(signo) {
153 	case 0:
154 	case SIGINT:
155 	case SIGHUP:
156 	case SIGQUIT:
157 	case SIGTERM:
158 		sig_term = signo;
159 		break;
160 	case SIGCHLD:
161 		sig_chld = 1;
162 		break;
163 	case SIGALRM:
164 		sig_alrm = 1;
165 		break;
166 	}
167 }
168 
169 static void
set_interval(double iv)170 set_interval(double iv)
171 {
172 	struct itimerval tim;
173 
174 	memset(&tim, 0, sizeof(tim));
175 	tim.it_value.tv_sec = (time_t)iv;
176 	iv -= (double)tim.it_value.tv_sec;
177 	tim.it_value.tv_usec = (suseconds_t)(iv * 1000000UL);
178 
179 	if (setitimer(ITIMER_REAL, &tim, NULL) == -1)
180 		err(EX_OSERR, "setitimer()");
181 }
182 
183 int
main(int argc,char ** argv)184 main(int argc, char **argv)
185 {
186 	int ch;
187 	unsigned long i;
188 	int foreground, preserve;
189 	int error, pstat, status;
190 	int killsig = SIGTERM;
191 	pid_t pgid, pid, cpid;
192 	double first_kill;
193 	double second_kill;
194 	bool timedout = false;
195 	bool do_second_kill = false;
196 	struct sigaction signals;
197 	int signums[] = {
198 		-1,
199 		SIGTERM,
200 		SIGINT,
201 		SIGHUP,
202 		SIGCHLD,
203 		SIGALRM,
204 		SIGQUIT,
205 	};
206 
207 	setprogname(argv[0]);
208 
209 	foreground = preserve = 0;
210 	second_kill = 0;
211 	cpid = -1;
212 	pgid = -1;
213 
214 	const struct option longopts[] = {
215 		{ "preserve-status", no_argument,       NULL,        'p'},
216 		{ "foreground",      no_argument,       NULL,        'f'},
217 		{ "kill-after",      required_argument, NULL,        'k'},
218 		{ "signal",          required_argument, NULL,        's'},
219 		{ "help",            no_argument,       NULL,        'h'},
220 		{ NULL,              0,                 NULL,         0 }
221 	};
222 
223 	while ((ch =
224 	    getopt_long(argc, argv, "+fk:ps:h", longopts, NULL)) != -1) {
225 		switch (ch) {
226 			case 'f':
227 				foreground = 1;
228 				break;
229 			case 'k':
230 				do_second_kill = true;
231 				second_kill = parse_duration(optarg);
232 				break;
233 			case 'p':
234 				preserve = 1;
235 				break;
236 			case 's':
237 				killsig = parse_signal(optarg);
238 				break;
239 			case 0:
240 				break;
241 			case 'h':
242 			default:
243 				usage();
244 				break;
245 		}
246 	}
247 
248 	argc -= optind;
249 	argv += optind;
250 
251 	if (argc < 2)
252 		usage();
253 
254 	first_kill = parse_duration(argv[0]);
255 	argc--;
256 	argv++;
257 
258 	if (!foreground) {
259 		pgid = setpgid(0,0);
260 
261 		if (pgid == -1)
262 			err(EX_OSERR, "setpgid()");
263 	}
264 
265 	memset(&signals, 0, sizeof(signals));
266 	sigemptyset(&signals.sa_mask);
267 
268 	if (killsig != SIGKILL && killsig != SIGSTOP)
269 		signums[0] = killsig;
270 
271 	for (i = 0; i < sizeof(signums) / sizeof(signums[0]); i ++)
272 		sigaddset(&signals.sa_mask, signums[i]);
273 
274 	signals.sa_handler = sig_handler;
275 	signals.sa_flags = SA_RESTART;
276 
277 	for (i = 0; i < sizeof(signums) / sizeof(signums[0]); i ++)
278 		if (signums[i] != -1 && signums[i] != 0 &&
279 		    sigaction(signums[i], &signals, NULL) == -1)
280 			err(EX_OSERR, "sigaction()");
281 
282 	signal(SIGTTIN, SIG_IGN);
283 	signal(SIGTTOU, SIG_IGN);
284 
285 	pid = fork();
286 	if (pid == -1)
287 		err(EX_OSERR, "fork()");
288 	else if (pid == 0) {
289 		/* child process */
290 		signal(SIGTTIN, SIG_DFL);
291 		signal(SIGTTOU, SIG_DFL);
292 
293 		error = execvp(argv[0], argv);
294 		if (error == -1)
295 			err(EX_UNAVAILABLE, "exec()");
296 	}
297 
298 	if (sigprocmask(SIG_BLOCK, &signals.sa_mask, NULL) == -1)
299 		err(EX_OSERR, "sigprocmask()");
300 
301 	/* parent continues here */
302 	set_interval(first_kill);
303 
304 	for (;;) {
305 		sigemptyset(&signals.sa_mask);
306 		sigsuspend(&signals.sa_mask);
307 
308 		if (sig_chld) {
309 			sig_chld = 0;
310 			while (((cpid = wait(&status)) < 0) && errno == EINTR)
311 				continue;
312 
313 			if (cpid == pid) {
314 				pstat = status;
315 				break;
316 			}
317 		} else if (sig_alrm) {
318 			sig_alrm = 0;
319 
320 			timedout = true;
321 			if (!foreground)
322 				killpg(pgid, killsig);
323 			else
324 				kill(pid, killsig);
325 
326 			if (do_second_kill) {
327 				set_interval(second_kill);
328 				second_kill = 0;
329 				sig_ign = killsig;
330 				killsig = SIGKILL;
331 			} else
332 				break;
333 
334 		} else if (sig_term) {
335 			if (!foreground)
336 				killpg(pgid, killsig);
337 			else
338 				kill(pid, (int)sig_term);
339 
340 			if (do_second_kill) {
341 				set_interval(second_kill);
342 				second_kill = 0;
343 				sig_ign = killsig;
344 				killsig = SIGKILL;
345 			} else
346 				break;
347 		}
348 	}
349 
350 	while (cpid != pid  && wait(&pstat) == -1) {
351 		if (errno != EINTR)
352 			err(EX_OSERR, "waitpid()");
353 	}
354 
355 	if (WEXITSTATUS(pstat))
356 		pstat = WEXITSTATUS(pstat);
357 	else if(WIFSIGNALED(pstat))
358 		pstat = 128 + WTERMSIG(pstat);
359 
360 	if (timedout && !preserve)
361 		pstat = EXIT_TIMEOUT;
362 
363 	return (pstat);
364 }
365