xref: /freebsd/bin/timeout/timeout.c (revision 08208cd694815cc855835960f55231342eb35934)
1 /*-
2  * Copyright (c) 2014 Baptiste Daroussin <bapt@FreeBSD.org>
3  * Copyright (c) 2014 Vsevolod Stakhov <vsevolod@FreeBSD.org>
4  * Copyright (c) 2025 Aaron LI <aly@aaronly.me>
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer
12  *    in this position and unchanged.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
18  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20  * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
21  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 #include <sys/procctl.h>
30 #include <sys/resource.h>
31 #include <sys/time.h>
32 #include <sys/wait.h>
33 
34 #include <err.h>
35 #include <fcntl.h>
36 #include <errno.h>
37 #include <getopt.h>
38 #include <signal.h>
39 #include <stdarg.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 #define EXIT_INVALID	125
48 #define EXIT_CMD_ERROR	126
49 #define EXIT_CMD_NOENT	127
50 
51 static volatile sig_atomic_t sig_chld = 0;
52 static volatile sig_atomic_t sig_alrm = 0;
53 static volatile sig_atomic_t sig_term = 0; /* signal to terminate children */
54 static volatile sig_atomic_t sig_other = 0; /* signal to propagate */
55 static int killsig = SIGTERM; /* signal to kill children */
56 static const char *command = NULL;
57 static bool verbose = false;
58 
59 static void __dead2
usage(void)60 usage(void)
61 {
62 	fprintf(stderr,
63 	    "Usage: %s [-f | --foreground] [-k time | --kill-after time]"
64 	    " [-p | --preserve-status] [-s signal | --signal signal] "
65 	    " [-v | --verbose] <duration> <command> [arg ...]\n",
66 	    getprogname());
67 	exit(EXIT_FAILURE);
68 }
69 
70 static void __printflike(1, 2)
logv(const char * fmt,...)71 logv(const char *fmt, ...)
72 {
73 	va_list ap;
74 
75 	if (!verbose)
76 		return;
77 
78 	va_start(ap, fmt);
79 	vwarnx(fmt, ap);
80 	va_end(ap);
81 }
82 
83 static double
parse_duration(const char * duration)84 parse_duration(const char *duration)
85 {
86 	double ret;
87 	char *suffix;
88 
89 	ret = strtod(duration, &suffix);
90 	if (suffix == duration)
91 		errx(EXIT_INVALID, "duration is not a number");
92 
93 	if (*suffix == '\0')
94 		return (ret);
95 
96 	if (suffix[1] != '\0')
97 		errx(EXIT_INVALID, "duration unit suffix too long");
98 
99 	switch (*suffix) {
100 	case 's':
101 		break;
102 	case 'm':
103 		ret *= 60;
104 		break;
105 	case 'h':
106 		ret *= 60 * 60;
107 		break;
108 	case 'd':
109 		ret *= 60 * 60 * 24;
110 		break;
111 	default:
112 		errx(EXIT_INVALID, "duration unit suffix invalid");
113 	}
114 
115 	if (ret < 0 || ret >= 100000000UL)
116 		errx(EXIT_INVALID, "duration out of range");
117 
118 	return (ret);
119 }
120 
121 static void
sig_handler(int signo)122 sig_handler(int signo)
123 {
124 	if (signo == killsig) {
125 		sig_term = signo;
126 		return;
127 	}
128 
129 	switch (signo) {
130 	case SIGCHLD:
131 		sig_chld = 1;
132 		break;
133 	case SIGALRM:
134 		sig_alrm = 1;
135 		break;
136 	case SIGHUP:
137 	case SIGINT:
138 	case SIGQUIT:
139 	case SIGILL:
140 	case SIGTRAP:
141 	case SIGABRT:
142 	case SIGEMT:
143 	case SIGFPE:
144 	case SIGBUS:
145 	case SIGSEGV:
146 	case SIGSYS:
147 	case SIGPIPE:
148 	case SIGTERM:
149 	case SIGXCPU:
150 	case SIGXFSZ:
151 	case SIGVTALRM:
152 	case SIGPROF:
153 	case SIGUSR1:
154 	case SIGUSR2:
155 		/*
156 		 * Signals with default action to terminate the process.
157 		 * See the sigaction(2) man page.
158 		 */
159 		sig_term = signo;
160 		break;
161 	default:
162 		sig_other = signo;
163 		break;
164 	}
165 }
166 
167 static void
send_sig(pid_t pid,int signo,bool foreground)168 send_sig(pid_t pid, int signo, bool foreground)
169 {
170 	struct procctl_reaper_kill rk;
171 
172 	logv("sending signal %s(%d) to command '%s'",
173 	    sys_signame[signo], signo, command);
174 	if (foreground) {
175 		if (kill(pid, signo) < 0 && errno != ESRCH)
176 			warn("kill(%d, %s)", (int)pid, sys_signame[signo]);
177 	} else {
178 		memset(&rk, 0, sizeof(rk));
179 		rk.rk_sig = signo;
180 		if (procctl(P_PID, getpid(), PROC_REAP_KILL, &rk) < 0 &&
181 		    errno != ESRCH) {
182 			warn("procctl(PROC_REAP_KILL)");
183 			if (rk.rk_fpid > 0) {
184 				warnx("failed to signal some processes:"
185 				    " first pid=%d", (int)rk.rk_fpid);
186 			}
187 		}
188 		logv("signaled %u processes", rk.rk_killed);
189 	}
190 
191 	/*
192 	 * If the child process was stopped by a signal, POSIX.1-2024
193 	 * requires to send a SIGCONT signal.  However, the standard also
194 	 * allows to send a SIGCONT regardless of the stop state, as we
195 	 * are doing here.
196 	 */
197 	if (signo != SIGKILL && signo != SIGSTOP && signo != SIGCONT) {
198 		logv("sending signal %s(%d) to command '%s'",
199 		    sys_signame[SIGCONT], SIGCONT, command);
200 		if (foreground) {
201 			kill(pid, SIGCONT);
202 		} else {
203 			memset(&rk, 0, sizeof(rk));
204 			rk.rk_sig = SIGCONT;
205 			procctl(P_PID, getpid(), PROC_REAP_KILL, &rk);
206 		}
207 	}
208 }
209 
210 static void
set_interval(double iv)211 set_interval(double iv)
212 {
213 	struct itimerval tim;
214 
215 	memset(&tim, 0, sizeof(tim));
216 	if (iv > 0) {
217 		tim.it_value.tv_sec = (time_t)iv;
218 		iv -= (double)(time_t)iv;
219 		tim.it_value.tv_usec = (suseconds_t)(iv * 1000000UL);
220 	}
221 
222 	if (setitimer(ITIMER_REAL, &tim, NULL) < 0)
223 		err(EXIT_FAILURE, "setitimer()");
224 }
225 
226 /*
227  * In order to avoid any possible ambiguity that a shell may not set '$?' to
228  * '128+signal_number', POSIX.1-2024 requires that timeout mimic the wait
229  * status of the child process by terminating itself with the same signal,
230  * while disabling core generation.
231  */
232 static void __dead2
kill_self(int signo)233 kill_self(int signo)
234 {
235 	sigset_t mask;
236 	struct rlimit rl;
237 
238 	logv("killing self with signal %s(%d)", sys_signame[signo], signo);
239 
240 	/* Disable core generation. */
241 	memset(&rl, 0, sizeof(rl));
242 	setrlimit(RLIMIT_CORE, &rl);
243 
244 	/* Reset the signal disposition and make sure it's unblocked. */
245 	signal(signo, SIG_DFL);
246 	sigfillset(&mask);
247 	sigdelset(&mask, signo);
248 	sigprocmask(SIG_SETMASK, &mask, NULL);
249 
250 	raise(signo);
251 	err(128 + signo, "raise(%d)", signo);
252 }
253 
254 static void
log_termination(const char * name,const siginfo_t * si)255 log_termination(const char *name, const siginfo_t *si)
256 {
257 	if (si->si_code == CLD_EXITED) {
258 		logv("%s: pid=%d, exit=%d", name, si->si_pid, si->si_status);
259 	} else if (si->si_code == CLD_DUMPED || si->si_code == CLD_KILLED) {
260 		logv("%s: pid=%d, sig=%d", name, si->si_pid, si->si_status);
261 	} else {
262 		logv("%s: pid=%d, reason=%d, status=%d", name, si->si_pid,
263 		    si->si_code, si->si_status);
264 	}
265 }
266 
267 int
main(int argc,char ** argv)268 main(int argc, char **argv)
269 {
270 	struct procctl_reaper_status info;
271 	siginfo_t si, child_si;
272 	struct sigaction sa;
273 	sigset_t zeromask, allmask, oldmask;
274 	double first_kill;
275 	double second_kill = 0;
276 	ssize_t error;
277 	pid_t pid;
278 	int pp[2];
279 	int ch, sig;
280 	int pstat = 0;
281 	char c;
282 	bool foreground = false;
283 	bool preserve = false;
284 	bool timedout = false;
285 	bool do_second_kill = false;
286 	bool child_done = false;
287 
288 	const char optstr[] = "+fhk:ps:v";
289 	const struct option longopts[] = {
290 		{ "foreground",      no_argument,       NULL, 'f' },
291 		{ "help",            no_argument,       NULL, 'h' },
292 		{ "kill-after",      required_argument, NULL, 'k' },
293 		{ "preserve-status", no_argument,       NULL, 'p' },
294 		{ "signal",          required_argument, NULL, 's' },
295 		{ "verbose",         no_argument,       NULL, 'v' },
296 		{ NULL,              0,                 NULL,  0  },
297 	};
298 
299 	while ((ch = getopt_long(argc, argv, optstr, longopts, NULL)) != -1) {
300 		switch (ch) {
301 		case 'f':
302 			foreground = true;
303 			break;
304 		case 'k':
305 			do_second_kill = true;
306 			second_kill = parse_duration(optarg);
307 			break;
308 		case 'p':
309 			preserve = true;
310 			break;
311 		case 's':
312 			if (str2sig(optarg, &killsig) != 0)
313 				errx(EXIT_INVALID, "invalid signal");
314 			break;
315 		case 'v':
316 			verbose = true;
317 			break;
318 		case 0:
319 			break;
320 		default:
321 			usage();
322 		}
323 	}
324 
325 	argc -= optind;
326 	argv += optind;
327 	if (argc < 2)
328 		usage();
329 
330 	first_kill = parse_duration(argv[0]);
331 	argc--;
332 	argv++;
333 	command = argv[0];
334 
335 	if (!foreground) {
336 		/* Acquire a reaper */
337 		if (procctl(P_PID, getpid(), PROC_REAP_ACQUIRE, NULL) < 0)
338 			err(EXIT_FAILURE, "procctl(PROC_REAP_ACQUIRE)");
339 	}
340 
341 	/* Block all signals to avoid racing against the child. */
342 	sigfillset(&allmask);
343 	if (sigprocmask(SIG_BLOCK, &allmask, &oldmask) < 0)
344 		err(EXIT_FAILURE, "sigprocmask()");
345 
346 	if (pipe2(pp, O_CLOEXEC) < 0)
347 		err(EXIT_FAILURE, "pipe2");
348 
349 	pid = fork();
350 	if (pid < 0)
351 		err(EXIT_FAILURE, "fork()");
352 	if (pid == 0) {
353 		/*
354 		 * child process
355 		 *
356 		 * POSIX.1-2024 requires that the child process inherit the
357 		 * same signal dispositions as the timeout(1) utility
358 		 * inherited, except for the signal to be sent upon timeout.
359 		 */
360 		signal(killsig, SIG_DFL);
361 		if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
362 			err(EXIT_FAILURE, "sigprocmask(oldmask)");
363 
364 		(void)close(pp[1]);
365 		error = read(pp[0], &c, 1);
366 		if (error < 0)
367 			err(EXIT_FAILURE, "read from control pipe");
368 		if (error == 0)
369 			errx(EXIT_FAILURE, "eof from control pipe");
370 		execvp(argv[0], argv);
371 		warn("exec(%s)", argv[0]);
372 		_exit(errno == ENOENT ? EXIT_CMD_NOENT : EXIT_CMD_ERROR);
373 	}
374 
375 	/* parent continues here */
376 	(void)close(pp[0]);
377 
378 	/* Catch all signals in order to propagate them. */
379 	memset(&sa, 0, sizeof(sa));
380 	sigfillset(&sa.sa_mask);
381 	sa.sa_handler = sig_handler;
382 	sa.sa_flags = SA_RESTART;
383 	for (sig = 1; sig < sys_nsig; sig++) {
384 		switch (sig) {
385 		case SIGTTIN:
386 		case SIGTTOU:
387 			/* Don't stop if background child needs TTY */
388 			if (signal(sig, SIG_IGN) == SIG_ERR)
389 				err(EXIT_FAILURE, "signal(%d)", sig);
390 			break;
391 		case SIGKILL:
392 		case SIGSTOP:
393 		case SIGCONT:
394 			/* These can't be caught or ignored */
395 			break;
396 		default:
397 			if (sigaction(sig, &sa, NULL) < 0)
398 				err(EXIT_FAILURE, "sigaction(%d)", sig);
399 		}
400 	}
401 
402 	/* Start the timer */
403 	set_interval(first_kill);
404 
405 	/* Let the child know we're ready */
406 	error = write(pp[1], "a", 1);
407 	if (error < 0)
408 		err(EXIT_FAILURE, "write to control pipe");
409 	if (error == 0)
410 		errx(EXIT_FAILURE, "short write to control pipe");
411 	(void)close(pp[1]);
412 
413 	sigemptyset(&zeromask);
414 	for (;;) {
415 		sigsuspend(&zeromask);
416 
417 		if (sig_chld) {
418 			sig_chld = 0;
419 
420 			for (;;) {
421 				memset(&si, 0, sizeof(si));
422 				if (waitid(P_ALL, -1, &si,
423 				    WEXITED | WNOHANG) < 0) {
424 					if (errno != EINTR)
425 						break;
426 				} else if (si.si_pid == pid) {
427 					child_si = si;
428 					child_done = true;
429 					log_termination("child terminated",
430 					    &child_si);
431 				} else if (si.si_pid != 0) {
432 					/*
433 					 * Collect grandchildren zombies.
434 					 * Only effective if we're a reaper.
435 					 */
436 					log_termination("collected zombie",
437 					    &si);
438 				} else /* si.si_pid == 0 */ {
439 					break;
440 				}
441 			}
442 			if (child_done) {
443 				if (foreground) {
444 					break;
445 				} else {
446 					procctl(P_PID, getpid(),
447 					    PROC_REAP_STATUS, &info);
448 					if (info.rs_children == 0)
449 						break;
450 				}
451 			}
452 		} else if (sig_alrm || sig_term) {
453 			if (sig_alrm) {
454 				sig = killsig;
455 				sig_alrm = 0;
456 				timedout = true;
457 				logv("time limit reached or received SIGALRM");
458 			} else {
459 				sig = sig_term;
460 				sig_term = 0;
461 				logv("received terminating signal %s(%d)",
462 				    sys_signame[sig], sig);
463 			}
464 
465 			send_sig(pid, sig, foreground);
466 
467 			if (do_second_kill) {
468 				set_interval(second_kill);
469 				do_second_kill = false;
470 				killsig = SIGKILL;
471 			}
472 
473 		} else if (sig_other) {
474 			/* Propagate any other signals. */
475 			sig = sig_other;
476 			sig_other = 0;
477 			logv("received signal %s(%d)", sys_signame[sig], sig);
478 
479 			send_sig(pid, sig, foreground);
480 		}
481 	}
482 
483 	if (!foreground)
484 		procctl(P_PID, getpid(), PROC_REAP_RELEASE, NULL);
485 
486 	if (timedout && !preserve) {
487 		pstat = EXIT_TIMEOUT;
488 	} else if (child_si.si_code == CLD_DUMPED ||
489 	    child_si.si_code == CLD_KILLED) {
490 		kill_self(child_si.si_status);
491 		/* NOTREACHED */
492 	} else if (child_si.si_code == CLD_EXITED) {
493 		pstat = child_si.si_status;
494 	} else {
495 		pstat = EXIT_FAILURE;
496 	}
497 
498 	return (pstat);
499 }
500