From 0f91b6174e178720fea2d01734736a6477e4feac Mon Sep 17 00:00:00 2001 From: Jim Meyering Date: Fri, 28 Jan 2011 21:19:50 +0100 Subject: copy: make extent_copy use sparse_copy, rather than its own code * src/copy.c (extent_copy): Before this change, extent_copy would fail to create holes, thus breaking --sparse=auto and --sparse=always. I.e., copying a large enough file of all zeros, cp --sparse=always should introduce a hole, but with extent_copy, it would not. --- src/copy.c | 109 +++++++++++++++++++++++++++---------------------------------- 1 file changed, 49 insertions(+), 60 deletions(-) diff --git a/src/copy.c b/src/copy.c index 4bfdce68b..96bb35b13 100644 --- a/src/copy.c +++ b/src/copy.c @@ -136,25 +136,28 @@ utimens_symlink (char const *file, struct timespec const *timespec) /* Copy the regular file open on SRC_FD/SRC_NAME to DST_FD/DST_NAME, honoring the MAKE_HOLES setting and using the BUF_SIZE-byte buffer - BUF for temporary storage. Return true upon successful completion; + BUF for temporary storage. Copy no more than MAX_N_READ bytes. + Return true upon successful completion; print a diagnostic and return false upon error. Note that for best results, BUF should be "well"-aligned. BUF must have sizeof(uintptr_t)-1 bytes of additional space - beyond BUF[BUF_SIZE-1]. */ + beyond BUF[BUF_SIZE-1]. + Set *LAST_WRITE_MADE_HOLE to true if the final operation on + DEST_FD introduced a hole. */ 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) + char const *src_name, char const *dst_name, + uintmax_t max_n_read, bool *last_write_made_hole) { typedef uintptr_t word; - off_t n_read_total = 0; - bool last_write_made_hole = false; + *last_write_made_hole = false; - while (true) + while (max_n_read) { word *wp = NULL; - ssize_t n_read = read (src_fd, buf, buf_size); + ssize_t n_read = read (src_fd, buf, MIN (max_n_read, buf_size)); if (n_read < 0) { #ifdef EINTR @@ -166,8 +169,7 @@ sparse_copy (int src_fd, int dest_fd, char *buf, size_t buf_size, } if (n_read == 0) break; - - n_read_total += n_read; + max_n_read -= n_read; if (make_holes) { @@ -209,7 +211,7 @@ sparse_copy (int src_fd, int dest_fd, char *buf, size_t buf_size, error (0, errno, _("cannot lseek %s"), quote (dst_name)); return false; } - last_write_made_hole = true; + *last_write_made_hole = true; } } @@ -221,7 +223,7 @@ sparse_copy (int src_fd, int dest_fd, char *buf, size_t buf_size, error (0, errno, _("writing %s"), quote (dst_name)); return false; } - last_write_made_hole = false; + *last_write_made_hole = false; /* It is tempting to return early here upon a short read from a regular file. That would save the final read syscall for each @@ -230,9 +232,16 @@ sparse_copy (int src_fd, int dest_fd, char *buf, size_t buf_size, } } - /* If the file ends with a `hole', we need to do something to record the - length of the file. On modern systems, calling ftruncate does the job. */ - if (last_write_made_hole && ftruncate (dest_fd, n_read_total) < 0) + return true; +} + +/* If the file ends with a `hole' (i.e., if sparse_copy set wrote_hole_at_eof), + call this function to record the length of the output file. */ +static bool +sparse_copy_finalize (int dest_fd, char const *dst_name) +{ + off_t len = lseek (dest_fd, 0, SEEK_CUR); + if (0 <= len && ftruncate (dest_fd, len) < 0) { error (0, errno, _("truncating %s"), quote (dst_name)); return false; @@ -309,10 +318,10 @@ extent_copy (int src_fd, int dest_fd, char *buf, size_t buf_size, struct extent_scan scan; off_t last_ext_start = 0; uint64_t last_ext_len = 0; - uint64_t last_read_size = 0; extent_scan_init (src_fd, &scan); + bool wrote_hole_at_eof = true; do { bool ok = extent_scan_read (&scan); @@ -356,8 +365,9 @@ extent_copy (int src_fd, int dest_fd, char *buf, size_t buf_size, } else { - /* We're not inducing holes; write zeros to the destination file - if there is a hole between the last and current extent. */ + /* When not inducing holes and when there is a hole between + the end of the previous extent and the beginning of the + current one, write zeros to the destination file. */ if (last_ext_start + last_ext_len < ext_start) { uint64_t hole_size = (ext_start @@ -373,39 +383,11 @@ extent_copy (int src_fd, int dest_fd, char *buf, size_t buf_size, last_ext_start = ext_start; last_ext_len = ext_len; - last_read_size = 0; - - while (ext_len) - { - /* Don't read from a following hole if EXT_LEN - is smaller than the buffer size. */ - size_t b_size = MIN (ext_len, buf_size); - ssize_t n_read = read (src_fd, buf, b_size); - if (n_read < 0) - { -#ifdef EINTR - if (errno == EINTR) - continue; -#endif - error (0, errno, _("reading %s"), quote (src_name)); - goto fail; - } - - if (n_read == 0) - { - /* Record number of bytes read from this extent-at-EOF. */ - last_read_size = last_ext_len - ext_len; - break; - } - - if (full_write (dest_fd, buf, n_read) != n_read) - { - error (0, errno, _("writing %s"), quote (dst_name)); - goto fail; - } - ext_len -= n_read; - } + if ( ! sparse_copy (src_fd, dest_fd, buf, buf_size, + make_holes, src_name, dst_name, ext_len, + &wrote_hole_at_eof)) + return false; } /* Release the space allocated to scan->ext_info. */ @@ -414,16 +396,19 @@ extent_copy (int src_fd, int dest_fd, char *buf, size_t buf_size, } while (! scan.hit_final_extent); - /* When the source file ends with a hole, the sum of the last extent start - offset and (the read-returned size or the last extent length) is smaller - than the actual size of the file. In that case, extend the destination - file to the required length. When MAKE_HOLES is set, use ftruncate; - otherwise, use write_zeros. */ - uint64_t eof_hole_len = (src_total_size - last_ext_start - - (last_read_size ? last_read_size : last_ext_len)); - if (eof_hole_len && (make_holes - ? ftruncate (dest_fd, src_total_size) - : ! write_zeros (dest_fd, eof_hole_len))) + /* When the source file ends with a hole, we have to do a little more work, + since the above copied only up to and including the final extent. + In order to complete the copy, we may have to insert a hole or write + zeros in the destination corresponding to the source file's hole-at-EOF. + + 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) + && (make_holes + ? ftruncate (dest_fd, src_total_size) + : ! write_zeros (dest_fd, src_total_size - dest_len))) { error (0, errno, _("failed to extend %s"), quote (dst_name)); return false; @@ -1002,8 +987,12 @@ copy_reg (char const *src_name, char const *dst_name, goto close_src_and_dst_desc; } + bool wrote_hole_at_eof; if ( ! sparse_copy (source_desc, dest_desc, buf, buf_size, - make_holes, src_name, dst_name)) + make_holes, src_name, dst_name, UINTMAX_MAX, + &wrote_hole_at_eof) + || (wrote_hole_at_eof && + ! sparse_copy_finalize (dest_desc, dst_name))) { return_val = false; goto close_src_and_dst_desc; -- cgit v1.2.3-54-g00ecf