/* watch.c - Execute a program periodically * * Copyright 2013 Sandeep Sharma * Copyright 2013 Kyungwan Han * * No standard. See http://man7.org/linux/man-pages/man1/watch.1.html * * TODO: trailing combining characters USE_WATCH(NEWTOY(watch, "^<1n%<100=2000tebx", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE)) config WATCH bool "watch" default y help usage: watch [-teb] [-n SEC] PROG ARGS Run PROG every -n seconds, showing output. Hit q to quit. -n Loop period in seconds (default 2) -t Don't print header -e Exit on error -b Beep on command error -x Exec command directly (vs "sh -c") */ #define FOR_watch #include "toys.h" GLOBALS( int n; pid_t pid, oldpid; ) // When a child process exits, stop tracking them. Handle errors for -be static void watch_child(int sig) { int status; pid_t pid = wait(&status); status = WIFEXITED(status) ? WEXITSTATUS(status) : WTERMSIG(status)+127; if (status) { // TODO should this be beep()? if (FLAG(b)) putchar('\b'); if (FLAG(e)) { printf("Exit status %d\r\n", status); tty_reset(); _exit(status); } } if (pid == TT.oldpid) TT.oldpid = 0; else if (pid == TT.pid) TT.pid = 0; } // Return early for low-ascii characters with special behavior, // discard remaining low ascii, escape other unprintable chars normally static int watch_escape(FILE *out, int cols, int wc) { if (wc==27 || (wc>=7 && wc<=13)) return -1; if (wc < 32) return 0; return crunch_escape(out, cols, wc); } void watch_main(void) { char *cmdv[] = {"/bin/sh", "-c", 0, 0}, *cmd, *ss; long long now, then = millitime(); unsigned width, height, i, cmdlen, len, xx = xx, yy = yy, active = active; struct pollfd pfd[2]; pid_t pid = 0; int fds[2], cc; // Assemble header line in cmd, cmdlen, and cmdv for (i = TT.n%1000, len = i ? 3 : 1; i && !(i%10); i /= 10) len--; len = sprintf(toybuf, "Every %u.%0*us:", TT.n/1000, len, i)+1; cmdlen = len; for (i = 0; toys.optargs[i]; i++) len += strlen(toys.optargs[i])+1; ss = stpcpy(cmd = xmalloc(len), toybuf); cmdv[2] = cmd+cmdlen; for (i = 0; toys.optargs[i]; i++) ss += sprintf(ss, " %s",toys.optargs[i]); cmdlen = ss-cmd; // Need to poll on process output and stdin memset(pfd, 0, sizeof(pfd)); pfd[0].events = pfd[1].events = POLLIN; xsignal_flags(SIGCHLD, watch_child, SA_RESTART|SA_NOCLDSTOP); for (;;) { // Time for a new period? if ((now = millitime())>=then) { // Incrementing then instead of adding offset to now avoids drift, // loop is in case we got suspend/resumed and need to skip periods while ((then += TT.n)<=now); start_redraw(&width, &height); // redraw the header if (!FLAG(t)) { time_t t = time(0); int pad, ctimelen; // Get and measure time string, trimming gratuitous \n ctimelen = strlen(ss = ctime(&t)); if (ss[ctimelen-1]=='\n') ss[--ctimelen] = 0; // print cmdline, then * or ' ' (showing truncation), then ctime pad = width-++ctimelen; if (pad>0) draw_trim(cmd, -pad, pad); printf("%c", padctimelen ? 0 : width-1)); if (yy>=3) xprintf("\r\n"); xx = 0; yy = 2; } // If child didn't exit, send TERM signal to current and KILL to previous if (TT.oldpid>0) kill(TT.oldpid, SIGKILL); if (TT.pid>0) kill(TT.pid, SIGTERM); TT.oldpid = pid; if (fds[0]>0) close(fds[0]); if (fds[1]>0) close(fds[1]); // Spawn child process fds[0] = fds[1] = -1; TT.pid = xpopen_both(FLAG(x) ? toys.optargs : cmdv, fds); pfd[1].fd = fds[1]; active = 1; } // Fetch data from child process or keyboard, with timeout len = 0; xpoll(pfd, 1+(active && yy=height) break; } xx += crunch_str(&ss, width-xx, stdout, 0, watch_escape); if (xx==width) { xx = 0; if (++yy>=height) break; continue; } if (ss-toybuf==len || *ss>27) break; cc = *ss++; if (cc==27) continue; // TODO // Handle BEL BS HT LF VT FF CR if (cc>=10 && cc<=12) { if (++yy>=height) break; if (cc=='\n') putchar('\r'), xx = 0; } putchar(cc); if (cc=='\b' && xx) xx--; else if (cc=='\t') { xx = (xx|7)+1; if (xx>width-1) xx = width-1; } } } if (CFG_TOYBOX_FREE) free(cmd); }