mtimeout.1: Use correct dash for number ranges.
[misc] / mtimeout.c
index dbdc83e..373f2b0 100644 (file)
@@ -50,6 +50,7 @@
 
 #include <ctype.h>
 #include <errno.h>
+#include <math.h>
 #include <signal.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -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 */