1 /*-
2 * Copyright (c) 2014 Baptiste Daroussin <bapt@FreeBSD.org>
3 * Copyright (c) 2014 Vsevolod Stakhov <vsevolod@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/procctl.h>
29 #include <sys/time.h>
30 #include <sys/wait.h>
31
32 #include <err.h>
33 #include <errno.h>
34 #include <getopt.h>
35 #include <signal.h>
36 #include <stdbool.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <sysexits.h>
41 #include <unistd.h>
42
43 #define EXIT_TIMEOUT 124
44 #define EXIT_INVALID 125
45 #define EXIT_CMD_ERROR 126
46 #define EXIT_CMD_NOENT 127
47
48 static sig_atomic_t sig_chld = 0;
49 static sig_atomic_t sig_term = 0;
50 static sig_atomic_t sig_alrm = 0;
51 static sig_atomic_t sig_ign = 0;
52 static const char *command = NULL;
53 static bool verbose = false;
54
55 static void __dead2
usage(void)56 usage(void)
57 {
58 fprintf(stderr, "Usage: %s [-k time | --kill-after time]"
59 " [-s sig | --signal sig] [-v | --verbose] [--foreground]"
60 " [--preserve-status] <duration> <command> <arg ...>\n",
61 getprogname());
62
63 exit(EX_USAGE);
64 }
65
66 static double
parse_duration(const char * duration)67 parse_duration(const char *duration)
68 {
69 double ret;
70 char *end;
71
72 ret = strtod(duration, &end);
73 if (ret == 0 && end == duration)
74 errx(EXIT_INVALID, "invalid duration");
75
76 if (end == NULL || *end == '\0')
77 return (ret);
78
79 if (end != NULL && *(end + 1) != '\0')
80 errx(EXIT_INVALID, "invalid duration");
81
82 switch (*end) {
83 case 's':
84 break;
85 case 'm':
86 ret *= 60;
87 break;
88 case 'h':
89 ret *= 60 * 60;
90 break;
91 case 'd':
92 ret *= 60 * 60 * 24;
93 break;
94 default:
95 errx(EXIT_INVALID, "invalid duration");
96 }
97
98 if (ret < 0 || ret >= 100000000UL)
99 errx(EXIT_INVALID, "invalid duration");
100
101 return (ret);
102 }
103
104 static int
parse_signal(const char * str)105 parse_signal(const char *str)
106 {
107 int sig, i;
108 const char *errstr;
109
110 sig = strtonum(str, 1, sys_nsig - 1, &errstr);
111 if (errstr == NULL)
112 return (sig);
113
114 if (strncasecmp(str, "SIG", 3) == 0)
115 str += 3;
116 for (i = 1; i < sys_nsig; i++) {
117 if (strcasecmp(str, sys_signame[i]) == 0)
118 return (i);
119 }
120
121 errx(EXIT_INVALID, "invalid signal");
122 }
123
124 static void
sig_handler(int signo)125 sig_handler(int signo)
126 {
127 if (sig_ign != 0 && signo == sig_ign) {
128 sig_ign = 0;
129 return;
130 }
131
132 switch (signo) {
133 case 0:
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
send_sig(pid_t pid,int signo)150 send_sig(pid_t pid, int signo)
151 {
152 if (verbose) {
153 warnx("sending signal %s(%d) to command '%s'",
154 sys_signame[signo], signo, command);
155 }
156 kill(pid, signo);
157 }
158
159 static void
set_interval(double iv)160 set_interval(double iv)
161 {
162 struct itimerval tim;
163
164 memset(&tim, 0, sizeof(tim));
165 tim.it_value.tv_sec = (time_t)iv;
166 iv -= (double)tim.it_value.tv_sec;
167 tim.it_value.tv_usec = (suseconds_t)(iv * 1000000UL);
168
169 if (setitimer(ITIMER_REAL, &tim, NULL) == -1)
170 err(EX_OSERR, "setitimer()");
171 }
172
173 int
main(int argc,char ** argv)174 main(int argc, char **argv)
175 {
176 int ch;
177 int foreground, preserve;
178 int error, pstat, status;
179 int killsig = SIGTERM;
180 size_t i;
181 pid_t pid, cpid;
182 double first_kill;
183 double second_kill;
184 bool timedout = false;
185 bool do_second_kill = false;
186 bool child_done = false;
187 struct sigaction signals;
188 union reaper_info info;
189 int signums[] = {
190 -1,
191 SIGTERM,
192 SIGINT,
193 SIGHUP,
194 SIGCHLD,
195 SIGALRM,
196 SIGQUIT,
197 };
198
199 foreground = preserve = 0;
200 second_kill = 0;
201
202 const struct option longopts[] = {
203 { "preserve-status", no_argument, &preserve, 1 },
204 { "foreground", no_argument, &foreground, 1 },
205 { "kill-after", required_argument, NULL, 'k'},
206 { "signal", required_argument, NULL, 's'},
207 { "help", no_argument, NULL, 'h'},
208 { "verbose", no_argument, NULL, 'v'},
209 { NULL, 0, NULL, 0 }
210 };
211
212 while ((ch = getopt_long(argc, argv, "+k:s:vh", longopts, NULL)) != -1) {
213 switch (ch) {
214 case 'k':
215 do_second_kill = true;
216 second_kill = parse_duration(optarg);
217 break;
218 case 's':
219 killsig = parse_signal(optarg);
220 break;
221 case 'v':
222 verbose = true;
223 break;
224 case 0:
225 break;
226 case 'h':
227 default:
228 usage();
229 break;
230 }
231 }
232
233 argc -= optind;
234 argv += optind;
235 if (argc < 2)
236 usage();
237
238 first_kill = parse_duration(argv[0]);
239 argc--;
240 argv++;
241 command = argv[0];
242
243 if (!foreground) {
244 /* Acquire a reaper */
245 if (procctl(P_PID, getpid(), PROC_REAP_ACQUIRE, NULL) == -1)
246 err(EX_OSERR, "Fail to acquire the reaper");
247 }
248
249 memset(&signals, 0, sizeof(signals));
250 sigemptyset(&signals.sa_mask);
251
252 if (killsig != SIGKILL && killsig != SIGSTOP)
253 signums[0] = killsig;
254
255 for (i = 0; i < sizeof(signums) / sizeof(signums[0]); i ++)
256 sigaddset(&signals.sa_mask, signums[i]);
257
258 signals.sa_handler = sig_handler;
259 signals.sa_flags = SA_RESTART;
260
261 for (i = 0; i < sizeof(signums) / sizeof(signums[0]); i ++) {
262 if (signums[i] != -1 && signums[i] != 0 &&
263 sigaction(signums[i], &signals, NULL) == -1)
264 err(EX_OSERR, "sigaction()");
265 }
266
267 /* Don't stop if background child needs TTY */
268 signal(SIGTTIN, SIG_IGN);
269 signal(SIGTTOU, SIG_IGN);
270
271 pid = fork();
272 if (pid == -1)
273 err(EX_OSERR, "fork()");
274 else if (pid == 0) {
275 /* child process */
276 signal(SIGTTIN, SIG_DFL);
277 signal(SIGTTOU, SIG_DFL);
278
279 error = execvp(argv[0], argv);
280 if (error == -1) {
281 if (errno == ENOENT)
282 err(EXIT_CMD_NOENT, "exec(%s)", argv[0]);
283 else
284 err(EXIT_CMD_ERROR, "exec(%s)", argv[0]);
285 }
286 }
287
288 if (sigprocmask(SIG_BLOCK, &signals.sa_mask, NULL) == -1)
289 err(EX_OSERR, "sigprocmask()");
290
291 /* parent continues here */
292 set_interval(first_kill);
293
294 for (;;) {
295 sigemptyset(&signals.sa_mask);
296 sigsuspend(&signals.sa_mask);
297
298 if (sig_chld) {
299 sig_chld = 0;
300
301 while ((cpid = waitpid(-1, &status, WNOHANG)) != 0) {
302 if (cpid < 0) {
303 if (errno == EINTR)
304 continue;
305 else
306 break;
307 } else if (cpid == pid) {
308 pstat = status;
309 child_done = true;
310 }
311 }
312 if (child_done) {
313 if (foreground) {
314 break;
315 } else {
316 procctl(P_PID, getpid(),
317 PROC_REAP_STATUS, &info);
318 if (info.status.refs == 0)
319 break;
320 }
321 }
322
323 } else if (sig_alrm || sig_term) {
324 int signo = (int)sig_term;
325
326 if (sig_alrm) {
327 signo = killsig;
328 sig_alrm = 0;
329 timedout = true;
330 }
331
332 if (!foreground) {
333 procctl(P_PID, getpid(), PROC_REAP_STATUS,
334 &info);
335 if (info.status.pid_head > 0)
336 send_sig(info.status.pid_head, signo);
337 } else
338 send_sig(pid, signo);
339
340 if (do_second_kill) {
341 set_interval(second_kill);
342 do_second_kill = false;
343 sig_ign = killsig;
344 killsig = SIGKILL;
345 } else
346 break;
347 }
348 }
349
350 while (!child_done && wait(&pstat) == -1) {
351 if (errno != EINTR)
352 err(EX_OSERR, "waitpid()");
353 }
354
355 if (!foreground)
356 procctl(P_PID, getpid(), PROC_REAP_RELEASE, NULL);
357
358 if (WEXITSTATUS(pstat))
359 pstat = WEXITSTATUS(pstat);
360 else if (WIFSIGNALED(pstat))
361 pstat = 128 + WTERMSIG(pstat);
362
363 if (timedout && !preserve)
364 pstat = EXIT_TIMEOUT;
365
366 return (pstat);
367 }
368