diff --git a/docs/ACKNOWLEDGEMENTS.md b/docs/ACKNOWLEDGEMENTS.md index 8115560..a0596ab 100644 --- a/docs/ACKNOWLEDGEMENTS.md +++ b/docs/ACKNOWLEDGEMENTS.md @@ -91,5 +91,6 @@ is acknowledged and greatly appreciated: * [gustav-b](https://codeberg.org/gustav-b) - suggested percentage formatting correction ([#80](https://codeberg.org/a-j-wood/pv/issues/80)) * [Thomas Bertels](https://codeberg.org/tbertels) - updated French translations ([#83](https://codeberg.org/a-j-wood/pv/pulls/83)) * [kevinruddy](https://codeberg.org/kevinruddy) - added decimal units option ([#85](https://codeberg.org/a-j-wood/pv/pulls/85)) + * [xmort](https://codeberg.org/xmort) - added "`--output`" option ([#90](https://codeberg.org/a-j-wood/pv/pulls/90)) --- diff --git a/docs/NEWS.md b/docs/NEWS.md index 1cad057..1bbf80b 100644 --- a/docs/NEWS.md +++ b/docs/NEWS.md @@ -1,3 +1,7 @@ +### NOT YET RELEASED + + * feature: new "`--output`" option to write to a file instead of standard output (pull request [#90](https://codeberg.org/a-j-wood/pv/pulls/90)) supplied by [xmort](https://codeberg.org/xmort) + ### 1.8.9 - 21 April 2024 * feature: new "`--si`" option to display and interpret size suffixes in multiples of 1000 rather than 1024 (pull request [#85](https://codeberg.org/a-j-wood/pv/pulls/85)) supplied by [kevinruddy](https://codeberg.org/kevinruddy) diff --git a/docs/pv.1 b/docs/pv.1 index 7b9f431..dd46415 100644 --- a/docs/pv.1 +++ b/docs/pv.1 @@ -347,7 +347,8 @@ invocations in a single, long, pipeline. .BI \-o\ FILE \fR,\ \fB\-\-output\ FILE Write data to .I FILE -rather than stdout. +rather than standard output. If the file already exists, it will be +overwritten from the start, but not truncated. .TP .BI \-L\ RATE \fR,\ \fB\-\-rate-limit\ RATE Limit the transfer to a maximum of diff --git a/docs/pv.1.md b/docs/pv.1.md index 7ac4446..3721016 100644 --- a/docs/pv.1.md +++ b/docs/pv.1.md @@ -257,6 +257,12 @@ that are explicitly switched on will be shown. # DATA TRANSFER MODIFIERS +**-o FILE, \--output FILE** + +: Write data to *FILE* rather than standard output. If the file + already exists, it will be overwritten from the start, but not + truncated. + **-L RATE, \--rate-limit RATE** : Limit the transfer to a maximum of *RATE* bytes per second. A suffix diff --git a/src/main/main.c b/src/main/main.c index 4411270..d4ae744 100644 --- a/src/main/main.c +++ b/src/main/main.c @@ -12,7 +12,7 @@ #include "pv.h" /* We do not set this because it breaks "dd" - see below. */ -/* #undef MAKE_STDOUT_NONBLOCKING */ +/* #undef MAKE_OUTPUT_NONBLOCKING */ #include #include @@ -306,23 +306,32 @@ int main(int argc, char **argv) if (NULL == opts->output || 0 == strcmp(opts->output, "-")) { pv_state_output_set(state, STDOUT_FILENO, "(stdout)"); } else { - int fd = open(opts->output, O_WRONLY | O_CREAT); + int fd = open(opts->output, O_WRONLY | O_CREAT, 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 (fd < 0) { fprintf(stderr, "%s: %s: %s\n", opts->program_name, opts->output, strerror(errno)); - return 1; + pv_state_free(state); + opts_free(opts); + return 2; } pv_state_output_set(state, fd, opts->output); } -#ifdef MAKE_STDOUT_NONBLOCKING +#ifdef MAKE_OUTPUT_NONBLOCKING /* - * Try and make standard output use non-blocking I/O. + * Try and make the output use non-blocking I/O. * * Note that this can cause problems with (broken) applications - * such as dd. + * such as dd when used in a pipeline. */ - fcntl(STDOUT_FILENO, F_SETFL, O_NONBLOCK | fcntl(STDOUT_FILENO, F_GETFL)); -#endif /* MAKE_STDOUT_NONBLOCKING */ + fcntl(state->control.output_fd, F_SETFL, O_NONBLOCK | fcntl(state->control.output_fd, F_GETFL)); +#endif /* MAKE_OUTPUT_NONBLOCKING */ /* Initialise the signal handling. */ pv_sig_init(state); diff --git a/src/pv/file.c b/src/pv/file.c index 3720c45..fbb83ae 100644 --- a/src/pv/file.c +++ b/src/pv/file.c @@ -144,7 +144,8 @@ static off_t pv_calc_total_bytes(pvstate_t state) total = end_position; } if (lseek(state->control.output_fd, 0, SEEK_SET) != 0) { - pv_error(state, "%s: %s: %s", state->control.output_name, + pv_error(state, "%s: %s: %s", + NULL == state->control.output_name ? "(null)" : state->control.output_name, _("failed to seek to start of output"), strerror(errno)); state->status.exit_status |= 2; } diff --git a/src/pv/state.c b/src/pv/state.c index 95e1c86..079ea99 100644 --- a/src/pv/state.c +++ b/src/pv/state.c @@ -107,6 +107,26 @@ void pv_state_free(pvstate_t state) if (0 == state) return; + /* + * Close the output file first, so we can report any errors while we + * still know the program name and output filename. + */ + if (state->control.output_fd >= 0) { + if (STDOUT_FILENO != state->control.output_fd) { + if (close(state->control.output_fd) < 0) { + fprintf(stderr, "%s: %s: %s\n", state->status.program_name, + NULL == state->control.output_name ? "(null)" : state->control.output_name, + strerror(errno)); + } + } + state->control.output_fd = -1; + } + + if (NULL != state->control.output_name) { + free(state->control.output_name); + state->control.output_name = NULL; + } + if (NULL != state->status.program_name) free(state->status.program_name); state->status.program_name = NULL; @@ -125,23 +145,6 @@ void pv_state_free(pvstate_t state) state->control.format_string = NULL; } - if (NULL != state->control.output_name) { - free(state->control.output_name); - state->control.output_name = NULL; - } - - if (NULL != state->control.output_name) { - free(state->control.output_name); - state->control.output_name = NULL; - } - - if (state->control.output_fd >= 0) { - if (STDOUT_FILENO != state->control.output_fd) { - close(state->control.output_fd); - } - state->control.output_fd = -1; - } - /*@-keeptrans@ */ if (NULL != state->transfer.transfer_buffer) free(state->transfer.transfer_buffer); @@ -367,12 +370,21 @@ void pv_state_watch_fd_set(pvstate_t state, int val) void pv_state_output_set(pvstate_t state, int fd, const char *name) { + /* + * Close any previous output file first, so we can report any errors + * before we store the new output filename. + */ + if (state->control.output_fd >= 0 && state->control.output_fd != STDOUT_FILENO) { + if (close(state->control.output_fd) < 0) { + fprintf(stderr, "%s: %s: %s\n", state->status.program_name, + NULL == state->control.output_name ? "(null)" : state->control.output_name, + strerror(errno)); + } + } if (NULL != state->control.output_name) free(state->control.output_name); - if (state->control.output_fd >= 0 && state->control.output_fd != STDOUT_FILENO) - close(state->control.output_fd); state->control.output_fd = fd; - state->control.output_name = strdup(name); + state->control.output_name = pv_strdup(name); } void pv_state_average_rate_window_set(pvstate_t state, unsigned int val) diff --git a/src/pv/transfer.c b/src/pv/transfer.c index 0d5419f..dbd91e8 100644 --- a/src/pv/transfer.c +++ b/src/pv/transfer.c @@ -931,7 +931,9 @@ ssize_t pv_transfer(pvstate_t state, int fd, bool *eof_in, bool *eof_out, off_t if (0 != fcntl(state->control.output_fd, F_SETFL, (state->control.direct_io ? O_DIRECT : 0) | fcntl(state->control.output_fd, F_GETFL))) { - debug("%s: %s: %s", state->control.output_name, "fcntl", strerror(errno)); + debug("%s: %s: %s", + NULL == state->control.output_name ? "(null)" : state->control.output_name, + "fcntl", strerror(errno)); } } state->control.direct_io_changed = false;