Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/bin/timeout/timeout.c
39475 views
1
/*-
2
* Copyright (c) 2014 Baptiste Daroussin <[email protected]>
3
* Copyright (c) 2014 Vsevolod Stakhov <[email protected]>
4
* Copyright (c) 2025 Aaron LI <[email protected]>
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
513