Home | History | Annotate | Download | only in other
      1 /* watch.c - Execute a program periodically
      2  *
      3  * Copyright 2013 Sandeep Sharma <sandeep.jack2756 (at) gmail.com>
      4  * Copyright 2013 Kyungwan Han <asura321 (at) gmail.com>
      5  *
      6  * No standard. See http://man7.org/linux/man-pages/man1/watch.1.html
      7  *
      8  * TODO: trailing combining characters
      9 USE_WATCH(NEWTOY(watch, "^<1n%<100=2000tebx", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE))
     10 
     11 config 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 
     29 GLOBALS(
     30   int n;
     31 
     32   pid_t pid, oldpid;
     33 )
     34 
     35 // When a child process exits, stop tracking them. Handle errors for -be
     36 void 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
     58 int 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 
     66 void 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