1 /* $OpenBSD: timeout.c,v 1.26 2023/11/03 19:16:31 cheloha Exp $ */
2
3 /*
4 * Copyright (c) 2021 Job Snijders <job@openbsd.org>
5 * Copyright (c) 2014 Baptiste Daroussin <bapt@FreeBSD.org>
6 * Copyright (c) 2014 Vsevolod Stakhov <vsevolod@FreeBSD.org>
7 * All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer
14 * in this position and unchanged.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
20 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22 * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
23 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
24 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31 #include <sys/types.h>
32 #include <sys/time.h>
33 #include <sys/wait.h>
34
35 #include <ctype.h>
36 #include <err.h>
37 #include <errno.h>
38 #include <getopt.h>
39 #include <limits.h>
40 #include <signal.h>
41 #include <stdbool.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <unistd.h>
46
47 #define EXIT_TIMEOUT 124
48
49 static volatile sig_atomic_t sig_chld = 0;
50 static volatile sig_atomic_t sig_term = 0;
51 static volatile sig_atomic_t sig_alrm = 0;
52 static volatile sig_atomic_t sig_ign = 0;
53
54 static void __dead
usage(void)55 usage(void)
56 {
57 fprintf(stderr,
58 "usage: timeout [-fp] [-k time] [-s signal] duration command"
59 " [arg ...]\n");
60 exit(1);
61 }
62
63 static double
parse_duration(const char * duration)64 parse_duration(const char *duration)
65 {
66 double ret;
67 char *suffix;
68
69 ret = strtod(duration, &suffix);
70 if (ret == 0 && suffix == duration)
71 errx(1, "duration is not a number");
72 if (ret < 0 || ret >= 100000000UL)
73 errx(1, "duration out of range");
74
75 if (suffix == NULL || *suffix == '\0')
76 return (ret);
77
78 if (suffix[1] != '\0')
79 errx(1, "duration unit suffix too long");
80
81 switch (*suffix) {
82 case 's':
83 break;
84 case 'm':
85 ret *= 60;
86 break;
87 case 'h':
88 ret *= 60 * 60;
89 break;
90 case 'd':
91 ret *= 60 * 60 * 24;
92 break;
93 default:
94 errx(1, "duration unit suffix is invalid");
95 }
96
97 return (ret);
98 }
99
100 static int
parse_signal(const char * str)101 parse_signal(const char *str)
102 {
103 long long sig;
104 const char *errstr;
105
106 if (isalpha((unsigned char)*str)) {
107 int i;
108
109 if (strncasecmp(str, "SIG", 3) == 0)
110 str += 3;
111 for (i = 1; i < NSIG; i++) {
112 if (strcasecmp(str, sys_signame[i]) == 0)
113 return (i);
114 }
115 errx(1, "invalid signal name");
116 }
117
118 sig = strtonum(str, 1, NSIG, &errstr);
119 if (errstr != NULL)
120 errx(1, "signal %s %s", str, errstr);
121
122 return (int)sig;
123 }
124
125 static void
sig_handler(int signo)126 sig_handler(int signo)
127 {
128 if (sig_ign != 0 && signo == sig_ign) {
129 sig_ign = 0;
130 return;
131 }
132
133 switch (signo) {
134 case SIGINT:
135 case SIGHUP:
136 case SIGQUIT:
137 case SIGTERM:
138 sig_term = signo;
139 break;
140 case SIGCHLD:
141 sig_chld = 1;
142 break;
143 case SIGALRM:
144 sig_alrm = 1;
145 break;
146 }
147 }
148
149 static void
set_interval(double iv)150 set_interval(double iv)
151 {
152 struct itimerval tim;
153
154 memset(&tim, 0, sizeof(tim));
155 tim.it_value.tv_sec = (time_t)iv;
156 iv -= (double)tim.it_value.tv_sec;
157 tim.it_value.tv_usec = (suseconds_t)(iv * 1000000UL);
158
159 if (setitimer(ITIMER_REAL, &tim, NULL) == -1)
160 err(1, "setitimer");
161 }
162
163 int
main(int argc,char ** argv)164 main(int argc, char **argv)
165 {
166 int ch;
167 unsigned long i;
168 int foreground = 0, preserve = 0;
169 int pstat, status;
170 int killsig = SIGTERM;
171 pid_t pgid = 0, pid, cpid = 0;
172 double first_kill;
173 double second_kill = 0;
174 bool timedout = false;
175 bool do_second_kill = false;
176 struct sigaction signals;
177 int signums[] = {-1, SIGTERM, SIGINT, SIGHUP, SIGCHLD,
178 SIGALRM, SIGQUIT};
179
180 const struct option longopts[] = {
181 { "preserve-status", no_argument, NULL, 'p'},
182 { "foreground", no_argument, NULL, 'f'},
183 { "kill-after", required_argument, NULL, 'k'},
184 { "signal", required_argument, NULL, 's'},
185 { "help", no_argument, NULL, 'h'},
186 { NULL, 0, NULL, 0 }
187 };
188
189 if (pledge("stdio proc exec", NULL) == -1)
190 err(1, "pledge");
191
192 while ((ch = getopt_long(argc, argv, "+fk:ps:h", longopts, NULL))
193 != -1) {
194 switch (ch) {
195 case 'f':
196 foreground = 1;
197 break;
198 case 'k':
199 do_second_kill = true;
200 second_kill = parse_duration(optarg);
201 break;
202 case 'p':
203 preserve = 1;
204 break;
205 case 's':
206 killsig = parse_signal(optarg);
207 break;
208 case 0:
209 break;
210 default:
211 usage();
212 break;
213 }
214 }
215
216 argc -= optind;
217 argv += optind;
218
219 if (argc < 2)
220 usage();
221
222 first_kill = parse_duration(argv[0]);
223 argc--;
224 argv++;
225
226 if (!foreground) {
227 pgid = setpgid(0, 0);
228
229 if (pgid == -1)
230 err(1, "setpgid");
231 }
232
233 memset(&signals, 0, sizeof(signals));
234 sigemptyset(&signals.sa_mask);
235
236 if (killsig != SIGKILL && killsig != SIGSTOP)
237 signums[0] = killsig;
238
239 for (i = 0; i < sizeof(signums) / sizeof(signums[0]); i++)
240 sigaddset(&signals.sa_mask, signums[i]);
241
242 signals.sa_handler = sig_handler;
243 signals.sa_flags = SA_RESTART;
244
245 for (i = 0; i < sizeof(signums) / sizeof(signums[0]); i++) {
246 if (signums[i] != -1 && signums[i] != 0 &&
247 sigaction(signums[i], &signals, NULL) == -1)
248 err(1, "sigaction");
249 }
250
251 signal(SIGTTIN, SIG_IGN);
252 signal(SIGTTOU, SIG_IGN);
253
254 pid = fork();
255 if (pid == -1)
256 err(1, "fork");
257 else if (pid == 0) {
258 /* child process */
259 signal(SIGTTIN, SIG_DFL);
260 signal(SIGTTOU, SIG_DFL);
261
262 execvp(argv[0], argv);
263 warn("%s", argv[0]);
264 _exit(errno == ENOENT ? 127 : 126);
265 }
266
267 /* parent continues here */
268
269 if (pledge("stdio proc", NULL) == -1)
270 err(1, "pledge");
271
272 if (sigprocmask(SIG_BLOCK, &signals.sa_mask, NULL) == -1)
273 err(1, "sigprocmask");
274
275 set_interval(first_kill);
276
277 for (;;) {
278 sigemptyset(&signals.sa_mask);
279 sigsuspend(&signals.sa_mask);
280
281 if (sig_chld) {
282 sig_chld = 0;
283 while (((cpid = wait(&status)) < 0) && errno == EINTR)
284 continue;
285
286 if (cpid == pid) {
287 pstat = status;
288 break;
289 }
290 } else if (sig_alrm) {
291 sig_alrm = 0;
292
293 timedout = true;
294 if (!foreground)
295 killpg(pgid, killsig);
296 else
297 kill(pid, killsig);
298
299 if (do_second_kill) {
300 set_interval(second_kill);
301 second_kill = 0;
302 sig_ign = killsig;
303 killsig = SIGKILL;
304 } else
305 break;
306
307 } else if (sig_term) {
308 if (!foreground)
309 killpg(pgid, killsig);
310 else
311 kill(pid, (int)sig_term);
312
313 if (do_second_kill) {
314 set_interval(second_kill);
315 second_kill = 0;
316 sig_ign = killsig;
317 killsig = SIGKILL;
318 } else
319 break;
320 }
321 }
322
323 while (cpid != pid && wait(&pstat) == -1) {
324 if (errno != EINTR)
325 err(1, "wait");
326 }
327
328 if (WEXITSTATUS(pstat))
329 pstat = WEXITSTATUS(pstat);
330 else if (WIFSIGNALED(pstat))
331 pstat = 128 + WTERMSIG(pstat);
332
333 if (timedout && !preserve)
334 pstat = EXIT_TIMEOUT;
335
336 return (pstat);
337 }
338