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 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