xref: /aosp_15_r20/external/toybox/toys/other/watch.c (revision cf5a6c84e2b8763fc1a7db14496fd4742913b199)
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