From 3c9ab822fed3b594a4075fc72fe4f82a88c7f00d Mon Sep 17 00:00:00 2001 From: Andrew Wood Date: Wed, 15 Oct 2025 23:08:33 +0100 Subject: [PATCH] Call lseek() at the start if output is appending, so that future lseek()s are relative to the right position, fixing the issue with incorrect truncation size (#45). --- src/main/main.c | 6 +++++- src/pv/state.c | 23 ++++++++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/main/main.c b/src/main/main.c index 7da13ca..1345ccd 100644 --- a/src/main/main.c +++ b/src/main/main.c @@ -448,7 +448,12 @@ int main(int argc, char **argv) * 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); @@ -509,7 +514,6 @@ int main(int argc, char **argv) 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_sparse_output_set(state, opts->sparse_output); 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); diff --git a/src/pv/state.c b/src/pv/state.c index 5442417..800b401 100644 --- a/src/pv/state.c +++ b/src/pv/state.c @@ -272,15 +272,20 @@ static void pv_truncate_output(pvstate_t state) if (state->control.output_fd < 0) return; - current_offset = lseek(state->control.output_fd, (off_t) 0, SEEK_CUR); + current_offset = (off_t) lseek(state->control.output_fd, (off_t) 0, SEEK_CUR); if (current_offset == (off_t) - 1) return; debug("%s: %ld", "truncating to current offset", (long) current_offset); + /*@+longintegral@ */ + /* + * splint has trouble with off_t / __off_t. + */ if (0 != ftruncate(state->control.output_fd, current_offset)) { debug("%s: %s", "output ftruncate() failed", strerror(errno)); } + /*@-longintegral@ */ } @@ -680,6 +685,22 @@ void pv_state_output_set(pvstate_t state, int fd, const char *name) */ fcntl(state->control.output_fd, F_SETFL, O_NONBLOCK | fcntl(state->control.output_fd, F_GETFL)); #endif /* MAKE_OUTPUT_NONBLOCKING */ + + /* + * In sparse output mode, if the output is in append mode (>>), + * explicitly lseek() to the end of the file. Otherwise, the file + * offset is not set until the first write(), which means that if + * the input starts with null bytes, when we lseek() past them + * relative to the current position, the "current position" is 0 + * rather than the end of the file, and the file gets truncated on + * exit to the wrong size. + */ + if (state->control.sparse_output && 0 != (fcntl(fd, F_GETFL) & O_APPEND)) { + debug("%s", "sparse output mode, and appending - seeking output to the end"); + if ((off_t) lseek(fd, 0, SEEK_END) == (off_t) - 1) { + debug("%s: %s", "lseek failed", strerror(errno)); + } + } } void pv_state_average_rate_window_set(pvstate_t state, unsigned int val)