toybox/toys/other/watch.c
<<
>>
Prefs
   1/* watch.c - Execute a program periodically
   2 *
   3 * Copyright 2013 Sandeep Sharma <sandeep.jack2756@gmail.com>
   4 * Copyright 2013 Kyungwan Han <asura321@gmail.com>
   5 *
   6 * No standard. See http://man7.org/linux/man-pages/man1/watch.1.html
   7 *
   8 * TODO: trailing combining characters
   9USE_WATCH(NEWTOY(watch, "^<1n%<100=2000tebx", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE))
  10
  11config WATCH
  12  bool "watch"
  13  default y
  14  help
  15    usage: watch [-teb] [-n SEC] PROG ARGS
  16
  17    Run PROG every -n seconds, showing output. Hit q to quit.
  18
  19    -n  Loop period in seconds (default 2)
  20    -t  Don't print header
  21    -e  Exit on error
  22    -b  Beep on command error
  23    -x  Exec command directly (vs "sh -c")
  24*/
  25
  26#define FOR_watch
  27#include "toys.h"
  28
  29GLOBALS(
  30  int n;
  31
  32  pid_t pid, oldpid;
  33)
  34
  35// When a child process exits, stop tracking them. Handle errors for -be
  36void watch_child(int sig)
  37{
  38  int status;
  39  pid_t pid = wait(&status);
  40
  41  status = WIFEXITED(status) ? WEXITSTATUS(status) : WTERMSIG(status)+127;
  42  if (status) {
  43    // TODO should this be beep()?
  44    if (toys.optflags&FLAG_b) putchar('\b');
  45    if (toys.optflags&FLAG_e) {
  46      printf("Exit status %d\r\n", status);
  47      tty_reset();
  48      _exit(status);
  49    }
  50  }
  51
  52  if (pid == TT.oldpid) TT.oldpid = 0;
  53  else if (pid == TT.pid) TT.pid = 0;
  54}
  55
  56// Return early for low-ascii characters with special behavior,
  57// discard remaining low ascii, escape other unprintable chars normally
  58int watch_escape(FILE *out, int cols, int wc)
  59{
  60  if (wc==27 || (wc>=7 && wc<=13)) return -1;
  61  if (wc < 32) return 0;
  62
  63  return crunch_escape(out, cols, wc);
  64}
  65
  66void watch_main(void)
  67{
  68  char *cmdv[] = {"/bin/sh", "-c", 0, 0}, *cmd, *ss;
  69  long long now, then = millitime();
  70  unsigned width, height, i, cmdlen, len, xx = xx, yy = yy, active = active;
  71  struct pollfd pfd[2];
  72  pid_t pid = 0;
  73  int fds[2], cc;
  74
  75  // Assemble header line in cmd, cmdlen, and cmdv
  76  for (i = TT.n%1000, len = i ? 3 : 1; i && !(i%10); i /= 10) len--;
  77  len = sprintf(toybuf, "Every %u.%0*us:", TT.n/1000, len, i)+1;
  78  cmdlen = len;
  79  for (i = 0; toys.optargs[i]; i++) len += strlen(toys.optargs[i])+1;
  80  ss = stpcpy(cmd = xmalloc(len), toybuf);
  81  cmdv[2] = cmd+cmdlen;
  82  for (i = 0; toys.optargs[i]; i++) ss += sprintf(ss, " %s",toys.optargs[i]);
  83  cmdlen = ss-cmd;
  84
  85  // Need to poll on process output and stdin
  86  memset(pfd, 0, sizeof(pfd));
  87  pfd[0].events = pfd[1].events = POLLIN;
  88
  89  xsignal_flags(SIGCHLD, watch_child, SA_RESTART|SA_NOCLDSTOP);
  90
  91  for (;;) {
  92
  93    // Time for a new period?
  94    if ((now = millitime())>=then) {
  95
  96      // Incrementing then instead of adding offset to now avoids drift,
  97      // loop is in case we got suspend/resumed and need to skip periods
  98      while ((then += TT.n)<=now);
  99      start_redraw(&width, &height);
 100
 101      // redraw the header
 102      if (!(toys.optflags&FLAG_t)) {
 103        time_t t = time(0);
 104        int pad, ctimelen;
 105
 106        // Get and measure time string, trimming gratuitous \n
 107        ctimelen = strlen(ss = ctime(&t));
 108        if (ss[ctimelen-1]=='\n') ss[--ctimelen] = 0;
 109 
 110        // print cmdline, then * or ' ' (showing truncation), then ctime 
 111        pad = width-++ctimelen;
 112        if (pad>0) draw_trim(cmd, -pad, pad);
 113        printf("%c", pad<cmdlen ? '*' : ' ');
 114        if (width) xputs(ss+(width>ctimelen ? 0 : width-1));
 115        if (yy>=3) xprintf("\r\n");
 116        xx = 0;
 117        yy = 2;
 118      }
 119
 120      // If child didn't exit, send TERM signal to current and KILL to previous
 121      if (TT.oldpid>0) kill(TT.oldpid, SIGKILL);
 122      if (TT.pid>0) kill(TT.pid, SIGTERM);
 123      TT.oldpid = pid;
 124      if (fds[0]>0) close(fds[0]);
 125      if (fds[1]>0) close(fds[1]);
 126
 127      // Spawn child process
 128      fds[0] = fds[1] = -1;
 129      TT.pid = xpopen_both(FLAG(x) ? toys.optargs : cmdv, fds);
 130      pfd[1].fd = fds[1];
 131      active = 1;
 132    }
 133
 134    // Fetch data from child process or keyboard, with timeout
 135    len = 0;
 136    xpoll(pfd, 1+(active && yy<height), then-now);
 137    if (pfd[0].revents&POLLIN) {
 138      memset(toybuf, 0, 16);
 139      cc = scan_key_getsize(toybuf, 0, &width, &height);
 140      // TODO: ctrl-Z suspend
 141      // TODO if (cc == -3) redraw();
 142      if (cc == 3 || tolower(cc) == 'q') xexit();
 143    }
 144    if (pfd[0].revents&POLLHUP) xexit();
 145    if (active) {
 146      if (pfd[1].revents&POLLIN) len = read(fds[1], toybuf, sizeof(toybuf)-1);
 147      if (pfd[1].revents&POLLHUP) active = 0;
 148    }
 149
 150    // Measure output, trim to available display area. Escape low ascii so
 151    // we don't have to try to parse ansi escapes. TODO: parse ansi escapes.
 152    if (len<1) continue;
 153    ss = toybuf;
 154    toybuf[len] = 0;
 155    while (yy<height) {
 156      if (xx==width) {
 157        xx = 0;
 158        if (++yy>=height) break;
 159      }
 160      xx += crunch_str(&ss, width-xx, stdout, 0, watch_escape);
 161      if (xx==width) {
 162        xx = 0;
 163        if (++yy>=height) break;
 164        continue;
 165      }
 166
 167      if (ss-toybuf==len || *ss>27) break;
 168      cc = *ss++;
 169      if (cc==27) continue; // TODO
 170
 171      // Handle BEL BS HT LF VT FF CR
 172      if (cc>=10 && cc<=12) {
 173        if (++yy>=height) break;
 174        if (cc=='\n') putchar('\r'), xx = 0;
 175      }
 176      putchar(cc);
 177      if (cc=='\b' && xx) xx--;
 178      else if (cc=='\t') {
 179        xx = (xx|7)+1;
 180        if (xx>width-1) xx = width-1;
 181      }
 182    }
 183  }
 184
 185  if (CFG_TOYBOX_FREE) free(cmd);
 186}
 187