1 /*- 2 * Copyright (c) 2014 Baptiste Daroussin <bapt@FreeBSD.org> 3 * Copyright (c) 2014 Vsevolod Stakhov <vsevolod@FreeBSD.org> 4 * Copyright (c) 2025 Aaron LI <aly@aaronly.me> 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer 12 * in this position and unchanged. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR 18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20 * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, 21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #include <sys/fcntl.h> 30 #include <sys/procctl.h> 31 #include <sys/resource.h> 32 #include <sys/time.h> 33 #include <sys/wait.h> 34 35 #include <err.h> 36 #include <errno.h> 37 #include <getopt.h> 38 #include <signal.h> 39 #include <stdarg.h> 40 #include <stdbool.h> 41 #include <stdio.h> 42 #include <stdlib.h> 43 #include <string.h> 44 #include <unistd.h> 45 46 #define EXIT_TIMEOUT 124 47 #define EXIT_INVALID 125 48 #define EXIT_CMD_ERROR 126 49 #define EXIT_CMD_NOENT 127 50 51 static volatile sig_atomic_t sig_chld = 0; 52 static volatile sig_atomic_t sig_alrm = 0; 53 static volatile sig_atomic_t sig_term = 0; /* signal to terminate children */ 54 static volatile sig_atomic_t sig_other = 0; /* signal to propagate */ 55 static int killsig = SIGTERM; /* signal to kill children */ 56 static const char *command = NULL; 57 static bool verbose = false; 58 59 static void __dead2 60 usage(void) 61 { 62 fprintf(stderr, 63 "Usage: %s [-f | --foreground] [-k time | --kill-after time]" 64 " [-p | --preserve-status] [-s signal | --signal signal] " 65 " [-v | --verbose] <duration> <command> [arg ...]\n", 66 getprogname()); 67 exit(EXIT_FAILURE); 68 } 69 70 static void 71 logv(const char *fmt, ...) 72 { 73 va_list ap; 74 75 if (!verbose) 76 return; 77 78 va_start(ap, fmt); 79 vwarnx(fmt, ap); 80 va_end(ap); 81 } 82 83 static double 84 parse_duration(const char *duration) 85 { 86 double ret; 87 char *suffix; 88 89 ret = strtod(duration, &suffix); 90 if (suffix == duration) 91 errx(EXIT_INVALID, "duration is not a number"); 92 93 if (*suffix == '\0') 94 return (ret); 95 96 if (suffix[1] != '\0') 97 errx(EXIT_INVALID, "duration unit suffix too long"); 98 99 switch (*suffix) { 100 case 's': 101 break; 102 case 'm': 103 ret *= 60; 104 break; 105 case 'h': 106 ret *= 60 * 60; 107 break; 108 case 'd': 109 ret *= 60 * 60 * 24; 110 break; 111 default: 112 errx(EXIT_INVALID, "duration unit suffix invalid"); 113 } 114 115 if (ret < 0 || ret >= 100000000UL) 116 errx(EXIT_INVALID, "duration out of range"); 117 118 return (ret); 119 } 120 121 static int 122 parse_signal(const char *str) 123 { 124 int sig, i; 125 const char *errstr; 126 127 sig = strtonum(str, 1, sys_nsig - 1, &errstr); 128 if (errstr == NULL) 129 return (sig); 130 131 if (strncasecmp(str, "SIG", 3) == 0) 132 str += 3; 133 for (i = 1; i < sys_nsig; i++) { 134 if (strcasecmp(str, sys_signame[i]) == 0) 135 return (i); 136 } 137 138 errx(EXIT_INVALID, "invalid signal"); 139 } 140 141 static void 142 sig_handler(int signo) 143 { 144 if (signo == killsig) { 145 sig_term = signo; 146 return; 147 } 148 149 switch (signo) { 150 case SIGCHLD: 151 sig_chld = 1; 152 break; 153 case SIGALRM: 154 sig_alrm = 1; 155 break; 156 case SIGHUP: 157 case SIGINT: 158 case SIGQUIT: 159 case SIGILL: 160 case SIGTRAP: 161 case SIGABRT: 162 case SIGEMT: 163 case SIGFPE: 164 case SIGBUS: 165 case SIGSEGV: 166 case SIGSYS: 167 case SIGPIPE: 168 case SIGTERM: 169 case SIGXCPU: 170 case SIGXFSZ: 171 case SIGVTALRM: 172 case SIGPROF: 173 case SIGUSR1: 174 case SIGUSR2: 175 /* 176 * Signals with default action to terminate the process. 177 * See the sigaction(2) man page. 178 */ 179 sig_term = signo; 180 break; 181 default: 182 sig_other = signo; 183 break; 184 } 185 } 186 187 static void 188 send_sig(pid_t pid, int signo, bool foreground) 189 { 190 struct procctl_reaper_kill rk; 191 int error; 192 193 logv("sending signal %s(%d) to command '%s'", 194 sys_signame[signo], signo, command); 195 if (foreground) { 196 if (kill(pid, signo) == -1) { 197 if (errno != ESRCH) 198 warn("kill(%d, %s)", (int)pid, 199 sys_signame[signo]); 200 } 201 } else { 202 memset(&rk, 0, sizeof(rk)); 203 rk.rk_sig = signo; 204 error = procctl(P_PID, getpid(), PROC_REAP_KILL, &rk); 205 if (error == 0 || (error == -1 && errno == ESRCH)) 206 ; 207 else if (error == -1) { 208 warn("procctl(PROC_REAP_KILL)"); 209 if (rk.rk_fpid > 0) 210 warnx( 211 "failed to signal some processes: first pid=%d", 212 (int)rk.rk_fpid); 213 } 214 logv("signaled %u processes", rk.rk_killed); 215 } 216 217 /* 218 * If the child process was stopped by a signal, POSIX.1-2024 219 * requires to send a SIGCONT signal. However, the standard also 220 * allows to send a SIGCONT regardless of the stop state, as we 221 * are doing here. 222 */ 223 if (signo != SIGKILL && signo != SIGSTOP && signo != SIGCONT) { 224 logv("sending signal %s(%d) to command '%s'", 225 sys_signame[SIGCONT], SIGCONT, command); 226 if (foreground) { 227 kill(pid, SIGCONT); 228 } else { 229 memset(&rk, 0, sizeof(rk)); 230 rk.rk_sig = SIGCONT; 231 procctl(P_PID, getpid(), PROC_REAP_KILL, &rk); 232 } 233 } 234 } 235 236 static void 237 set_interval(double iv) 238 { 239 struct itimerval tim; 240 241 memset(&tim, 0, sizeof(tim)); 242 if (iv > 0) { 243 tim.it_value.tv_sec = (time_t)iv; 244 iv -= (double)(time_t)iv; 245 tim.it_value.tv_usec = (suseconds_t)(iv * 1000000UL); 246 } 247 248 if (setitimer(ITIMER_REAL, &tim, NULL) == -1) 249 err(EXIT_FAILURE, "setitimer()"); 250 } 251 252 /* 253 * In order to avoid any possible ambiguity that a shell may not set '$?' to 254 * '128+signal_number', POSIX.1-2024 requires that timeout mimic the wait 255 * status of the child process by terminating itself with the same signal, 256 * while disabling core generation. 257 */ 258 static void __dead2 259 kill_self(int signo) 260 { 261 sigset_t mask; 262 struct rlimit rl; 263 264 /* Reset the signal disposition and make sure it's unblocked. */ 265 signal(signo, SIG_DFL); 266 sigfillset(&mask); 267 sigdelset(&mask, signo); 268 sigprocmask(SIG_SETMASK, &mask, NULL); 269 270 /* Disable core generation. */ 271 memset(&rl, 0, sizeof(rl)); 272 setrlimit(RLIMIT_CORE, &rl); 273 274 logv("killing self with signal %s(%d)", sys_signame[signo], signo); 275 kill(getpid(), signo); 276 err(128 + signo, "signal %s(%d) failed to kill self", 277 sys_signame[signo], signo); 278 } 279 280 static void 281 log_termination(const char *name, const siginfo_t *si) 282 { 283 if (si->si_code == CLD_EXITED) { 284 logv("%s: pid=%d, exit=%d", name, si->si_pid, si->si_status); 285 } else if (si->si_code == CLD_DUMPED || si->si_code == CLD_KILLED) { 286 logv("%s: pid=%d, sig=%d", name, si->si_pid, si->si_status); 287 } else { 288 logv("%s: pid=%d, reason=%d, status=%d", si->si_pid, 289 si->si_code, si->si_status); 290 } 291 } 292 293 int 294 main(int argc, char **argv) 295 { 296 int ch, sig; 297 int pstat = 0; 298 pid_t pid; 299 int pp[2], error; 300 char c; 301 double first_kill; 302 double second_kill = 0; 303 bool foreground = false; 304 bool preserve = false; 305 bool timedout = false; 306 bool do_second_kill = false; 307 bool child_done = false; 308 sigset_t zeromask, allmask, oldmask; 309 struct sigaction sa; 310 struct procctl_reaper_status info; 311 siginfo_t si, child_si; 312 313 const char optstr[] = "+fhk:ps:v"; 314 const struct option longopts[] = { 315 { "foreground", no_argument, NULL, 'f' }, 316 { "help", no_argument, NULL, 'h' }, 317 { "kill-after", required_argument, NULL, 'k' }, 318 { "preserve-status", no_argument, NULL, 'p' }, 319 { "signal", required_argument, NULL, 's' }, 320 { "verbose", no_argument, NULL, 'v' }, 321 { NULL, 0, NULL, 0 }, 322 }; 323 324 while ((ch = getopt_long(argc, argv, optstr, longopts, NULL)) != -1) { 325 switch (ch) { 326 case 'f': 327 foreground = true; 328 break; 329 case 'k': 330 do_second_kill = true; 331 second_kill = parse_duration(optarg); 332 break; 333 case 'p': 334 preserve = true; 335 break; 336 case 's': 337 killsig = parse_signal(optarg); 338 break; 339 case 'v': 340 verbose = true; 341 break; 342 case 0: 343 break; 344 default: 345 usage(); 346 } 347 } 348 349 argc -= optind; 350 argv += optind; 351 if (argc < 2) 352 usage(); 353 354 first_kill = parse_duration(argv[0]); 355 argc--; 356 argv++; 357 command = argv[0]; 358 359 if (!foreground) { 360 /* Acquire a reaper */ 361 if (procctl(P_PID, getpid(), PROC_REAP_ACQUIRE, NULL) == -1) 362 err(EXIT_FAILURE, "procctl(PROC_REAP_ACQUIRE)"); 363 } 364 365 /* Block all signals to avoid racing against the child. */ 366 sigfillset(&allmask); 367 if (sigprocmask(SIG_BLOCK, &allmask, &oldmask) == -1) 368 err(EXIT_FAILURE, "sigprocmask()"); 369 370 if (pipe2(pp, O_CLOEXEC) == -1) 371 err(EXIT_FAILURE, "pipe2"); 372 373 pid = fork(); 374 if (pid == -1) { 375 err(EXIT_FAILURE, "fork()"); 376 } else if (pid == 0) { 377 /* 378 * child process 379 * 380 * POSIX.1-2024 requires that the child process inherit the 381 * same signal dispositions as the timeout(1) utility 382 * inherited, except for the signal to be sent upon timeout. 383 */ 384 signal(killsig, SIG_DFL); 385 if (sigprocmask(SIG_SETMASK, &oldmask, NULL) == -1) 386 err(EXIT_FAILURE, "sigprocmask(oldmask)"); 387 388 error = read(pp[0], &c, 1); 389 if (error == -1) 390 err(EXIT_FAILURE, "read from control pipe"); 391 if (error == 0) 392 errx(EXIT_FAILURE, "eof from control pipe"); 393 execvp(argv[0], argv); 394 warn("exec(%s)", argv[0]); 395 _exit(errno == ENOENT ? EXIT_CMD_NOENT : EXIT_CMD_ERROR); 396 } 397 398 /* parent continues here */ 399 400 /* Catch all signals in order to propagate them. */ 401 memset(&sa, 0, sizeof(sa)); 402 sigfillset(&sa.sa_mask); 403 sa.sa_handler = sig_handler; 404 sa.sa_flags = SA_RESTART; 405 for (sig = 1; sig < sys_nsig; sig++) { 406 if (sig == SIGKILL || sig == SIGSTOP || sig == SIGCONT || 407 sig == SIGTTIN || sig == SIGTTOU) 408 continue; 409 if (sigaction(sig, &sa, NULL) == -1) 410 err(EXIT_FAILURE, "sigaction(%d)", sig); 411 } 412 413 /* Don't stop if background child needs TTY */ 414 signal(SIGTTIN, SIG_IGN); 415 signal(SIGTTOU, SIG_IGN); 416 417 set_interval(first_kill); 418 error = write(pp[1], "a", 1); 419 if (error == -1) 420 err(EXIT_FAILURE, "write to control pipe"); 421 if (error == 0) 422 errx(EXIT_FAILURE, "short write to control pipe"); 423 sigemptyset(&zeromask); 424 425 for (;;) { 426 sigsuspend(&zeromask); 427 428 if (sig_chld) { 429 sig_chld = 0; 430 431 for (;;) { 432 memset(&si, 0, sizeof(si)); 433 error = waitid(P_ALL, -1, &si, WEXITED | 434 WNOHANG); 435 if (error == -1) { 436 if (errno != EINTR) 437 break; 438 } else if (si.si_pid == pid) { 439 child_si = si; 440 child_done = true; 441 log_termination("child terminated", 442 &child_si); 443 } else if (si.si_pid != 0) { 444 /* 445 * Collect grandchildren zombies. 446 * Only effective if we're a reaper. 447 */ 448 log_termination("collected zombie", 449 &si); 450 } else /* si.si_pid == 0 */ { 451 break; 452 } 453 } 454 if (child_done) { 455 if (foreground) { 456 break; 457 } else { 458 procctl(P_PID, getpid(), 459 PROC_REAP_STATUS, &info); 460 if (info.rs_children == 0) 461 break; 462 } 463 } 464 } else if (sig_alrm || sig_term) { 465 if (sig_alrm) { 466 sig = killsig; 467 sig_alrm = 0; 468 timedout = true; 469 logv("time limit reached or received SIGALRM"); 470 } else { 471 sig = sig_term; 472 sig_term = 0; 473 logv("received terminating signal %s(%d)", 474 sys_signame[sig], sig); 475 } 476 477 send_sig(pid, sig, foreground); 478 479 if (do_second_kill) { 480 set_interval(second_kill); 481 do_second_kill = false; 482 killsig = SIGKILL; 483 } 484 485 } else if (sig_other) { 486 /* Propagate any other signals. */ 487 sig = sig_other; 488 sig_other = 0; 489 logv("received signal %s(%d)", sys_signame[sig], sig); 490 491 send_sig(pid, sig, foreground); 492 } 493 } 494 495 if (!foreground) 496 procctl(P_PID, getpid(), PROC_REAP_RELEASE, NULL); 497 498 if (timedout && !preserve) { 499 pstat = EXIT_TIMEOUT; 500 } else if (child_si.si_code == CLD_DUMPED || 501 child_si.si_code == CLD_KILLED) { 502 kill_self(child_si.si_status); 503 /* NOTREACHED */ 504 } else if (child_si.si_code == CLD_EXITED) { 505 pstat = child_si.si_status; 506 } else { 507 pstat = EXIT_FAILURE; 508 } 509 510 return (pstat); 511 } 512