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/cdefs.h> 30 #include <sys/procctl.h> 31 #include <sys/time.h> 32 #include <sys/wait.h> 33 34 #include <err.h> 35 #include <errno.h> 36 #include <getopt.h> 37 #include <signal.h> 38 #include <stdarg.h> 39 #include <stdbool.h> 40 #include <stdio.h> 41 #include <stdlib.h> 42 #include <string.h> 43 #include <unistd.h> 44 45 #define EXIT_TIMEOUT 124 46 #define EXIT_INVALID 125 47 #define EXIT_CMD_ERROR 126 48 #define EXIT_CMD_NOENT 127 49 50 static volatile sig_atomic_t sig_chld = 0; 51 static volatile sig_atomic_t sig_alrm = 0; 52 static volatile sig_atomic_t sig_term = 0; /* signal to terminate children */ 53 static volatile sig_atomic_t sig_other = 0; /* signal to propagate */ 54 static int killsig = SIGTERM; /* signal to kill children */ 55 static const char *command = NULL; 56 static bool verbose = false; 57 58 static void __dead2 59 usage(void) 60 { 61 fprintf(stderr, 62 "Usage: %s [-f | --foreground] [-k time | --kill-after time]" 63 " [-p | --preserve-status] [-s signal | --signal signal] " 64 " [-v | --verbose] <duration> <command> [arg ...]\n", 65 getprogname()); 66 exit(EXIT_FAILURE); 67 } 68 69 static void 70 logv(const char *fmt, ...) 71 { 72 va_list ap; 73 74 if (!verbose) 75 return; 76 77 va_start(ap, fmt); 78 vwarnx(fmt, ap); 79 va_end(ap); 80 } 81 82 static double 83 parse_duration(const char *duration) 84 { 85 double ret; 86 char *suffix; 87 88 ret = strtod(duration, &suffix); 89 if (suffix == duration) 90 errx(EXIT_INVALID, "duration is not a number"); 91 92 if (*suffix == '\0') 93 return (ret); 94 95 if (suffix[1] != '\0') 96 errx(EXIT_INVALID, "duration unit suffix too long"); 97 98 switch (*suffix) { 99 case 's': 100 break; 101 case 'm': 102 ret *= 60; 103 break; 104 case 'h': 105 ret *= 60 * 60; 106 break; 107 case 'd': 108 ret *= 60 * 60 * 24; 109 break; 110 default: 111 errx(EXIT_INVALID, "duration unit suffix invalid"); 112 } 113 114 if (ret < 0 || ret >= 100000000UL) 115 errx(EXIT_INVALID, "duration out of range"); 116 117 return (ret); 118 } 119 120 static int 121 parse_signal(const char *str) 122 { 123 int sig, i; 124 const char *errstr; 125 126 sig = strtonum(str, 1, sys_nsig - 1, &errstr); 127 if (errstr == NULL) 128 return (sig); 129 130 if (strncasecmp(str, "SIG", 3) == 0) 131 str += 3; 132 for (i = 1; i < sys_nsig; i++) { 133 if (strcasecmp(str, sys_signame[i]) == 0) 134 return (i); 135 } 136 137 errx(EXIT_INVALID, "invalid signal"); 138 } 139 140 static void 141 sig_handler(int signo) 142 { 143 if (signo == killsig) { 144 sig_term = signo; 145 return; 146 } 147 148 switch (signo) { 149 case SIGCHLD: 150 sig_chld = 1; 151 break; 152 case SIGALRM: 153 sig_alrm = 1; 154 break; 155 case SIGHUP: 156 case SIGINT: 157 case SIGQUIT: 158 case SIGILL: 159 case SIGTRAP: 160 case SIGABRT: 161 case SIGEMT: 162 case SIGFPE: 163 case SIGBUS: 164 case SIGSEGV: 165 case SIGSYS: 166 case SIGPIPE: 167 case SIGTERM: 168 case SIGXCPU: 169 case SIGXFSZ: 170 case SIGVTALRM: 171 case SIGPROF: 172 case SIGUSR1: 173 case SIGUSR2: 174 /* 175 * Signals with default action to terminate the process. 176 * See the sigaction(2) man page. 177 */ 178 sig_term = signo; 179 break; 180 default: 181 sig_other = signo; 182 break; 183 } 184 } 185 186 static void 187 send_sig(pid_t pid, int signo, bool foreground) 188 { 189 struct procctl_reaper_kill rk; 190 191 logv("sending signal %s(%d) to command '%s'", 192 sys_signame[signo], signo, command); 193 if (foreground) { 194 if (kill(pid, signo) == -1) 195 warnx("kill(%d, %s)", (int)pid, sys_signame[signo]); 196 } else { 197 memset(&rk, 0, sizeof(rk)); 198 rk.rk_sig = signo; 199 if (procctl(P_PID, getpid(), PROC_REAP_KILL, &rk) == -1) 200 warnx("procctl(PROC_REAP_KILL)"); 201 else if (rk.rk_fpid > 0) 202 warnx("failed to signal some processes: first pid=%d", 203 (int)rk.rk_fpid); 204 logv("signaled %u processes", rk.rk_killed); 205 } 206 207 /* 208 * If the child process was stopped by a signal, POSIX.1-2024 209 * requires to send a SIGCONT signal. However, the standard also 210 * allows to send a SIGCONT regardless of the stop state, as we 211 * are doing here. 212 */ 213 if (signo != SIGKILL && signo != SIGSTOP && signo != SIGCONT) { 214 logv("sending signal %s(%d) to command '%s'", 215 sys_signame[SIGCONT], SIGCONT, command); 216 if (foreground) { 217 kill(pid, SIGCONT); 218 } else { 219 memset(&rk, 0, sizeof(rk)); 220 rk.rk_sig = SIGCONT; 221 procctl(P_PID, getpid(), PROC_REAP_KILL, &rk); 222 } 223 } 224 } 225 226 static void 227 set_interval(double iv) 228 { 229 struct itimerval tim; 230 231 memset(&tim, 0, sizeof(tim)); 232 if (iv > 0) { 233 tim.it_value.tv_sec = (time_t)iv; 234 iv -= (double)(time_t)iv; 235 tim.it_value.tv_usec = (suseconds_t)(iv * 1000000UL); 236 } 237 238 if (setitimer(ITIMER_REAL, &tim, NULL) == -1) 239 err(EXIT_FAILURE, "setitimer()"); 240 } 241 242 int 243 main(int argc, char **argv) 244 { 245 int ch, status, sig; 246 int pstat = 0; 247 pid_t pid, cpid; 248 double first_kill; 249 double second_kill = 0; 250 bool foreground = false; 251 bool preserve = false; 252 bool timedout = false; 253 bool do_second_kill = false; 254 bool child_done = false; 255 sigset_t zeromask, allmask, oldmask; 256 struct sigaction sa; 257 struct procctl_reaper_status info; 258 259 const char optstr[] = "+fhk:ps:v"; 260 const struct option longopts[] = { 261 { "foreground", no_argument, NULL, 'f' }, 262 { "help", no_argument, NULL, 'h' }, 263 { "kill-after", required_argument, NULL, 'k' }, 264 { "preserve-status", no_argument, NULL, 'p' }, 265 { "signal", required_argument, NULL, 's' }, 266 { "verbose", no_argument, NULL, 'v' }, 267 { NULL, 0, NULL, 0 }, 268 }; 269 270 while ((ch = getopt_long(argc, argv, optstr, longopts, NULL)) != -1) { 271 switch (ch) { 272 case 'f': 273 foreground = true; 274 break; 275 case 'k': 276 do_second_kill = true; 277 second_kill = parse_duration(optarg); 278 break; 279 case 'p': 280 preserve = true; 281 break; 282 case 's': 283 killsig = parse_signal(optarg); 284 break; 285 case 'v': 286 verbose = true; 287 break; 288 case 0: 289 break; 290 default: 291 usage(); 292 } 293 } 294 295 argc -= optind; 296 argv += optind; 297 if (argc < 2) 298 usage(); 299 300 first_kill = parse_duration(argv[0]); 301 argc--; 302 argv++; 303 command = argv[0]; 304 305 if (!foreground) { 306 /* Acquire a reaper */ 307 if (procctl(P_PID, getpid(), PROC_REAP_ACQUIRE, NULL) == -1) 308 err(EXIT_FAILURE, "procctl(PROC_REAP_ACQUIRE)"); 309 } 310 311 /* Block all signals to avoid racing against the child. */ 312 sigfillset(&allmask); 313 if (sigprocmask(SIG_BLOCK, &allmask, &oldmask) == -1) 314 err(EXIT_FAILURE, "sigprocmask()"); 315 316 pid = fork(); 317 if (pid == -1) { 318 err(EXIT_FAILURE, "fork()"); 319 } else if (pid == 0) { 320 /* 321 * child process 322 * 323 * POSIX.1-2024 requires that the child process inherit the 324 * same signal dispositions as the timeout(1) utility 325 * inherited, except for the signal to be sent upon timeout. 326 */ 327 signal(killsig, SIG_DFL); 328 if (sigprocmask(SIG_SETMASK, &oldmask, NULL) == -1) 329 err(EXIT_FAILURE, "sigprocmask(oldmask)"); 330 331 execvp(argv[0], argv); 332 warn("exec(%s)", argv[0]); 333 _exit(errno == ENOENT ? EXIT_CMD_NOENT : EXIT_CMD_ERROR); 334 } 335 336 /* parent continues here */ 337 338 /* Catch all signals in order to propagate them. */ 339 memset(&sa, 0, sizeof(sa)); 340 sigfillset(&sa.sa_mask); 341 sa.sa_handler = sig_handler; 342 sa.sa_flags = SA_RESTART; 343 for (sig = 1; sig < sys_nsig; sig++) { 344 if (sig == SIGKILL || sig == SIGSTOP || sig == SIGCONT || 345 sig == SIGTTIN || sig == SIGTTOU) 346 continue; 347 if (sigaction(sig, &sa, NULL) == -1) 348 err(EXIT_FAILURE, "sigaction(%d)", sig); 349 } 350 351 /* Don't stop if background child needs TTY */ 352 signal(SIGTTIN, SIG_IGN); 353 signal(SIGTTOU, SIG_IGN); 354 355 set_interval(first_kill); 356 sigemptyset(&zeromask); 357 358 for (;;) { 359 sigsuspend(&zeromask); 360 361 if (sig_chld) { 362 sig_chld = 0; 363 364 while ((cpid = waitpid(-1, &status, WNOHANG)) != 0) { 365 if (cpid < 0) { 366 if (errno != EINTR) 367 break; 368 } else if (cpid == pid) { 369 pstat = status; 370 child_done = true; 371 logv("child terminated: pid=%d, " 372 "exit=%d, signal=%d", 373 (int)pid, WEXITSTATUS(status), 374 WTERMSIG(status)); 375 } else { 376 /* 377 * Collect grandchildren zombies. 378 * Only effective if we're a reaper. 379 */ 380 logv("collected zombie: pid=%d, " 381 "exit=%d, signal=%d", 382 (int)cpid, WEXITSTATUS(status), 383 WTERMSIG(status)); 384 } 385 } 386 if (child_done) { 387 if (foreground) { 388 break; 389 } else { 390 procctl(P_PID, getpid(), 391 PROC_REAP_STATUS, &info); 392 if (info.rs_children == 0) 393 break; 394 } 395 } 396 } else if (sig_alrm || sig_term) { 397 if (sig_alrm) { 398 sig = killsig; 399 sig_alrm = 0; 400 timedout = true; 401 logv("time limit reached or received SIGALRM"); 402 } else { 403 sig = sig_term; 404 sig_term = 0; 405 logv("received terminating signal %s(%d)", 406 sys_signame[sig], sig); 407 } 408 409 send_sig(pid, sig, foreground); 410 411 if (do_second_kill) { 412 set_interval(second_kill); 413 do_second_kill = false; 414 killsig = SIGKILL; 415 } 416 417 } else if (sig_other) { 418 /* Propagate any other signals. */ 419 sig = sig_other; 420 sig_other = 0; 421 logv("received signal %s(%d)", sys_signame[sig], sig); 422 423 send_sig(pid, sig, foreground); 424 } 425 } 426 427 if (!foreground) 428 procctl(P_PID, getpid(), PROC_REAP_RELEASE, NULL); 429 430 if (timedout && !preserve) { 431 pstat = EXIT_TIMEOUT; 432 } else { 433 if (WIFEXITED(pstat)) 434 pstat = WEXITSTATUS(pstat); 435 else if (WIFSIGNALED(pstat)) 436 pstat = 128 + WTERMSIG(pstat); 437 } 438 439 return (pstat); 440 } 441