xref: /freebsd/bin/timeout/timeout.c (revision 8a9927ecd4b6e8cb306e4b940201b4efbfbc5dcb)
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 <stdbool.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <unistd.h>
42 
43 #define EXIT_TIMEOUT	124
44 #define EXIT_INVALID	125
45 #define EXIT_CMD_ERROR	126
46 #define EXIT_CMD_NOENT	127
47 
48 static volatile sig_atomic_t sig_chld = 0;
49 static volatile sig_atomic_t sig_term = 0;
50 static volatile sig_atomic_t sig_alrm = 0;
51 static volatile sig_atomic_t sig_ign = 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 double
67 parse_duration(const char *duration)
68 {
69 	double ret;
70 	char *suffix;
71 
72 	ret = strtod(duration, &suffix);
73 	if (suffix == duration)
74 		errx(EXIT_INVALID, "duration is not a number");
75 
76 	if (*suffix == '\0')
77 		return (ret);
78 
79 	if (suffix[1] != '\0')
80 		errx(EXIT_INVALID, "duration unit suffix too long");
81 
82 	switch (*suffix) {
83 	case 's':
84 		break;
85 	case 'm':
86 		ret *= 60;
87 		break;
88 	case 'h':
89 		ret *= 60 * 60;
90 		break;
91 	case 'd':
92 		ret *= 60 * 60 * 24;
93 		break;
94 	default:
95 		errx(EXIT_INVALID, "duration unit suffix invalid");
96 	}
97 
98 	if (ret < 0 || ret >= 100000000UL)
99 		errx(EXIT_INVALID, "duration out of range");
100 
101 	return (ret);
102 }
103 
104 static int
105 parse_signal(const char *str)
106 {
107 	int sig, i;
108 	const char *errstr;
109 
110 	sig = strtonum(str, 1, sys_nsig - 1, &errstr);
111 	if (errstr == NULL)
112 		return (sig);
113 
114 	if (strncasecmp(str, "SIG", 3) == 0)
115 		str += 3;
116 	for (i = 1; i < sys_nsig; i++) {
117 		if (strcasecmp(str, sys_signame[i]) == 0)
118 			return (i);
119 	}
120 
121 	errx(EXIT_INVALID, "invalid signal");
122 }
123 
124 static void
125 sig_handler(int signo)
126 {
127 	if (sig_ign != 0 && signo == sig_ign) {
128 		sig_ign = 0;
129 		return;
130 	}
131 
132 	switch (signo) {
133 	case SIGINT:
134 	case SIGHUP:
135 	case SIGQUIT:
136 	case SIGTERM:
137 		sig_term = signo;
138 		break;
139 	case SIGCHLD:
140 		sig_chld = 1;
141 		break;
142 	case SIGALRM:
143 		sig_alrm = 1;
144 		break;
145 	}
146 }
147 
148 static void
149 send_sig(pid_t pid, int signo)
150 {
151 	if (verbose) {
152 		warnx("sending signal %s(%d) to command '%s'",
153 		      sys_signame[signo], signo, command);
154 	}
155 	kill(pid, signo);
156 }
157 
158 static void
159 set_interval(double iv)
160 {
161 	struct itimerval tim;
162 
163 	memset(&tim, 0, sizeof(tim));
164 	if (iv > 0) {
165 		tim.it_value.tv_sec = (time_t)iv;
166 		iv -= (double)(time_t)iv;
167 		tim.it_value.tv_usec = (suseconds_t)(iv * 1000000UL);
168 	}
169 
170 	if (setitimer(ITIMER_REAL, &tim, NULL) == -1)
171 		err(EXIT_FAILURE, "setitimer()");
172 }
173 
174 int
175 main(int argc, char **argv)
176 {
177 	int ch, status, sig;
178 	int pstat = 0;
179 	int killsig = SIGTERM;
180 	size_t i;
181 	pid_t pid, cpid;
182 	double first_kill;
183 	double second_kill = 0;
184 	bool foreground = false;
185 	bool preserve = false;
186 	bool timedout = false;
187 	bool do_second_kill = false;
188 	bool child_done = false;
189 	struct sigaction signals;
190 	struct procctl_reaper_status info;
191 	struct procctl_reaper_kill killemall;
192 	int signums[] = {
193 		-1,
194 		SIGTERM,
195 		SIGINT,
196 		SIGHUP,
197 		SIGCHLD,
198 		SIGALRM,
199 		SIGQUIT,
200 	};
201 
202 	const char optstr[] = "+fhk:ps:v";
203 	const struct option longopts[] = {
204 		{ "foreground",      no_argument,       NULL, 'f' },
205 		{ "help",            no_argument,       NULL, 'h' },
206 		{ "kill-after",      required_argument, NULL, 'k' },
207 		{ "preserve-status", no_argument,       NULL, 'p' },
208 		{ "signal",          required_argument, NULL, 's' },
209 		{ "verbose",         no_argument,       NULL, 'v' },
210 		{ NULL,              0,                 NULL,  0  },
211 	};
212 
213 	while ((ch = getopt_long(argc, argv, optstr, longopts, NULL)) != -1) {
214 		switch (ch) {
215 		case 'f':
216 			foreground = true;
217 			break;
218 		case 'k':
219 			do_second_kill = true;
220 			second_kill = parse_duration(optarg);
221 			break;
222 		case 'p':
223 			preserve = true;
224 			break;
225 		case 's':
226 			killsig = parse_signal(optarg);
227 			break;
228 		case 'v':
229 			verbose = true;
230 			break;
231 		case 0:
232 			break;
233 		default:
234 			usage();
235 		}
236 	}
237 
238 	argc -= optind;
239 	argv += optind;
240 	if (argc < 2)
241 		usage();
242 
243 	first_kill = parse_duration(argv[0]);
244 	argc--;
245 	argv++;
246 	command = argv[0];
247 
248 	if (!foreground) {
249 		/* Acquire a reaper */
250 		if (procctl(P_PID, getpid(), PROC_REAP_ACQUIRE, NULL) == -1)
251 			err(EXIT_FAILURE, "procctl(PROC_REAP_ACQUIRE)");
252 	}
253 
254 	memset(&signals, 0, sizeof(signals));
255 	sigemptyset(&signals.sa_mask);
256 
257 	if (killsig != SIGKILL && killsig != SIGSTOP)
258 		signums[0] = killsig;
259 
260 	for (i = 0; i < sizeof(signums) / sizeof(signums[0]); i++)
261 		sigaddset(&signals.sa_mask, signums[i]);
262 
263 	signals.sa_handler = sig_handler;
264 	signals.sa_flags = SA_RESTART;
265 
266 	for (i = 0; i < sizeof(signums) / sizeof(signums[0]); i++) {
267 		if (signums[i] > 0 &&
268 		    sigaction(signums[i], &signals, NULL) == -1)
269 			err(EXIT_FAILURE, "sigaction()");
270 	}
271 
272 	/* Don't stop if background child needs TTY */
273 	signal(SIGTTIN, SIG_IGN);
274 	signal(SIGTTOU, SIG_IGN);
275 
276 	pid = fork();
277 	if (pid == -1) {
278 		err(EXIT_FAILURE, "fork()");
279 	} else if (pid == 0) {
280 		/* child process */
281 		signal(SIGTTIN, SIG_DFL);
282 		signal(SIGTTOU, SIG_DFL);
283 
284 		execvp(argv[0], argv);
285 		warn("exec(%s)", argv[0]);
286 		_exit(errno == ENOENT ? EXIT_CMD_NOENT : EXIT_CMD_ERROR);
287 	}
288 
289 	/* parent continues here */
290 
291 	if (sigprocmask(SIG_BLOCK, &signals.sa_mask, NULL) == -1)
292 		err(EXIT_FAILURE, "sigprocmask()");
293 
294 	set_interval(first_kill);
295 	sigemptyset(&signals.sa_mask);
296 
297 	for (;;) {
298 		sigsuspend(&signals.sa_mask);
299 
300 		if (sig_chld) {
301 			sig_chld = 0;
302 
303 			while ((cpid = waitpid(-1, &status, WNOHANG)) != 0) {
304 				if (cpid < 0) {
305 					if (errno != EINTR)
306 						break;
307 				} else if (cpid == pid) {
308 					pstat = status;
309 					child_done = true;
310 				}
311 			}
312 			if (child_done) {
313 				if (foreground) {
314 					break;
315 				} else {
316 					procctl(P_PID, getpid(),
317 					    	PROC_REAP_STATUS, &info);
318 					if (info.rs_children == 0)
319 						break;
320 				}
321 			}
322 		} else if (sig_alrm || sig_term) {
323 			if (sig_alrm) {
324 				sig = killsig;
325 				sig_alrm = 0;
326 				timedout = true;
327 			} else {
328 				sig = sig_term;
329 				sig_term = 0;
330 			}
331 
332 			if (foreground) {
333 				send_sig(pid, sig);
334 			} else {
335 				killemall.rk_sig = sig;
336 				killemall.rk_flags = 0;
337 				procctl(P_PID, getpid(), PROC_REAP_KILL,
338 					&killemall);
339 			}
340 
341 			if (do_second_kill) {
342 				set_interval(second_kill);
343 				do_second_kill = false;
344 				sig_ign = killsig;
345 				killsig = SIGKILL;
346 			} else {
347 				break;
348 			}
349 		}
350 	}
351 
352 	while (!child_done && wait(&pstat) == -1) {
353 		if (errno != EINTR)
354 			err(EXIT_FAILURE, "wait()");
355 	}
356 
357 	if (!foreground)
358 		procctl(P_PID, getpid(), PROC_REAP_RELEASE, NULL);
359 
360 	if (timedout && !preserve) {
361 		pstat = EXIT_TIMEOUT;
362 	} else {
363 		if (WIFEXITED(pstat))
364 			pstat = WEXITSTATUS(pstat);
365 		else if (WIFSIGNALED(pstat))
366 			pstat = 128 + WTERMSIG(pstat);
367 	}
368 
369 	return (pstat);
370 }
371