xref: /freebsd/bin/timeout/timeout.c (revision aae3eb24dfdbbe0a9e62fe7239d6038060cd07f6)
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 	struct sigaction signals;
228 	struct procctl_reaper_status info;
229 	int signums[] = {
230 		-1,
231 		SIGTERM,
232 		SIGINT,
233 		SIGHUP,
234 		SIGCHLD,
235 		SIGALRM,
236 		SIGQUIT,
237 	};
238 
239 	const char optstr[] = "+fhk:ps:v";
240 	const struct option longopts[] = {
241 		{ "foreground",      no_argument,       NULL, 'f' },
242 		{ "help",            no_argument,       NULL, 'h' },
243 		{ "kill-after",      required_argument, NULL, 'k' },
244 		{ "preserve-status", no_argument,       NULL, 'p' },
245 		{ "signal",          required_argument, NULL, 's' },
246 		{ "verbose",         no_argument,       NULL, 'v' },
247 		{ NULL,              0,                 NULL,  0  },
248 	};
249 
250 	while ((ch = getopt_long(argc, argv, optstr, longopts, NULL)) != -1) {
251 		switch (ch) {
252 		case 'f':
253 			foreground = true;
254 			break;
255 		case 'k':
256 			do_second_kill = true;
257 			second_kill = parse_duration(optarg);
258 			break;
259 		case 'p':
260 			preserve = true;
261 			break;
262 		case 's':
263 			killsig = parse_signal(optarg);
264 			break;
265 		case 'v':
266 			verbose = true;
267 			break;
268 		case 0:
269 			break;
270 		default:
271 			usage();
272 		}
273 	}
274 
275 	argc -= optind;
276 	argv += optind;
277 	if (argc < 2)
278 		usage();
279 
280 	first_kill = parse_duration(argv[0]);
281 	argc--;
282 	argv++;
283 	command = argv[0];
284 
285 	if (!foreground) {
286 		/* Acquire a reaper */
287 		if (procctl(P_PID, getpid(), PROC_REAP_ACQUIRE, NULL) == -1)
288 			err(EXIT_FAILURE, "procctl(PROC_REAP_ACQUIRE)");
289 	}
290 
291 	memset(&signals, 0, sizeof(signals));
292 	sigemptyset(&signals.sa_mask);
293 
294 	if (killsig != SIGKILL && killsig != SIGSTOP)
295 		signums[0] = killsig;
296 
297 	for (i = 0; i < sizeof(signums) / sizeof(signums[0]); i++)
298 		sigaddset(&signals.sa_mask, signums[i]);
299 
300 	signals.sa_handler = sig_handler;
301 	signals.sa_flags = SA_RESTART;
302 
303 	for (i = 0; i < sizeof(signums) / sizeof(signums[0]); i++) {
304 		if (signums[i] > 0 &&
305 		    sigaction(signums[i], &signals, NULL) == -1)
306 			err(EXIT_FAILURE, "sigaction()");
307 	}
308 
309 	/* Don't stop if background child needs TTY */
310 	signal(SIGTTIN, SIG_IGN);
311 	signal(SIGTTOU, SIG_IGN);
312 
313 	pid = fork();
314 	if (pid == -1) {
315 		err(EXIT_FAILURE, "fork()");
316 	} else if (pid == 0) {
317 		/* child process */
318 		signal(SIGTTIN, SIG_DFL);
319 		signal(SIGTTOU, SIG_DFL);
320 
321 		execvp(argv[0], argv);
322 		warn("exec(%s)", argv[0]);
323 		_exit(errno == ENOENT ? EXIT_CMD_NOENT : EXIT_CMD_ERROR);
324 	}
325 
326 	/* parent continues here */
327 
328 	if (sigprocmask(SIG_BLOCK, &signals.sa_mask, NULL) == -1)
329 		err(EXIT_FAILURE, "sigprocmask()");
330 
331 	set_interval(first_kill);
332 	sigemptyset(&signals.sa_mask);
333 
334 	for (;;) {
335 		sigsuspend(&signals.sa_mask);
336 
337 		if (sig_chld) {
338 			sig_chld = 0;
339 
340 			while ((cpid = waitpid(-1, &status, WNOHANG)) != 0) {
341 				if (cpid < 0) {
342 					if (errno != EINTR)
343 						break;
344 				} else if (cpid == pid) {
345 					pstat = status;
346 					child_done = true;
347 				}
348 			}
349 			if (child_done) {
350 				if (foreground) {
351 					break;
352 				} else {
353 					procctl(P_PID, getpid(),
354 					    	PROC_REAP_STATUS, &info);
355 					if (info.rs_children == 0)
356 						break;
357 				}
358 			}
359 		} else if (sig_alrm || sig_term) {
360 			if (sig_alrm) {
361 				sig = killsig;
362 				sig_alrm = 0;
363 				timedout = true;
364 			} else {
365 				sig = sig_term;
366 				sig_term = 0;
367 			}
368 
369 			send_sig(pid, sig, foreground);
370 
371 			if (do_second_kill) {
372 				set_interval(second_kill);
373 				do_second_kill = false;
374 				killsig = SIGKILL;
375 			}
376 		}
377 	}
378 
379 	if (!foreground)
380 		procctl(P_PID, getpid(), PROC_REAP_RELEASE, NULL);
381 
382 	if (timedout && !preserve) {
383 		pstat = EXIT_TIMEOUT;
384 	} else {
385 		if (WIFEXITED(pstat))
386 			pstat = WEXITSTATUS(pstat);
387 		else if (WIFSIGNALED(pstat))
388 			pstat = 128 + WTERMSIG(pstat);
389 	}
390 
391 	return (pstat);
392 }
393