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