xref: /openbsd-src/usr.bin/timeout/timeout.c (revision 17e3ddc9933714bd053ce70afd6d575a774aee60)
1*17e3ddc9Scheloha /* $OpenBSD: timeout.c,v 1.26 2023/11/03 19:16:31 cheloha Exp $ */
22b403d74Sjob 
3c3eb16d0Sjob /*
4e42df31dSjob  * Copyright (c) 2021 Job Snijders <job@openbsd.org>
52b403d74Sjob  * Copyright (c) 2014 Baptiste Daroussin <bapt@FreeBSD.org>
62b403d74Sjob  * Copyright (c) 2014 Vsevolod Stakhov <vsevolod@FreeBSD.org>
72b403d74Sjob  * All rights reserved.
82b403d74Sjob  *
92b403d74Sjob  * Redistribution and use in source and binary forms, with or without
102b403d74Sjob  * modification, are permitted provided that the following conditions
112b403d74Sjob  * are met:
122b403d74Sjob  * 1. Redistributions of source code must retain the above copyright
132b403d74Sjob  *    notice, this list of conditions and the following disclaimer
142b403d74Sjob  *    in this position and unchanged.
152b403d74Sjob  * 2. Redistributions in binary form must reproduce the above copyright
162b403d74Sjob  *    notice, this list of conditions and the following disclaimer in the
172b403d74Sjob  *    documentation and/or other materials provided with the distribution.
182b403d74Sjob  *
192b403d74Sjob  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
202b403d74Sjob  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
212b403d74Sjob  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
222b403d74Sjob  * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
232b403d74Sjob  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
242b403d74Sjob  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
252b403d74Sjob  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
262b403d74Sjob  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
272b403d74Sjob  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
282b403d74Sjob  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
292b403d74Sjob  */
302b403d74Sjob 
31959603a4Sjob #include <sys/types.h>
322b403d74Sjob #include <sys/time.h>
332b403d74Sjob #include <sys/wait.h>
342b403d74Sjob 
352a4adf00Skn #include <ctype.h>
362b403d74Sjob #include <err.h>
372b403d74Sjob #include <errno.h>
382b403d74Sjob #include <getopt.h>
392b403d74Sjob #include <limits.h>
402b403d74Sjob #include <signal.h>
412b403d74Sjob #include <stdbool.h>
422b403d74Sjob #include <stdio.h>
432b403d74Sjob #include <stdlib.h>
442b403d74Sjob #include <string.h>
452b403d74Sjob #include <unistd.h>
462b403d74Sjob 
472b403d74Sjob #define EXIT_TIMEOUT 124
482b403d74Sjob 
498074af79Scheloha static volatile sig_atomic_t sig_chld = 0;
508074af79Scheloha static volatile sig_atomic_t sig_term = 0;
518074af79Scheloha static volatile sig_atomic_t sig_alrm = 0;
528074af79Scheloha static volatile sig_atomic_t sig_ign = 0;
532b403d74Sjob 
542b403d74Sjob static void __dead
usage(void)552b403d74Sjob usage(void)
562b403d74Sjob {
578e6ed8b7Sjmc 	fprintf(stderr,
5877a16aa3Sjmc 	    "usage: timeout [-fp] [-k time] [-s signal] duration command"
59846b88a7Sjob 	    " [arg ...]\n");
60959603a4Sjob 	exit(1);
612b403d74Sjob }
622b403d74Sjob 
632b403d74Sjob static double
parse_duration(const char * duration)642b403d74Sjob parse_duration(const char *duration)
652b403d74Sjob {
662b403d74Sjob 	double	 ret;
67c3eb16d0Sjob 	char	*suffix;
682b403d74Sjob 
69c3eb16d0Sjob 	ret = strtod(duration, &suffix);
70c3eb16d0Sjob 	if (ret == 0 && suffix == duration)
718509b4d6Sschwarze 		errx(1, "duration is not a number");
72c3eb16d0Sjob 	if (ret < 0 || ret >= 100000000UL)
738509b4d6Sschwarze 		errx(1, "duration out of range");
742b403d74Sjob 
75c3eb16d0Sjob 	if (suffix == NULL || *suffix == '\0')
762b403d74Sjob 		return (ret);
772b403d74Sjob 
788509b4d6Sschwarze 	if (suffix[1] != '\0')
798509b4d6Sschwarze 		errx(1, "duration unit suffix too long");
802b403d74Sjob 
81c3eb16d0Sjob 	switch (*suffix) {
822b403d74Sjob 	case 's':
832b403d74Sjob 		break;
842b403d74Sjob 	case 'm':
852b403d74Sjob 		ret *= 60;
862b403d74Sjob 		break;
872b403d74Sjob 	case 'h':
882b403d74Sjob 		ret *= 60 * 60;
892b403d74Sjob 		break;
902b403d74Sjob 	case 'd':
912b403d74Sjob 		ret *= 60 * 60 * 24;
922b403d74Sjob 		break;
932b403d74Sjob 	default:
948509b4d6Sschwarze 		errx(1, "duration unit suffix is invalid");
952b403d74Sjob 	}
962b403d74Sjob 
972b403d74Sjob 	return (ret);
982b403d74Sjob }
992b403d74Sjob 
1002b403d74Sjob static int
parse_signal(const char * str)1012b403d74Sjob parse_signal(const char *str)
1022b403d74Sjob {
1033a18642fSderaadt 	long long	 sig;
1047da26772Sjob 	const char	*errstr;
1052b403d74Sjob 
1062a4adf00Skn 	if (isalpha((unsigned char)*str)) {
1073a18642fSderaadt 		int i;
1082b403d74Sjob 
1092a4adf00Skn 		if (strncasecmp(str, "SIG", 3) == 0)
1103a18642fSderaadt 			str += 3;
1112b403d74Sjob 		for (i = 1; i < NSIG; i++) {
1122b403d74Sjob 			if (strcasecmp(str, sys_signame[i]) == 0)
1132b403d74Sjob 				return (i);
1142b403d74Sjob 		}
115844bf4eaSderaadt 		errx(1, "invalid signal name");
1162b403d74Sjob 	}
1172b403d74Sjob 
1185ff327d0Sderaadt 	sig = strtonum(str, 1, NSIG, &errstr);
1197da26772Sjob 	if (errstr != NULL)
120844bf4eaSderaadt 		errx(1, "signal %s %s", str, errstr);
1212b403d74Sjob 
1222b403d74Sjob 	return (int)sig;
1232b403d74Sjob }
1242b403d74Sjob 
1252b403d74Sjob static void
sig_handler(int signo)1262b403d74Sjob sig_handler(int signo)
1272b403d74Sjob {
1282b403d74Sjob 	if (sig_ign != 0 && signo == sig_ign) {
1292b403d74Sjob 		sig_ign = 0;
1302b403d74Sjob 		return;
1312b403d74Sjob 	}
1322b403d74Sjob 
1332b403d74Sjob 	switch (signo) {
1342b403d74Sjob 	case SIGINT:
1352b403d74Sjob 	case SIGHUP:
1362b403d74Sjob 	case SIGQUIT:
1372b403d74Sjob 	case SIGTERM:
1382b403d74Sjob 		sig_term = signo;
1392b403d74Sjob 		break;
1402b403d74Sjob 	case SIGCHLD:
1412b403d74Sjob 		sig_chld = 1;
1422b403d74Sjob 		break;
1432b403d74Sjob 	case SIGALRM:
1442b403d74Sjob 		sig_alrm = 1;
1452b403d74Sjob 		break;
1462b403d74Sjob 	}
1472b403d74Sjob }
1482b403d74Sjob 
1492b403d74Sjob static void
set_interval(double iv)1502b403d74Sjob set_interval(double iv)
1512b403d74Sjob {
1522b403d74Sjob 	struct itimerval tim;
1532b403d74Sjob 
1542b403d74Sjob 	memset(&tim, 0, sizeof(tim));
1552b403d74Sjob 	tim.it_value.tv_sec = (time_t)iv;
1562b403d74Sjob 	iv -= (double)tim.it_value.tv_sec;
1572b403d74Sjob 	tim.it_value.tv_usec = (suseconds_t)(iv * 1000000UL);
1582b403d74Sjob 
1592b403d74Sjob 	if (setitimer(ITIMER_REAL, &tim, NULL) == -1)
160e42df31dSjob 		err(1, "setitimer");
1612b403d74Sjob }
1622b403d74Sjob 
1632b403d74Sjob int
main(int argc,char ** argv)1642b403d74Sjob main(int argc, char **argv)
1652b403d74Sjob {
1662b403d74Sjob 	int		ch;
1672b403d74Sjob 	unsigned long	i;
1683a18642fSderaadt 	int		foreground = 0, preserve = 0;
169e6363970Ssemarie 	int		pstat, status;
1702b403d74Sjob 	int		killsig = SIGTERM;
1713a18642fSderaadt 	pid_t		pgid = 0, pid, cpid = 0;
1722b403d74Sjob 	double		first_kill;
1733a18642fSderaadt 	double		second_kill = 0;
1742b403d74Sjob 	bool		timedout = false;
1752b403d74Sjob 	bool		do_second_kill = false;
1762b403d74Sjob 	struct		sigaction signals;
177c4e81f13Sjob 	int		signums[] = {-1, SIGTERM, SIGINT, SIGHUP, SIGCHLD,
178c4e81f13Sjob 			    SIGALRM, SIGQUIT};
1792b403d74Sjob 
1802b403d74Sjob 	const struct option longopts[] = {
181846b88a7Sjob 		{ "preserve-status", no_argument,       NULL,        'p'},
182846b88a7Sjob 		{ "foreground",      no_argument,       NULL,        'f'},
1832b403d74Sjob 		{ "kill-after",      required_argument, NULL,        'k'},
1842b403d74Sjob 		{ "signal",          required_argument, NULL,        's'},
1852b403d74Sjob 		{ "help",            no_argument,       NULL,        'h'},
1862b403d74Sjob 		{ NULL,              0,                 NULL,         0 }
1872b403d74Sjob 	};
1882b403d74Sjob 
189811c0a7eSjob 	if (pledge("stdio proc exec", NULL) == -1)
190811c0a7eSjob 		err(1, "pledge");
191811c0a7eSjob 
192846b88a7Sjob 	while ((ch = getopt_long(argc, argv, "+fk:ps:h", longopts, NULL))
193846b88a7Sjob 	    != -1) {
1942b403d74Sjob 		switch (ch) {
195846b88a7Sjob 		case 'f':
196846b88a7Sjob 			foreground = 1;
197846b88a7Sjob 			break;
1982b403d74Sjob 		case 'k':
1992b403d74Sjob 			do_second_kill = true;
2002b403d74Sjob 			second_kill = parse_duration(optarg);
2012b403d74Sjob 			break;
202846b88a7Sjob 		case 'p':
203846b88a7Sjob 			preserve = 1;
204846b88a7Sjob 			break;
2052b403d74Sjob 		case 's':
2062b403d74Sjob 			killsig = parse_signal(optarg);
2072b403d74Sjob 			break;
2082b403d74Sjob 		case 0:
2092b403d74Sjob 			break;
2102b403d74Sjob 		default:
2112b403d74Sjob 			usage();
2122b403d74Sjob 			break;
2132b403d74Sjob 		}
2142b403d74Sjob 	}
2152b403d74Sjob 
2162b403d74Sjob 	argc -= optind;
2172b403d74Sjob 	argv += optind;
2182b403d74Sjob 
2192b403d74Sjob 	if (argc < 2)
2202b403d74Sjob 		usage();
2212b403d74Sjob 
2222b403d74Sjob 	first_kill = parse_duration(argv[0]);
2232b403d74Sjob 	argc--;
2242b403d74Sjob 	argv++;
2252b403d74Sjob 
2262b403d74Sjob 	if (!foreground) {
2272b403d74Sjob 		pgid = setpgid(0, 0);
2282b403d74Sjob 
2292b403d74Sjob 		if (pgid == -1)
230e42df31dSjob 			err(1, "setpgid");
2312b403d74Sjob 	}
2322b403d74Sjob 
2332b403d74Sjob 	memset(&signals, 0, sizeof(signals));
2342b403d74Sjob 	sigemptyset(&signals.sa_mask);
2352b403d74Sjob 
2362b403d74Sjob 	if (killsig != SIGKILL && killsig != SIGSTOP)
2372b403d74Sjob 		signums[0] = killsig;
2382b403d74Sjob 
2392b403d74Sjob 	for (i = 0; i < sizeof(signums) / sizeof(signums[0]); i++)
2402b403d74Sjob 		sigaddset(&signals.sa_mask, signums[i]);
2412b403d74Sjob 
2422b403d74Sjob 	signals.sa_handler = sig_handler;
2432b403d74Sjob 	signals.sa_flags = SA_RESTART;
2442b403d74Sjob 
2452b403d74Sjob 	for (i = 0; i < sizeof(signums) / sizeof(signums[0]); i++) {
2462b403d74Sjob 		if (signums[i] != -1 && signums[i] != 0 &&
2472b403d74Sjob 		    sigaction(signums[i], &signals, NULL) == -1)
248e42df31dSjob 			err(1, "sigaction");
2492b403d74Sjob 	}
2502b403d74Sjob 
2512b403d74Sjob 	signal(SIGTTIN, SIG_IGN);
2522b403d74Sjob 	signal(SIGTTOU, SIG_IGN);
2532b403d74Sjob 
2542b403d74Sjob 	pid = fork();
2552b403d74Sjob 	if (pid == -1)
256e42df31dSjob 		err(1, "fork");
2572b403d74Sjob 	else if (pid == 0) {
2582b403d74Sjob 		/* child process */
2592b403d74Sjob 		signal(SIGTTIN, SIG_DFL);
2602b403d74Sjob 		signal(SIGTTOU, SIG_DFL);
2612b403d74Sjob 
262e6363970Ssemarie 		execvp(argv[0], argv);
263*17e3ddc9Scheloha 		warn("%s", argv[0]);
264*17e3ddc9Scheloha 		_exit(errno == ENOENT ? 127 : 126);
2652b403d74Sjob 	}
2662b403d74Sjob 
267aa841cc6Sderaadt 	/* parent continues here */
268aa841cc6Sderaadt 
269d2e9d22cSderaadt 	if (pledge("stdio proc", NULL) == -1)
270811c0a7eSjob 		err(1, "pledge");
271811c0a7eSjob 
2722b403d74Sjob 	if (sigprocmask(SIG_BLOCK, &signals.sa_mask, NULL) == -1)
273e42df31dSjob 		err(1, "sigprocmask");
2742b403d74Sjob 
2752b403d74Sjob 	set_interval(first_kill);
2762b403d74Sjob 
2772b403d74Sjob 	for (;;) {
2782b403d74Sjob 		sigemptyset(&signals.sa_mask);
2792b403d74Sjob 		sigsuspend(&signals.sa_mask);
2802b403d74Sjob 
2812b403d74Sjob 		if (sig_chld) {
2822b403d74Sjob 			sig_chld = 0;
2832b403d74Sjob 			while (((cpid = wait(&status)) < 0) && errno == EINTR)
2842b403d74Sjob 				continue;
2852b403d74Sjob 
2862b403d74Sjob 			if (cpid == pid) {
2872b403d74Sjob 				pstat = status;
2882b403d74Sjob 				break;
2892b403d74Sjob 			}
2902b403d74Sjob 		} else if (sig_alrm) {
2912b403d74Sjob 			sig_alrm = 0;
2922b403d74Sjob 
2932b403d74Sjob 			timedout = true;
2942b403d74Sjob 			if (!foreground)
2952b403d74Sjob 				killpg(pgid, killsig);
2962b403d74Sjob 			else
2972b403d74Sjob 				kill(pid, killsig);
2982b403d74Sjob 
2992b403d74Sjob 			if (do_second_kill) {
3002b403d74Sjob 				set_interval(second_kill);
3012b403d74Sjob 				second_kill = 0;
3022b403d74Sjob 				sig_ign = killsig;
3032b403d74Sjob 				killsig = SIGKILL;
3042b403d74Sjob 			} else
3052b403d74Sjob 				break;
3062b403d74Sjob 
3072b403d74Sjob 		} else if (sig_term) {
3082b403d74Sjob 			if (!foreground)
3092b403d74Sjob 				killpg(pgid, killsig);
3102b403d74Sjob 			else
3112b403d74Sjob 				kill(pid, (int)sig_term);
3122b403d74Sjob 
3132b403d74Sjob 			if (do_second_kill) {
3142b403d74Sjob 				set_interval(second_kill);
3152b403d74Sjob 				second_kill = 0;
3162b403d74Sjob 				sig_ign = killsig;
3172b403d74Sjob 				killsig = SIGKILL;
3182b403d74Sjob 			} else
3192b403d74Sjob 				break;
3202b403d74Sjob 		}
3212b403d74Sjob 	}
3222b403d74Sjob 
3232b403d74Sjob 	while (cpid != pid && wait(&pstat) == -1) {
3242b403d74Sjob 		if (errno != EINTR)
325e42df31dSjob 			err(1, "wait");
3262b403d74Sjob 	}
3272b403d74Sjob 
3282b403d74Sjob 	if (WEXITSTATUS(pstat))
3292b403d74Sjob 		pstat = WEXITSTATUS(pstat);
3302b403d74Sjob 	else if (WIFSIGNALED(pstat))
3312b403d74Sjob 		pstat = 128 + WTERMSIG(pstat);
3322b403d74Sjob 
3332b403d74Sjob 	if (timedout && !preserve)
3342b403d74Sjob 		pstat = EXIT_TIMEOUT;
3352b403d74Sjob 
3362b403d74Sjob 	return (pstat);
3372b403d74Sjob }
338