/*- * Copyright (c) 2014 Baptiste Daroussin * All rights reserved. *~ * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer * in this position and unchanged. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. *~ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define EXIT_TIMEOUT 124 extern char **environ; static void usage(void) { fprintf(stderr, "Usage: %s [OPTIONS] \n", getprogname()); exit(EXIT_FAILURE); } static double parse_duration(const char *duration) { double ret; char *end; ret = strtod(duration, &end); if (ret == 0 && end == duration) errx(EXIT_FAILURE, "invalid duration"); if (end == NULL || *end == '\0') return (ret); if (end != NULL && *(end + 1) == '\0') errx(EXIT_FAILURE, "invalid duration"); switch (*end) { case 's': break; case 'm': ret *= 60; break; case 'h': ret *= 60 * 60; break; case 'd': ret *= 60 * 60 * 24; break; default: errx(EXIT_FAILURE, "invalid duration"); } return (ret); } static int parse_signal(const char *str) { int sig, i; const char *err; sig = strtonum(str, 1, sys_nsig, &err); if (err == NULL) return (sig); if (strncasecmp(str, "SIG", 3) == 0) str += 3; for (i = 1; i < sys_nsig; i++) { if (strcasecmp(str, sys_signame[i]) == 0) return (i); } errx(EXIT_FAILURE, "invalid signal"); } int main(int argc, char **argv) { int ch, kq, nev, i; int foreground, preserve; int error, pstat, sig; int killsig = SIGTERM; pid_t pgid, pid; double first_kill; double second_kill; struct kevent ke, evlist[2]; bool timedout = false; foreground = preserve = 0; struct option longopts[] = { { "preserve-status", no_argument, &preserve, 1 }, { "foreground", no_argument, &foreground, 1 }, { "kill-after", required_argument, NULL, 'k'}, { "signal", required_argument, NULL, 's'}, { "help", no_argument, NULL, 'h'}, { NULL, 0, NULL, 0 } }; while ((ch = getopt_long(argc, argv, "k:s:h", longopts, NULL)) != -1) { switch (ch) { case 'k': second_kill = parse_duration(optarg); break; case 's': killsig = parse_signal(optarg); break; case 0: break; case 'h': default: usage(); break; } } argc -= optind; argv += optind; if (argc < 2) usage(); first_kill = parse_duration(argv[0]); argc--; argv++; if (!foreground) pgid = setpgid(0,0); if ((error = posix_spawnp(&pid, argv[0], NULL, NULL, argv, environ)) != 0) { errno = error; err(EXIT_FAILURE, "posix_spawn()"); } kq = kqueue(); EV_SET(&ke, 1, EVFILT_TIMER, EV_ADD|EV_ONESHOT, 0, first_kill * 1000, NULL); kevent(kq, &ke, 1, NULL, 0, NULL); EV_SET(&ke, pid, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, NULL); kevent(kq, &ke, 1, NULL, 0, NULL); for (;;) { nev = kevent(kq, NULL, 0, evlist, 2, NULL); for (i = 0; i < nev; i++) { if (evlist[i].filter == EVFILT_PROC) { pstat = evlist[i].data; if (WEXITSTATUS(pstat) != 0) pstat = WEXITSTATUS(pstat); else if (WIFSIGNALED (pstat)) { sig = WTERMSIG (pstat); if (!timedout) { signal(sig, SIG_DFL); raise(sig); } pstat = sig + 128; } if (timedout && !preserve) return (EXIT_TIMEOUT); return (pstat); } if (evlist[i].filter == EVFILT_TIMER) { timedout = true; if (second_kill) { EV_SET(&ke, 1, EVFILT_TIMER, EV_ADD|EV_ONESHOT, 0, second_kill * 1000, NULL); kevent(kq, &ke, 1, NULL, 0, NULL); } if (pgid) killpg(pgid, killsig); else kill(pid, killsig); } } } return (pstat); }