From 9492c7fce507cd5cd5172d6e2bf2b86573198806 Mon Sep 17 00:00:00 2001 From: Rob Landley Date: Wed, 10 Oct 2018 06:36:12 -0500 Subject: Rewrite of watch. --- lib/interestingtimes.c | 4 +- lib/linestack.c | 15 ++-- toys/pending/watch.c | 219 +++++++++++++++++++++++++++++++++++++++---------- 3 files changed, 187 insertions(+), 51 deletions(-) diff --git a/lib/interestingtimes.c b/lib/interestingtimes.c index 636964aa..c3ed9f9a 100644 --- a/lib/interestingtimes.c +++ b/lib/interestingtimes.c @@ -216,8 +216,8 @@ int scan_key(char *scratch, int miliwait) // Read 1 byte so we don't overshoot sequence match. (We can deviate // and fail to match, but match consumes entire buffer.) - if (toys.signal || 1 != read(0, scratch+1+*scratch, 1)) - return toys.signal ? -3 : -1; + if (toys.signal>0 || 1 != read(0, scratch+1+*scratch, 1)) + return (toys.signal>0) ? -3 : -1; ++*scratch; } diff --git a/lib/linestack.c b/lib/linestack.c index 99807ccf..fb6cc1e4 100644 --- a/lib/linestack.c +++ b/lib/linestack.c @@ -1,6 +1,11 @@ #include "toys.h" -// A linestack is an array of struct ptr_len. +// The design idea here is indexing a big blob of (potentially mmaped) data +// instead of copying the data into a zillion seperate malloc()s. + +// A linestack is an array of struct ptr_len, with a currently used len +// and max tracking the memory allocation. This indexes existing string data, +// the lifetime of which is tracked externally. // Insert one stack into another before position in old stack. // (Does not copy contents of strings, just shuffles index array contents.) @@ -92,8 +97,7 @@ int crunch_str(char **str, int width, FILE *out, char *escmore, for (end = start = *str; *end; columns += col, end += bytes) { wchar_t wc; - if ((bytes = utf8towc(&wc, end, 4))>0 && (col = wcwidth(wc))>=0) - { + if ((bytes = utf8towc(&wc, end, 4))>0 && (col = wcwidth(wc))>=0) { if (!escmore || wc>255 || !strchr(escmore, wc)) { if (width-columns * Copyright 2013 Kyungwan Han * -USE_WATCH(NEWTOY(watch, "^<1n#<0=2teb", TOYFLAG_USR|TOYFLAG_BIN)) + * No standard. See http://man7.org/linux/man-pages/man1/watch.1.html + * + * TODO: add +USE_WATCH(NEWTOY(watch, "^<1n#<100=2000tebx", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE)) config WATCH bool "watch" default n help - usage: watch [-n SEC] [-t] PROG ARGS + usage: watch [-teb] [-n SEC] PROG ARGS - Run PROG periodically + Run PROG every -n seconds, showing output. Hit q to quit. -n Loop period in seconds (default 2) -t Don't print header - -e Freeze updates on command error, and exit after enter. + -e Exit on error -b Beep on command error + -x Exec command directly (vs "sh -c") */ + #define FOR_watch #include "toys.h" GLOBALS( - int interval; + int n; + + pid_t pid, oldpid; ) -void watch_main(void) +void start_redraw(unsigned *width, unsigned *height) { - int i = 0, hlen; - time_t t; - unsigned width = 80, len = sizeof("Www Mmm dd hh:mm:ss yyyy") - 1 ; - char *header, *cmd = *toys.optargs; - int retval; - - while (toys.optargs[++i]) - { - char * oldcmd = cmd; - cmd = xmprintf("%s %s", oldcmd, toys.optargs[i]); - if (CFG_TOYBOX_FREE) free(oldcmd); + // If never signaled, do raw mode setup. + if (!toys.signal) { + *width = 80; + *height = 25; + set_terminal(0, 1, 0, 0); + sigatexit(tty_sigreset); + xsignal(SIGWINCH, generic_signal); } - header = xmprintf("Every %us: %s", TT.interval, cmd); - hlen = strlen(header); - - while(1) { - xprintf("\033[H\033[J"); - if (!(toys.optflags & FLAG_t)) { - terminal_size(&width, NULL); - if (!width) width = 80; //on serial it may return 0. - time(&t); - if (width > (hlen + len)) xprintf("%s", header); - if (width >= len) - xprintf("%*s\n",width + ((width > (hlen + len))?-hlen:0) + 1, ctime(&t)); - else - xprintf("\n\n"); - } - fflush(NULL); //making sure the screen is clear - retval = system(cmd); - if ((toys.optflags & FLAG_b) && retval) - xprintf("\007"); - if ((toys.optflags & FLAG_e) && retval) { - xprintf("command exit with non-zero status, press enter to exit\n"); - getchar(); - break; + if (toys.signal != -1) { + toys.signal = -1; + terminal_probesize(width, height); + } + xprintf("\033[H\033[J"); +} + +// When a child process exits, stop tracking them. Handle errors for -be +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 (toys.optflags&FLAG_b) putchar('\b'); + if (toys.optflags&FLAG_e) { + printf("Exit status %d\r\n", status); + tty_reset(); + _exit(status); } - sleep(TT.interval); } - if (CFG_TOYBOX_FREE){ - free(header); - if (cmd != *toys.optargs) free(cmd); + 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 +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, xx, yy, i, cmdlen, len, 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 (!(toys.optflags&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 + memset(fds, 0, sizeof(fds)); + TT.pid = xpopen_both((toys.optflags&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); } -- cgit v1.2.3