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