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
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
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 int
parse_signal(const char * str)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
sig_handler(int signo)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
send_sig(pid_t pid,int signo,bool foreground)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
set_interval(double iv)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
kill_self(int signo)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
log_termination(const char * name,const siginfo_t * si)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
main(int argc,char ** argv)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