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/procctl.h> 30 #include <sys/resource.h> 31 #include <sys/time.h> 32 #include <sys/wait.h> 33 34 #include <err.h> 35 #include <fcntl.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 __printflike(1, 2) 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 void 122 sig_handler(int signo) 123 { 124 if (signo == killsig) { 125 sig_term = signo; 126 return; 127 } 128 129 switch (signo) { 130 case SIGCHLD: 131 sig_chld = 1; 132 break; 133 case SIGALRM: 134 sig_alrm = 1; 135 break; 136 case SIGHUP: 137 case SIGINT: 138 case SIGQUIT: 139 case SIGILL: 140 case SIGTRAP: 141 case SIGABRT: 142 case SIGEMT: 143 case SIGFPE: 144 case SIGBUS: 145 case SIGSEGV: 146 case SIGSYS: 147 case SIGPIPE: 148 case SIGTERM: 149 case SIGXCPU: 150 case SIGXFSZ: 151 case SIGVTALRM: 152 case SIGPROF: 153 case SIGUSR1: 154 case SIGUSR2: 155 /* 156 * Signals with default action to terminate the process. 157 * See the sigaction(2) man page. 158 */ 159 sig_term = signo; 160 break; 161 default: 162 sig_other = signo; 163 break; 164 } 165 } 166 167 static void 168 send_sig(pid_t pid, int signo, bool foreground) 169 { 170 struct procctl_reaper_kill rk; 171 172 logv("sending signal %s(%d) to command '%s'", 173 sys_signame[signo], signo, command); 174 if (foreground) { 175 if (kill(pid, signo) < 0 && errno != ESRCH) 176 warn("kill(%d, %s)", (int)pid, sys_signame[signo]); 177 } else { 178 memset(&rk, 0, sizeof(rk)); 179 rk.rk_sig = signo; 180 if (procctl(P_PID, getpid(), PROC_REAP_KILL, &rk) < 0 && 181 errno != ESRCH) { 182 warn("procctl(PROC_REAP_KILL)"); 183 if (rk.rk_fpid > 0) { 184 warnx("failed to signal some processes:" 185 " first pid=%d", (int)rk.rk_fpid); 186 } 187 } 188 logv("signaled %u processes", rk.rk_killed); 189 } 190 191 /* 192 * If the child process was stopped by a signal, POSIX.1-2024 193 * requires to send a SIGCONT signal. However, the standard also 194 * allows to send a SIGCONT regardless of the stop state, as we 195 * are doing here. 196 */ 197 if (signo != SIGKILL && signo != SIGSTOP && signo != SIGCONT) { 198 logv("sending signal %s(%d) to command '%s'", 199 sys_signame[SIGCONT], SIGCONT, command); 200 if (foreground) { 201 kill(pid, SIGCONT); 202 } else { 203 memset(&rk, 0, sizeof(rk)); 204 rk.rk_sig = SIGCONT; 205 procctl(P_PID, getpid(), PROC_REAP_KILL, &rk); 206 } 207 } 208 } 209 210 static void 211 set_interval(double iv) 212 { 213 struct itimerval tim; 214 215 memset(&tim, 0, sizeof(tim)); 216 if (iv > 0) { 217 tim.it_value.tv_sec = (time_t)iv; 218 iv -= (double)(time_t)iv; 219 tim.it_value.tv_usec = (suseconds_t)(iv * 1000000UL); 220 } 221 222 if (setitimer(ITIMER_REAL, &tim, NULL) < 0) 223 err(EXIT_FAILURE, "setitimer()"); 224 } 225 226 /* 227 * In order to avoid any possible ambiguity that a shell may not set '$?' to 228 * '128+signal_number', POSIX.1-2024 requires that timeout mimic the wait 229 * status of the child process by terminating itself with the same signal, 230 * while disabling core generation. 231 */ 232 static void __dead2 233 kill_self(int signo) 234 { 235 sigset_t mask; 236 struct rlimit rl; 237 238 logv("killing self with signal %s(%d)", sys_signame[signo], signo); 239 240 /* Disable core generation. */ 241 memset(&rl, 0, sizeof(rl)); 242 setrlimit(RLIMIT_CORE, &rl); 243 244 /* Reset the signal disposition and make sure it's unblocked. */ 245 signal(signo, SIG_DFL); 246 sigfillset(&mask); 247 sigdelset(&mask, signo); 248 sigprocmask(SIG_SETMASK, &mask, NULL); 249 250 raise(signo); 251 err(128 + signo, "raise(%d)", signo); 252 } 253 254 static void 255 log_termination(const char *name, const siginfo_t *si) 256 { 257 if (si->si_code == CLD_EXITED) { 258 logv("%s: pid=%d, exit=%d", name, si->si_pid, si->si_status); 259 } else if (si->si_code == CLD_DUMPED || si->si_code == CLD_KILLED) { 260 logv("%s: pid=%d, sig=%d", name, si->si_pid, si->si_status); 261 } else { 262 logv("%s: pid=%d, reason=%d, status=%d", name, si->si_pid, 263 si->si_code, si->si_status); 264 } 265 } 266 267 int 268 main(int argc, char **argv) 269 { 270 struct procctl_reaper_status info; 271 siginfo_t si, child_si; 272 struct sigaction sa; 273 sigset_t zeromask, allmask, oldmask; 274 double first_kill; 275 double second_kill = 0; 276 ssize_t error; 277 pid_t pid; 278 int pp[2]; 279 int ch, sig; 280 int pstat = 0; 281 char c; 282 bool foreground = false; 283 bool preserve = false; 284 bool timedout = false; 285 bool do_second_kill = false; 286 bool child_done = false; 287 288 const char optstr[] = "+fhk:ps:v"; 289 const struct option longopts[] = { 290 { "foreground", no_argument, NULL, 'f' }, 291 { "help", no_argument, NULL, 'h' }, 292 { "kill-after", required_argument, NULL, 'k' }, 293 { "preserve-status", no_argument, NULL, 'p' }, 294 { "signal", required_argument, NULL, 's' }, 295 { "verbose", no_argument, NULL, 'v' }, 296 { NULL, 0, NULL, 0 }, 297 }; 298 299 while ((ch = getopt_long(argc, argv, optstr, longopts, NULL)) != -1) { 300 switch (ch) { 301 case 'f': 302 foreground = true; 303 break; 304 case 'k': 305 do_second_kill = true; 306 second_kill = parse_duration(optarg); 307 break; 308 case 'p': 309 preserve = true; 310 break; 311 case 's': 312 if (str2sig(optarg, &killsig) != 0) 313 errx(EXIT_INVALID, "invalid signal"); 314 break; 315 case 'v': 316 verbose = true; 317 break; 318 case 0: 319 break; 320 default: 321 usage(); 322 } 323 } 324 325 argc -= optind; 326 argv += optind; 327 if (argc < 2) 328 usage(); 329 330 first_kill = parse_duration(argv[0]); 331 argc--; 332 argv++; 333 command = argv[0]; 334 335 if (!foreground) { 336 /* Acquire a reaper */ 337 if (procctl(P_PID, getpid(), PROC_REAP_ACQUIRE, NULL) < 0) 338 err(EXIT_FAILURE, "procctl(PROC_REAP_ACQUIRE)"); 339 } 340 341 /* Block all signals to avoid racing against the child. */ 342 sigfillset(&allmask); 343 if (sigprocmask(SIG_BLOCK, &allmask, &oldmask) < 0) 344 err(EXIT_FAILURE, "sigprocmask()"); 345 346 if (pipe2(pp, O_CLOEXEC) < 0) 347 err(EXIT_FAILURE, "pipe2"); 348 349 pid = fork(); 350 if (pid < 0) 351 err(EXIT_FAILURE, "fork()"); 352 if (pid == 0) { 353 /* 354 * child process 355 * 356 * POSIX.1-2024 requires that the child process inherit the 357 * same signal dispositions as the timeout(1) utility 358 * inherited, except for the signal to be sent upon timeout. 359 */ 360 signal(killsig, SIG_DFL); 361 if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) 362 err(EXIT_FAILURE, "sigprocmask(oldmask)"); 363 364 (void)close(pp[1]); 365 error = read(pp[0], &c, 1); 366 if (error < 0) 367 err(EXIT_FAILURE, "read from control pipe"); 368 if (error == 0) 369 errx(EXIT_FAILURE, "eof from control pipe"); 370 execvp(argv[0], argv); 371 warn("exec(%s)", argv[0]); 372 _exit(errno == ENOENT ? EXIT_CMD_NOENT : EXIT_CMD_ERROR); 373 } 374 375 /* parent continues here */ 376 (void)close(pp[0]); 377 378 /* Catch all signals in order to propagate them. */ 379 memset(&sa, 0, sizeof(sa)); 380 sigfillset(&sa.sa_mask); 381 sa.sa_handler = sig_handler; 382 sa.sa_flags = SA_RESTART; 383 for (sig = 1; sig < sys_nsig; sig++) { 384 switch (sig) { 385 case SIGTTIN: 386 case SIGTTOU: 387 /* Don't stop if background child needs TTY */ 388 if (signal(sig, SIG_IGN) == SIG_ERR) 389 err(EXIT_FAILURE, "signal(%d)", sig); 390 break; 391 case SIGKILL: 392 case SIGSTOP: 393 case SIGCONT: 394 /* These can't be caught or ignored */ 395 break; 396 default: 397 if (sigaction(sig, &sa, NULL) < 0) 398 err(EXIT_FAILURE, "sigaction(%d)", sig); 399 } 400 } 401 402 /* Start the timer */ 403 set_interval(first_kill); 404 405 /* Let the child know we're ready */ 406 error = write(pp[1], "a", 1); 407 if (error < 0) 408 err(EXIT_FAILURE, "write to control pipe"); 409 if (error == 0) 410 errx(EXIT_FAILURE, "short write to control pipe"); 411 (void)close(pp[1]); 412 413 sigemptyset(&zeromask); 414 for (;;) { 415 sigsuspend(&zeromask); 416 417 if (sig_chld) { 418 sig_chld = 0; 419 420 for (;;) { 421 memset(&si, 0, sizeof(si)); 422 if (waitid(P_ALL, -1, &si, 423 WEXITED | WNOHANG) < 0) { 424 if (errno != EINTR) 425 break; 426 } else if (si.si_pid == pid) { 427 child_si = si; 428 child_done = true; 429 log_termination("child terminated", 430 &child_si); 431 } else if (si.si_pid != 0) { 432 /* 433 * Collect grandchildren zombies. 434 * Only effective if we're a reaper. 435 */ 436 log_termination("collected zombie", 437 &si); 438 } else /* si.si_pid == 0 */ { 439 break; 440 } 441 } 442 if (child_done) { 443 if (foreground) { 444 break; 445 } else { 446 procctl(P_PID, getpid(), 447 PROC_REAP_STATUS, &info); 448 if (info.rs_children == 0) 449 break; 450 } 451 } 452 } else if (sig_alrm || sig_term) { 453 if (sig_alrm) { 454 sig = killsig; 455 sig_alrm = 0; 456 timedout = true; 457 logv("time limit reached or received SIGALRM"); 458 } else { 459 sig = sig_term; 460 sig_term = 0; 461 logv("received terminating signal %s(%d)", 462 sys_signame[sig], sig); 463 } 464 465 send_sig(pid, sig, foreground); 466 467 if (do_second_kill) { 468 set_interval(second_kill); 469 do_second_kill = false; 470 killsig = SIGKILL; 471 } 472 473 } else if (sig_other) { 474 /* Propagate any other signals. */ 475 sig = sig_other; 476 sig_other = 0; 477 logv("received signal %s(%d)", sys_signame[sig], sig); 478 479 send_sig(pid, sig, foreground); 480 } 481 } 482 483 if (!foreground) 484 procctl(P_PID, getpid(), PROC_REAP_RELEASE, NULL); 485 486 if (timedout && !preserve) { 487 pstat = EXIT_TIMEOUT; 488 } else if (child_si.si_code == CLD_DUMPED || 489 child_si.si_code == CLD_KILLED) { 490 kill_self(child_si.si_status); 491 /* NOTREACHED */ 492 } else if (child_si.si_code == CLD_EXITED) { 493 pstat = child_si.si_status; 494 } else { 495 pstat = EXIT_FAILURE; 496 } 497 498 return (pstat); 499 } 500