Files
pv/src/main/main.c
T

834 lines
25 KiB
C

/*
* Main program entry point - read the command line options, then perform
* the appropriate actions.
*
* Copyright 2002-2008, 2010, 2012-2015, 2017, 2021, 2023-2026 Andrew Wood
*
* License GPLv3+: GNU GPL version 3 or later; see `docs/COPYING'.
*/
#include "config.h"
#include "options.h"
#include "pv.h"
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#ifdef HAVE_LANGINFO_H
#include <langinfo.h>
#endif
/*
* Write a PID file, returning nonzero on error. Write it atomically, such
* that the file either exists and contains the PID, or is not updated at
* all. This is done by writing to a temporary file in the same directory
* first, and then renaming the temporary file to the target name.
*/
static int pv__write_pidfile(opts_t opts)
{
char *pidfile_tmp_name;
size_t pidfile_tmp_bufsize;
int pidfile_tmp_fd;
FILE *pidfile_tmp_fptr;
mode_t prev_umask;
const char *pidfile_template = "%s.XXXXXX";
if (NULL == opts->pidfile)
return 0;
/*
* The buffer needs to be long enough to hold the pidfile with the
* mkstemp template ".XXXXXX" after it. The "%s" of our
* pidfile_template adds 2 extra bytes to the length, of which we
* need 1 byte for the terminating \0, so we subtract 1 byte more to
* get the exact amount of space we need.
*/
pidfile_tmp_bufsize = strlen(pidfile_template) + strlen(opts->pidfile) - 1; /* flawfinder: ignore */
/*
* flawfinder rationale: flawfinder never likes strlen() in case
* it's called on a string that isn't \0 terminated. We have to use
* strlen() to find the length of opts->pidfile, so have to trust
* that the arguments in argv[] were \0 terminated. We can be sure
* that pidfile_template is \0 terminated because we've set it to a
* constant value. So we tell flawfinder to skip this check here.
*/
pidfile_tmp_name = malloc(pidfile_tmp_bufsize);
if (NULL == pidfile_tmp_name) {
fprintf(stderr, "%s: %s\n", opts->program_name, strerror(errno));
return PV_ERROREXIT_REMOTE_OR_PID;
}
memset(pidfile_tmp_name, 0, pidfile_tmp_bufsize);
(void) pv_snprintf(pidfile_tmp_name, pidfile_tmp_bufsize, pidfile_template, opts->pidfile);
/*@-type@ *//* splint doesn't like mode_t */
prev_umask = umask(0000); /* flawfinder: ignore */
(void) umask(prev_umask | 0133); /* flawfinder: ignore */
/*@-unrecog@ *//* splint doesn't know mkstemp() */
pidfile_tmp_fd = mkstemp(pidfile_tmp_name); /* flawfinder: ignore */
/*@+unrecog@ */
if (pidfile_tmp_fd < 0) {
fprintf(stderr, "%s: %s: %s\n", opts->program_name, pidfile_tmp_name, strerror(errno));
(void) umask(prev_umask); /* flawfinder: ignore */
free(pidfile_tmp_name);
return PV_ERROREXIT_REMOTE_OR_PID;
}
(void) umask(prev_umask); /* flawfinder: ignore */
/*
* flawfinder rationale (umask, mkstemp) - flawfinder
* recommends setting the most restrictive umask possible
* when calling mkstemp(), so this is what we have done.
*
* We get the original umask and OR it with 0133 to make
* sure new files will be at least chmod 644. Then we put
* the umask back to what it was, after creating the
* temporary file.
*/
/*@+type@ */
pidfile_tmp_fptr = fdopen(pidfile_tmp_fd, "w");
if (NULL == pidfile_tmp_fptr) {
fprintf(stderr, "%s: %s: %s\n", opts->program_name, pidfile_tmp_name, strerror(errno));
(void) close(pidfile_tmp_fd);
(void) remove(pidfile_tmp_name);
free(pidfile_tmp_name);
return PV_ERROREXIT_REMOTE_OR_PID;
}
fprintf(pidfile_tmp_fptr, "%d\n", getpid());
if (0 != fclose(pidfile_tmp_fptr)) {
fprintf(stderr, "%s: %s: %s\n", opts->program_name, opts->pidfile, strerror(errno));
}
if (rename(pidfile_tmp_name, opts->pidfile) < 0) {
fprintf(stderr, "%s: %s: %s\n", opts->program_name, opts->pidfile, strerror(errno));
(void) remove(pidfile_tmp_name);
}
free(pidfile_tmp_name);
return 0;
}
/*
* Set the output file, if applicable. Returns nonzero on error.
*/
static int pv__set_output(pvstate_t state, opts_t opts, /*@null@ */ const char *output_file)
{
int output_fd;
if ((NULL == state) || (NULL == opts))
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
* explicitly provided, and in many cases the operator will
* want to write to device files and other special
* destinations, so there is no sense-checking we can do to
* make this safer.
*/
if (output_fd < 0) {
fprintf(stderr, "%s: %s: %s\n", opts->program_name, output_file, strerror(errno));
return PV_ERROREXIT_ACCESS;
}
pv_state_output_set(state, output_fd, output_file);
return 0;
}
/*
* Run in store-and-forward mode: run the main loop once with the output
* forced to the store-and-forward file (taking care of creation and removal
* of a temporary file if "-" was specified); then run the main loop again
* 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)
{
char tmp_filename[4096]; /* flawfinder: ignore */
bool use_temporary_file;
char *real_store_and_forward_file;
int retcode;
/* flawfinder: zeroed with memset and bounded by pv_snprintf. */
if ((NULL == state) || (NULL == opts) || (NULL == opts->store_and_forward_file))
return 0;
memset(tmp_filename, 0, sizeof(tmp_filename));
use_temporary_file = false;
if (0 == strcmp(opts->store_and_forward_file, "-"))
use_temporary_file = true;
/*
* Create a temporary file if the specified file was "-".
*/
if (use_temporary_file) {
char *tmpdir;
int tmp_fd;
tmpdir = (char *) getenv("TMPDIR"); /* flawfinder: ignore */
if ((NULL == tmpdir) || ('\0' == tmpdir[0]))
tmpdir = (char *) getenv("TMP"); /* flawfinder: ignore */
if ((NULL == tmpdir) || ('\0' == tmpdir[0]))
tmpdir = "/tmp";
/*
* flawfinder rationale: null and zero-size values of $TMPDIR and
* $TMP are rejected, and the destination buffer is bounded.
*/
(void) pv_snprintf(tmp_filename, sizeof(tmp_filename), "%s/pv.XXXXXX", tmpdir);
/*@-unrecog@ *//* splint doesn't know mkstemp() */
tmp_fd = mkstemp(tmp_filename); /* flawfinder: ignore */
/*@+unrecog@ */
if (tmp_fd < 0) {
fprintf(stderr, "%s: %s: %s\n", opts->program_name, tmp_filename, strerror(errno));
return PV_ERROREXIT_SAF;
}
(void) close(tmp_fd);
}
/*
* Real store-and-forward file: either the one we were given, or the
* temporary file we created if we were given "-".
*/
real_store_and_forward_file = use_temporary_file ? tmp_filename : opts->store_and_forward_file;
/*
* First, set the output file to the store-and-forward file.
*/
debug("%s: %s", "setting output to store-and-forward file", real_store_and_forward_file);
retcode = pv__set_output(state, opts, real_store_and_forward_file);
if (0 != retcode)
goto end_store_and_forward;
/* Reset the formatting to set the displayed name to "(input)". */
/*@-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)"));
/*@+mustfreefresh@ *//* see below about gettext _() calls. */
/* Run the main loop as normal. */
debug("%s", "running store-and-forward receiver");
retcode = pv_main_loop(state);
if (0 != retcode)
goto end_store_and_forward;
/* Set the output file back to what it originally was. */
debug("%s: %s", "setting output to original value", NULL == opts->output ? "(null)" : opts->output);
retcode = pv__set_output(state, opts, opts->output);
if (0 != retcode)
goto end_store_and_forward;
/* Replace the list of input files with the store-and-forward file. */
debug("%s", "resetting input file list");
pv_state_inputfiles(state, 1, (const char **) &real_store_and_forward_file);
/* Recalculate the input size. */
pv_state_size_set(state, pv_calc_total_size(state));
/* 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);
/* Reset calculated values in the state. */
pv_state_reset(state);
/* Run the main loop again. */
debug("%s", "running store-and-forward transmitter");
retcode = pv_main_loop(state);
end_store_and_forward:
if (use_temporary_file)
(void) remove(tmp_filename);
return retcode;
}
/*
* 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,
* "other_side_pid" is the PID of the other side.
*
* Returns the appropriate exit status.
*
* As side effect, "command_fd" is closed.
*/
static int pv__run_monitor(const char *program_name, pvstate_t state, pvside_t side, int command_fd, /*@unused@ */
__attribute__((unused)) pid_t other_side_pid)
{
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. */
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. */
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));
}
/* TODO: use other_side_pid, for ratio display. */
return pv_main_loop(state);
}
/*
* Run in monitor mode: run a process and run the main transfer loop on its
* input, output, or both. Returns the appropriate exit status.
*/
static int pv__monitor(pvstate_t state, opts_t opts)
{
int pipefd_in[2];
int pipefd_out[2];
int retcode;
pid_t command_pid, in_monitor_pid, out_monitor_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_in[0] = -1;
pipefd_in[1] = -1;
pipefd_out[0] = -1;
pipefd_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_in)) {
fprintf(stderr, "%s: %s\n", opts->program_name, strerror(errno));
return PV_ERROREXIT_MONITOR;
}
debug("pipefd_in[]=(%d,%d)", pipefd_in[0], pipefd_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_out)) {
fprintf(stderr, "%s: %s\n", opts->program_name, strerror(errno));
if (-1 != pipefd_in[0])
(void) close(pipefd_in[0]);
if (-1 != pipefd_in[1])
(void) close(pipefd_in[1]);
return PV_ERROREXIT_MONITOR;
}
debug("pipefd_out[]=(%d,%d)", pipefd_out[0], pipefd_out[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));
if (-1 != pipefd_in[0])
(void) close(pipefd_in[0]);
if (-1 != pipefd_in[1])
(void) close(pipefd_in[1]);
if (-1 != pipefd_out[0])
(void) close(pipefd_out[0]);
if (-1 != pipefd_out[1])
(void) close(pipefd_out[1]);
return PV_ERROREXIT_MONITOR;
} else if (0 == command_pid) {
/* Command process. */
/* Close the write end of the "in" pipe. */
if (-1 != pipefd_in[1]) {
(void) close(pipefd_in[1]);
pipefd_in[1] = -1;
}
/* Close the read end of the "out" pipe. */
if (-1 != pipefd_out[0]) {
(void) close(pipefd_out[0]);
pipefd_out[0] = -1;
}
/* Put the read end of the "in" pipe on stdin. */
if (-1 != pipefd_in[0]) {
debug("replacing command stdin with fd %d", pipefd_in[0]);
if (dup2(pipefd_in[0], STDIN_FILENO) < 0) {
fprintf(stderr, "%s: %s\n", opts->program_name, strerror(errno));
exit(EXIT_FAILURE);
}
(void) close(pipefd_in[0]);
debug("replaced command stdin with fd %d", pipefd_in[0]);
pipefd_in[0] = -1;
}
/* Put the write end of the "out" pipe on stdout. */
if (-1 != pipefd_out[1]) {
debug("replacing command stdout with fd %d", pipefd_out[1]);
if (dup2(pipefd_out[1], STDOUT_FILENO) < 0) {
fprintf(stderr, "%s: %s\n", opts->program_name, strerror(errno));
exit(EXIT_FAILURE);
}
(void) close(pipefd_out[1]);
debug("replaced command stdout with fd %d", pipefd_out[1]);
pipefd_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. */
if (-1 != pipefd_in[0]) {
(void) close(pipefd_in[0]);
pipefd_in[0] = -1;
}
/* Close the write end of the "out" pipe. */
if (-1 != pipefd_out[1]) {
(void) close(pipefd_out[1]);
pipefd_out[1] = -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));
if (-1 != pipefd_in[1])
(void) close(pipefd_in[1]);
if (-1 != pipefd_out[0])
(void) close(pipefd_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 "in" pipe. */
if (-1 != pipefd_in[1])
(void) close(pipefd_in[1]);
return pv__run_monitor(opts->program_name, state, PV_SIDE_OUT, pipefd_out[0], in_monitor_pid);
}
}
/* 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);
}
/* TODO: call pv_state_set_format(). */
/*@fallthrough@ */
/* falling through as "out" is in another process (above). */
__attribute__((fallthrough));
case PV_SIDE_IN:
/* Close the read end of the "out" pipe. */
if (-1 != pipefd_out[0])
(void) close(pipefd_out[0]);
pipefd_out[0] = -1;
retcode = pv__run_monitor(opts->program_name, state, PV_SIDE_IN, pipefd_in[1], out_monitor_pid);
break;
case PV_SIDE_OUT:
/* Close the write end of the "in" pipe. */
if (-1 != pipefd_in[1])
(void) close(pipefd_in[1]);
pipefd_in[1] = -1;
retcode = pv__run_monitor(opts->program_name, state, PV_SIDE_OUT, pipefd_out[0], in_monitor_pid);
break;
}
return retcode;
}
/*
* Process command-line arguments and set option flags, then call functions
* to initialise, and finally enter the main loop.
*/
int main(int argc, char **argv)
{
/*@only@ */ opts_t opts = NULL;
/*@only@ */ pvstate_t state = NULL;
int retcode = 0;
bool can_have_eta = true;
bool terminal_supports_utf8 = false;
#if ! HAVE_SETPROCTITLE
initproctitle(argc, argv);
#endif
#ifdef ENABLE_NLS
/* Initialise language translation. */
(void) setlocale(LC_ALL, "");
(void) bindtextdomain(PACKAGE, LOCALEDIR);
(void) textdomain(PACKAGE);
#ifdef HAVE_LANGINFO_H
/*@-mustfreefresh@ *//* splint thinks nl_langinfo() leaks memory */
if (0 == strcmp(nl_langinfo(CODESET), "UTF-8"))
terminal_supports_utf8 = true;
/*@+mustfreefresh@ */
#endif
#endif
/* Parse the command line arguments. */
opts = opts_parse(argc >= 0 ? (unsigned int) argc : 0, argv);
if (NULL == opts) {
debug("%s: %d", "exiting with status", PV_ERROREXIT_MEMORY);
return PV_ERROREXIT_MEMORY;
}
/* Early exit if necessary, such as with "-h". */
if (PV_ACTION_NOTHING == opts->action) {
debug("%s", "nothing to do - exiting with status 0");
opts_free(opts);
return 0;
}
/* Set the error message prefix. */
/*@-keeptrans@ */
pv_set_error_prefix(opts->program_name);
/* splint - this function doesn't add an alias or release it. */
/*@+keeptrans@ */
/*
* Allocate our internal state buffer.
*/
state = pv_state_alloc();
if (NULL == state) {
/*@-mustfreefresh@ */
/*
* splint note: the gettext calls made by _() cause memory
* leak warnings, but in this case it's unavoidable, and
* mitigated by the fact we only translate each string once.
*/
fprintf(stderr, "%s: %s: %s\n", opts->program_name, _("state allocation failed"), strerror(errno));
opts_free(opts);
debug("%s: %d", "exiting with status", PV_ERROREXIT_MEMORY);
return PV_ERROREXIT_MEMORY;
/*@+mustfreefresh@ */
}
/*
* Write a PID file if -P was specified.
*/
if (opts->pidfile != NULL) {
int pidfile_rc;
pidfile_rc = pv__write_pidfile(opts);
if (0 != pidfile_rc) {
pv_state_free(state);
opts_free(opts);
return pidfile_rc;
}
}
/*
* If no files were given, pretend "-" was given (stdin).
*/
if (0 == opts->argc) {
debug("%s", "no files given - adding fake argument `-'");
if (!opts_add_file(opts, "-")) {
pv_state_free(state);
opts_free(opts);
return PV_ERROREXIT_MEMORY;
}
}
/*
* 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) && (PV_ACTION_MONITOR != opts->action)) {
pv_state_inputfiles(state, opts->argc, (const char **) (opts->argv));
}
/*
* Put the list of watchfd items into the PV internal state.
*/
if ((opts->watchfd_count > 0) && (NULL != opts->watchfd_pid) && (NULL != opts->watchfd_fd)) {
pv_state_watchfds(state, opts->watchfd_count, opts->watchfd_pid, opts->watchfd_fd);
}
/*
* If stderr is not a terminal and we're neither forcing output nor
* outputting numerically, we will have nothing to display at all.
*/
if ((0 == isatty(STDERR_FILENO))
&& (false == opts->force)
&& (false == opts->numeric)) {
opts->no_display = true;
debug("%s", "nothing to display - setting no_display");
}
/*
* Auto-detect width or height if either are unspecified.
*/
if ((0 == opts->width) || (0 == opts->height)) {
unsigned int width, height;
width = 0;
height = 0;
pv_screensize(&width, &height);
if (0 == opts->width) {
opts->width = width;
debug("%s: %u", "auto-detected terminal width", width);
}
if (0 == opts->height) {
opts->height = height;
debug("%s: %u", "auto-detected terminal height", height);
}
}
/*
* Width and height bounds checking (and defaults).
*/
if (opts->width < 1)
opts->width = 80;
if (opts->height < 1)
opts->height = 25;
if (opts->width > 999999)
opts->width = 999999;
if (opts->height > 999999)
opts->height = 999999;
/*
* Interval must be at least 0.1 second, and at most 10 minutes.
*/
if (opts->interval < 0.1)
opts->interval = 0.1;
if (opts->interval > 600)
opts->interval = 600;
/*
* Set output file, treating no output or "-" as stdout; we have to
* do this before looking at setting the size, as the size
* calculation looks at the output file if the input size can't be
* calculated (issue #91).
*
* We have to set the sparse output flag before doing this, so that
* in sparse mode the lseek() on O_APPEND can be done (issue #45);
* see the comments in pv_state_output_set() in src/pv/state.c.
*/
pv_state_sparse_output_set(state, opts->sparse_output);
retcode = pv__set_output(state, opts, opts->output);
if (0 != retcode) {
pv_state_free(state);
opts_free(opts);
return retcode;
}
/*
* Copy the "stop at size" option before checking the total size,
* since calculating the size from the output block device size
* after this may want to force this setting on, and if we set it
* afterwards, we undo the override.
*/
pv_state_stop_at_size_set(state, opts->stop_at_size);
/* Total size calculation, in normal transfer mode. */
if (PV_ACTION_TRANSFER == opts->action) {
/*
* If no size was given, try to calculate the total size.
*/
if (0 == opts->size) {
pv_state_linemode_set(state, opts->linemode);
pv_state_null_terminated_lines_set(state, opts->null_terminated_lines);
opts->size = pv_calc_total_size(state);
debug("%s: %llu", "no size given - calculated", opts->size);
}
/*
* If the size is unknown, we cannot have an ETA.
*/
if (opts->size < 1) {
can_have_eta = false;
debug("%s", "size unknown - ETA disabled");
}
}
/* TODO: size calculation for monitor mode. */
/* Initialise the signal handling. */
pv_sig_init(state);
/*
* Get the total size, in query mode.
*
* Note that this uses signals, so signal handling has to have been
* set up first.
*/
if (PV_ACTION_QUERY == opts->action) {
opts->size = 0;
retcode = pv_remote_transferstate_fetch(state, opts->query, &(opts->size), false);
if (0 != retcode) {
pv_sig_fini(state);
pv_state_free(state);
opts_free(opts);
return retcode;
}
/* As above - no ETA if the size is unknown. */
if (opts->size < 1) {
can_have_eta = false;
debug("%s", "size unknown - ETA disabled");
}
}
/*
* Copy the remaining parameters from the options into the main
* state.
*/
pv_state_interval_set(state, opts->interval);
pv_state_width_set(state, opts->width, opts->width_set_manually);
pv_state_height_set(state, opts->height, opts->height_set_manually);
pv_state_no_display_set(state, opts->no_display);
pv_state_force_set(state, opts->force);
pv_state_cursor_set(state, opts->cursor);
pv_state_show_stats_set(state, opts->show_stats);
pv_state_numeric_set(state, opts->numeric);
pv_state_wait_set(state, opts->wait);
pv_state_delay_start_set(state, opts->delay_start);
pv_state_rate_gauge_set(state, opts->rate_gauge);
pv_state_linemode_set(state, opts->linemode);
pv_state_bits_set(state, opts->bits);
pv_state_decimal_units_set(state, opts->decimal_units);
pv_state_null_terminated_lines_set(state, opts->null_terminated_lines);
pv_state_skip_errors_set(state, opts->skip_errors);
pv_state_error_skip_block_set(state, opts->error_skip_block);
pv_state_sync_after_write_set(state, opts->sync_after_write);
pv_state_direct_io_set(state, opts->direct_io);
pv_state_discard_input_set(state, opts->discard_input);
pv_state_rate_limit_set(state, opts->rate_limit);
pv_state_target_buffer_size_set(state, opts->buffer_size);
pv_state_no_splice_set(state, opts->no_splice);
pv_state_size_set(state, opts->size);
pv_state_name_set(state, opts->name);
pv_state_default_bar_style_set(state, opts->default_bar_style);
pv_state_format_string_set(state, opts->format);
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);
debug("%s: %s", "terminal_supports_utf8", terminal_supports_utf8 ? "true" : "false");
pv_state_set_terminal_supports_utf8(state, terminal_supports_utf8);
/* Run the appropriate main loop. */
switch (opts->action) {
case PV_ACTION_NOTHING:
break;
case PV_ACTION_TRANSFER:
/* Normal "transfer data" mode. */
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);
break;
case PV_ACTION_WATCHFD:
/* "Watch file descriptor(s) of another process" mode. */
retcode = pv_watchfd_loop(state);
break;
case PV_ACTION_REMOTE_CONTROL:
/* Change the options of another running pv. */
retcode = pv_remote_set(state, opts->remote);
break;
case PV_ACTION_QUERY:
/* 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);
break;
}
/* Clear up the PID file, if one was written. */
if (opts->pidfile != NULL) {
if (0 != remove(opts->pidfile)) {
fprintf(stderr, "%s: %s: %s\n", opts->program_name, opts->pidfile, strerror(errno));
}
}
/* Close down the signal handling. */
pv_sig_fini(state);
/* Free the internal PV state. */
pv_state_free(state);
/* Free the data from parsing the command-line arguments. */
opts_free(opts);
debug("%s: %d", "exiting with status", retcode);
return retcode;
}