/* * 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 #include #include #include #include #include #include #include #include #ifdef HAVE_LANGINFO_H #include #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 PV_ERROREXIT_REMOTE_OR_PID; } 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; } /* * Store-and-forward mode: run the main loop once with the output forced to * the store-and-forward file (creating and removing 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, pvformatoptions_s format_options) { 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; /* Set the displayed name to "(input)" and trigger a format reparse. */ /*@-mustfreefresh@ */ 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. */ 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)); /* 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. */ 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); /* 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, "othermonitor_pid" is the PID of the monitor on * the other side, "othermonitor_read_fd" is for reading transfer progress * from the other monitor, and "othermonitor_write_fd" is for writing * transfer progress 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, off_t size, pvformatoptions_s format_options) { 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; } /* Close command_fd now it's been duplicated to the appropriate fd. */ if (close(command_fd) < 0) { fprintf(stderr, "%s: %s\n", program_name, strerror(errno)); } /* Copy details of the other monitor into the main state. */ pv_state_othermonitor_set(state, othermonitor_pid, othermonitor_read_fd, othermonitor_write_fd); /* Set the input files list to just "-". */ /*@-observertrans@ */ dummy_argv[0] = "-"; /*@+observertrans@ */ pv_state_inputfiles(state, 1, dummy_argv); /* Calculate the size for this side. */ if (0 == size) { size = pv_calc_total_size(state); debug("%s: %llu", "no size given - calculated", size); } /* If the size is unknown, we cannot have an ETA. */ if (size < 1) { format_options.eta = false; format_options.fineta = false; pv_state_set_format_options(state, format_options); debug("%s", "size unknown - ETA disabled"); } /* Copy the calculated size into the main state. */ pv_state_size_set(state, size); /* Add ratio to the default format on the out side of a "both". */ if ((othermonitor_pid > 0) && (PV_SIDE_OUT == side)) { pv_state_append_to_default_format(state, "%{ratio}"); } /* Prevent any progress output if the format was explicitly made empty. */ pv_state_cancel_output_if_empty_format_string(state); /* Run the main transfer loop. */ 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]); 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], opts->size, format_options); /* 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); /* Now monitor the "in" side, as the "out" monitor was spawned above. */ /*@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], opts->size, format_options); 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], opts->size, format_options); 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. */ 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; pvformatoptions_s format_options; #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 the 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 into the main state before * checking the total size. If pv_calc_total_size() finds that the * output is a block device, it will use the size of the block * device, and also set the stop-at-size flag. So * pv_state_stop_at_size_set() must be called before * pv_calc_total_size(), otherwise it would undo that 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"); } } /* Initialise the signal handling. */ pv_sig_init(state); /* * Get the total size, in query mode. * * Since pv_remote_transferstate_fetch() uses signals, 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); 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); /* Run the appropriate main loop. */ switch (opts->action) { case PV_ACTION_NOTHING: 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. */ 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. */ 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, format_options); 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; }