diff options
author | Kaz Kylheku <kaz@kylheku.com> | 2023-06-08 22:59:40 -0700 |
---|---|---|
committer | Kaz Kylheku <kaz@kylheku.com> | 2023-06-08 22:59:40 -0700 |
commit | d2ae79caf381389d5bda82b324ec82f980e4a6a8 (patch) | |
tree | 9610f3ee93bb1ccf50bb874da4083efd86124592 | |
parent | 53cb074b0db3fb20b1496ef608bb6b7fadad59ad (diff) | |
download | pw-d2ae79caf381389d5bda82b324ec82f980e4a6a8.tar.gz pw-d2ae79caf381389d5bda82b324ec82f980e4a6a8.tar.bz2 pw-d2ae79caf381389d5bda82b324ec82f980e4a6a8.zip |
Feature: implement pass-through mode.
In pass-through mode, pw can be used in the middle of a pipeline,
or redirected to a file or device. Then instead of discarding
data, it copies from standard input to standard output.
pw's implementation assumes that standard output is the terminal,
and uses some functions that operate implicitly on standard output.
To avoid changing all that code, dup2 calls are used to rearrange
the file descriptors. The tty descriptor opened from /dev/tty is
installed as standard output, and the original standard output
(the device, file or pipe to which data is to be passed through)
is tied to a dedicated stream held in the local variable out.
Whenever out is not null, bytes are to be sent to it rather than
discarded.
In pass-through mode, though, standard output is not the
terminal.
-rw-r--r-- | pw.1 | 66 | ||||
-rw-r--r-- | pw.c | 37 |
2 files changed, 82 insertions, 21 deletions
@@ -30,22 +30,50 @@ pw \- Pipe Watch: monitor recent lines of output from pipe .SH SYNOPSIS -command | pw [-i interval] [-l interval] [-n number-of-lines] [-dEB] +command | pw [options] +command | pw [options] | next-command +command | pw [options] > file .SH DESCRIPTION .I pw -stands for Pipe Watch, a utility that continuously reads lines of -text from a pipe or pipe-like source, passes them through a FIFO buffer, and -maintains a display based on the occasional sampling of the contents of the -FIFO buffer, with useful features such as triggering and filtering. +("Pipe Watch") is an interactive data monitoring utility which may be +used as the terminating point of a command pipeline, or a middle element. -Upon successful startup, .I pw -simultaneously monitors its standard input for the -arrival of new data, as well as the TTY for interactive commands. -Lines from standard input are placed into a FIFO buffer. -While the FIFO buffer is not yet full, lines are displayed immediately. -After the FIFO buffer fills up with the specified number of lines +requires a session with a controlling TTY. The TTY should be connected +to an ANSI terminal. The controlling TTY does not have to be the +standard input or output of +.IR pw , +since the utility gains access to the TTY by opening +.BR /dev/tty . + +When +.I pw +is used as the last element of the pipeline, it reads data +from its standard input, and discards it (discard mode). When used as a middle +pipeline element, or when its output is redirected to a file, or to a device +other than the controlling TTY of the session, it passes data from its standard +input to its standard output (pass-through mode). + +In parallel with passing or discarding data +.I pw +provides a useful visual display on the controlling terminal, and also takes +interactive commands from the terminal. + +In addition, when +.I pw +is placed into the job control background and permitted to execute, it +keeps reading and passing/discarding it without updating the display, +while all of its features such as triggers remain active. + +The visual display is based on a concept whereby lines are passing through +a line-oriented FIFO buffer, which is sampled at various times. The samples +are used to update the display. + +Around program startup, while the FIFO buffer is not yet full, lines arriving +into the FIFO are displayed immediately. After the FIFO buffer fills up with +the specified +number of lines (controlled by the .B -n option) then @@ -520,8 +548,9 @@ and then requested to execute in the background using the shell's .B bg command. When .I pw -executes in the background, it continues reading from the pipe and discard -input, but doesn't update the display. This useful behavior allows +executes in the background, it continues reading from its standard input, +and possibly pass the data it to its standard output, +without updating the display. This useful behavior allows .I pw to be used for monitoring multiple programs which continuously produce output, all from the same job control session. Redirecting the output @@ -661,6 +690,12 @@ separated by commas. The inverted patterns are indicated by a leading .B ! character. +Note that the grep filtering has no effect on the data passing through +.IR pw . +When the utility is in pass-through mode, it copies all bytes from +standard input to standard output, regardless of the snapshots, triggers +or filtering, which only affect the interactive features. + .IP \fB:r\fP[\fB!\fP] Remove and forget the most recent grep pattern .RB ( :g or :v ) @@ -863,8 +898,9 @@ A control character like counts as two characters. The behavior of .I pw is that when it is collecting a line, and the line has reached the maximum -length, it keeps reading characters from the standard input, but discards -them, while remaining responsive to TTY input. +length, it keeps reading and processing characters from the standard input, +without appending them to the currently accumulating line, until the +newline character occurs. .IP \fB-d\fP Disable auto-quit: when no more input is available, instead of updating @@ -1156,14 +1156,19 @@ static char **resizebuf(char **buf, size_t nlfrom, size_t nlto) return buf; } -int isbkgnd(FILE *tty) +int isbkgnd(int ttyfd) { - int fd = fileno(tty); pid_t grp = getpgrp(); - pid_t fgrp = tcgetpgrp(fd); + pid_t fgrp = tcgetpgrp(ttyfd); return (grp != fgrp); } +int ismytty(int ttyfd) +{ + pid_t fgrp = tcgetpgrp(ttyfd); + return fgrp != -1; +} + void clipsplits(pwstate *pw) { int columns = pw->columns; @@ -1188,6 +1193,7 @@ int main(int argc, char **argv) size_t maxlen = 2047; int opt; int ifd = fileno(stdin); + int ofd = fileno(stdout); int ttyfd = tty ? fileno(tty) : -1; struct termios tty_saved, tty_new; struct winsize ws = { 0 }; @@ -1198,6 +1204,7 @@ int main(int argc, char **argv) int auto_quit = 1; int quit_count = 1, quit_cntdwn = quit_count; int exit_status = EXIT_FAILURE; + FILE *out = NULL; #ifdef SIGWINCH static struct sigaction sa; #endif @@ -1220,6 +1227,12 @@ int main(int argc, char **argv) } } + if (!isatty(ofd) || !ismytty(ttyfd)) { + int dup_ofd = dup(ofd); + dup2(ttyfd, ofd); + out = fdopen(dup_ofd, "w"); + } + while ((opt = getopt(argc, argv, "n:i:l:dEBg:q:m:p:e:f:")) != -1) { switch (opt) { case 'n': @@ -1392,7 +1405,7 @@ int main(int argc, char **argv) if (fcntl(ifd, F_SETFL, O_NONBLOCK) < 0) panic("unable to set stdin nonblocking"); - if (!isbkgnd(stdout)) + if (!isbkgnd(ttyfd)) ttyset(ttyfd, &tty_new); else pw.stat = stat_bkgnd; @@ -1415,9 +1428,14 @@ int main(int argc, char **argv) if ((pw.stat & stat_eof) == 0) { int ch; - while ((ch = getc(stdin)) != EOF && ch != '\n' && dslen(line) < maxlen) + while ((ch = getchar()) != EOF && ch != '\n' && dslen(line) < maxlen) { line = addchesc(line, pw.tstop, ch); + if (out) + putc(ch, out); + } if (ch == EOF) { + if (out) + fflush(out); if (feof(stdin) || (errno != EAGAIN && errno != EWOULDBLOCK)) { nfds = 1; if (!ferror(stdin)) @@ -1430,9 +1448,13 @@ int main(int argc, char **argv) pw.stat |= stat_eof; clrline(pw.stat); drawstatus(&pw); + if (out) + fflush(out); } clearerr(stdin); } else if (ch == '\n') { + if (out) + putc(ch, out); nfds = 1; line = dsensure(line); if ((pw.stat & stat_grep)) { @@ -1552,7 +1574,7 @@ int main(int argc, char **argv) work = workbout; if ((pw.stat & stat_bkgnd)) { - if (!isbkgnd(stdout)) { + if (!isbkgnd(ttyfd)) { pw.stat &= ~stat_bkgnd; ttyset(ttyfd, &tty_new); for (int i = 0; i < pw.nlines; i++) @@ -2055,6 +2077,9 @@ int main(int argc, char **argv) ttyset(ttyfd, &tty_saved); } + if (out) + fclose(out); + #if CONFIG_DEBUG_LEAKS freebuf(pw.circbuf, pw.maxlines); free(pw.circbuf); |