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