1/* $NetBSD: timeout.c,v 1.5 2022/12/13 13:25:36 kre Exp $ */ 2 3/*- 4 * Copyright (c) 2014 Baptiste Daroussin <bapt@FreeBSD.org> 5 * Copyright (c) 2014 Vsevolod Stakhov <vsevolod@FreeBSD.org> 6 * All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer 13 * in this position and unchanged. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR 19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 21 * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, 22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 23 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 */ 29 30#include <sys/cdefs.h> 31#if !defined(lint) 32#if 0 33__FBSDID("$FreeBSD: head/usr.bin/timeout/timeout.c 268763 2014-07-16 13:52:05Z bapt $"); 34#else 35__RCSID("$NetBSD: timeout.c,v 1.5 2022/12/13 13:25:36 kre Exp $"); 36#endif 37#endif /* not lint */ 38 39#include <sys/time.h> 40#include <sys/wait.h> 41 42#include <err.h> 43#include <errno.h> 44#include <getopt.h> 45#include <limits.h> 46#include <signal.h> 47#include <stdbool.h> 48#include <stdio.h> 49#include <stdlib.h> 50#include <string.h> 51#include <sysexits.h> 52#include <unistd.h> 53 54#define EXIT_TIMEOUT 124 55 56static sig_atomic_t sig_chld = 0; 57static sig_atomic_t sig_term = 0; 58static sig_atomic_t sig_alrm = 0; 59static sig_atomic_t sig_ign = 0; 60 61static void __dead 62usage(void) 63{ 64 65 fprintf(stderr, "Usage: %s [--signal sig | -s sig] [--preserve-status]" 66 " [--kill-after time | -k time] [--foreground] <duration> <command>" 67 " <arg ...>\n", getprogname()); 68 69 exit(EX_USAGE); 70} 71 72static double 73parse_duration(const char *duration) 74{ 75 double ret; 76 char *end; 77 78 ret = strtod(duration, &end); 79 if (ret == 0 && end == duration) 80 errx(EXIT_FAILURE, "invalid duration"); 81 82 if (end == NULL || *end == '\0') 83 return (ret); 84 85 if (end != NULL && *(end + 1) != '\0') 86 errx(EX_USAGE, "invalid duration"); 87 88 switch (*end) { 89 case 's': 90 break; 91 case 'm': 92 ret *= 60; 93 break; 94 case 'h': 95 ret *= 60 * 60; 96 break; 97 case 'd': 98 ret *= 60 * 60 * 24; 99 break; 100 default: 101 errx(EX_USAGE, "invalid duration"); 102 } 103 104 if (ret < 0 || ret >= 100000000UL) 105 errx(EX_USAGE, "invalid duration"); 106 107 return (ret); 108} 109 110static int 111parse_signal(const char *str) 112{ 113 long sig; 114 int i; 115 char *ep; 116 117 if (strncasecmp(str, "SIG", 3) == 0) { 118 str += 3; 119 120 for (i = 1; i < sys_nsig; i++) { 121 if (strcasecmp(str, sys_signame[i]) == 0) 122 return (i); 123 } 124 125 goto err; 126 } 127 128 errno = 0; 129 sig = strtol(str, &ep, 10); 130 131 if (str[0] == '\0' || *ep != '\0') 132 goto err; 133 if (errno == ERANGE && (sig == LONG_MAX || sig == LONG_MIN)) 134 goto err; 135 if (sig >= sys_nsig || sig < 0) 136 goto err; 137 138 return (int)sig; 139 140err: 141 errx(EX_USAGE, "invalid signal"); 142} 143 144static void 145sig_handler(int signo) 146{ 147 if (sig_ign != 0 && signo == sig_ign) { 148 sig_ign = 0; 149 return; 150 } 151 152 switch(signo) { 153 case 0: 154 case SIGINT: 155 case SIGHUP: 156 case SIGQUIT: 157 case SIGTERM: 158 sig_term = signo; 159 break; 160 case SIGCHLD: 161 sig_chld = 1; 162 break; 163 case SIGALRM: 164 sig_alrm = 1; 165 break; 166 } 167} 168 169static void 170set_interval(double iv) 171{ 172 struct itimerval tim; 173 174 memset(&tim, 0, sizeof(tim)); 175 tim.it_value.tv_sec = (time_t)iv; 176 iv -= (double)tim.it_value.tv_sec; 177 tim.it_value.tv_usec = (suseconds_t)(iv * 1000000UL); 178 179 if (setitimer(ITIMER_REAL, &tim, NULL) == -1) 180 err(EX_OSERR, "setitimer()"); 181} 182 183int 184main(int argc, char **argv) 185{ 186 int ch; 187 unsigned long i; 188 int foreground, preserve; 189 int error, pstat, status; 190 int killsig = SIGTERM; 191 pid_t pgid, pid, cpid; 192 double first_kill; 193 double second_kill; 194 bool timedout = false; 195 bool do_second_kill = false; 196 struct sigaction signals; 197 int signums[] = { 198 -1, 199 SIGTERM, 200 SIGINT, 201 SIGHUP, 202 SIGCHLD, 203 SIGALRM, 204 SIGQUIT, 205 }; 206 207 setprogname(argv[0]); 208 209 foreground = preserve = 0; 210 second_kill = 0; 211 cpid = -1; 212 pgid = -1; 213 214 const struct option longopts[] = { 215 { "preserve-status", no_argument, NULL, 'p'}, 216 { "foreground", no_argument, NULL, 'f'}, 217 { "kill-after", required_argument, NULL, 'k'}, 218 { "signal", required_argument, NULL, 's'}, 219 { "help", no_argument, NULL, 'h'}, 220 { NULL, 0, NULL, 0 } 221 }; 222 223 while ((ch = 224 getopt_long(argc, argv, "+fk:ps:h", longopts, NULL)) != -1) { 225 switch (ch) { 226 case 'f': 227 foreground = 1; 228 break; 229 case 'k': 230 do_second_kill = true; 231 second_kill = parse_duration(optarg); 232 break; 233 case 'p': 234 preserve = 1; 235 break; 236 case 's': 237 killsig = parse_signal(optarg); 238 break; 239 case 0: 240 break; 241 case 'h': 242 default: 243 usage(); 244 break; 245 } 246 } 247 248 argc -= optind; 249 argv += optind; 250 251 if (argc < 2) 252 usage(); 253 254 first_kill = parse_duration(argv[0]); 255 argc--; 256 argv++; 257 258 if (!foreground) { 259 pgid = setpgid(0,0); 260 261 if (pgid == -1) 262 err(EX_OSERR, "setpgid()"); 263 } 264 265 memset(&signals, 0, sizeof(signals)); 266 sigemptyset(&signals.sa_mask); 267 268 if (killsig != SIGKILL && killsig != SIGSTOP) 269 signums[0] = killsig; 270 271 for (i = 0; i < sizeof(signums) / sizeof(signums[0]); i ++) 272 sigaddset(&signals.sa_mask, signums[i]); 273 274 signals.sa_handler = sig_handler; 275 signals.sa_flags = SA_RESTART; 276 277 for (i = 0; i < sizeof(signums) / sizeof(signums[0]); i ++) 278 if (signums[i] != -1 && signums[i] != 0 && 279 sigaction(signums[i], &signals, NULL) == -1) 280 err(EX_OSERR, "sigaction()"); 281 282 signal(SIGTTIN, SIG_IGN); 283 signal(SIGTTOU, SIG_IGN); 284 285 pid = fork(); 286 if (pid == -1) 287 err(EX_OSERR, "fork()"); 288 else if (pid == 0) { 289 /* child process */ 290 signal(SIGTTIN, SIG_DFL); 291 signal(SIGTTOU, SIG_DFL); 292 293 error = execvp(argv[0], argv); 294 if (error == -1) 295 err(EX_UNAVAILABLE, "exec()"); 296 } 297 298 if (sigprocmask(SIG_BLOCK, &signals.sa_mask, NULL) == -1) 299 err(EX_OSERR, "sigprocmask()"); 300 301 /* parent continues here */ 302 set_interval(first_kill); 303 304 for (;;) { 305 sigemptyset(&signals.sa_mask); 306 sigsuspend(&signals.sa_mask); 307 308 if (sig_chld) { 309 sig_chld = 0; 310 while (((cpid = wait(&status)) < 0) && errno == EINTR) 311 continue; 312 313 if (cpid == pid) { 314 pstat = status; 315 break; 316 } 317 } else if (sig_alrm) { 318 sig_alrm = 0; 319 320 timedout = true; 321 if (!foreground) 322 killpg(pgid, killsig); 323 else 324 kill(pid, killsig); 325 326 if (do_second_kill) { 327 set_interval(second_kill); 328 second_kill = 0; 329 sig_ign = killsig; 330 killsig = SIGKILL; 331 } else 332 break; 333 334 } else if (sig_term) { 335 if (!foreground) 336 killpg(pgid, killsig); 337 else 338 kill(pid, (int)sig_term); 339 340 if (do_second_kill) { 341 set_interval(second_kill); 342 second_kill = 0; 343 sig_ign = killsig; 344 killsig = SIGKILL; 345 } else 346 break; 347 } 348 } 349 350 while (cpid != pid && wait(&pstat) == -1) { 351 if (errno != EINTR) 352 err(EX_OSERR, "waitpid()"); 353 } 354 355 if (WEXITSTATUS(pstat)) 356 pstat = WEXITSTATUS(pstat); 357 else if(WIFSIGNALED(pstat)) 358 pstat = 128 + WTERMSIG(pstat); 359 360 if (timedout && !preserve) 361 pstat = EXIT_TIMEOUT; 362 363 return (pstat); 364} 365