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
usage(void)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)
logv(const char * fmt,...)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
parse_duration(const char * duration)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
sig_handler(int signo)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
send_sig(pid_t pid,int signo,bool foreground)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
set_interval(double iv)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
kill_self(int signo)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
log_termination(const char * name,const siginfo_t * si)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
main(int argc,char ** argv)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