diff --git a/Makefile.am b/Makefile.am index 936b152..486b230 100644 --- a/Makefile.am +++ b/Makefile.am @@ -32,6 +32,7 @@ src/pv/format/name.c \ src/pv/format/previousline.c \ src/pv/format/progressbar.c \ src/pv/format/rate.c \ +src/pv/format/ratio.c \ src/pv/format/sgr.c \ src/pv/format/timer.c \ src/pv/loop.c \ diff --git a/docs/pv.1 b/docs/pv.1 index c9f75ff..387bbef 100644 --- a/docs/pv.1 +++ b/docs/pv.1 @@ -13,6 +13,8 @@ pv \- monitor and manage the progress of data through a pipe \fBpv\fR \fB\-R\fR|\fB\-\-remote\fR \fIPID\fR [\fIOPTION\fR]... .PP \fBpv\fR \fB\-Q\fR|\fB\-\-query\fR \fIPID\fR [\fIOPTION\fR]... +.PP +\fBpv\fR [\fIOPTION\fR]... \fB\-M\fR|\fB\-\-monitor\fR \fISIDE\fR \-\- \fICOMMAND\fR [\fIARGS\fR]... .\" .SH DESCRIPTION Show the progress of data through a pipeline by giving information such as @@ -27,6 +29,9 @@ read. .PP In \*(lq\fB\-\-watchfd\fR\*(rq mode, inspect another process and show its progress through the files it has open. +.PP +In \*(lq\fB\-\-monitor\fR\*(rq mode, run a command and display the progress +of data through both its standard input and its standard output. .\" .SH OPTIONS .\" @@ -409,6 +414,37 @@ they match those of that process - such as \*(lq\fB\-\-null\fR\*(rq, and \*(lq\fB\-\-average\-rate\-window\fR\*(rq. Data transfer modifiers will have no effect. +.TP +.BR \-M " \fISIDE\fR, " \-\-monitor " \fISIDE\fR" +Run the command specified by the remaining arguments, and monitor its +standard input, its standard output, or both, depending on whether +\fISIDE\fR is \*(lq\fBin\fR\*(rq, \*(lq\fBout\fR\*(rq, or +\*(lq\fBboth\fR\*(rq. +Use \*(lq\fB\-\-\fR\*(rq after all of the \fBpv\fR options to keep the +monitored command's options separate. +.IP +With a \fISIDE\fR of \*(lq\fBboth\fR\*(rq, two progress bars will be shown. +All of \fBpv\fR's display switches, output modifiers, and data transfer +modifiers will apply to both sides. +.IP +For example, +\*(lq\fBpv\~\-\-cursor\~\-\-monitor\~both\~\-\-\~gzip\~\-9\fR\*(rq is +equivalent to +\*(lq\fBpv\~\-\-cursor\~|\~gzip\~\-9\~|\~pv\~\-\-cursor\fR\*(rq, except that +the latter can't show the input:output ratio. +.IP +The \*(lq\fB\-\-format\fR\*(rq and \*(lq\fB\-\-name\fR\*(rq options may be +specified twice, in which case the first one applies to the input side and +the second applies to the output side. +If no \*(lq\fB\-\-format\fR\*(rq options are specified, the default format +based on the other display switches is used as normal, except that the +ratio is added to the output side when \fISIDE\fR is \*(lq\fBboth\fR\*(rq. +.IP +The values \*(lq\fB0\fR\*(rq, \*(lq\fB1\fR\*(rq, and \*(lq\fB2\fR\*(rq may +be used as synonyms for \*(lq\fBin\fR\*(rq, \*(lq\fBout\fR\*(rq, and +\*(lq\fBboth\fR\*(rq. +.IP +This option cannot be used with \*(lq\fB\-\-store\-and\-forward\*(rq. .\" .SS "Other options" .TP @@ -498,6 +534,10 @@ Equivalent to \*(lq\fB\-\-bytes\fR\*(rq. If \*(lq\fB\-\-bits\fR\*(rq was specified, \*(lq\fB%b\fR\*(rq shows the bits transferred so far, not bytes. .TP +.B %{ratio} +Show the ratio of bytes transferred by the other side to the bytes +transferred by this side, in \*(lq\fB\-\-monitor\~both\fR\*(rq mode. +.TP .BR %T ", " %{buffer\-percent} Percentage of the transfer buffer in use. Equivalent to \*(lq\fB\-\-buffer\-percent\fR\*(rq. @@ -721,7 +761,8 @@ pv \-\-numeric \-\-format '{"elapsed":%t,"bytes":%b,"rate":%r,"percentage":%{pro .\" .SH EXIT STATUS An exit status of 1 indicates a problem with the \*(lq\fB\-\-remote\fR\*(rq, -\*(lq\fB\-\-query\fR\*(rq, or \*(lq\fB\-\-pidfile\fR\*(rq options. +\*(lq\fB\-\-query\fR\*(rq, \*(lq\fB\-\-pidfile\fR\*(rq, or +\*(lq\fB\-\-monitor\fR\*(rq options. .PP Any other exit status is a bitmask of the following: .TP 5 diff --git a/src/include/config-aux.h b/src/include/config-aux.h index 45a5f3b..45fed1b 100644 --- a/src/include/config-aux.h +++ b/src/include/config-aux.h @@ -51,3 +51,10 @@ typedef bool _Bool; #define PV_REMOTE_CONTROL 1 # endif #endif + +#ifndef SPLINT +/* Remove __attribute__(()) if not using GCC. */ +#ifndef __GNUC__ +#define __attribute__(x) /* GCC-only feature */ +#endif +#endif diff --git a/src/include/options.h b/src/include/options.h index 2b339d8..3997895 100644 --- a/src/include/options.h +++ b/src/include/options.h @@ -32,9 +32,22 @@ typedef enum { PV_ACTION_STORE_AND_FORWARD, /* store to file, then output from it */ PV_ACTION_WATCHFD, /* watch process file descriptors */ PV_ACTION_REMOTE_CONTROL, /* remotely control another pv */ - PV_ACTION_QUERY /* watch the state of another pv */ + PV_ACTION_QUERY, /* watch the state of another pv */ + PV_ACTION_MONITOR /* run a process, watch its stdin/out */ } pvaction_t; +/* + * Sides of a monitored command to monitor with PV_ACTION_MONITOR. + */ +typedef enum { + PV_SIDE_NONE, /* don't monitor a command */ + PV_SIDE_IN, /* monitor only the input side */ + PV_SIDE_OUT, /* monitor only the output side */ + PV_SIDE_BOTH /* monitor both sides */ +} pvside_t; + +typedef /*@null@*/ const char * argv_string; + /* * Structure describing run-time options. * @@ -46,14 +59,16 @@ struct opts_s { /*@keep@*/ const char *program_name; /* name the program is running as */ /*@keep@*/ /*@null@*/ char *output; /* fd to write output to */ /*@keep@*/ /*@null@*/ char *name; /* display name, if any */ + /*@keep@*/ /*@null@*/ char *name1; /* first name, if two were given */ /*@keep@*/ /*@null@*/ char *default_bar_style; /* default bar style */ /*@keep@*/ /*@null@*/ char *format; /* output format, if any */ + /*@keep@*/ /*@null@*/ char *format1; /* first format, if two were given */ /*@keep@*/ /*@null@*/ char *pidfile; /* PID file, if any */ /*@keep@*/ /*@null@*/ char *store_and_forward_file; /* store and forward file, if any */ /*@keep@*/ /*@null@*/ char *extra_display; /* extra display specifier, if any */ /*@keep@*/ /*@null@*/ pid_t *watchfd_pid; /* array of processes to watch fds of */ /*@keep@*/ /*@null@*/ int *watchfd_fd; /* array of fds to watch in each one (0=all) */ - /*@keep@*/ /*@null@*/ const char **argv; /* array of non-option arguments */ + /*@keep@*/ /*@null@*/ argv_string *argv; /* array of non-option arguments */ size_t lastwritten; /* show N bytes last written */ off_t rate_limit; /* rate limit, in bytes per second */ size_t buffer_size; /* buffer size, in bytes (0=default) */ @@ -70,6 +85,7 @@ struct opts_s { unsigned int watchfd_count; /* number of watchfd items */ unsigned int watchfd_length; /* allocated array size */ pvaction_t action; /* the program action to perform */ + pvside_t side; /* which side of the monitored command to monitor */ bool progress; /* progress bar flag */ bool timer; /* timer flag */ bool eta; /* ETA flag */ @@ -77,6 +93,7 @@ struct opts_s { bool rate; /* rate counter flag */ bool average_rate; /* average rate counter flag */ bool bytes; /* bytes transferred flag */ + bool ratio; /* in:out ratio flag */ bool bits; /* report transfer size in bits */ bool decimal_units; /* decimal prefix flag */ bool bufpercent; /* transfer buffer percentage flag */ diff --git a/src/include/pv-internal.h b/src/include/pv-internal.h index ba9830c..1faf224 100644 --- a/src/include/pv-internal.h +++ b/src/include/pv-internal.h @@ -26,6 +26,7 @@ extern "C" { #define RATE_GRANULARITY 100000000 /* nsec between -L rate chunks */ #define RATE_BURST_WINDOW 5 /* rate burst window (multiples of rate) */ #define REMOTE_INTERVAL 100000000 /* nsec between checks for -R and -Q */ +#define MONITOR_EXCHANGE_INTERVAL 100000000 /* nsec between "-M both" data exchanges */ #define BUFFER_SIZE (size_t) 409600 /* default transfer buffer size */ #define BUFFER_SIZE_MAX (size_t) 524288 /* max auto transfer buffer size */ #define MAX_READ_AT_ONCE (size_t) 524288 /* max to read() in one go */ @@ -201,22 +202,15 @@ struct pvstate_s { off_t size; /* total size of data */ unsigned int skip_errors; /* skip read errors counter */ int output_fd; /* fd to write output to */ + pid_t othermonitor_pid; /* pid of the other monitor, in "-M both" mode */ + int othermonitor_read_fd; /* fd to read transfer counts from other monitor */ + int othermonitor_write_fd; /* fd to write transfer counts to other monitor */ unsigned int average_rate_window; /* time window in seconds for average rate calculations */ unsigned int history_interval; /* seconds between each average rate calc history entry */ pvdisplay_width_t width; /* screen width */ unsigned int height; /* screen height */ unsigned int extra_displays; /* bitmask of extra display destinations */ - struct { /* old-style format options (used by -R) */ - size_t lastwritten; /* --last-written (amount) */ - bool progress; /* --progress */ - bool timer; /* --timer */ - bool eta; /* --eta */ - bool fineta; /* --fineta */ - bool rate; /* --rate */ - bool average_rate; /* --average-rate */ - bool bytes; /* --bytes */ - bool bufpercent; /* --buffer-percent */ - } format_option; + pvformatoptions_s format_option; /* old-style format options (used by -R) */ bool force; /* display even if not on terminal */ bool cursor; /* use cursor positioning */ bool numeric; /* numeric output only */ @@ -324,6 +318,7 @@ struct pvstate_s { bool showing_timer; /* set if showing timer */ bool showing_bytes; /* set if showing byte/line count */ + bool showing_ratio; /* set if showing monitoring in:out ratio */ bool showing_rate; /* set if showing transfer rate */ bool showing_last_written; /* set if displaying the last few bytes written */ bool showing_previous_line; /* set if displaying the previously output line */ @@ -427,6 +422,8 @@ struct pvstate_s { off_t total_written; /* total bytes or lines written */ off_t transferred; /* amount transferred (written - unconsumed) */ + off_t otherside_transferred; /* amount transferred by the other side ("-M both") */ + /* Keep track of line positions to backtrack written_but_not_consumed. */ /*@only@*/ /*@null@*/ off_t *line_positions; /* line separator write positions (circular buffer) */ size_t line_positions_capacity; /* total size of line position array */ @@ -606,6 +603,7 @@ pvdisplay_bytecount_t pv_formatter_fineta(pvformatter_args_t); pvdisplay_bytecount_t pv_formatter_rate(pvformatter_args_t); pvdisplay_bytecount_t pv_formatter_average_rate(pvformatter_args_t); pvdisplay_bytecount_t pv_formatter_bytes(pvformatter_args_t); +pvdisplay_bytecount_t pv_formatter_ratio(pvformatter_args_t); pvdisplay_bytecount_t pv_formatter_buffer_percent(pvformatter_args_t); pvdisplay_bytecount_t pv_formatter_last_written(pvformatter_args_t); pvdisplay_bytecount_t pv_formatter_previous_line(pvformatter_args_t); diff --git a/src/include/pv.h b/src/include/pv.h index fe9fc8c..031f1c2 100644 --- a/src/include/pv.h +++ b/src/include/pv.h @@ -29,6 +29,7 @@ extern "C" { */ #define PV_ERROREXIT_REMOTE_OR_PID 1 #define PV_ERROREXIT_SAF 1 /* store and forward error */ +#define PV_ERROREXIT_MONITOR 1 /* monitor mode error */ #define PV_ERROREXIT_ACCESS 2 #define PV_ERROREXIT_OUROBOROS 4 #define PV_ERROREXIT_TRANSITION 8 @@ -192,15 +193,24 @@ extern /*@null@*/ /*@only@*/ pvstate_t pv_state_alloc(void); extern void pv_state_reset(pvstate_t state); /* - * Set the formatting string, given a set of old-style formatting options. + * Set the format options and use them to build a default formatting string. + * The default string is used if no format string is explicitly set. */ -extern void pv_state_set_format(pvstate_t state, bool progress, - bool timer, bool eta, - bool fineta, bool rate, - bool average_rate, bool bytes, - bool bufpercent, - size_t lastwritten, - /*@null@*/ const char *name); +typedef struct { + size_t lastwritten; /* --last-written (amount) */ + bool progress; /* --progress */ + bool timer; /* --timer */ + bool eta; /* --eta */ + bool fineta; /* --fineta */ + bool rate; /* --rate */ + bool average_rate; /* --average-rate */ + bool bytes; /* --bytes */ + bool bufpercent; /* --buffer-percent */ +} pvformatoptions_s; +extern void pv_state_set_format_options(pvstate_t, pvformatoptions_s); + +/* Append a string to the default format. */ +void pv_state_append_to_default_format(pvstate_t, /*@null@ */ const char *); /* * Set the various options. @@ -238,6 +248,8 @@ extern void pv_state_extra_display_set(pvstate_t, /*@null@*/ const char *); extern void pv_state_output_set(pvstate_t, int, const char *); extern void pv_state_average_rate_window_set(pvstate_t, unsigned int); extern void pv_state_set_terminal_supports_utf8(pvstate_t, bool); +extern void pv_state_othermonitor_set(pvstate_t, pid_t, int, int); +extern void pv_state_cancel_output_if_empty_format_string(pvstate_t); extern void pv_state_inputfiles(pvstate_t, unsigned int, const char **); extern void pv_state_watchfds(pvstate_t, unsigned int, const pid_t *, const int *); diff --git a/src/main/help.c b/src/main/help.c index 8c67489..dd02ebd 100644 --- a/src/main/help.c +++ b/src/main/help.c @@ -372,6 +372,9 @@ void display_help(void) N_("show progress of process PID"), { 0, 0, 0, 0} }, #endif /* PV_REMOTE_CONTROL */ + { "-M", "--monitor", "in|0|out|1|both|2", + N_("run a command and monitor its standard input, output, or both"), + { 0, 0, 0, 0} }, { "", NULL, NULL, NULL, { 0, 0, 0, 0} }, { "-P", "--pidfile", N_("FILE"), N_("save process ID in FILE"), diff --git a/src/main/main.c b/src/main/main.c index 816287e..dda3c55 100644 --- a/src/main/main.c +++ b/src/main/main.c @@ -19,6 +19,7 @@ #include #include #include +#include #ifdef HAVE_LANGINFO_H #include #endif @@ -131,10 +132,12 @@ static int pv__set_output(pvstate_t state, opts_t opts, /*@null@ */ const char * return 0; if (NULL == output_file || 0 == strcmp(output_file, "-")) { + debug("%s", "setting output to stdout"); pv_state_output_set(state, STDOUT_FILENO, "(stdout)"); return 0; } + debug("%s: %s", "setting output", output_file); output_fd = open(output_file, O_WRONLY | O_CREAT | O_TRUNC, 0600); /* flawfinder: ignore */ /* * flawfinder rationale: the output filename has been @@ -160,7 +163,7 @@ static int pv__set_output(pvstate_t state, opts_t opts, /*@null@ */ const char * * with the input file list forced to be just the store-and-forward file. * Returns nonzero on error. */ -static int pv__store_and_forward(pvstate_t state, opts_t opts, bool can_have_eta) +static int pv__store_and_forward(pvstate_t state, opts_t opts, pvformatoptions_s format_options) { char tmp_filename[4096]; /* flawfinder: ignore */ bool use_temporary_file; @@ -221,11 +224,10 @@ static int pv__store_and_forward(pvstate_t state, opts_t opts, bool can_have_eta if (0 != retcode) goto end_store_and_forward; - /* Reset the formatting to set the displayed name to "(input)". */ + /* Set the displayed name to "(input)" and trigger a format reparse. */ /*@-mustfreefresh@ */ - pv_state_set_format(state, opts->progress, opts->timer, can_have_eta ? opts->eta : false, - can_have_eta ? opts->fineta : false, opts->rate, opts->average_rate, - opts->bytes, opts->bufpercent, opts->lastwritten, _("(input)")); + pv_state_name_set(state, _("(input)")); + pv_state_set_format_options(state, format_options); /*@+mustfreefresh@ *//* see below about gettext _() calls. */ /* Run the main loop as normal. */ @@ -247,10 +249,12 @@ static int pv__store_and_forward(pvstate_t state, opts_t opts, bool can_have_eta /* Recalculate the input size. */ pv_state_size_set(state, pv_calc_total_size(state)); + /* Set the displayed name to whatever was requested. */ + pv_state_name_set(state, opts->name); /* Reset the format, since we might have been asked to show ETA. */ - pv_state_set_format(state, opts->progress, opts->timer, opts->eta, - opts->fineta, opts->rate, opts->average_rate, - opts->bytes, opts->bufpercent, opts->lastwritten, opts->name); + format_options.eta = opts->eta; + format_options.fineta = opts->fineta; + pv_state_set_format_options(state, format_options); /* Reset calculated values in the state. */ pv_state_reset(state); @@ -267,6 +271,355 @@ static int pv__store_and_forward(pvstate_t state, opts_t opts, bool can_have_eta } +/* + * Run the main transfer loop for the given side of monitor mode, using the + * "command_fd" file descriptor as the transfer loop's output (on the "in" + * side, it's a pipe to the command) or as its input (on the "out" side, + * it's a pipe from the command). + * + * If both sides are active, "othermonitor_pid" is the PID of the monitor on + * the other side, "othermonitor_read_fd" is a pipe file descriptor to read + * info from the other monitor, and "othermonitor_write_fd" is for writing + * info to the other monitor. + * + * Returns the appropriate exit status. + * + * As a side effect, "command_fd" is closed. + */ +static int pv__run_monitor(const char *program_name, pvstate_t state, pvside_t side, int command_fd, + pid_t othermonitor_pid, int othermonitor_read_fd, int othermonitor_write_fd) +{ + const char *dummy_argv[1]; /* flawfinder: ignore */ + + /* flawfinder - the array length is passed along with the array. */ + + switch (side) { + case PV_SIDE_NONE: /* fall through */ + case PV_SIDE_BOTH: + return PV_ERROREXIT_MONITOR; + case PV_SIDE_IN: + /* Replace stdout with the pipe to the command. */ + debug("replacing stdout with fd %d", command_fd); + if (dup2(command_fd, STDOUT_FILENO) < 0) { + fprintf(stderr, "%s: %s\n", program_name, strerror(errno)); + return PV_ERROREXIT_MONITOR; + } + break; + case PV_SIDE_OUT: + /* Replace stdin with the pipe from the command. */ + debug("replacing stdin with fd %d", command_fd); + if (dup2(command_fd, STDIN_FILENO) < 0) { + fprintf(stderr, "%s: %s\n", program_name, strerror(errno)); + return PV_ERROREXIT_MONITOR; + } + break; + } + + if (close(command_fd) < 0) { + fprintf(stderr, "%s: %s\n", program_name, strerror(errno)); + } + + pv_state_othermonitor_set(state, othermonitor_pid, othermonitor_read_fd, othermonitor_write_fd); + + /*@-observertrans@ */ + dummy_argv[0] = "-"; + /*@+observertrans@ */ + pv_state_inputfiles(state, 1, dummy_argv); + + pv_state_cancel_output_if_empty_format_string(state); + return pv_main_loop(state); +} + + +/* + * Monitor mode: run a process and run the main transfer loop on its input, + * output, or both. Returns the appropriate exit status. + * + * When monitoring both sides, two pipes are set up between the monitor + * processes for each side - in to out, and out to in - for them to exchange + * information about the transfer, so both sides can see how many bytes the + * other side has transferred. This is what allows the in:out ratio to be + * displayed. + */ +static int pv__monitor(pvstate_t state, opts_t opts, pvformatoptions_s format_options) +{ + int pipefd_cmd_in[2]; /* pipe from the monitor to the command */ + int pipefd_cmd_out[2]; /* pipe from the command to the monitor */ + int pipefd_in_to_out[2]; /* from in monitor to out monitor */ + int pipefd_out_to_in[2]; /* from out monitor to in monitor */ + int retcode, pid_status; + pid_t command_pid, in_monitor_pid, out_monitor_pid, waited_pid; + + /* Arguments check. */ + if ((NULL == opts->argv) || (opts->argc < 1) || (NULL == opts->argv[0])) { + /*@-mustfreefresh@ */ + fprintf(stderr, "%s: -M: %s\n", opts->program_name, _("a command to run must be specified")); + return PV_ERROREXIT_MONITOR; + /*@+mustfreefresh@ *//* see below about gettext _() calls. */ + } + + retcode = 0; + + /* + * Create the pipes for communicating with the command. + */ + + pipefd_cmd_in[0] = -1; + pipefd_cmd_in[1] = -1; + pipefd_cmd_out[0] = -1; + pipefd_cmd_out[1] = -1; + + /* Pipe for the input side of the command, if we're monitoring it. */ + if ((PV_SIDE_IN == opts->side) || (PV_SIDE_BOTH == opts->side)) { + if (0 != pipe(pipefd_cmd_in)) { + fprintf(stderr, "%s: %s\n", opts->program_name, strerror(errno)); + return PV_ERROREXIT_MONITOR; + } + debug("pipefd_cmd_in[]=(%d,%d)", pipefd_cmd_in[0], pipefd_cmd_in[1]); + } + + /* Pipe for the output side of the command, if we're monitoring it. */ + if ((PV_SIDE_OUT == opts->side) || (PV_SIDE_BOTH == opts->side)) { + if (0 != pipe(pipefd_cmd_out)) { + fprintf(stderr, "%s: %s\n", opts->program_name, strerror(errno)); + if (-1 != pipefd_cmd_in[0]) + (void) close(pipefd_cmd_in[0]); + if (-1 != pipefd_cmd_in[1]) + (void) close(pipefd_cmd_in[1]); + return PV_ERROREXIT_MONITOR; + } + debug("pipefd_cmd_out[]=(%d,%d)", pipefd_cmd_out[0], pipefd_cmd_out[1]); + } + + /* Common idiom to close an fd, and set it to -1, if it's open. */ +#define close_if_open(x) if (-1 != x) { \ +(void) close(x); \ +x = 1; \ +} + + /* Create a process to run the command. */ + command_pid = (pid_t) fork(); + if (command_pid < 0) { + fprintf(stderr, "%s: %s\n", opts->program_name, strerror(errno)); + close_if_open(pipefd_cmd_in[0]); + close_if_open(pipefd_cmd_in[1]); + close_if_open(pipefd_cmd_out[0]); + close_if_open(pipefd_cmd_out[1]); + return PV_ERROREXIT_MONITOR; + } else if (0 == command_pid) { + /* Command process. */ + + /* Close the write end of the "in" pipe. */ + close_if_open(pipefd_cmd_in[1]); + + /* Close the read end of the "out" pipe. */ + close_if_open(pipefd_cmd_out[0]); + + /* Put the read end of the "in" pipe on stdin. */ + if (-1 != pipefd_cmd_in[0]) { + debug("replacing command stdin with fd %d", pipefd_cmd_in[0]); + if (dup2(pipefd_cmd_in[0], STDIN_FILENO) < 0) { + fprintf(stderr, "%s: %s\n", opts->program_name, strerror(errno)); + exit(EXIT_FAILURE); + } + (void) close(pipefd_cmd_in[0]); + debug("replaced command stdin with fd %d", pipefd_cmd_in[0]); + pipefd_cmd_in[0] = -1; + } + + /* Put the write end of the "out" pipe on stdout. */ + if (-1 != pipefd_cmd_out[1]) { + debug("replacing command stdout with fd %d", pipefd_cmd_out[1]); + if (dup2(pipefd_cmd_out[1], STDOUT_FILENO) < 0) { + fprintf(stderr, "%s: %s\n", opts->program_name, strerror(errno)); + exit(EXIT_FAILURE); + } + (void) close(pipefd_cmd_out[1]); + debug("replaced command stdout with fd %d", pipefd_cmd_out[1]); + pipefd_cmd_out[1] = -1; + } + + /* Execute the command. */ + (void) execvp(opts->argv[0], (char *const *) (opts->argv)); /* flawfinder: ignore */ + fprintf(stderr, "%s: %s\n", opts->program_name, strerror(errno)); + exit(EXIT_FAILURE); + + /* + * flawfinder recommends using a library call instead of + * executing another program with execvp(), but that isn't + * appropriate here, the whole purpose of monitor mode is to + * execute a process and monitor it. + */ + } + + /* Main process, not the command process. */ + + /* Close the read end of the "in" pipe. */ + close_if_open(pipefd_cmd_in[0]); + + /* Close the write end of the "out" pipe. */ + close_if_open(pipefd_cmd_out[1]); + + /* + * If monitoring both input and output, create pipes for the two + * monitors to talk to each other in both directions. + */ + pipefd_in_to_out[0] = -1; + pipefd_in_to_out[1] = -1; + pipefd_out_to_in[0] = -1; + pipefd_out_to_in[1] = -1; + if (PV_SIDE_BOTH == opts->side) { + if (0 != pipe(pipefd_in_to_out)) { + fprintf(stderr, "%s: %s\n", opts->program_name, strerror(errno)); + close_if_open(pipefd_cmd_in[1]); + close_if_open(pipefd_cmd_out[0]); + (void) kill(command_pid, SIGTERM); + return PV_ERROREXIT_MONITOR; + } + debug("pipefd_in_to_out[]=(%d,%d)", pipefd_in_to_out[0], pipefd_in_to_out[1]); + if (0 != pipe(pipefd_out_to_in)) { + fprintf(stderr, "%s: %s\n", opts->program_name, strerror(errno)); + close_if_open(pipefd_in_to_out[0]); + close_if_open(pipefd_in_to_out[1]); + close_if_open(pipefd_cmd_in[1]); + close_if_open(pipefd_cmd_out[0]); + (void) kill(command_pid, SIGTERM); + return PV_ERROREXIT_MONITOR; + } + debug("pipefd_out_to_in[]=(%d,%d)", pipefd_out_to_in[0], pipefd_out_to_in[1]); + } + + /* + * If monitoring both input and output, create a process for + * monitoring the output side. + */ + in_monitor_pid = -1; + out_monitor_pid = -1; + if (PV_SIDE_BOTH == opts->side) { + in_monitor_pid = (pid_t) getpid(); + out_monitor_pid = (pid_t) fork(); + if (out_monitor_pid < 0) { + fprintf(stderr, "%s: %s\n", opts->program_name, strerror(errno)); + close_if_open(pipefd_in_to_out[0]); + close_if_open(pipefd_in_to_out[1]); + close_if_open(pipefd_out_to_in[0]); + close_if_open(pipefd_out_to_in[1]); + close_if_open(pipefd_cmd_in[1]); + close_if_open(pipefd_cmd_out[0]); + (void) kill(command_pid, SIGTERM); + return PV_ERROREXIT_MONITOR; + } else if (0 == out_monitor_pid) { + /* Output side monitoring process. */ + + /* Close the write end of the command "in" pipe. */ + close_if_open(pipefd_cmd_in[1]); + + /* Close the write end of the in-to-out pipe. */ + close_if_open(pipefd_in_to_out[1]); + + /* Close the read end of the out-to-in pipe. */ + close_if_open(pipefd_out_to_in[0]); + + /* Add ratio to the default format on the out side. */ + if (PV_SIDE_BOTH == opts->side) { + pv_state_append_to_default_format(state, "%{ratio}"); + } + + retcode = + pv__run_monitor(opts->program_name, state, PV_SIDE_OUT, pipefd_cmd_out[0], in_monitor_pid, + pipefd_in_to_out[0], pipefd_out_to_in[1]); + + /* Close the other ends of the intra-monitor pipes. */ + close_if_open(pipefd_in_to_out[0]); + close_if_open(pipefd_out_to_in[1]); + + return retcode; + } + + /* Input side monitoring process. */ + + /* Close the read end of the in-to-out pipe. */ + close_if_open(pipefd_in_to_out[0]); + + /* Close the write end of the out-to-in pipe. */ + close_if_open(pipefd_out_to_in[1]); + } + + /* Monitor the remaining side. */ + switch (opts->side) { + case PV_SIDE_NONE: + retcode = PV_ERROREXIT_MONITOR; + break; + case PV_SIDE_BOTH: + /* Use name1, format1 for the "in" side. */ + if (NULL != opts->name1) { + pv_state_name_set(state, opts->name1); + } + if (NULL != opts->format1) { + pv_state_format_string_set(state, opts->format1); + } + /* Trigger a format reparse. */ + pv_state_set_format_options(state, format_options); + /*@fallthrough@ */ + /* falling through as "out" is in another process (above). */ +#ifndef SPLINT + __attribute__((fallthrough)); +#endif + case PV_SIDE_IN: + /* Close the read end of the "out" pipe. */ + close_if_open(pipefd_cmd_out[0]); + retcode = + pv__run_monitor(opts->program_name, state, PV_SIDE_IN, pipefd_cmd_in[1], out_monitor_pid, + pipefd_out_to_in[0], pipefd_in_to_out[1]); + break; + case PV_SIDE_OUT: + /* Close the write end of the "in" pipe. */ + close_if_open(pipefd_cmd_in[1]); + retcode = + pv__run_monitor(opts->program_name, state, PV_SIDE_OUT, pipefd_cmd_out[0], in_monitor_pid, + pipefd_in_to_out[0], pipefd_out_to_in[1]); + break; + } + + /* + * If monitoring the "in" side, close stdout to signal EOF, + * otherwise when we wait for the monitored command, we'll wait + * forever. + */ + if (PV_SIDE_IN == opts->side || PV_SIDE_BOTH == opts->side) { + if (close(STDOUT_FILENO) < 0) { + fprintf(stderr, "%s: %s\n", opts->program_name, strerror(errno)); + } + } + + /* Wait for the monitored command to exit. */ + do { + /*@-type@ */ + /* splint disagreement about __pid_t vs pid_t. */ + pid_status = 0; + waited_pid = waitpid(command_pid, &pid_status, 0); + /*@+type@ */ + } while (-1 == waited_pid && EINTR == errno); + + /* If monitoring both sides, wait for the "out" side to exit. */ + if (PV_SIDE_BOTH == opts->side && -1 != out_monitor_pid) { + /* Close our ends of the intra-monitor pipes. */ + close_if_open(pipefd_in_to_out[1]); + close_if_open(pipefd_out_to_in[0]); + /* Wait for the "out" side to exit. */ + do { + /*@-type@ */ + /* splint disagreement about __pid_t vs pid_t. */ + pid_status = 0; + waited_pid = waitpid(out_monitor_pid, &pid_status, 0); + /*@+type@ */ + } while (-1 == waited_pid && EINTR == errno); + } + + return retcode; +} + + /* * Process command-line arguments and set option flags, then call functions * to initialise, and finally enter the main loop. @@ -278,6 +631,7 @@ int main(int argc, char **argv) int retcode = 0; bool can_have_eta = true; bool terminal_supports_utf8 = false; + pvformatoptions_s format_options; #if ! HAVE_SETPROCTITLE initproctitle(argc, argv); @@ -361,8 +715,11 @@ int main(int argc, char **argv) /* * Put our list of input files into the PV internal state. + * + * Don't do this in monitor mode, since the rest of PV won't be + * using the list in that case. */ - if (NULL != opts->argv) { + if ((NULL != opts->argv) && (PV_ACTION_MONITOR != opts->action)) { pv_state_inputfiles(state, opts->argc, (const char **) (opts->argv)); } @@ -469,6 +826,8 @@ int main(int argc, char **argv) } } + /* TODO: size calculation for monitor mode. */ + /* Initialise the signal handling. */ pv_sig_init(state); @@ -529,9 +888,17 @@ int main(int argc, char **argv) pv_state_extra_display_set(state, opts->extra_display); pv_state_average_rate_window_set(state, opts->average_rate_window); - pv_state_set_format(state, opts->progress, opts->timer, can_have_eta ? opts->eta : false, - can_have_eta ? opts->fineta : false, opts->rate, opts->average_rate, - opts->bytes, opts->bufpercent, opts->lastwritten, opts->name); + format_options.progress = opts->progress; + format_options.timer = opts->timer; + format_options.eta = can_have_eta ? opts->eta : false; + format_options.fineta = can_have_eta ? opts->fineta : false; + format_options.rate = opts->rate; + format_options.average_rate = opts->average_rate; + format_options.bytes = opts->bytes; + format_options.bufpercent = opts->bufpercent; + format_options.lastwritten = opts->lastwritten; + + pv_state_set_format_options(state, format_options); debug("%s: %s", "terminal_supports_utf8", terminal_supports_utf8 ? "true" : "false"); pv_state_set_terminal_supports_utf8(state, terminal_supports_utf8); @@ -542,11 +909,13 @@ int main(int argc, char **argv) break; case PV_ACTION_TRANSFER: /* Normal "transfer data" mode. */ + pv_state_cancel_output_if_empty_format_string(state); retcode = pv_main_loop(state); break; case PV_ACTION_STORE_AND_FORWARD: /* Store-and-forward transfer mode. */ - retcode = pv__store_and_forward(state, opts, can_have_eta); + pv_state_cancel_output_if_empty_format_string(state); + retcode = pv__store_and_forward(state, opts, format_options); break; case PV_ACTION_WATCHFD: /* "Watch file descriptor(s) of another process" mode. */ @@ -560,6 +929,10 @@ int main(int argc, char **argv) /* Query the progress of another running pv. */ retcode = pv_query_loop(state, opts->query); break; + case PV_ACTION_MONITOR: + /* Run a process and monitor its input and output. */ + retcode = pv__monitor(state, opts, format_options); + break; } /* Clear up the PID file, if one was written. */ diff --git a/src/main/options.c b/src/main/options.c index f49a676..ead226e 100644 --- a/src/main/options.c +++ b/src/main/options.c @@ -64,8 +64,12 @@ void opts_free( /*@only@ */ opts_t opts) */ if (NULL != opts->name) free(opts->name); + if (NULL != opts->name1) + free(opts->name1); if (NULL != opts->format) free(opts->format); + if (NULL != opts->format1) + free(opts->format1); if (NULL != opts->pidfile) free(opts->pidfile); if (NULL != opts->output) @@ -88,12 +92,13 @@ void opts_free( /*@only@ */ opts_t opts) /* * Add a filename to the list of non-option arguments, returning false on - * error. The filename is not copied - the pointer is stored. + * error. The filename is not copied - the pointer is stored. The list is + * guaranteed to have a NULL after the last item. */ bool opts_add_file(opts_t opts, const char *filename) { /*@-branchstate@ */ - if ((opts->argc >= opts->argv_length) || (NULL == opts->argv)) { + if (((1 + opts->argc) >= opts->argv_length) || (NULL == opts->argv)) { opts->argv_length = opts->argc + 10; /*@-keeptrans@ */ opts->argv = realloc(opts->argv, opts->argv_length * sizeof(char *)); @@ -117,6 +122,7 @@ bool opts_add_file(opts_t opts, const char *filename) */ opts->argv[opts->argc++] = filename; + opts->argv[opts->argc] = NULL; return true; } @@ -416,6 +422,27 @@ static bool opts_watchfd_parse(opts_t opts, const char *argument, /*@null@ */ co } +/* + * Return true if the first string is at least as long as the second, and + * its start matches the entirety of the second. Both strings must be + * null-terminated. + */ +static bool string_starts_with(const char *string, const char *match) +{ + size_t string_length, match_length; + + string_length = strlen(string); /* flawfinder: ignore */ + match_length = strlen(match); /* flawfinder: ignore */ + /* flawfinder - these are null-terminated strings. */ + + if (string_length < match_length) + return false; + if (0 == strncmp(string, match, match_length)) + return true; + return false; +} + + #ifdef HAVE_NFTW /* * Callback function for nftw() to add the size of the given file to the @@ -680,6 +707,7 @@ opts_t opts_parse(unsigned int argc, char **argv) { "watchfd", 1, NULL, (int) 'd' }, { "output", 1, NULL, (int) 'o' }, { "average-rate-window", 1, NULL, (int) 'm' }, + { "monitor", 1, NULL, (int) 'M' }, #ifdef ENABLE_DEBUGGING { "debug", 1, NULL, (int) '!' }, #endif /* ENABLE_DEBUGGING */ @@ -688,7 +716,7 @@ opts_t opts_parse(unsigned int argc, char **argv) /*@+nullassign@ */ int option_index = 0; #endif /* HAVE_GETOPT_LONG */ - char *short_options = "hVpteIrab8kTA:fvnqcWD:s:gl0i:w:H:N:u:F:x:L:B:CEZ:SYKOXU:R:Q:P:d:m:o:" + char *short_options = "hVpteIrab8kTA:fvnqcWD:s:gl0i:w:H:N:u:F:x:L:B:CEZ:SYKOXU:R:Q:P:d:m:o:M:" #ifdef ENABLE_DEBUGGING "!:" #endif @@ -736,6 +764,7 @@ opts_t opts_parse(unsigned int argc, char **argv) numopts = 0; opts->action = PV_ACTION_TRANSFER; + opts->side = PV_SIDE_NONE; opts->interval = 1; opts->delay_start = 0; opts->average_rate_window = 30; @@ -995,6 +1024,7 @@ opts_t opts_parse(unsigned int argc, char **argv) opts->height_set_manually = opts->height == 0 ? false : true; break; case 'N': + opts->name1 = opts->name; opts->name = pv_strdup(optarg); if (NULL == opts->name) { fprintf(stderr, "%s: -N: %s\n", opts->program_name, strerror(errno)); @@ -1069,6 +1099,7 @@ opts_t opts_parse(unsigned int argc, char **argv) } break; case 'F': + opts->format1 = opts->format; opts->format = pv_strdup(optarg); if (NULL == opts->format) { fprintf(stderr, "%s: -F: %s\n", opts->program_name, strerror(errno)); @@ -1102,6 +1133,33 @@ opts_t opts_parse(unsigned int argc, char **argv) case 'm': opts->average_rate_window = pv_getnum_count(optarg, opts->decimal_units); break; + case 'M': + opts->action = PV_ACTION_MONITOR; + if (string_starts_with(optarg, "0")) { + opts->side = PV_SIDE_IN; + } else if (string_starts_with(optarg, "in")) { + opts->side = PV_SIDE_IN; + } else if (string_starts_with(optarg, "stdin")) { + opts->side = PV_SIDE_IN; + } else if (string_starts_with(optarg, "1")) { + opts->side = PV_SIDE_OUT; + } else if (string_starts_with(optarg, "out")) { + opts->side = PV_SIDE_OUT; + } else if (string_starts_with(optarg, "stdout")) { + opts->side = PV_SIDE_OUT; + } else if (string_starts_with(optarg, "2")) { + opts->side = PV_SIDE_BOTH; + } else if (string_starts_with(optarg, "both")) { + opts->side = PV_SIDE_BOTH; + } else { + /*@-mustfreefresh@ *//* see above */ + fprintf(stderr, "%s: -M: %s: %s\n", + opts->program_name, optarg, _("invalid side specification")); + opts_free(opts); + return NULL; + /*@+mustfreefresh@ */ + } + break; #ifdef ENABLE_DEBUGGING case '!': debugging_output_destination(optarg); @@ -1238,6 +1296,31 @@ opts_t opts_parse(unsigned int argc, char **argv) if (opts->error_skip_block > 0 && 0 == opts->skip_errors) opts->skip_errors = 1; + /* + * Don't allow -R or -Q with -M. + */ + if ((PV_ACTION_MONITOR == opts->action) && ((0 != opts->remote) || (0 != opts->query))) { + /*@-mustfreefresh@ *//* see above */ + fprintf(stderr, "%s: %s: %s\n", opts->program_name, 0 != opts->remote ? "-R" : "-Q", + _("monitor mode cannot be specified with this option")); + opts_free(opts); + return NULL; + /*@+mustfreefresh@ */ + } + + /* + * Don't allow -U with -M. + */ + if ((PV_ACTION_STORE_AND_FORWARD == opts->action && PV_SIDE_NONE != opts->side) + || (PV_ACTION_MONITOR == opts->action && NULL != opts->store_and_forward_file)) { + /*@-mustfreefresh@ *//* see above */ + fprintf(stderr, "%s: %s\n", opts->program_name, + _("monitor mode cannot be used with store-and-forward")); + opts_free(opts); + return NULL; + /*@+mustfreefresh@ */ + } + /* * Don't allow any non-option arguments with -R or -Q. */ @@ -1250,6 +1333,17 @@ opts_t opts_parse(unsigned int argc, char **argv) /*@+mustfreefresh@ */ } + /* + * At least one non-option argument is required with -M. + */ + if ((PV_ACTION_MONITOR == opts->action) && (optind >= (int) argc)) { + /*@-mustfreefresh@ *//* see above */ + fprintf(stderr, "%s: -M: %s\n", opts->program_name, _("a command to run must be specified")); + opts_free(opts); + return NULL; + /*@+mustfreefresh@ */ + } + /* * Store remaining command-line arguments. */ diff --git a/src/pv/display.c b/src/pv/display.c index 4b06dab..5d555f8 100644 --- a/src/pv/display.c +++ b/src/pv/display.c @@ -533,6 +533,7 @@ pvdisplay_bytecount_t pv_formatter_segmentcontent(char *content, pvformatter_arg { "b", &pv_formatter_bytes, false }, { "{bytes}", &pv_formatter_bytes, false }, { "{transferred}", &pv_formatter_bytes, false }, + { "{ratio}", &pv_formatter_ratio, false }, { "T", &pv_formatter_buffer_percent, false }, { "{buffer-percent}", &pv_formatter_buffer_percent, false }, { "A", &pv_formatter_last_written, false }, @@ -620,6 +621,7 @@ static void pv__format_init(pvprogramstatus_t status, readonly_pvcontrol_t contr display->showing_timer = false; display->showing_bytes = false; + display->showing_ratio = false; display->showing_rate = false; display->showing_last_written = false; display->showing_previous_line = false; diff --git a/src/pv/format/ratio.c b/src/pv/format/ratio.c new file mode 100644 index 0000000..cce8567 --- /dev/null +++ b/src/pv/format/ratio.c @@ -0,0 +1,50 @@ +/* + * Formatter function for ratio of bytes transferred by the other monitored + * side to bytes transferred by this side. + * + * Copyright 2026 Andrew Wood + * + * License GPLv3+: GNU GPL version 3 or later; see `docs/COPYING'. + */ + +#include "config.h" +#include "pv.h" +#include "pv-internal.h" + + +/* + * Ratio of bytes or lines transferred by the other monitored side ("-M + * both") to the bytes or lines transferred by this side. + */ +pvdisplay_bytecount_t pv_formatter_ratio(pvformatter_args_t args) +{ + char content[128]; /* flawfinder: ignore - always bounded */ + + args->display->showing_ratio = true; + + if (0 == args->buffer_size) + return 0; + + content[0] = '\0'; + + if (0 == args->transfer->otherside_transferred || 0 == args->transfer->transferred) { + (void) pv_snprintf(content, sizeof(content), "%s:%s", "-", "-"); + } else if (args->transfer->otherside_transferred == args->transfer->transferred) { + (void) pv_snprintf(content, sizeof(content), "%d:%d", 1, 1); + } else { + long double side1, side2, ratio; + + side1 = (long double) (args->transfer->otherside_transferred); + side2 = (long double) (args->transfer->transferred); + + if (side1 > side2) { + ratio = side1 / side2; + (void) pv_snprintf(content, sizeof(content), "%.4Lg:%d", ratio, 1); + } else { + ratio = side2 / side1; + (void) pv_snprintf(content, sizeof(content), "%d:%.4Lg", 1, ratio); + } + } + + return pv_formatter_segmentcontent(content, args); +} diff --git a/src/pv/loop.c b/src/pv/loop.c index cc533d8..90aa016 100644 --- a/src/pv/loop.c +++ b/src/pv/loop.c @@ -166,6 +166,121 @@ static long double pv__elapsed_transfer_time(const struct timespec *loop_start_t } +/* + * Exchange information with the other side of the "-M both" monitor about + * the amount of data transferred. + */ +static void pv__monitor_exchange(pvstate_t state) +{ + bool written_yet = false; + + while ((state->control.othermonitor_read_fd >= 0) || (state->control.othermonitor_write_fd >= 0)) { + struct timeval tv; + fd_set readfds; + fd_set writefds; + fd_set exceptfds; + int max_fd; + int result; + + max_fd = -1; + if (state->control.othermonitor_read_fd > max_fd) + max_fd = state->control.othermonitor_read_fd; + if ((!written_yet) && (state->control.othermonitor_write_fd > max_fd)) + max_fd = state->control.othermonitor_write_fd; + + memset(&tv, 0, sizeof(tv)); + +#if SPLINT + /* splint doesn't like FD_ZERO, FD_SET, FD_ISSET. */ + /* Refer to src/pv/transfer.c for more details. */ + memset(&readfds, 0, sizeof(readfds)); + memset(&writefds, 0, sizeof(writefds)); + memset(&exceptfds, 0, sizeof(exceptfds)); +#else /* !SPLINT */ + FD_ZERO(&readfds); + FD_ZERO(&writefds); + FD_ZERO(&exceptfds); + if (state->control.othermonitor_read_fd >= 0) + FD_SET(state->control.othermonitor_read_fd, &readfds); + if ((!written_yet) && (state->control.othermonitor_write_fd >= 0)) + FD_SET(state->control.othermonitor_write_fd, &writefds); +#endif /* !SPLINT */ + + tv.tv_sec = 0; + tv.tv_usec = 0; + + result = select(max_fd + 1, &readfds, &writefds, &exceptfds, &tv); + + if (result <= 0) + break; + + if ((state->control.othermonitor_read_fd >= 0) +#ifndef SPLINT + && (FD_ISSET(state->control.othermonitor_read_fd, &readfds)) +#endif + ) { + off_t otherside_transferred; + ssize_t nread; + + otherside_transferred = 0; + + nread = read(state->control.othermonitor_read_fd, &otherside_transferred, /* flawfinder: ignore */ + sizeof(otherside_transferred)); + /* + * flawfinder rationale: no buffer overflow possible + * as we read a fixed size to a fixed point. + */ + + if (0 == nread) { + /* EOF - close. */ + debug("%d: %s", state->control.othermonitor_read_fd, "EOF - closing"); + (void) close(state->control.othermonitor_read_fd); + state->control.othermonitor_read_fd = -1; + } else if (nread < 0) { + debug("%d: %s: %s", state->control.othermonitor_read_fd, "error on read", + strerror(errno)); + } else if (nread != (ssize_t) (sizeof(otherside_transferred))) { + debug("%d: %d: %s", state->control.othermonitor_read_fd, (int) nread, + "incorrect byte count - ignoring"); + } else { + debug("%s: %lu", "other side transfer amount", (unsigned long) otherside_transferred); + state->transfer.otherside_transferred = otherside_transferred; + } + } + + if ((!written_yet) && (state->control.othermonitor_write_fd >= 0) +#ifndef SPLINT + && (FD_ISSET(state->control.othermonitor_write_fd, &writefds)) +#endif + ) { + ssize_t nwritten; + + nwritten = + write(state->control.othermonitor_write_fd, &(state->transfer.transferred), + sizeof(state->transfer.transferred)); + + if ((nwritten < 0) && ((EINTR == errno) || (EAGAIN == errno))) { + debug("%d: %s: %s", state->control.othermonitor_write_fd, "transient write error", + strerror(errno)); + } else if (nwritten < 0) { + debug("%d: %s: %s", state->control.othermonitor_write_fd, "write error - closing", + strerror(errno)); + (void) close(state->control.othermonitor_write_fd); + state->control.othermonitor_write_fd = -1; + } else if (nwritten != (ssize_t) (sizeof(state->transfer.transferred))) { + debug("%d: %d: %s", state->control.othermonitor_write_fd, (int) nwritten, + "incorrect byte count written - closing"); + (void) close(state->control.othermonitor_write_fd); + state->control.othermonitor_write_fd = -1; + } else { + written_yet = true; + } + + } + } +} + + /* * Pipe data from a list of files to standard output, giving information * about the transfer on standard error according to the given options. @@ -180,7 +295,7 @@ int pv_main_loop(pvstate_t state) long double target; bool eof_in, eof_out, final_update; struct timespec start_time, next_update, next_ratecheck, cur_time; - struct timespec next_remotecheck; + struct timespec next_remotecheck, next_monitor_exchange; int input_fd, output_fd; unsigned int file_idx; bool output_is_pipe; @@ -213,7 +328,7 @@ int pv_main_loop(pvstate_t state) /*@-type@ */ if ((sb.st_mode & S_IFMT) == S_IFIFO) { output_is_pipe = true; - debug("%s", "output is a pipe"); + debug("%s (fd %d)", "output is a pipe", output_fd); } /*@+type@ *//* splint says st_mode is __mode_t, not mode_t */ } else { @@ -250,10 +365,12 @@ int pv_main_loop(pvstate_t state) memset(&next_ratecheck, 0, sizeof(next_ratecheck)); memset(&next_remotecheck, 0, sizeof(next_remotecheck)); + memset(&next_monitor_exchange, 0, sizeof(next_monitor_exchange)); memset(&next_update, 0, sizeof(next_update)); pv_elapsedtime_copy(&next_ratecheck, &cur_time); pv_elapsedtime_copy(&next_remotecheck, &cur_time); + pv_elapsedtime_copy(&next_monitor_exchange, &cur_time); pv_elapsedtime_copy(&next_update, &cur_time); if ((state->control.delay_start > 0) && (state->control.delay_start > state->control.interval)) { @@ -338,6 +455,16 @@ int pv_main_loop(pvstate_t state) pv_elapsedtime_add_nsec(&next_remotecheck, REMOTE_INTERVAL); } + /* + * Exchange messages with the other side of the monitor + * every short while, in monitor mode. + */ + if ((state->control.othermonitor_pid > 0) + && (pv_elapsedtime_compare(&cur_time, &next_monitor_exchange) > 0)) { + pv__monitor_exchange(state); + pv_elapsedtime_add_nsec(&next_monitor_exchange, MONITOR_EXCHANGE_INTERVAL); + } + if (1 == state->flags.trigger_exit) break; diff --git a/src/pv/remote.c b/src/pv/remote.c index aeeec91..4867a9f 100644 --- a/src/pv/remote.c +++ b/src/pv/remote.c @@ -247,8 +247,8 @@ int pv_remote_set(pvstate_t state, pid_t remote) * If a message was received, update the current process's options with the * ones in the message. * - * Note that this relies on pv_state_set_format() causing the output format - * to be reparsed. + * Note that this relies on pv_state_set_format_options() causing the output + * format to be reparsed. */ static bool pv__rxsignal_usr2(pvstate_t state) { @@ -256,6 +256,7 @@ static bool pv__rxsignal_usr2(pvstate_t state) char control_filename[4096]; /* flawfinder: ignore */ FILE *control_fptr; struct remote_msg msgbuf; + pvformatoptions_s format_options; /* flawfinder rationale: as above. */ @@ -306,11 +307,19 @@ static bool pv__rxsignal_usr2(pvstate_t state) msgbuf.format[sizeof(msgbuf.format) - 1] = '\0'; msgbuf.extra_display[sizeof(msgbuf.extra_display) - 1] = '\0'; - pv_state_set_format(state, msgbuf.progress, msgbuf.timer, - msgbuf.eta, msgbuf.fineta, msgbuf.rate, - msgbuf.average_rate, - msgbuf.bytes, msgbuf.bufpercent, - msgbuf.lastwritten, '\0' == msgbuf.name[0] ? NULL : msgbuf.name); + pv_state_name_set(state, '\0' == msgbuf.name[0] ? NULL : msgbuf.name); + + format_options.progress = msgbuf.progress; + format_options.timer = msgbuf.timer; + format_options.eta = msgbuf.eta; + format_options.fineta = msgbuf.fineta; + format_options.rate = msgbuf.rate; + format_options.average_rate = msgbuf.average_rate; + format_options.bytes = msgbuf.bytes; + format_options.bufpercent = msgbuf.bufpercent; + format_options.lastwritten = msgbuf.lastwritten; + + pv_state_set_format_options(state, format_options); if (msgbuf.rate_limit > 0) pv_state_rate_limit_set(state, msgbuf.rate_limit); @@ -475,8 +484,8 @@ static bool pv__rxsignal_usr1(pvstate_t state, pid_t match_sender) * send our transfer state to the sending process, depending on the content * of the message. * - * NB --remote relies on pv_state_set_format() causing the output format to - * be reparsed. + * NB --remote relies on pv_state_set_format_options() causing the output + * format to be reparsed. * * Returns true if a --remote message was received, false otherwise. */ diff --git a/src/pv/state.c b/src/pv/state.c index 2345576..be226e7 100644 --- a/src/pv/state.c +++ b/src/pv/state.c @@ -191,6 +191,9 @@ pvstate_t pv_state_alloc(void) state->watchfd.count = 0; state->control.output_fd = -1; + state->control.othermonitor_pid = 0; + state->control.othermonitor_read_fd = -1; + state->control.othermonitor_write_fd = -1; #ifdef HAVE_IPC state->cursor.shmid = -1; state->cursor.pvcount = 1; @@ -408,10 +411,13 @@ void pv_state_free(pvstate_t state) /* - * Set the formatting string, given a set of old-style formatting options. + * Set the format options and use them to build a default formatting string. + * The default string is used if no format string is explicitly set. + * + * Call this *after* setting a name, so it can determine whether there + * should be a name in the default format. */ -void pv_state_set_format(pvstate_t state, bool progress, bool timer, bool eta, bool fineta, bool rate, bool average_rate, bool bytes, bool bufpercent, size_t lastwritten, /*@null@ */ - const char *name) +void pv_state_set_format_options(pvstate_t state, pvformatoptions_s format) { #define PV_ADDFORMAT(x,y) if (x) { \ if (state->control.default_format[0] != '\0') \ @@ -419,15 +425,15 @@ void pv_state_set_format(pvstate_t state, bool progress, bool timer, bool eta, b (void) pv_strlcat(state->control.default_format, y, sizeof(state->control.default_format)); \ } - state->control.format_option.progress = progress; - state->control.format_option.timer = timer; - state->control.format_option.eta = eta; - state->control.format_option.fineta = fineta; - state->control.format_option.rate = rate; - state->control.format_option.average_rate = average_rate; - state->control.format_option.bytes = bytes; - state->control.format_option.bufpercent = bufpercent; - state->control.format_option.lastwritten = lastwritten; + state->control.format_option.progress = format.progress; + state->control.format_option.timer = format.timer; + state->control.format_option.eta = format.eta; + state->control.format_option.fineta = format.fineta; + state->control.format_option.rate = format.rate; + state->control.format_option.average_rate = format.average_rate; + state->control.format_option.bytes = format.bytes; + state->control.format_option.bufpercent = format.bufpercent; + state->control.format_option.lastwritten = format.lastwritten; state->control.default_format[0] = '\0'; @@ -438,21 +444,21 @@ void pv_state_set_format(pvstate_t state, bool progress, bool timer, bool eta, b * Add the format strings for the enabled options in a * standard order. */ - PV_ADDFORMAT(name, "%N"); - PV_ADDFORMAT(bytes, "%b"); - PV_ADDFORMAT(bufpercent, "%T"); - PV_ADDFORMAT(timer, "%t"); - PV_ADDFORMAT(rate, "%r"); - PV_ADDFORMAT(average_rate, "%a"); - PV_ADDFORMAT(progress, "%p"); - PV_ADDFORMAT(eta, "%e"); - PV_ADDFORMAT(fineta, "%I"); + PV_ADDFORMAT(NULL != state->control.name, "%N"); + PV_ADDFORMAT(format.bytes, "%b"); + PV_ADDFORMAT(format.bufpercent, "%T"); + PV_ADDFORMAT(format.timer, "%t"); + PV_ADDFORMAT(format.rate, "%r"); + PV_ADDFORMAT(format.average_rate, "%a"); + PV_ADDFORMAT(format.progress, "%p"); + PV_ADDFORMAT(format.eta, "%e"); + PV_ADDFORMAT(format.fineta, "%I"); - if (lastwritten > 0) { + if (format.lastwritten > 0) { char buf[16]; /* flawfinder: ignore */ memset(buf, 0, sizeof(buf)); - (void) pv_snprintf(buf, sizeof(buf), "%%%uA", (unsigned int) lastwritten); - PV_ADDFORMAT(lastwritten > 0, buf); + (void) pv_snprintf(buf, sizeof(buf), "%%%uA", (unsigned int) format.lastwritten); + PV_ADDFORMAT(format.lastwritten > 0, buf); /* * flawfinder rationale: large enough for string, * zeroed before use, only written to by @@ -463,30 +469,31 @@ void pv_state_set_format(pvstate_t state, bool progress, bool timer, bool eta, b } else { /* Numeric mode has different behaviour. */ - PV_ADDFORMAT(timer, "%t"); - PV_ADDFORMAT(bytes, "%b"); - PV_ADDFORMAT(rate, "%r"); - PV_ADDFORMAT(!(bytes || rate), "%{progress-amount-only}"); + PV_ADDFORMAT(format.timer, "%t"); + PV_ADDFORMAT(format.bytes, "%b"); + PV_ADDFORMAT(format.rate, "%r"); + PV_ADDFORMAT(!(format.bytes || format.rate), "%{progress-amount-only}"); } debug("%s: [%s]", "default format set", state->control.default_format); - /* Free any previously set name. */ - if (NULL != state->control.name) { - free(state->control.name); - state->control.name = NULL; - } - - /* Set a new name if one was given. */ - if (NULL != name) - state->control.name = pv_strdup(name); - /* Tell pv_format() that the format has changed. */ state->flags.reparse_display = 1; } +/* + * Append the given string to the default format, and trigger a format + * reparse. + */ +void pv_state_append_to_default_format(pvstate_t state, /*@null@ */ const char *val) +{ + PV_ADDFORMAT(NULL != val, val); + state->flags.reparse_display = 1; +} + + void pv_state_force_set(pvstate_t state, bool val) { state->control.force = val; @@ -769,6 +776,25 @@ void pv_state_set_terminal_supports_utf8(pvstate_t state, bool val) state->status.terminal_supports_utf8 = val; } +void pv_state_othermonitor_set(pvstate_t state, pid_t pid, int read_fd, int write_fd) +{ + state->control.othermonitor_pid = pid; + state->control.othermonitor_read_fd = read_fd; + state->control.othermonitor_write_fd = write_fd; +} + +/* If the format string is set to an empty string, stop all display output. */ +void pv_state_cancel_output_if_empty_format_string(pvstate_t state) +{ + if (NULL == state->control.format_string) + return; + if ('\0' != state->control.format_string[0]) + return; + debug("%s", "empty format string - setting no_display and turning off cursor positioning"); + state->control.no_display = true; + state->control.cursor = false; +} + /* * Set the array of input files. */