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