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