diff options
author | Jim Meyering <meyering@redhat.com> | 2011-01-31 18:25:58 +0100 |
---|---|---|
committer | Jim Meyering <meyering@redhat.com> | 2011-01-31 18:26:06 +0100 |
commit | cc0645fc643d3ea5a06f12f5fad784a2b8097888 (patch) | |
tree | 003a918196b8804d758cdc3d10c8504785ef8885 | |
parent | 96a93f9480e0a420c21db7b58662a6e3ddf16124 (diff) | |
download | coreutils-cc0645fc643d3ea5a06f12f5fad784a2b8097888.tar.xz |
cp: fix copying a sparse file to a pipe
The recent FIEMAP-related changes made it so the unusual case of
copying a sparse file to a non-regular destination (e.g., a pipe)
would erroneously write one byte too many to that destination.
That happened because extent_copy assumed that it could use lseek
to obtain the number of bytes written to the output file descriptor.
That was valid only for regular files.
* src/copy.c (sparse_copy): Add a parameter, to be used by extent_copy,
but not by reg_copy. Adjust callers.
(extent_copy): Maintain new local, dest_pos, using new arg, n_read.
Don't call lseek on dest_fd; use new var, dest_pos, instead.
(copy_reg): Add unused arg.
-rw-r--r-- | src/copy.c | 27 |
1 files changed, 20 insertions, 7 deletions
diff --git a/src/copy.c b/src/copy.c index d32a11c50..8ba09e042 100644 --- a/src/copy.c +++ b/src/copy.c @@ -139,15 +139,18 @@ utimens_symlink (char const *file, struct timespec const *timespec) BUF must have sizeof(uintptr_t)-1 bytes of additional space beyond BUF[BUF_SIZE-1]. Set *LAST_WRITE_MADE_HOLE to true if the final operation on - DEST_FD introduced a hole. */ + DEST_FD introduced a hole. Set *TOTAL_N_READ to the number of + bytes read. */ static bool sparse_copy (int src_fd, int dest_fd, char *buf, size_t buf_size, bool make_holes, char const *src_name, char const *dst_name, - uintmax_t max_n_read, bool *last_write_made_hole) + uintmax_t max_n_read, off_t *total_n_read, + bool *last_write_made_hole) { typedef uintptr_t word; *last_write_made_hole = false; + *total_n_read = 0; while (max_n_read) { @@ -164,6 +167,7 @@ sparse_copy (int src_fd, int dest_fd, char *buf, size_t buf_size, if (n_read == 0) break; max_n_read -= n_read; + *total_n_read += n_read; if (make_holes) { @@ -313,6 +317,10 @@ extent_copy (int src_fd, int dest_fd, char *buf, size_t buf_size, off_t last_ext_start = 0; uint64_t last_ext_len = 0; + /* Keep track of the output position. + We may need this at the end, for a final ftruncate. */ + off_t dest_pos = 0; + extent_scan_init (src_fd, &scan); bool wrote_hole_at_eof = true; @@ -378,10 +386,14 @@ extent_copy (int src_fd, int dest_fd, char *buf, size_t buf_size, last_ext_start = ext_start; last_ext_len = ext_len; + off_t n_read; if ( ! sparse_copy (src_fd, dest_fd, buf, buf_size, - make_holes, src_name, dst_name, ext_len, + make_holes, src_name, dst_name, + ext_len, &n_read, &wrote_hole_at_eof)) return false; + + dest_pos = ext_start + n_read; } /* Release the space allocated to scan->ext_info. */ @@ -398,11 +410,10 @@ extent_copy (int src_fd, int dest_fd, char *buf, size_t buf_size, In addition, if the final extent was a block of zeros at EOF and we've just converted them to a hole in the destination, we must call ftruncate here in order to record the proper length in the destination. */ - off_t dest_len = lseek (dest_fd, 0, SEEK_CUR); - if ((dest_len < src_total_size || wrote_hole_at_eof) + if ((dest_pos < src_total_size || wrote_hole_at_eof) && (make_holes ? ftruncate (dest_fd, src_total_size) - : ! write_zeros (dest_fd, src_total_size - dest_len))) + : ! write_zeros (dest_fd, src_total_size - dest_pos))) { error (0, errno, _("failed to extend %s"), quote (dst_name)); return false; @@ -981,9 +992,11 @@ copy_reg (char const *src_name, char const *dst_name, goto close_src_and_dst_desc; } + off_t n_read; bool wrote_hole_at_eof; if ( ! sparse_copy (source_desc, dest_desc, buf, buf_size, - make_holes, src_name, dst_name, UINTMAX_MAX, + make_holes, src_name, dst_name, + UINTMAX_MAX, &n_read, &wrote_hole_at_eof) || (wrote_hole_at_eof && ! sparse_copy_finalize (dest_desc, dst_name))) |