/* $NetBSD: timeout.c,v 1.5 2022/12/13 13:25:36 kre Exp $ */ /*- * Copyright (c) 2014 Baptiste Daroussin * Copyright (c) 2014 Vsevolod Stakhov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer * in this position and unchanged. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #if !defined(lint) #if 0 __FBSDID("$FreeBSD: head/usr.bin/timeout/timeout.c 268763 2014-07-16 13:52:05Z bapt $"); #else __RCSID("$NetBSD: timeout.c,v 1.5 2022/12/13 13:25:36 kre Exp $"); #endif #endif /* not lint */ #include #include #include #include #include #include #include #include #include #include #include #include #include #define EXIT_TIMEOUT 124 static sig_atomic_t sig_chld = 0; static sig_atomic_t sig_term = 0; static sig_atomic_t sig_alrm = 0; static sig_atomic_t sig_ign = 0; static void __dead usage(void) { fprintf(stderr, "Usage: %s [--signal sig | -s sig] [--preserve-status]" " [--kill-after time | -k time] [--foreground] " " \n", getprogname()); exit(EX_USAGE); } static double parse_duration(const char *duration) { double ret; char *end; ret = strtod(duration, &end); if (ret == 0 && end == duration) errx(EXIT_FAILURE, "invalid duration"); if (end == NULL || *end == '\0') return (ret); if (end != NULL && *(end + 1) != '\0') errx(EX_USAGE, "invalid duration"); switch (*end) { case 's': break; case 'm': ret *= 60; break; case 'h': ret *= 60 * 60; break; case 'd': ret *= 60 * 60 * 24; break; default: errx(EX_USAGE, "invalid duration"); } if (ret < 0 || ret >= 100000000UL) errx(EX_USAGE, "invalid duration"); return (ret); } static int parse_signal(const char *str) { long sig; int i; char *ep; if (strncasecmp(str, "SIG", 3) == 0) { str += 3; for (i = 1; i < sys_nsig; i++) { if (strcasecmp(str, sys_signame[i]) == 0) return (i); } goto err; } errno = 0; sig = strtol(str, &ep, 10); if (str[0] == '\0' || *ep != '\0') goto err; if (errno == ERANGE && (sig == LONG_MAX || sig == LONG_MIN)) goto err; if (sig >= sys_nsig || sig < 0) goto err; return (int)sig; err: errx(EX_USAGE, "invalid signal"); } static void sig_handler(int signo) { if (sig_ign != 0 && signo == sig_ign) { sig_ign = 0; return; } switch(signo) { case 0: case SIGINT: case SIGHUP: case SIGQUIT: case SIGTERM: sig_term = signo; break; case SIGCHLD: sig_chld = 1; break; case SIGALRM: sig_alrm = 1; break; } } static void set_interval(double iv) { struct itimerval tim; memset(&tim, 0, sizeof(tim)); tim.it_value.tv_sec = (time_t)iv; iv -= (double)tim.it_value.tv_sec; tim.it_value.tv_usec = (suseconds_t)(iv * 1000000UL); if (setitimer(ITIMER_REAL, &tim, NULL) == -1) err(EX_OSERR, "setitimer()"); } int main(int argc, char **argv) { int ch; unsigned long i; int foreground, preserve; int error, pstat, status; int killsig = SIGTERM; pid_t pgid, pid, cpid; double first_kill; double second_kill; bool timedout = false; bool do_second_kill = false; struct sigaction signals; int signums[] = { -1, SIGTERM, SIGINT, SIGHUP, SIGCHLD, SIGALRM, SIGQUIT, }; setprogname(argv[0]); foreground = preserve = 0; second_kill = 0; cpid = -1; pgid = -1; const struct option longopts[] = { { "preserve-status", no_argument, NULL, 'p'}, { "foreground", no_argument, NULL, 'f'}, { "kill-after", required_argument, NULL, 'k'}, { "signal", required_argument, NULL, 's'}, { "help", no_argument, NULL, 'h'}, { NULL, 0, NULL, 0 } }; while ((ch = getopt_long(argc, argv, "+fk:ps:h", longopts, NULL)) != -1) { switch (ch) { case 'f': foreground = 1; break; case 'k': do_second_kill = true; second_kill = parse_duration(optarg); break; case 'p': preserve = 1; break; case 's': killsig = parse_signal(optarg); break; case 0: break; case 'h': default: usage(); break; } } argc -= optind; argv += optind; if (argc < 2) usage(); first_kill = parse_duration(argv[0]); argc--; argv++; if (!foreground) { pgid = setpgid(0,0); if (pgid == -1) err(EX_OSERR, "setpgid()"); } memset(&signals, 0, sizeof(signals)); sigemptyset(&signals.sa_mask); if (killsig != SIGKILL && killsig != SIGSTOP) signums[0] = killsig; for (i = 0; i < sizeof(signums) / sizeof(signums[0]); i ++) sigaddset(&signals.sa_mask, signums[i]); signals.sa_handler = sig_handler; signals.sa_flags = SA_RESTART; for (i = 0; i < sizeof(signums) / sizeof(signums[0]); i ++) if (signums[i] != -1 && signums[i] != 0 && sigaction(signums[i], &signals, NULL) == -1) err(EX_OSERR, "sigaction()"); signal(SIGTTIN, SIG_IGN); signal(SIGTTOU, SIG_IGN); pid = fork(); if (pid == -1) err(EX_OSERR, "fork()"); else if (pid == 0) { /* child process */ signal(SIGTTIN, SIG_DFL); signal(SIGTTOU, SIG_DFL); error = execvp(argv[0], argv); if (error == -1) err(EX_UNAVAILABLE, "exec()"); } if (sigprocmask(SIG_BLOCK, &signals.sa_mask, NULL) == -1) err(EX_OSERR, "sigprocmask()"); /* parent continues here */ set_interval(first_kill); for (;;) { sigemptyset(&signals.sa_mask); sigsuspend(&signals.sa_mask); if (sig_chld) { sig_chld = 0; while (((cpid = wait(&status)) < 0) && errno == EINTR) continue; if (cpid == pid) { pstat = status; break; } } else if (sig_alrm) { sig_alrm = 0; timedout = true; if (!foreground) killpg(pgid, killsig); else kill(pid, killsig); if (do_second_kill) { set_interval(second_kill); second_kill = 0; sig_ign = killsig; killsig = SIGKILL; } else break; } else if (sig_term) { if (!foreground) killpg(pgid, killsig); else kill(pid, (int)sig_term); if (do_second_kill) { set_interval(second_kill); second_kill = 0; sig_ign = killsig; killsig = SIGKILL; } else break; } } while (cpid != pid && wait(&pstat) == -1) { if (errno != EINTR) err(EX_OSERR, "waitpid()"); } if (WEXITSTATUS(pstat)) pstat = WEXITSTATUS(pstat); else if(WIFSIGNALED(pstat)) pstat = 128 + WTERMSIG(pstat); if (timedout && !preserve) pstat = EXIT_TIMEOUT; return (pstat); }