return ret;
}
-/* Create a hole at the end of a file,
- avoiding preallocation if requested. */
+/* Create a hole at the end of the file with descriptor FD and name NAME.
+ If PUNCH_HOLES, avoid preallocation if requested.
+ The hole is of size SIZE. Assume FD is already at file end,
+ and advance FD past the newly-created hole. */
static bool
create_hole (int fd, char const *name, bool punch_holes, off_t size)
/* 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
*ABUF for temporary storage, allocating it lazily if *ABUF is null.
- If SCAN_HOLES, look for holes in the input.
+ For best results, *ABUF should be well-aligned.
If PUNCH_HOLES, punch holes in the output.
Copy no more than MAX_N_READ bytes.
+ If HOLE_SIZE, look for holes in the input; *HOLE_SIZE contains
+ the size of the current hole so far, and update *HOLE_SIZE
+ at end to be the size of the hole at the end of the copy.
+ Set *TOTAL_N_READ to the number of bytes read; this counts
+ the trailing hole, which has not yet been output.
Return true upon successful completion;
- print a diagnostic and return false upon error.
- Note that for best results, BUF should be "well"-aligned.
- Set *LAST_WRITE_MADE_HOLE to true if the final operation on
- DEST_FD introduced a hole. Set *TOTAL_N_READ to the number of
- bytes read. */
+ print a diagnostic and return false upon error. */
static bool
sparse_copy (int src_fd, int dest_fd, char **abuf, size_t buf_size,
- bool scan_holes, bool punch_holes, bool allow_reflink,
+ bool punch_holes, bool allow_reflink,
char const *src_name, char const *dst_name,
- uintmax_t max_n_read, off_t *total_n_read,
- bool *last_write_made_hole)
+ uintmax_t max_n_read, off_t *hole_size, off_t *total_n_read)
{
- *last_write_made_hole = false;
*total_n_read = 0;
if (copy_debug.sparse_detection == COPY_DEBUG_UNKNOWN)
- copy_debug.sparse_detection = scan_holes ? COPY_DEBUG_YES : COPY_DEBUG_NO;
- else if (scan_holes && copy_debug.sparse_detection == COPY_DEBUG_EXTERNAL)
+ copy_debug.sparse_detection = hole_size ? COPY_DEBUG_YES : COPY_DEBUG_NO;
+ else if (hole_size && copy_debug.sparse_detection == COPY_DEBUG_EXTERNAL)
copy_debug.sparse_detection = COPY_DEBUG_EXTERNAL_INTERNAL;
/* If not looking for holes, use copy_file_range if functional,
but don't use if reflink disallowed as that may be implicit. */
- if (!scan_holes && allow_reflink)
+ if (!hole_size && allow_reflink)
while (max_n_read)
{
/* Copy at most COPY_MAX bytes at a time; this is min
copy_debug.offload = COPY_DEBUG_AVOIDED;
- bool make_hole = false;
- off_t psize = 0;
+ off_t psize = hole_size ? *hole_size : 0;
+ bool make_hole = !!psize;
while (max_n_read)
{
whole buffer. struct stat does not report the minimum hole
size for a file, so use ST_NBLOCKSIZE which should be the
minimum for all file systems on this platform. */
- size_t csize = scan_holes ? ST_NBLOCKSIZE : buf_size;
+ size_t csize = hole_size ? ST_NBLOCKSIZE : buf_size;
char *cbuf = buf;
char *pbuf = buf;
bool prev_hole = make_hole;
csize = MIN (csize, n_read);
- if (scan_holes && csize)
+ if (hole_size)
make_hole = is_nul (cbuf, csize);
bool transition = (make_hole != prev_hole) && psize;
- bool last_chunk = (n_read == csize && ! make_hole) || ! csize;
+ bool last_chunk = n_read == csize && !make_hole;
if (transition || last_chunk)
{
if (! transition)
psize += csize;
+ else if (prev_hole)
+ {
+ if (! create_hole (dest_fd, dst_name, punch_holes, psize))
+ return false;
+ pbuf = cbuf;
+ psize = csize;
+ }
- if (! prev_hole)
+ if (!prev_hole || (transition && last_chunk))
{
if (full_write (dest_fd, pbuf, psize) != psize)
{
quoteaf (dst_name));
return false;
}
- }
- else
- {
- if (! create_hole (dest_fd, dst_name, punch_holes, psize))
- return false;
- }
-
- pbuf = cbuf;
- psize = csize;
-
- if (last_chunk)
- {
- if (! csize)
- n_read = 0; /* Finished processing buffer. */
-
- if (transition)
- csize = 0; /* Loop again to deal with last chunk. */
- else
- psize = 0; /* Reset for next read loop. */
+ psize = !prev_hole && transition ? csize : 0;
}
}
else /* Coalesce writes/seeks. */
cbuf += csize;
}
- *last_write_made_hole = make_hole;
-
/* It's tempting to break early here upon a short read from
a regular file. That would save the final read syscall
for each file. Unfortunately that doesn't work for
certain files in /proc or /sys with linux kernels. */
}
- /* Ensure a trailing hole is created, so that subsequent
- calls of sparse_copy() start at the correct offset. */
- if (make_hole && ! create_hole (dest_fd, dst_name, punch_holes, psize))
- return false;
- else
- return true;
+ if (hole_size)
+ *hole_size = make_hole ? psize : 0;
+ return true;
}
/* Perform the O(1) btrfs clone operation, if possible.
The input file is of size SRC_TOTAL_SIZE.
Use SPARSE_MODE to determine whether to create holes in the output.
SRC_NAME and DST_NAME are the input and output file names.
+ Set *HOLE_SIZE to be the size of the hole at the end of the input.
+ Set *TOTAL_N_READ to the number of bytes read; this counts
+ the trailing hole, which has not yet been output.
Return true if successful, false (with a diagnostic) otherwise. */
static bool
off_t ext_start, off_t src_total_size,
enum Sparse_type sparse_mode,
bool allow_reflink,
- char const *src_name, char const *dst_name)
+ char const *src_name, char const *dst_name,
+ off_t *hole_size, off_t *total_n_read)
{
off_t last_ext_start = 0;
off_t last_ext_len = 0;
off_t dest_pos = 0;
- bool wrote_hole_at_eof = true;
copy_debug.sparse_detection = COPY_DEBUG_EXTERNAL;
if (lseek (src_fd, ext_start, SEEK_SET) < 0)
goto cannot_lseek;
- wrote_hole_at_eof = false;
off_t ext_hole_size = ext_start - last_ext_start - last_ext_len;
if (ext_hole_size)
{
- if (sparse_mode != SPARSE_NEVER)
+ if (sparse_mode == SPARSE_ALWAYS)
+ *hole_size += ext_hole_size;
+ else if (sparse_mode != SPARSE_NEVER)
{
if (! create_hole (dest_fd, dst_name,
sparse_mode == SPARSE_ALWAYS,
ext_hole_size))
return false;
- wrote_hole_at_eof = true;
}
else
{
bother to write zeros if --sparse=always, since SEEK_HOLE
is conservative and may miss some holes. */
off_t n_read;
- bool read_hole;
if ( ! sparse_copy (src_fd, dest_fd, abuf, buf_size,
- sparse_mode == SPARSE_ALWAYS,
true, allow_reflink, src_name, dst_name,
- ext_len, &n_read, &read_hole))
+ ext_len,
+ sparse_mode == SPARSE_ALWAYS ? hole_size : nullptr,
+ &n_read))
return false;
dest_pos = ext_start + n_read;
- if (n_read)
- wrote_hole_at_eof = read_hole;
if (n_read < ext_len)
{
/* The input file shrank. */
goto cannot_lseek;
}
- /* 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. */
- if ((dest_pos < src_total_size || wrote_hole_at_eof)
- && ! (sparse_mode == SPARSE_NEVER
- ? write_zeros (dest_fd, src_total_size - dest_pos)
- : ftruncate (dest_fd, src_total_size) == 0))
- {
- error (0, errno, _("failed to extend %s"), quoteaf (dst_name));
- return false;
- }
-
- if (sparse_mode == SPARSE_ALWAYS && dest_pos < src_total_size
- && punch_hole (dest_fd, dest_pos, src_total_size - dest_pos) < 0)
- {
- error (0, errno, _("error deallocating %s"), quoteaf (dst_name));
- return false;
- }
-
+ *hole_size += src_total_size - (last_ext_start + last_ext_len);
+ *total_n_read = src_total_size;
return true;
cannot_lseek:
buf_size = blcm;
}
- off_t n_read;
- bool wrote_hole_at_eof = false;
+ off_t hole_size = 0, n_read;
if (! (
#ifdef SEEK_HOLE
scantype == LSEEK_SCANTYPE
scan_inference.ext_start, src_open_sb.st_size,
make_holes ? x->sparse_mode : SPARSE_NEVER,
x->reflink_mode != REFLINK_NEVER,
- src_name, dst_name)
+ src_name, dst_name, &hole_size, &n_read)
:
#endif
sparse_copy (source_desc, dest_desc, &buf, buf_size,
- make_holes,
x->sparse_mode == SPARSE_ALWAYS,
x->reflink_mode != REFLINK_NEVER,
- src_name, dst_name, UINTMAX_MAX, &n_read,
- &wrote_hole_at_eof)))
+ src_name, dst_name, UINTMAX_MAX,
+ make_holes ? &hole_size : nullptr, &n_read)))
{
return_val = false;
goto close_src_and_dst_desc;
}
- else if (wrote_hole_at_eof && ftruncate (dest_desc, n_read) < 0)
+ else if (0 < hole_size)
{
- error (0, errno, _("failed to extend %s"), quoteaf (dst_name));
- return_val = false;
- goto close_src_and_dst_desc;
+ if (make_holes
+ ? ftruncate (dest_desc, n_read) < 0
+ : !write_zeros (dest_desc, hole_size))
+ {
+ error (0, errno, _("failed to extend %s"), quoteaf (dst_name));
+ return_val = false;
+ goto close_src_and_dst_desc;
+ }
+ if (x->sparse_mode == SPARSE_ALWAYS
+ && punch_hole (dest_desc, n_read - hole_size, hole_size) < 0)
+ {
+ error (0, errno, _("error deallocating %s"), quoteaf (dst_name));
+ return_val = false;
+ goto close_src_and_dst_desc;
+ }
}
}