X-Git-Url: https://git.distorted.org.uk/~mdw/misc/blobdiff_plain/58b430827fb4ed30ff46e252664dfcf1b15dd869..HEAD:/mtimeout.c diff --git a/mtimeout.c b/mtimeout.c index dbdc83e..373f2b0 100644 --- a/mtimeout.c +++ b/mtimeout.c @@ -50,6 +50,7 @@ #include #include +#include #include #include #include @@ -104,27 +105,31 @@ int cmp_namesig(const void *k, const void *v) static int namesig(const char *p) { const static struct namesig tab[] = { -/* - ;;; The signal name table is very boring to type. To make life less awful, - ;;; put the signal names in this list and evaluate the code to get Emacs to - ;;; regenerate it. We use @bsearch@ on it, so it's important that it be - ;;; sorted: Emacs does this for us. - (let ((signals '(HUP INT QUIT ILL ABRT FPE KILL SEGV PIPE ALRM TERM - USR1 USR2 CHLD CONT STOP TSTP TTIN TTOU BUS POLL - PROF SYS TRAP URG VTALRM XCPU XFSZ IOT EMT STKFLT - IO CLD PWR INFO LOST WINCH))) - (save-excursion - (goto-char (point-min)) - (let ((start (search-forward (concat "/" "* SIGLIST *" "/\n"))) - (end (search-forward (concat "/" "* END *" "/\n")))) - (delete-region start end)) - (dolist (sig (sort (copy-list signals) #'string<)) - (insert (format "#ifdef SIG%s\n { \"%s\", SIG%s },\n#endif\n" - sig sig sig))) - (insert (concat "/" "* END *" "/\n")))) -*/ - -/* SIGLIST */ + /* + ;;; The signal name table is very boring to type. To make life less + ;;; awful, put the signal names in this list and evaluate the code to + ;;; get Emacs to regenerate it. We use @bsearch@ on it, so it's + ;;; important that it be sorted: Emacs does this for us. + + (let ((signals '(HUP INT QUIT ILL ABRT FPE KILL SEGV PIPE ALRM TERM + USR1 USR2 CHLD CONT STOP TSTP TTIN TTOU BUS POLL + PROF SYS TRAP URG VTALRM XCPU XFSZ IOT EMT STKFLT + IO CLD PWR INFO LOST WINCH))) + (save-excursion + (goto-char (point-min)) + (search-forward (concat "***" "BEGIN siglist" "***")) + (beginning-of-line 2) + (delete-region (point) + (progn + (search-forward "***END***") + (beginning-of-line) + (point))) + (dolist (sig (sort (copy-list signals) #'string<)) + (insert (format "#ifdef SIG%s\n { \"%s\", SIG%s },\n#endif\n" + sig sig sig))))) + */ + + /***BEGIN siglist***/ #ifdef SIGABRT { "ABRT", SIGABRT }, #endif @@ -236,7 +241,7 @@ static int namesig(const char *p) #ifdef SIGXFSZ { "XFSZ", SIGXFSZ }, #endif -/* END */ + /***END***/ }; const struct namesig *ns = bsearch(p, tab, N(tab), sizeof(tab[0]), @@ -246,10 +251,47 @@ static int namesig(const char *p) else return (-1); } +/* --- @strtotime@ --- * + * + * Arguments: @const char *p@ = pointer to string + * @struct timeval *tv@ = where to put the result + * + * Returns: --- + * + * Use: Converts a string representation of a duration into an + * internal version. Understands various time units. + */ + +static void strtotime(const char *p, struct timeval *tv) +{ + char *q = (/*unconst*/ char *)p; + double t, i, f; + + while (isspace((unsigned char)*q)) q++; + t = strtod(q, &q); + while (isspace((unsigned char)*q)) q++; + switch (*q) { + case 'd': case 'D': t *= 24; + case 'h': case 'H': t *= 60; + case 'm': case 'M': t *= 60; + case 's': case 'S': + q++; + while (isspace((unsigned char)*q)) q++; + } + if (*q) die(253, "bad time value `%s'", p); + f = modf(t, &i); + tv->tv_sec = i; + tv->tv_usec = f * 1000000; +} + /*----- 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"); } @@ -260,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\ "); } @@ -276,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 ---------------------------------------------------*/ @@ -365,12 +445,35 @@ static void sigpropagate(int sig, void *p) int main(int argc, char *const argv[]) { - char *p; - double t; - int signo = SIGTERM; pid_t kid; - struct timeval 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; @@ -388,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; @@ -407,12 +525,7 @@ int main(int argc, char *const argv[]) } argc -= optind; argv += optind; if ((f & F_BOGUS) || argc < 2) { usage(stderr); exit(253); } - - p = argv[0]; - while (isspace((unsigned char)*p)) p++; - t = strtod(argv[0], &p); - while (isspace((unsigned char)*p)) p++; - if (*p) die(253, "bad time value `%s'", argv[0]); + strtotime(argv[0], &ta[taoff_sigwait].u.tv); /* --- Get things set up --- */ @@ -445,12 +558,11 @@ int main(int argc, char *const argv[]) /* --- Set up the timer --- */ + gettimeofday(&now, 0); to.kid = kid; - to.sig = signo; - to.panic = 0; - gettimeofday(&tv, 0); - TV_ADDL(&tv, &tv, (time_t)t, ((long)(t * 1000000))%1000000); - sel_addtimer(&sel, &to.t, &tv, timeout, &to); + to.ta = ta; + to.ip = 0; + timeout(&now, &to); /* --- Main @select@ loop */