xref: /netbsd/usr.bin/timeout/timeout.c (revision 65f114bc)
1*65f114bcSkre /* $NetBSD: timeout.c,v 1.5 2022/12/13 13:25:36 kre Exp $ */
2138cb133Schristos 
31ac5a68eSchristos /*-
41ac5a68eSchristos  * Copyright (c) 2014 Baptiste Daroussin <bapt@FreeBSD.org>
51ac5a68eSchristos  * Copyright (c) 2014 Vsevolod Stakhov <vsevolod@FreeBSD.org>
61ac5a68eSchristos  * All rights reserved.
71ac5a68eSchristos  *
81ac5a68eSchristos  * Redistribution and use in source and binary forms, with or without
91ac5a68eSchristos  * modification, are permitted provided that the following conditions
101ac5a68eSchristos  * are met:
111ac5a68eSchristos  * 1. Redistributions of source code must retain the above copyright
121ac5a68eSchristos  *    notice, this list of conditions and the following disclaimer
131ac5a68eSchristos  *    in this position and unchanged.
141ac5a68eSchristos  * 2. Redistributions in binary form must reproduce the above copyright
151ac5a68eSchristos  *    notice, this list of conditions and the following disclaimer in the
161ac5a68eSchristos  *    documentation and/or other materials provided with the distribution.
171ac5a68eSchristos  *
181ac5a68eSchristos  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
191ac5a68eSchristos  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
201ac5a68eSchristos  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
211ac5a68eSchristos  * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
221ac5a68eSchristos  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
231ac5a68eSchristos  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
241ac5a68eSchristos  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
251ac5a68eSchristos  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
261ac5a68eSchristos  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
271ac5a68eSchristos  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
281ac5a68eSchristos  */
291ac5a68eSchristos 
301ac5a68eSchristos #include <sys/cdefs.h>
31138cb133Schristos #if !defined(lint)
32138cb133Schristos #if 0
331ac5a68eSchristos __FBSDID("$FreeBSD: head/usr.bin/timeout/timeout.c 268763 2014-07-16 13:52:05Z bapt $");
34138cb133Schristos #else
35*65f114bcSkre __RCSID("$NetBSD: timeout.c,v 1.5 2022/12/13 13:25:36 kre Exp $");
36138cb133Schristos #endif
37138cb133Schristos #endif /* not lint */
381ac5a68eSchristos 
391ac5a68eSchristos #include <sys/time.h>
401ac5a68eSchristos #include <sys/wait.h>
411ac5a68eSchristos 
421ac5a68eSchristos #include <err.h>
431ac5a68eSchristos #include <errno.h>
441ac5a68eSchristos #include <getopt.h>
45138cb133Schristos #include <limits.h>
461ac5a68eSchristos #include <signal.h>
471ac5a68eSchristos #include <stdbool.h>
481ac5a68eSchristos #include <stdio.h>
491ac5a68eSchristos #include <stdlib.h>
501ac5a68eSchristos #include <string.h>
511ac5a68eSchristos #include <sysexits.h>
521ac5a68eSchristos #include <unistd.h>
531ac5a68eSchristos 
541ac5a68eSchristos #define EXIT_TIMEOUT 124
551ac5a68eSchristos 
561ac5a68eSchristos static sig_atomic_t sig_chld = 0;
571ac5a68eSchristos static sig_atomic_t sig_term = 0;
581ac5a68eSchristos static sig_atomic_t sig_alrm = 0;
591ac5a68eSchristos static sig_atomic_t sig_ign = 0;
601ac5a68eSchristos 
61138cb133Schristos static void __dead
usage(void)621ac5a68eSchristos usage(void)
631ac5a68eSchristos {
641ac5a68eSchristos 
651ac5a68eSchristos 	fprintf(stderr, "Usage: %s [--signal sig | -s sig] [--preserve-status]"
661ac5a68eSchristos 	    " [--kill-after time | -k time] [--foreground] <duration> <command>"
671ac5a68eSchristos 	    " <arg ...>\n", getprogname());
681ac5a68eSchristos 
691ac5a68eSchristos 	exit(EX_USAGE);
701ac5a68eSchristos }
711ac5a68eSchristos 
721ac5a68eSchristos static double
parse_duration(const char * duration)731ac5a68eSchristos parse_duration(const char *duration)
741ac5a68eSchristos {
751ac5a68eSchristos 	double ret;
761ac5a68eSchristos 	char *end;
771ac5a68eSchristos 
781ac5a68eSchristos 	ret = strtod(duration, &end);
791ac5a68eSchristos 	if (ret == 0 && end == duration)
801ac5a68eSchristos 		errx(EXIT_FAILURE, "invalid duration");
811ac5a68eSchristos 
821ac5a68eSchristos 	if (end == NULL || *end == '\0')
831ac5a68eSchristos 		return (ret);
841ac5a68eSchristos 
851ac5a68eSchristos 	if (end != NULL && *(end + 1) != '\0')
861ac5a68eSchristos 		errx(EX_USAGE, "invalid duration");
871ac5a68eSchristos 
881ac5a68eSchristos 	switch (*end) {
891ac5a68eSchristos 	case 's':
901ac5a68eSchristos 		break;
911ac5a68eSchristos 	case 'm':
921ac5a68eSchristos 		ret *= 60;
931ac5a68eSchristos 		break;
941ac5a68eSchristos 	case 'h':
951ac5a68eSchristos 		ret *= 60 * 60;
961ac5a68eSchristos 		break;
971ac5a68eSchristos 	case 'd':
981ac5a68eSchristos 		ret *= 60 * 60 * 24;
991ac5a68eSchristos 		break;
1001ac5a68eSchristos 	default:
1011ac5a68eSchristos 		errx(EX_USAGE, "invalid duration");
1021ac5a68eSchristos 	}
1031ac5a68eSchristos 
1041ac5a68eSchristos 	if (ret < 0 || ret >= 100000000UL)
1051ac5a68eSchristos 		errx(EX_USAGE, "invalid duration");
1061ac5a68eSchristos 
1071ac5a68eSchristos 	return (ret);
1081ac5a68eSchristos }
1091ac5a68eSchristos 
1101ac5a68eSchristos static int
parse_signal(const char * str)1111ac5a68eSchristos parse_signal(const char *str)
1121ac5a68eSchristos {
113138cb133Schristos 	long sig;
114138cb133Schristos 	int i;
115138cb133Schristos 	char *ep;
1161ac5a68eSchristos 
117138cb133Schristos 	if (strncasecmp(str, "SIG", 3) == 0) {
1181ac5a68eSchristos 		str += 3;
1191ac5a68eSchristos 
1201ac5a68eSchristos 		for (i = 1; i < sys_nsig; i++) {
1211ac5a68eSchristos 			if (strcasecmp(str, sys_signame[i]) == 0)
1221ac5a68eSchristos 				return (i);
1231ac5a68eSchristos 		}
1241ac5a68eSchristos 
125138cb133Schristos 		goto err;
126138cb133Schristos 	}
127138cb133Schristos 
128138cb133Schristos 	errno = 0;
129138cb133Schristos 	sig = strtol(str, &ep, 10);
130138cb133Schristos 
131138cb133Schristos 	if (str[0] == '\0' || *ep != '\0')
132138cb133Schristos 		goto err;
133fb41f11dSchristos 	if (errno == ERANGE && (sig == LONG_MAX || sig == LONG_MIN))
134138cb133Schristos 		goto err;
135138cb133Schristos 	if (sig >= sys_nsig || sig < 0)
136138cb133Schristos 		goto err;
137138cb133Schristos 
138138cb133Schristos 	return (int)sig;
139138cb133Schristos 
140138cb133Schristos err:
1411ac5a68eSchristos 	errx(EX_USAGE, "invalid signal");
1421ac5a68eSchristos }
1431ac5a68eSchristos 
1441ac5a68eSchristos static void
sig_handler(int signo)1451ac5a68eSchristos sig_handler(int signo)
1461ac5a68eSchristos {
1471ac5a68eSchristos 	if (sig_ign != 0 && signo == sig_ign) {
1481ac5a68eSchristos 		sig_ign = 0;
1491ac5a68eSchristos 		return;
1501ac5a68eSchristos 	}
1511ac5a68eSchristos 
1521ac5a68eSchristos 	switch(signo) {
1531ac5a68eSchristos 	case 0:
1541ac5a68eSchristos 	case SIGINT:
1551ac5a68eSchristos 	case SIGHUP:
1561ac5a68eSchristos 	case SIGQUIT:
1571ac5a68eSchristos 	case SIGTERM:
1581ac5a68eSchristos 		sig_term = signo;
1591ac5a68eSchristos 		break;
1601ac5a68eSchristos 	case SIGCHLD:
1611ac5a68eSchristos 		sig_chld = 1;
1621ac5a68eSchristos 		break;
1631ac5a68eSchristos 	case SIGALRM:
1641ac5a68eSchristos 		sig_alrm = 1;
1651ac5a68eSchristos 		break;
1661ac5a68eSchristos 	}
1671ac5a68eSchristos }
1681ac5a68eSchristos 
1691ac5a68eSchristos static void
set_interval(double iv)1701ac5a68eSchristos set_interval(double iv)
1711ac5a68eSchristos {
1721ac5a68eSchristos 	struct itimerval tim;
1731ac5a68eSchristos 
1741ac5a68eSchristos 	memset(&tim, 0, sizeof(tim));
1751ac5a68eSchristos 	tim.it_value.tv_sec = (time_t)iv;
176138cb133Schristos 	iv -= (double)tim.it_value.tv_sec;
1771ac5a68eSchristos 	tim.it_value.tv_usec = (suseconds_t)(iv * 1000000UL);
1781ac5a68eSchristos 
1791ac5a68eSchristos 	if (setitimer(ITIMER_REAL, &tim, NULL) == -1)
1801ac5a68eSchristos 		err(EX_OSERR, "setitimer()");
1811ac5a68eSchristos }
1821ac5a68eSchristos 
1831ac5a68eSchristos int
main(int argc,char ** argv)1841ac5a68eSchristos main(int argc, char **argv)
1851ac5a68eSchristos {
1861ac5a68eSchristos 	int ch;
1871ac5a68eSchristos 	unsigned long i;
1881ac5a68eSchristos 	int foreground, preserve;
1891ac5a68eSchristos 	int error, pstat, status;
1901ac5a68eSchristos 	int killsig = SIGTERM;
1911ac5a68eSchristos 	pid_t pgid, pid, cpid;
1921ac5a68eSchristos 	double first_kill;
1931ac5a68eSchristos 	double second_kill;
1941ac5a68eSchristos 	bool timedout = false;
1951ac5a68eSchristos 	bool do_second_kill = false;
1961ac5a68eSchristos 	struct sigaction signals;
1971ac5a68eSchristos 	int signums[] = {
1981ac5a68eSchristos 		-1,
1991ac5a68eSchristos 		SIGTERM,
2001ac5a68eSchristos 		SIGINT,
2011ac5a68eSchristos 		SIGHUP,
2021ac5a68eSchristos 		SIGCHLD,
2031ac5a68eSchristos 		SIGALRM,
2041ac5a68eSchristos 		SIGQUIT,
2051ac5a68eSchristos 	};
2061ac5a68eSchristos 
207138cb133Schristos 	setprogname(argv[0]);
208138cb133Schristos 
2091ac5a68eSchristos 	foreground = preserve = 0;
2101ac5a68eSchristos 	second_kill = 0;
2111ac5a68eSchristos 	cpid = -1;
2121ac5a68eSchristos 	pgid = -1;
2131ac5a68eSchristos 
2141ac5a68eSchristos 	const struct option longopts[] = {
215*65f114bcSkre 		{ "preserve-status", no_argument,       NULL,        'p'},
216*65f114bcSkre 		{ "foreground",      no_argument,       NULL,        'f'},
2171ac5a68eSchristos 		{ "kill-after",      required_argument, NULL,        'k'},
2181ac5a68eSchristos 		{ "signal",          required_argument, NULL,        's'},
2191ac5a68eSchristos 		{ "help",            no_argument,       NULL,        'h'},
2201ac5a68eSchristos 		{ NULL,              0,                 NULL,         0 }
2211ac5a68eSchristos 	};
2221ac5a68eSchristos 
223*65f114bcSkre 	while ((ch =
224*65f114bcSkre 	    getopt_long(argc, argv, "+fk:ps:h", longopts, NULL)) != -1) {
2251ac5a68eSchristos 		switch (ch) {
226*65f114bcSkre 			case 'f':
227*65f114bcSkre 				foreground = 1;
228*65f114bcSkre 				break;
2291ac5a68eSchristos 			case 'k':
2301ac5a68eSchristos 				do_second_kill = true;
2311ac5a68eSchristos 				second_kill = parse_duration(optarg);
2321ac5a68eSchristos 				break;
233*65f114bcSkre 			case 'p':
234*65f114bcSkre 				preserve = 1;
235*65f114bcSkre 				break;
2361ac5a68eSchristos 			case 's':
2371ac5a68eSchristos 				killsig = parse_signal(optarg);
2381ac5a68eSchristos 				break;
2391ac5a68eSchristos 			case 0:
2401ac5a68eSchristos 				break;
2411ac5a68eSchristos 			case 'h':
2421ac5a68eSchristos 			default:
2431ac5a68eSchristos 				usage();
2441ac5a68eSchristos 				break;
2451ac5a68eSchristos 		}
2461ac5a68eSchristos 	}
2471ac5a68eSchristos 
2481ac5a68eSchristos 	argc -= optind;
2491ac5a68eSchristos 	argv += optind;
2501ac5a68eSchristos 
2511ac5a68eSchristos 	if (argc < 2)
2521ac5a68eSchristos 		usage();
2531ac5a68eSchristos 
2541ac5a68eSchristos 	first_kill = parse_duration(argv[0]);
2551ac5a68eSchristos 	argc--;
2561ac5a68eSchristos 	argv++;
2571ac5a68eSchristos 
2581ac5a68eSchristos 	if (!foreground) {
2591ac5a68eSchristos 		pgid = setpgid(0,0);
2601ac5a68eSchristos 
2611ac5a68eSchristos 		if (pgid == -1)
2621ac5a68eSchristos 			err(EX_OSERR, "setpgid()");
2631ac5a68eSchristos 	}
2641ac5a68eSchristos 
2651ac5a68eSchristos 	memset(&signals, 0, sizeof(signals));
2661ac5a68eSchristos 	sigemptyset(&signals.sa_mask);
2671ac5a68eSchristos 
2681ac5a68eSchristos 	if (killsig != SIGKILL && killsig != SIGSTOP)
2691ac5a68eSchristos 		signums[0] = killsig;
2701ac5a68eSchristos 
2711ac5a68eSchristos 	for (i = 0; i < sizeof(signums) / sizeof(signums[0]); i ++)
2721ac5a68eSchristos 		sigaddset(&signals.sa_mask, signums[i]);
2731ac5a68eSchristos 
2741ac5a68eSchristos 	signals.sa_handler = sig_handler;
2751ac5a68eSchristos 	signals.sa_flags = SA_RESTART;
2761ac5a68eSchristos 
2771ac5a68eSchristos 	for (i = 0; i < sizeof(signums) / sizeof(signums[0]); i ++)
2781ac5a68eSchristos 		if (signums[i] != -1 && signums[i] != 0 &&
2791ac5a68eSchristos 		    sigaction(signums[i], &signals, NULL) == -1)
2801ac5a68eSchristos 			err(EX_OSERR, "sigaction()");
2811ac5a68eSchristos 
2821ac5a68eSchristos 	signal(SIGTTIN, SIG_IGN);
2831ac5a68eSchristos 	signal(SIGTTOU, SIG_IGN);
2841ac5a68eSchristos 
2851ac5a68eSchristos 	pid = fork();
2861ac5a68eSchristos 	if (pid == -1)
2871ac5a68eSchristos 		err(EX_OSERR, "fork()");
2881ac5a68eSchristos 	else if (pid == 0) {
2891ac5a68eSchristos 		/* child process */
2901ac5a68eSchristos 		signal(SIGTTIN, SIG_DFL);
2911ac5a68eSchristos 		signal(SIGTTOU, SIG_DFL);
2921ac5a68eSchristos 
2931ac5a68eSchristos 		error = execvp(argv[0], argv);
2941ac5a68eSchristos 		if (error == -1)
2951ac5a68eSchristos 			err(EX_UNAVAILABLE, "exec()");
2961ac5a68eSchristos 	}
2971ac5a68eSchristos 
2981ac5a68eSchristos 	if (sigprocmask(SIG_BLOCK, &signals.sa_mask, NULL) == -1)
2991ac5a68eSchristos 		err(EX_OSERR, "sigprocmask()");
3001ac5a68eSchristos 
3011ac5a68eSchristos 	/* parent continues here */
3021ac5a68eSchristos 	set_interval(first_kill);
3031ac5a68eSchristos 
3041ac5a68eSchristos 	for (;;) {
3051ac5a68eSchristos 		sigemptyset(&signals.sa_mask);
3061ac5a68eSchristos 		sigsuspend(&signals.sa_mask);
3071ac5a68eSchristos 
3081ac5a68eSchristos 		if (sig_chld) {
3091ac5a68eSchristos 			sig_chld = 0;
3101ac5a68eSchristos 			while (((cpid = wait(&status)) < 0) && errno == EINTR)
3111ac5a68eSchristos 				continue;
3121ac5a68eSchristos 
3131ac5a68eSchristos 			if (cpid == pid) {
3141ac5a68eSchristos 				pstat = status;
3151ac5a68eSchristos 				break;
3161ac5a68eSchristos 			}
3171ac5a68eSchristos 		} else if (sig_alrm) {
3181ac5a68eSchristos 			sig_alrm = 0;
3191ac5a68eSchristos 
3201ac5a68eSchristos 			timedout = true;
3211ac5a68eSchristos 			if (!foreground)
3221ac5a68eSchristos 				killpg(pgid, killsig);
3231ac5a68eSchristos 			else
3241ac5a68eSchristos 				kill(pid, killsig);
3251ac5a68eSchristos 
3261ac5a68eSchristos 			if (do_second_kill) {
3271ac5a68eSchristos 				set_interval(second_kill);
3281ac5a68eSchristos 				second_kill = 0;
3291ac5a68eSchristos 				sig_ign = killsig;
3301ac5a68eSchristos 				killsig = SIGKILL;
3311ac5a68eSchristos 			} else
3321ac5a68eSchristos 				break;
3331ac5a68eSchristos 
3341ac5a68eSchristos 		} else if (sig_term) {
3351ac5a68eSchristos 			if (!foreground)
3361ac5a68eSchristos 				killpg(pgid, killsig);
3371ac5a68eSchristos 			else
3387eebb173Smartin 				kill(pid, (int)sig_term);
3391ac5a68eSchristos 
3401ac5a68eSchristos 			if (do_second_kill) {
3411ac5a68eSchristos 				set_interval(second_kill);
3421ac5a68eSchristos 				second_kill = 0;
3431ac5a68eSchristos 				sig_ign = killsig;
3441ac5a68eSchristos 				killsig = SIGKILL;
3451ac5a68eSchristos 			} else
3461ac5a68eSchristos 				break;
3471ac5a68eSchristos 		}
3481ac5a68eSchristos 	}
3491ac5a68eSchristos 
3501ac5a68eSchristos 	while (cpid != pid  && wait(&pstat) == -1) {
3511ac5a68eSchristos 		if (errno != EINTR)
3521ac5a68eSchristos 			err(EX_OSERR, "waitpid()");
3531ac5a68eSchristos 	}
3541ac5a68eSchristos 
3551ac5a68eSchristos 	if (WEXITSTATUS(pstat))
3561ac5a68eSchristos 		pstat = WEXITSTATUS(pstat);
3571ac5a68eSchristos 	else if(WIFSIGNALED(pstat))
3581ac5a68eSchristos 		pstat = 128 + WTERMSIG(pstat);
3591ac5a68eSchristos 
3601ac5a68eSchristos 	if (timedout && !preserve)
3611ac5a68eSchristos 		pstat = EXIT_TIMEOUT;
3621ac5a68eSchristos 
3631ac5a68eSchristos 	return (pstat);
3641ac5a68eSchristos }
365