xref: /netbsd-src/usr.bin/timeout/timeout.c (revision f2d4de93b11cc398649ce949e24df4d95a0f367f)
1*f2d4de93Skre /* $NetBSD: timeout.c,v 1.5 2022/12/13 13:25:36 kre Exp $ */
23f7fdb96Schristos 
37a7434feSchristos /*-
47a7434feSchristos  * Copyright (c) 2014 Baptiste Daroussin <bapt@FreeBSD.org>
57a7434feSchristos  * Copyright (c) 2014 Vsevolod Stakhov <vsevolod@FreeBSD.org>
67a7434feSchristos  * All rights reserved.
77a7434feSchristos  *
87a7434feSchristos  * Redistribution and use in source and binary forms, with or without
97a7434feSchristos  * modification, are permitted provided that the following conditions
107a7434feSchristos  * are met:
117a7434feSchristos  * 1. Redistributions of source code must retain the above copyright
127a7434feSchristos  *    notice, this list of conditions and the following disclaimer
137a7434feSchristos  *    in this position and unchanged.
147a7434feSchristos  * 2. Redistributions in binary form must reproduce the above copyright
157a7434feSchristos  *    notice, this list of conditions and the following disclaimer in the
167a7434feSchristos  *    documentation and/or other materials provided with the distribution.
177a7434feSchristos  *
187a7434feSchristos  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
197a7434feSchristos  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
207a7434feSchristos  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
217a7434feSchristos  * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
227a7434feSchristos  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
237a7434feSchristos  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
247a7434feSchristos  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
257a7434feSchristos  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
267a7434feSchristos  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
277a7434feSchristos  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
287a7434feSchristos  */
297a7434feSchristos 
307a7434feSchristos #include <sys/cdefs.h>
313f7fdb96Schristos #if !defined(lint)
323f7fdb96Schristos #if 0
337a7434feSchristos __FBSDID("$FreeBSD: head/usr.bin/timeout/timeout.c 268763 2014-07-16 13:52:05Z bapt $");
343f7fdb96Schristos #else
35*f2d4de93Skre __RCSID("$NetBSD: timeout.c,v 1.5 2022/12/13 13:25:36 kre Exp $");
363f7fdb96Schristos #endif
373f7fdb96Schristos #endif /* not lint */
387a7434feSchristos 
397a7434feSchristos #include <sys/time.h>
407a7434feSchristos #include <sys/wait.h>
417a7434feSchristos 
427a7434feSchristos #include <err.h>
437a7434feSchristos #include <errno.h>
447a7434feSchristos #include <getopt.h>
453f7fdb96Schristos #include <limits.h>
467a7434feSchristos #include <signal.h>
477a7434feSchristos #include <stdbool.h>
487a7434feSchristos #include <stdio.h>
497a7434feSchristos #include <stdlib.h>
507a7434feSchristos #include <string.h>
517a7434feSchristos #include <sysexits.h>
527a7434feSchristos #include <unistd.h>
537a7434feSchristos 
547a7434feSchristos #define EXIT_TIMEOUT 124
557a7434feSchristos 
567a7434feSchristos static sig_atomic_t sig_chld = 0;
577a7434feSchristos static sig_atomic_t sig_term = 0;
587a7434feSchristos static sig_atomic_t sig_alrm = 0;
597a7434feSchristos static sig_atomic_t sig_ign = 0;
607a7434feSchristos 
613f7fdb96Schristos static void __dead
usage(void)627a7434feSchristos usage(void)
637a7434feSchristos {
647a7434feSchristos 
657a7434feSchristos 	fprintf(stderr, "Usage: %s [--signal sig | -s sig] [--preserve-status]"
667a7434feSchristos 	    " [--kill-after time | -k time] [--foreground] <duration> <command>"
677a7434feSchristos 	    " <arg ...>\n", getprogname());
687a7434feSchristos 
697a7434feSchristos 	exit(EX_USAGE);
707a7434feSchristos }
717a7434feSchristos 
727a7434feSchristos static double
parse_duration(const char * duration)737a7434feSchristos parse_duration(const char *duration)
747a7434feSchristos {
757a7434feSchristos 	double ret;
767a7434feSchristos 	char *end;
777a7434feSchristos 
787a7434feSchristos 	ret = strtod(duration, &end);
797a7434feSchristos 	if (ret == 0 && end == duration)
807a7434feSchristos 		errx(EXIT_FAILURE, "invalid duration");
817a7434feSchristos 
827a7434feSchristos 	if (end == NULL || *end == '\0')
837a7434feSchristos 		return (ret);
847a7434feSchristos 
857a7434feSchristos 	if (end != NULL && *(end + 1) != '\0')
867a7434feSchristos 		errx(EX_USAGE, "invalid duration");
877a7434feSchristos 
887a7434feSchristos 	switch (*end) {
897a7434feSchristos 	case 's':
907a7434feSchristos 		break;
917a7434feSchristos 	case 'm':
927a7434feSchristos 		ret *= 60;
937a7434feSchristos 		break;
947a7434feSchristos 	case 'h':
957a7434feSchristos 		ret *= 60 * 60;
967a7434feSchristos 		break;
977a7434feSchristos 	case 'd':
987a7434feSchristos 		ret *= 60 * 60 * 24;
997a7434feSchristos 		break;
1007a7434feSchristos 	default:
1017a7434feSchristos 		errx(EX_USAGE, "invalid duration");
1027a7434feSchristos 	}
1037a7434feSchristos 
1047a7434feSchristos 	if (ret < 0 || ret >= 100000000UL)
1057a7434feSchristos 		errx(EX_USAGE, "invalid duration");
1067a7434feSchristos 
1077a7434feSchristos 	return (ret);
1087a7434feSchristos }
1097a7434feSchristos 
1107a7434feSchristos static int
parse_signal(const char * str)1117a7434feSchristos parse_signal(const char *str)
1127a7434feSchristos {
1133f7fdb96Schristos 	long sig;
1143f7fdb96Schristos 	int i;
1153f7fdb96Schristos 	char *ep;
1167a7434feSchristos 
1173f7fdb96Schristos 	if (strncasecmp(str, "SIG", 3) == 0) {
1187a7434feSchristos 		str += 3;
1197a7434feSchristos 
1207a7434feSchristos 		for (i = 1; i < sys_nsig; i++) {
1217a7434feSchristos 			if (strcasecmp(str, sys_signame[i]) == 0)
1227a7434feSchristos 				return (i);
1237a7434feSchristos 		}
1247a7434feSchristos 
1253f7fdb96Schristos 		goto err;
1263f7fdb96Schristos 	}
1273f7fdb96Schristos 
1283f7fdb96Schristos 	errno = 0;
1293f7fdb96Schristos 	sig = strtol(str, &ep, 10);
1303f7fdb96Schristos 
1313f7fdb96Schristos 	if (str[0] == '\0' || *ep != '\0')
1323f7fdb96Schristos 		goto err;
133f70ccee6Schristos 	if (errno == ERANGE && (sig == LONG_MAX || sig == LONG_MIN))
1343f7fdb96Schristos 		goto err;
1353f7fdb96Schristos 	if (sig >= sys_nsig || sig < 0)
1363f7fdb96Schristos 		goto err;
1373f7fdb96Schristos 
1383f7fdb96Schristos 	return (int)sig;
1393f7fdb96Schristos 
1403f7fdb96Schristos err:
1417a7434feSchristos 	errx(EX_USAGE, "invalid signal");
1427a7434feSchristos }
1437a7434feSchristos 
1447a7434feSchristos static void
sig_handler(int signo)1457a7434feSchristos sig_handler(int signo)
1467a7434feSchristos {
1477a7434feSchristos 	if (sig_ign != 0 && signo == sig_ign) {
1487a7434feSchristos 		sig_ign = 0;
1497a7434feSchristos 		return;
1507a7434feSchristos 	}
1517a7434feSchristos 
1527a7434feSchristos 	switch(signo) {
1537a7434feSchristos 	case 0:
1547a7434feSchristos 	case SIGINT:
1557a7434feSchristos 	case SIGHUP:
1567a7434feSchristos 	case SIGQUIT:
1577a7434feSchristos 	case SIGTERM:
1587a7434feSchristos 		sig_term = signo;
1597a7434feSchristos 		break;
1607a7434feSchristos 	case SIGCHLD:
1617a7434feSchristos 		sig_chld = 1;
1627a7434feSchristos 		break;
1637a7434feSchristos 	case SIGALRM:
1647a7434feSchristos 		sig_alrm = 1;
1657a7434feSchristos 		break;
1667a7434feSchristos 	}
1677a7434feSchristos }
1687a7434feSchristos 
1697a7434feSchristos static void
set_interval(double iv)1707a7434feSchristos set_interval(double iv)
1717a7434feSchristos {
1727a7434feSchristos 	struct itimerval tim;
1737a7434feSchristos 
1747a7434feSchristos 	memset(&tim, 0, sizeof(tim));
1757a7434feSchristos 	tim.it_value.tv_sec = (time_t)iv;
1763f7fdb96Schristos 	iv -= (double)tim.it_value.tv_sec;
1777a7434feSchristos 	tim.it_value.tv_usec = (suseconds_t)(iv * 1000000UL);
1787a7434feSchristos 
1797a7434feSchristos 	if (setitimer(ITIMER_REAL, &tim, NULL) == -1)
1807a7434feSchristos 		err(EX_OSERR, "setitimer()");
1817a7434feSchristos }
1827a7434feSchristos 
1837a7434feSchristos int
main(int argc,char ** argv)1847a7434feSchristos main(int argc, char **argv)
1857a7434feSchristos {
1867a7434feSchristos 	int ch;
1877a7434feSchristos 	unsigned long i;
1887a7434feSchristos 	int foreground, preserve;
1897a7434feSchristos 	int error, pstat, status;
1907a7434feSchristos 	int killsig = SIGTERM;
1917a7434feSchristos 	pid_t pgid, pid, cpid;
1927a7434feSchristos 	double first_kill;
1937a7434feSchristos 	double second_kill;
1947a7434feSchristos 	bool timedout = false;
1957a7434feSchristos 	bool do_second_kill = false;
1967a7434feSchristos 	struct sigaction signals;
1977a7434feSchristos 	int signums[] = {
1987a7434feSchristos 		-1,
1997a7434feSchristos 		SIGTERM,
2007a7434feSchristos 		SIGINT,
2017a7434feSchristos 		SIGHUP,
2027a7434feSchristos 		SIGCHLD,
2037a7434feSchristos 		SIGALRM,
2047a7434feSchristos 		SIGQUIT,
2057a7434feSchristos 	};
2067a7434feSchristos 
2073f7fdb96Schristos 	setprogname(argv[0]);
2083f7fdb96Schristos 
2097a7434feSchristos 	foreground = preserve = 0;
2107a7434feSchristos 	second_kill = 0;
2117a7434feSchristos 	cpid = -1;
2127a7434feSchristos 	pgid = -1;
2137a7434feSchristos 
2147a7434feSchristos 	const struct option longopts[] = {
215*f2d4de93Skre 		{ "preserve-status", no_argument,       NULL,        'p'},
216*f2d4de93Skre 		{ "foreground",      no_argument,       NULL,        'f'},
2177a7434feSchristos 		{ "kill-after",      required_argument, NULL,        'k'},
2187a7434feSchristos 		{ "signal",          required_argument, NULL,        's'},
2197a7434feSchristos 		{ "help",            no_argument,       NULL,        'h'},
2207a7434feSchristos 		{ NULL,              0,                 NULL,         0 }
2217a7434feSchristos 	};
2227a7434feSchristos 
223*f2d4de93Skre 	while ((ch =
224*f2d4de93Skre 	    getopt_long(argc, argv, "+fk:ps:h", longopts, NULL)) != -1) {
2257a7434feSchristos 		switch (ch) {
226*f2d4de93Skre 			case 'f':
227*f2d4de93Skre 				foreground = 1;
228*f2d4de93Skre 				break;
2297a7434feSchristos 			case 'k':
2307a7434feSchristos 				do_second_kill = true;
2317a7434feSchristos 				second_kill = parse_duration(optarg);
2327a7434feSchristos 				break;
233*f2d4de93Skre 			case 'p':
234*f2d4de93Skre 				preserve = 1;
235*f2d4de93Skre 				break;
2367a7434feSchristos 			case 's':
2377a7434feSchristos 				killsig = parse_signal(optarg);
2387a7434feSchristos 				break;
2397a7434feSchristos 			case 0:
2407a7434feSchristos 				break;
2417a7434feSchristos 			case 'h':
2427a7434feSchristos 			default:
2437a7434feSchristos 				usage();
2447a7434feSchristos 				break;
2457a7434feSchristos 		}
2467a7434feSchristos 	}
2477a7434feSchristos 
2487a7434feSchristos 	argc -= optind;
2497a7434feSchristos 	argv += optind;
2507a7434feSchristos 
2517a7434feSchristos 	if (argc < 2)
2527a7434feSchristos 		usage();
2537a7434feSchristos 
2547a7434feSchristos 	first_kill = parse_duration(argv[0]);
2557a7434feSchristos 	argc--;
2567a7434feSchristos 	argv++;
2577a7434feSchristos 
2587a7434feSchristos 	if (!foreground) {
2597a7434feSchristos 		pgid = setpgid(0,0);
2607a7434feSchristos 
2617a7434feSchristos 		if (pgid == -1)
2627a7434feSchristos 			err(EX_OSERR, "setpgid()");
2637a7434feSchristos 	}
2647a7434feSchristos 
2657a7434feSchristos 	memset(&signals, 0, sizeof(signals));
2667a7434feSchristos 	sigemptyset(&signals.sa_mask);
2677a7434feSchristos 
2687a7434feSchristos 	if (killsig != SIGKILL && killsig != SIGSTOP)
2697a7434feSchristos 		signums[0] = killsig;
2707a7434feSchristos 
2717a7434feSchristos 	for (i = 0; i < sizeof(signums) / sizeof(signums[0]); i ++)
2727a7434feSchristos 		sigaddset(&signals.sa_mask, signums[i]);
2737a7434feSchristos 
2747a7434feSchristos 	signals.sa_handler = sig_handler;
2757a7434feSchristos 	signals.sa_flags = SA_RESTART;
2767a7434feSchristos 
2777a7434feSchristos 	for (i = 0; i < sizeof(signums) / sizeof(signums[0]); i ++)
2787a7434feSchristos 		if (signums[i] != -1 && signums[i] != 0 &&
2797a7434feSchristos 		    sigaction(signums[i], &signals, NULL) == -1)
2807a7434feSchristos 			err(EX_OSERR, "sigaction()");
2817a7434feSchristos 
2827a7434feSchristos 	signal(SIGTTIN, SIG_IGN);
2837a7434feSchristos 	signal(SIGTTOU, SIG_IGN);
2847a7434feSchristos 
2857a7434feSchristos 	pid = fork();
2867a7434feSchristos 	if (pid == -1)
2877a7434feSchristos 		err(EX_OSERR, "fork()");
2887a7434feSchristos 	else if (pid == 0) {
2897a7434feSchristos 		/* child process */
2907a7434feSchristos 		signal(SIGTTIN, SIG_DFL);
2917a7434feSchristos 		signal(SIGTTOU, SIG_DFL);
2927a7434feSchristos 
2937a7434feSchristos 		error = execvp(argv[0], argv);
2947a7434feSchristos 		if (error == -1)
2957a7434feSchristos 			err(EX_UNAVAILABLE, "exec()");
2967a7434feSchristos 	}
2977a7434feSchristos 
2987a7434feSchristos 	if (sigprocmask(SIG_BLOCK, &signals.sa_mask, NULL) == -1)
2997a7434feSchristos 		err(EX_OSERR, "sigprocmask()");
3007a7434feSchristos 
3017a7434feSchristos 	/* parent continues here */
3027a7434feSchristos 	set_interval(first_kill);
3037a7434feSchristos 
3047a7434feSchristos 	for (;;) {
3057a7434feSchristos 		sigemptyset(&signals.sa_mask);
3067a7434feSchristos 		sigsuspend(&signals.sa_mask);
3077a7434feSchristos 
3087a7434feSchristos 		if (sig_chld) {
3097a7434feSchristos 			sig_chld = 0;
3107a7434feSchristos 			while (((cpid = wait(&status)) < 0) && errno == EINTR)
3117a7434feSchristos 				continue;
3127a7434feSchristos 
3137a7434feSchristos 			if (cpid == pid) {
3147a7434feSchristos 				pstat = status;
3157a7434feSchristos 				break;
3167a7434feSchristos 			}
3177a7434feSchristos 		} else if (sig_alrm) {
3187a7434feSchristos 			sig_alrm = 0;
3197a7434feSchristos 
3207a7434feSchristos 			timedout = true;
3217a7434feSchristos 			if (!foreground)
3227a7434feSchristos 				killpg(pgid, killsig);
3237a7434feSchristos 			else
3247a7434feSchristos 				kill(pid, killsig);
3257a7434feSchristos 
3267a7434feSchristos 			if (do_second_kill) {
3277a7434feSchristos 				set_interval(second_kill);
3287a7434feSchristos 				second_kill = 0;
3297a7434feSchristos 				sig_ign = killsig;
3307a7434feSchristos 				killsig = SIGKILL;
3317a7434feSchristos 			} else
3327a7434feSchristos 				break;
3337a7434feSchristos 
3347a7434feSchristos 		} else if (sig_term) {
3357a7434feSchristos 			if (!foreground)
3367a7434feSchristos 				killpg(pgid, killsig);
3377a7434feSchristos 			else
33872c3b098Smartin 				kill(pid, (int)sig_term);
3397a7434feSchristos 
3407a7434feSchristos 			if (do_second_kill) {
3417a7434feSchristos 				set_interval(second_kill);
3427a7434feSchristos 				second_kill = 0;
3437a7434feSchristos 				sig_ign = killsig;
3447a7434feSchristos 				killsig = SIGKILL;
3457a7434feSchristos 			} else
3467a7434feSchristos 				break;
3477a7434feSchristos 		}
3487a7434feSchristos 	}
3497a7434feSchristos 
3507a7434feSchristos 	while (cpid != pid  && wait(&pstat) == -1) {
3517a7434feSchristos 		if (errno != EINTR)
3527a7434feSchristos 			err(EX_OSERR, "waitpid()");
3537a7434feSchristos 	}
3547a7434feSchristos 
3557a7434feSchristos 	if (WEXITSTATUS(pstat))
3567a7434feSchristos 		pstat = WEXITSTATUS(pstat);
3577a7434feSchristos 	else if(WIFSIGNALED(pstat))
3587a7434feSchristos 		pstat = 128 + WTERMSIG(pstat);
3597a7434feSchristos 
3607a7434feSchristos 	if (timedout && !preserve)
3617a7434feSchristos 		pstat = EXIT_TIMEOUT;
3627a7434feSchristos 
3637a7434feSchristos 	return (pstat);
3647a7434feSchristos }
365