Add `pause' program.
[misc] / pause.c
diff --git a/pause.c b/pause.c
new file mode 100644 (file)
index 0000000..fd688e5
--- /dev/null
+++ b/pause.c
@@ -0,0 +1,304 @@
+/* -*-c-*-
+ *
+ * $Id$
+ *
+ * Pause for a while
+ *
+ * (c) 1999 Mark Wooding
+ */
+
+/*----- Licensing notice --------------------------------------------------* 
+ *
+ * This file is part of Pause.
+ *
+ * Pause is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later ersion.
+ * 
+ * Pause is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Pause; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <errno.h>
+#include <math.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <termios.h>
+
+#include <mLib/mdwopt.h>
+#include <mLib/quis.h>
+#include <mLib/report.h>
+#include <mLib/tv.h>
+
+/*----- Static variables --------------------------------------------------*/
+
+static struct termios ts_old, ts;
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @sig@ --- *
+ *
+ * Arguments:  @int s@ = signal number
+ *
+ * Returns:    Doesn't.
+ *
+ * Use:                Handles various fatal signals by resetting the terminal state
+ *             and re-emitting the signal.
+ */
+
+static void sig(int s)
+{
+  tcsetattr(STDIN_FILENO, TCSAFLUSH, &ts_old);
+  signal(s, SIG_DFL);
+  raise(s);
+}
+
+/* --- @suspend@ --- *
+ *
+ * Arguments:  @int s@ = signal number
+ *
+ * Returns:    ---
+ *
+ * Use:                Handles a job control signal, as recommended in APUE.  It
+ *             resets the terminal state, resets the signal disposition,
+ *             unblocks the signal and re-emits it (so that the shell
+ *             shows the right message).  When that call returns, the
+ *             signal handler is reattached, and the terminal state is set
+ *             again.
+ */
+
+static void suspend(int s)
+{
+  sigset_t ss;
+
+  tcsetattr(STDIN_FILENO, TCSAFLUSH, &ts_old);
+  signal(s, SIG_DFL);
+  sigemptyset(&ss);
+  sigaddset(&ss, s);
+  sigprocmask(SIG_UNBLOCK, &ss, 0);
+  raise(s);
+  signal(s, suspend);
+  tcsetattr(STDIN_FILENO, TCSAFLUSH, &ts);
+}
+
+/* --- Traditional GNUey help options --- */
+
+static void usage(FILE *fp)
+{
+  pquis(fp, "Usage: $ [time]\n");
+}
+
+static void version(FILE *fp)
+{
+  pquis(fp, "$ version " VERSION "\n");
+}
+
+static void help(FILE *fp)
+{
+  version(fp);
+  fputc('\n', fp);
+  usage(fp);
+  pquis(fp, "\n\
+Pauses until a key is pressed or an optional time-limit expires.  The\n\
+time is given as a floating point number with an option suffix of `s',\n\
+`m', `h' or `d' (for `seconds', `minutes', `hours' or `days'\n\
+respectively).\n\
+\n\
+Options available:\n\
+\n\
+-h, --help     Show this help message.\n\
+-v, --version  Show program's version number.\n\
+-u, --usage    Show terse usage summary.\n\
+");
+}
+
+/* --- @main@ --- *
+ *
+ * Arguments:  @int argc@ = number of command line arguments
+ *             @char *argv[]@ = vector of command line arguments
+ *
+ * Returns:    Nonzero if it failed, zero if everything went well.
+ *
+ * Use:                Souped-up version of `sleep'.
+ */
+
+int main(int argc, char *argv[])
+{
+  struct termios ts;
+  unsigned f = 0;
+  struct timeval when;
+
+  enum {
+    f_bogus = 1u,
+    f_timer = 2u,
+    f_key = 4u
+  };
+
+  /* --- Library initialization --- */
+
+  ego(argv[0]);
+
+  /* --- Parse command line options --- */
+
+  for (;;) {
+    static struct option opt[] = {
+      { "help",                0,              0,      'h' },
+      { "version",     0,              0,      'v' },
+      { "usage",       0,              0,      'u' },
+      { 0,             0,              0,      0 }
+    };
+    int i = mdwopt(argc, argv, "hvu", opt, 0, 0, 0);
+    if (i < 0)
+      break;
+    switch (i) {
+      case 'h':
+       help(stderr);
+       exit(0);
+      case 'v':
+       version(stderr);
+       exit(0);
+      case 'u':
+       usage(stderr);
+       exit(0);
+      default:
+       f |= f_bogus;
+       break;
+    }
+  }
+
+  /* --- Parse a timer spec --- */
+
+  if (!(f & f_bogus) && optind < argc) {
+    const char *p =argv[optind++];
+    char *q;
+    double t = strtod(p, &q);
+    switch (*q) {
+      case 'd': t *= 24;
+      case 'h': t *= 60;
+      case 'm': t *= 60;
+      case 's':        if (q[1] != 0)
+      default:    t = 0;
+      case 0:  break;
+    }
+    if (t <= 0)
+      die(1, "bad time specification `%s'", p);
+    gettimeofday(&when, 0);
+    TV_ADDL(&when, &when, t, (t - floor(t)) * MILLION);
+    f |= f_timer;
+  }
+
+  /* --- Report a syntax error --- */
+
+  if (optind < argc)
+    f |= f_bogus;
+  if (f & f_bogus) {
+    usage(stderr);
+    exit(1);
+  }
+
+  /* --- Set up terminal for single keypresses --- */
+
+  if (tcgetattr(STDIN_FILENO, &ts_old) == 0) {
+    static struct { int sig; void (*func)(int); } sigs[] = {
+      { SIGINT, sig },
+      { SIGQUIT, sig },
+      { SIGTERM, sig },
+      { SIGTSTP, suspend },
+      { 0, 0 }
+    };
+    int i;
+
+    /* --- Set up the new terminal attributes --- */
+
+    ts = ts_old;
+    ts.c_lflag &= ~(ECHO | ICANON | TOSTOP);
+
+    /* --- Set up signal handlers to reset attributes --- */
+
+    for (i = 0; sigs[i].sig; i++) {
+      struct sigaction sa;
+      sigaction(sigs[i].sig, 0, &sa);
+      if (sa.sa_handler != SIG_IGN)
+       signal(sigs[i].sig, sigs[i].func);
+    }
+    signal(SIGTTIN, suspend);
+
+    /* --- Commit the terminal attribute changes --- */
+
+    tcsetattr(STDIN_FILENO, TCSAFLUSH, &ts);
+    f |= f_key;
+  }
+
+  /* --- Main loop --- */
+
+  for (;;) {
+    fd_set fd, *pfd;
+    int maxfd;
+    struct timeval tv, *ptv;
+
+    /* --- Wait for a keypress --- */
+
+    if (f & f_key) {
+      FD_ZERO(&fd);
+      FD_SET(STDIN_FILENO, &fd);
+      maxfd = STDIN_FILENO + 1;
+      pfd = &fd;
+    } else {
+      maxfd = 0;
+      pfd = 0;
+    }
+
+    /* --- Wait for the timer to expire --- */
+
+    if (f & f_timer) {
+      gettimeofday(&tv, 0);
+      TV_SUB(&tv, &when, &tv);
+      ptv = &tv;
+    } else
+      ptv = 0;
+
+    /* --- See what interesting things have happened --- */
+
+    if (select(maxfd, pfd, 0, 0, ptv) < 0) {
+      if (errno == EINTR || errno == EAGAIN)
+       continue;
+      die(1, "error in select: %s", strerror(errno));
+    }
+
+    /* --- Check the timer --- */
+
+    if (f & f_timer) {
+      gettimeofday(&tv, 0);
+      if (TV_CMP(&tv, >=, &when))
+       break;
+    }
+
+    /* --- Check the key state --- */
+
+    if (f & f_key && FD_ISSET(STDIN_FILENO, &fd))
+      break;
+  }
+
+  /* --- See what gives --- */
+
+  if (f & f_key)
+    tcsetattr(STDIN_FILENO, TCSAFLUSH, &ts_old);
+  return (0);
+}
+
+/*----- That's all, folks -------------------------------------------------*/