X-Git-Url: https://git.distorted.org.uk/~mdw/misc/blobdiff_plain/753f86deeb79fb61d962b2066dde8c8cc31be85f..8fc3647061acb492fe5cb01d7d2820d7489d31e0:/mtimeout.c diff --git a/mtimeout.c b/mtimeout.c index 38723ae..373f2b0 100644 --- a/mtimeout.c +++ b/mtimeout.c @@ -287,7 +287,11 @@ static void strtotime(const char *p, struct timeval *tv) /*----- Help functions ----------------------------------------------------*/ static void usage(FILE *fp) - { pquis(fp, "Usage: $ [-s SIG] SECONDS COMMAND [ARGUMENTS ...]\n"); } +{ + pquis(fp, + "Usage: $ [-K] [-b TIME] [-k TIME] [-s SIG] " + "TIME COMMAND [ARGUMENTS ...]\n"); +} static void version(FILE *fp) { pquis(fp, "$ (version " VERSION ")\n"); } @@ -298,14 +302,21 @@ static void help(FILE *fp) usage(fp); pquis(fp, "\n\ Run COMMAND, giving it the ARGUMENTS. If it fails to complete within the\n\ -specified number of SECONDS, kill it. Otherwise exit with the status it\n\ -returns.\n \ +specified number of TIME, kill it. Otherwise exit with the status it\n\ +returns.\n\ +\n\ +A TIME is a possibly fractional number followed by an optional unit\n\ +designator, which may be `s', `m', `h', or `d', for seconds, minutes,\n\ +hours, or days, respectively. The default units are seconds.\n\ \n\ Options:\n\ -h, --help Show this help text.\n\ -v, --version Show version string.\n\ -u, --usage Show a terse usage summary.\n\ \n\ + -b, --bored-after=TIME Wait for TIME after sending SIGKILL.\n\ + -k, --kill-after=TIME Wait for TIME after signal before sending SIGKILL.\n\ + -K, --no-kill Don't send SIGKILL; just give up when bored.\n\ -s, --signal=SIG Send signal SIG to the command.\n\ "); } @@ -314,42 +325,73 @@ Options:\n\ /* --- @timeout@ --- * * - * The first time, we send the signal requested by the caller. Then we wait - * five seconds for the child to die, and send @SIGKILL@. If that still - * doesn't help, then we just give up. It's not like there's anything else - * we can do which is likely to help. And it's not like the process is going - * to be doing anything else in user mode ever again. + * The timeout sequencing stuff is complicated, so here's a simple machine to + * make it work. */ +enum { + TA_MOAN, /* Report message to user */ +#define TAARG_MOAN s + + TA_KILL, /* Send a signal */ +#define TAARG_KILL i + + TA_GOTO, /* Goto different state */ +#define TAARG_GOTO i + + TA_WAIT, /* Wait for more time */ +#define TAARG_WAIT tv + + TA_STATE, /* Alter the internal state */ +#define TAARG_STATE i +}; + +struct tmact { + unsigned act; + union { + const char *s; /* String parameter */ + int i; /* Integer parameter */ + struct timeval tv; /* Time parameter */ + } u; +}; + struct timeout { - sel_timer t; - int panic; - int sig; + sel_timer t; /* Timer intrusion */ + struct tmact *ta; /* Instruction vector */ + int ip; /* Instruction pointer */ pid_t kid; }; static void timeout(struct timeval *now, void *p) { struct timeout *t = p; + struct tmact *ta; struct timeval tv; - switch (t->panic) { - case 0: - moan("timed out: killing child process"); - kill(-t->kid, t->sig); - break; - case 1: - moan("child hasn't responded: killing harder"); - kill(-t->kid, SIGKILL); - break; - case 2: - moan("child still undead: giving up"); - state = ST_ABORT; - break; + for (;;) { + ta = &t->ta[t->ip++]; + switch (ta->act) { + case TA_MOAN: + moan("%s", ta->u.s); + break; + case TA_KILL: + kill(-t->kid, ta->u.i); + break; + case TA_GOTO: + t->ip = ta->u.i; + break; + case TA_STATE: + state = ta->u.i; + return; + case TA_WAIT: + TV_ADD(&tv, now, &ta->u.tv); + sel_addtimer(&sel, &t->t, &tv, timeout, t); + return; + default: + moan("unexpected tmact %u", ta->act); + abort(); + } } - TV_ADDL(&tv, now, 5, 0); - sel_addtimer(&sel, &t->t, &tv, timeout, t); - t->panic++; } /*----- Signal handling ---------------------------------------------------*/ @@ -403,10 +445,35 @@ static void sigpropagate(int sig, void *p) int main(int argc, char *const argv[]) { - int signo = SIGTERM; pid_t kid; - struct timeval now, tv; struct timeout to; + struct timeval now; + +#define PAIR(x, y) { x, y } +#define TACODE(I) \ + I(sigwait, WAIT, PAIR(0, 0)) \ + I(_a, MOAN, "timed out: killing child process") \ + I(sig, KILL, SIGTERM) \ + I(killwait, WAIT, PAIR(5, 0)) \ + I(_b, MOAN, "child hasn't responded: killing harder") \ + I(_c, KILL, SIGKILL) \ + I(boredwait, WAIT, PAIR(5, 0)) \ + I(_d, MOAN, "child still undead: giving up") \ + I(_e, STATE, ST_ABORT) + + enum { +#define TALBL(label, op, arg) taoff_##label, + TACODE(TALBL) +#undef TALBL + taoff_end + }; + + static struct tmact ta[] = { +#define TAASM(label, op, arg) { TA_##op, { .TAARG_##op = arg } }, + TACODE(TAASM) +#undef TAASM + }; + struct sigchld sc; sig sig_CHLD; #define DEFSIG(tag) sig sig_##tag; @@ -424,18 +491,33 @@ int main(int argc, char *const argv[]) { "help", 0, 0, 'h' }, { "version", 0, 0, 'v' }, { "usage", 0, 0, 'u' }, + { "no-kill", 0, 0, 'K' }, + { "kill-after", OPTF_ARGREQ, 0, 'k' }, + { "bored-after", OPTF_ARGREQ, 0, 'b' }, { "signal", OPTF_ARGREQ, 0, 's' }, { 0, 0, 0, 0 } }; - int i = mdwopt(argc, argv, "+hvus:", opts, 0, 0, 0); + int i = mdwopt(argc, argv, "+hvuKb:k:s:", opts, 0, 0, 0); if (i < 0) break; switch (i) { case 'h': help(stdout); exit(0); case 'v': version(stdout); exit(0); case 'u': usage(stdout); exit(0); + case 'b': + ta[taoff_boredwait].act = TA_WAIT; + strtotime(optarg, &ta[taoff_boredwait].u.tv); + break; + case 'k': + ta[taoff_killwait].act = TA_WAIT; + strtotime(optarg, &ta[taoff_killwait].u.tv); + break; + case 'K': + ta[taoff_killwait].act = TA_GOTO; + ta[taoff_killwait].u.i = taoff_boredwait; + break; case 's': - if ((signo = namesig(optarg)) < 0) + if ((ta[taoff_sig].u.i = namesig(optarg)) < 0) die(253, "bad signal spec `%s'", optarg); break; default: f |= F_BOGUS; break; @@ -443,7 +525,7 @@ int main(int argc, char *const argv[]) } argc -= optind; argv += optind; if ((f & F_BOGUS) || argc < 2) { usage(stderr); exit(253); } - strtotime(argv[0], &tv); + strtotime(argv[0], &ta[taoff_sigwait].u.tv); /* --- Get things set up --- */ @@ -476,12 +558,11 @@ int main(int argc, char *const argv[]) /* --- Set up the timer --- */ - to.kid = kid; - to.sig = signo; - to.panic = 0; gettimeofday(&now, 0); - TV_ADD(&tv, &now, &tv); - sel_addtimer(&sel, &to.t, &tv, timeout, &to); + to.kid = kid; + to.ta = ta; + to.ip = 0; + timeout(&now, &to); /* --- Main @select@ loop */