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