xref: /freebsd/bin/timeout/timeout.c (revision 2390cbfe55f55916eca25e8ba94a3320535e01c9)
1 /*-
2  * Copyright (c) 2014 Baptiste Daroussin <bapt@FreeBSD.org>
3  * Copyright (c) 2014 Vsevolod Stakhov <vsevolod@FreeBSD.org>
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer
11  *    in this position and unchanged.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
17  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19  * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
20  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27 
28 #include <sys/cdefs.h>
29 #include <sys/procctl.h>
30 #include <sys/time.h>
31 #include <sys/wait.h>
32 
33 #include <err.h>
34 #include <errno.h>
35 #include <getopt.h>
36 #include <signal.h>
37 #include <stdarg.h>
38 #include <stdbool.h>
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <unistd.h>
43 
44 #define EXIT_TIMEOUT	124
45 #define EXIT_INVALID	125
46 #define EXIT_CMD_ERROR	126
47 #define EXIT_CMD_NOENT	127
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 const char *command = NULL;
53 static bool verbose = false;
54 
55 static void __dead2
56 usage(void)
57 {
58 	fprintf(stderr,
59 		"Usage: %s [-f | --foreground] [-k time | --kill-after time]"
60 		" [-p | --preserve-status] [-s signal | --signal signal] "
61 		" [-v | --verbose] <duration> <command> [arg ...]\n",
62 		getprogname());
63 	exit(EXIT_FAILURE);
64 }
65 
66 static void
67 logv(const char *fmt, ...)
68 {
69 	va_list ap;
70 
71 	if (!verbose)
72 		return;
73 
74 	va_start(ap, fmt);
75 	vwarnx(fmt, ap);
76 	va_end(ap);
77 }
78 
79 static double
80 parse_duration(const char *duration)
81 {
82 	double ret;
83 	char *suffix;
84 
85 	ret = strtod(duration, &suffix);
86 	if (suffix == duration)
87 		errx(EXIT_INVALID, "duration is not a number");
88 
89 	if (*suffix == '\0')
90 		return (ret);
91 
92 	if (suffix[1] != '\0')
93 		errx(EXIT_INVALID, "duration unit suffix too long");
94 
95 	switch (*suffix) {
96 	case 's':
97 		break;
98 	case 'm':
99 		ret *= 60;
100 		break;
101 	case 'h':
102 		ret *= 60 * 60;
103 		break;
104 	case 'd':
105 		ret *= 60 * 60 * 24;
106 		break;
107 	default:
108 		errx(EXIT_INVALID, "duration unit suffix invalid");
109 	}
110 
111 	if (ret < 0 || ret >= 100000000UL)
112 		errx(EXIT_INVALID, "duration out of range");
113 
114 	return (ret);
115 }
116 
117 static int
118 parse_signal(const char *str)
119 {
120 	int sig, i;
121 	const char *errstr;
122 
123 	sig = strtonum(str, 1, sys_nsig - 1, &errstr);
124 	if (errstr == NULL)
125 		return (sig);
126 
127 	if (strncasecmp(str, "SIG", 3) == 0)
128 		str += 3;
129 	for (i = 1; i < sys_nsig; i++) {
130 		if (strcasecmp(str, sys_signame[i]) == 0)
131 			return (i);
132 	}
133 
134 	errx(EXIT_INVALID, "invalid signal");
135 }
136 
137 static void
138 sig_handler(int signo)
139 {
140 	switch (signo) {
141 	case SIGINT:
142 	case SIGHUP:
143 	case SIGQUIT:
144 	case SIGTERM:
145 		sig_term = signo;
146 		break;
147 	case SIGCHLD:
148 		sig_chld = 1;
149 		break;
150 	case SIGALRM:
151 		sig_alrm = 1;
152 		break;
153 	}
154 }
155 
156 static void
157 send_sig(pid_t pid, int signo, bool foreground)
158 {
159 	struct procctl_reaper_kill rk;
160 
161 	logv("sending signal %s(%d) to command '%s'",
162 	     sys_signame[signo], signo, command);
163 	if (foreground) {
164 		if (kill(pid, signo) == -1)
165 			warnx("kill(%d, %s)", (int)pid, sys_signame[signo]);
166 	} else {
167 		memset(&rk, 0, sizeof(rk));
168 		rk.rk_sig = signo;
169 		if (procctl(P_PID, getpid(), PROC_REAP_KILL, &rk) == -1)
170 			warnx("procctl(PROC_REAP_KILL)");
171 		else if (rk.rk_fpid > 0)
172 			warnx("failed to signal some processes: first pid=%d",
173 			      (int)rk.rk_fpid);
174 		logv("signaled %u processes", rk.rk_killed);
175 	}
176 
177 	/*
178 	 * If the child process was stopped by a signal, POSIX.1-2024
179 	 * requires to send a SIGCONT signal.  However, the standard also
180 	 * allows to send a SIGCONT regardless of the stop state, as we
181 	 * are doing here.
182 	 */
183 	if (signo != SIGKILL && signo != SIGSTOP && signo != SIGCONT) {
184 		logv("sending signal %s(%d) to command '%s'",
185 		     sys_signame[SIGCONT], SIGCONT, command);
186 		if (foreground) {
187 			kill(pid, SIGCONT);
188 		} else {
189 			memset(&rk, 0, sizeof(rk));
190 			rk.rk_sig = SIGCONT;
191 			procctl(P_PID, getpid(), PROC_REAP_KILL, &rk);
192 		}
193 	}
194 }
195 
196 static void
197 set_interval(double iv)
198 {
199 	struct itimerval tim;
200 
201 	memset(&tim, 0, sizeof(tim));
202 	if (iv > 0) {
203 		tim.it_value.tv_sec = (time_t)iv;
204 		iv -= (double)(time_t)iv;
205 		tim.it_value.tv_usec = (suseconds_t)(iv * 1000000UL);
206 	}
207 
208 	if (setitimer(ITIMER_REAL, &tim, NULL) == -1)
209 		err(EXIT_FAILURE, "setitimer()");
210 }
211 
212 int
213 main(int argc, char **argv)
214 {
215 	int ch, status, sig;
216 	int pstat = 0;
217 	int killsig = SIGTERM;
218 	size_t i;
219 	pid_t pid, cpid;
220 	double first_kill;
221 	double second_kill = 0;
222 	bool foreground = false;
223 	bool preserve = false;
224 	bool timedout = false;
225 	bool do_second_kill = false;
226 	bool child_done = false;
227 	sigset_t zeromask, allmask, oldmask;
228 	struct sigaction signals;
229 	struct procctl_reaper_status info;
230 	int signums[] = {
231 		-1,
232 		SIGTERM,
233 		SIGINT,
234 		SIGHUP,
235 		SIGCHLD,
236 		SIGALRM,
237 		SIGQUIT,
238 	};
239 
240 	const char optstr[] = "+fhk:ps:v";
241 	const struct option longopts[] = {
242 		{ "foreground",      no_argument,       NULL, 'f' },
243 		{ "help",            no_argument,       NULL, 'h' },
244 		{ "kill-after",      required_argument, NULL, 'k' },
245 		{ "preserve-status", no_argument,       NULL, 'p' },
246 		{ "signal",          required_argument, NULL, 's' },
247 		{ "verbose",         no_argument,       NULL, 'v' },
248 		{ NULL,              0,                 NULL,  0  },
249 	};
250 
251 	while ((ch = getopt_long(argc, argv, optstr, longopts, NULL)) != -1) {
252 		switch (ch) {
253 		case 'f':
254 			foreground = true;
255 			break;
256 		case 'k':
257 			do_second_kill = true;
258 			second_kill = parse_duration(optarg);
259 			break;
260 		case 'p':
261 			preserve = true;
262 			break;
263 		case 's':
264 			killsig = parse_signal(optarg);
265 			break;
266 		case 'v':
267 			verbose = true;
268 			break;
269 		case 0:
270 			break;
271 		default:
272 			usage();
273 		}
274 	}
275 
276 	argc -= optind;
277 	argv += optind;
278 	if (argc < 2)
279 		usage();
280 
281 	first_kill = parse_duration(argv[0]);
282 	argc--;
283 	argv++;
284 	command = argv[0];
285 
286 	if (!foreground) {
287 		/* Acquire a reaper */
288 		if (procctl(P_PID, getpid(), PROC_REAP_ACQUIRE, NULL) == -1)
289 			err(EXIT_FAILURE, "procctl(PROC_REAP_ACQUIRE)");
290 	}
291 
292 	/* Block all signals to avoid racing against the child. */
293 	sigfillset(&allmask);
294 	if (sigprocmask(SIG_BLOCK, &allmask, &oldmask) == -1)
295 		err(EXIT_FAILURE, "sigprocmask()");
296 
297 	pid = fork();
298 	if (pid == -1) {
299 		err(EXIT_FAILURE, "fork()");
300 	} else if (pid == 0) {
301 		/*
302 		 * child process
303 		 *
304 		 * POSIX.1-2024 requires that the child process inherit the
305 		 * same signal dispositions as the timeout(1) utility
306 		 * inherited, except for the signal to be sent upon timeout.
307 		 */
308 		signal(killsig, SIG_DFL);
309 		if (sigprocmask(SIG_SETMASK, &oldmask, NULL) == -1)
310 			err(EXIT_FAILURE, "sigprocmask(oldmask)");
311 
312 		execvp(argv[0], argv);
313 		warn("exec(%s)", argv[0]);
314 		_exit(errno == ENOENT ? EXIT_CMD_NOENT : EXIT_CMD_ERROR);
315 	}
316 
317 	/* parent continues here */
318 
319 	memset(&signals, 0, sizeof(signals));
320 	sigemptyset(&signals.sa_mask);
321 
322 	if (killsig != SIGKILL && killsig != SIGSTOP)
323 		signums[0] = killsig;
324 
325 	for (i = 0; i < sizeof(signums) / sizeof(signums[0]); i++)
326 		sigaddset(&signals.sa_mask, signums[i]);
327 
328 	signals.sa_handler = sig_handler;
329 	signals.sa_flags = SA_RESTART;
330 
331 	for (i = 0; i < sizeof(signums) / sizeof(signums[0]); i++) {
332 		if (signums[i] > 0 &&
333 		    sigaction(signums[i], &signals, NULL) == -1)
334 			err(EXIT_FAILURE, "sigaction()");
335 	}
336 
337 	/* Don't stop if background child needs TTY */
338 	signal(SIGTTIN, SIG_IGN);
339 	signal(SIGTTOU, SIG_IGN);
340 
341 	set_interval(first_kill);
342 	sigemptyset(&zeromask);
343 
344 	for (;;) {
345 		sigsuspend(&zeromask);
346 
347 		if (sig_chld) {
348 			sig_chld = 0;
349 
350 			while ((cpid = waitpid(-1, &status, WNOHANG)) != 0) {
351 				if (cpid < 0) {
352 					if (errno != EINTR)
353 						break;
354 				} else if (cpid == pid) {
355 					pstat = status;
356 					child_done = true;
357 					logv("child terminated: pid=%d, "
358 					     "exit=%d, signal=%d",
359 					     (int)pid, WEXITSTATUS(status),
360 					     WTERMSIG(status));
361 				} else {
362 					/*
363 					 * Collect grandchildren zombies.
364 					 * Only effective if we're a reaper.
365 					 */
366 					logv("collected zombie: pid=%d, "
367 					     "exit=%d, signal=%d",
368 					     (int)cpid, WEXITSTATUS(status),
369 					     WTERMSIG(status));
370 				}
371 			}
372 			if (child_done) {
373 				if (foreground) {
374 					break;
375 				} else {
376 					procctl(P_PID, getpid(),
377 					    	PROC_REAP_STATUS, &info);
378 					if (info.rs_children == 0)
379 						break;
380 				}
381 			}
382 		} else if (sig_alrm || sig_term) {
383 			if (sig_alrm) {
384 				sig = killsig;
385 				sig_alrm = 0;
386 				timedout = true;
387 				logv("time limit reached or received SIGALRM");
388 			} else {
389 				sig = sig_term;
390 				sig_term = 0;
391 				logv("received terminating signal %s(%d)",
392 				     sys_signame[sig], sig);
393 			}
394 
395 			send_sig(pid, sig, foreground);
396 
397 			if (do_second_kill) {
398 				set_interval(second_kill);
399 				do_second_kill = false;
400 				killsig = SIGKILL;
401 			}
402 		}
403 	}
404 
405 	if (!foreground)
406 		procctl(P_PID, getpid(), PROC_REAP_RELEASE, NULL);
407 
408 	if (timedout && !preserve) {
409 		pstat = EXIT_TIMEOUT;
410 	} else {
411 		if (WIFEXITED(pstat))
412 			pstat = WEXITSTATUS(pstat);
413 		else if (WIFSIGNALED(pstat))
414 			pstat = 128 + WTERMSIG(pstat);
415 	}
416 
417 	return (pstat);
418 }
419