|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#include <config.h> |
|
|
|
|
|
#include "system.h" |
|
|
|
|
|
#include "alignalloc.h" |
|
|
#include "backupfile.h" |
|
|
#include "buffer-lcm.h" |
|
|
#include "copy.h" |
|
|
#include "fadvise.h" |
|
|
#include "full-write.h" |
|
|
#include "ioblksize.h" |
|
|
|
|
|
|
|
|
struct scan_inference |
|
|
{ |
|
|
|
|
|
off_t ext_start; |
|
|
|
|
|
|
|
|
off_t hole_start; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static int |
|
|
punch_hole (int fd, off_t offset, off_t length) |
|
|
{ |
|
|
int ret = 0; |
|
|
|
|
|
#if HAVE_FALLOCATE + 0 |
|
|
# if defined FALLOC_FL_PUNCH_HOLE && defined FALLOC_FL_KEEP_SIZE |
|
|
ret = fallocate (fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, |
|
|
offset, length); |
|
|
if (ret < 0 && (is_ENOTSUP (errno) || errno == ENOSYS)) |
|
|
ret = 0; |
|
|
# endif |
|
|
#endif |
|
|
return ret; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static off_t |
|
|
create_hole (int fd, char const *name, off_t size) |
|
|
{ |
|
|
off_t file_end = lseek (fd, size, SEEK_CUR); |
|
|
|
|
|
if (file_end < 0) |
|
|
{ |
|
|
error (0, errno, _("cannot lseek %s"), quoteaf (name)); |
|
|
return -1; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (punch_hole (fd, file_end - size, size) < 0) |
|
|
{ |
|
|
error (0, errno, _("error deallocating %s"), quoteaf (name)); |
|
|
return -1; |
|
|
} |
|
|
|
|
|
return file_end; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static bool |
|
|
is_CLONENOTSUP (int err) |
|
|
{ |
|
|
return err == ENOSYS || err == ENOTTY || is_ENOTSUP (err) |
|
|
|| err == EINVAL || err == EBADF |
|
|
|| err == EXDEV || err == ETXTBSY |
|
|
|| err == EPERM || err == EACCES; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static intmax_t |
|
|
sparse_copy (int src_fd, int dest_fd, char **abuf, idx_t buf_size, |
|
|
bool allow_reflink, |
|
|
char const *src_name, char const *dst_name, |
|
|
count_t max_n_read, off_t *hole_size, |
|
|
struct copy_debug *debug) |
|
|
{ |
|
|
count_t total_n_read = 0; |
|
|
|
|
|
if (debug->sparse_detection == COPY_DEBUG_UNKNOWN) |
|
|
debug->sparse_detection = hole_size ? COPY_DEBUG_YES : COPY_DEBUG_NO; |
|
|
else if (hole_size && debug->sparse_detection == COPY_DEBUG_EXTERNAL) |
|
|
debug->sparse_detection = COPY_DEBUG_EXTERNAL_INTERNAL; |
|
|
|
|
|
|
|
|
|
|
|
if (!hole_size && allow_reflink) |
|
|
while (0 < max_n_read) |
|
|
{ |
|
|
|
|
|
|
|
|
|
|
|
ssize_t copy_max = MIN (SSIZE_MAX, SIZE_MAX) >> 30 << 30; |
|
|
ssize_t n_copied = copy_file_range (src_fd, nullptr, dest_fd, nullptr, |
|
|
MIN (max_n_read, copy_max), 0); |
|
|
if (n_copied == 0) |
|
|
{ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (total_n_read == 0) |
|
|
break; |
|
|
debug->offload = COPY_DEBUG_YES; |
|
|
return total_n_read; |
|
|
} |
|
|
if (n_copied < 0) |
|
|
{ |
|
|
debug->offload = COPY_DEBUG_UNSUPPORTED; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (total_n_read == 0 && is_CLONENOTSUP (errno)) |
|
|
break; |
|
|
|
|
|
|
|
|
|
|
|
if (total_n_read == 0 && errno == ENOENT) |
|
|
break; |
|
|
|
|
|
if (errno == EINTR) |
|
|
n_copied = 0; |
|
|
else |
|
|
{ |
|
|
error (0, errno, _("error copying %s to %s"), |
|
|
quoteaf_n (0, src_name), quoteaf_n (1, dst_name)); |
|
|
return -1; |
|
|
} |
|
|
} |
|
|
debug->offload = COPY_DEBUG_YES; |
|
|
max_n_read -= n_copied; |
|
|
total_n_read += n_copied; |
|
|
} |
|
|
else |
|
|
debug->offload = COPY_DEBUG_AVOIDED; |
|
|
|
|
|
|
|
|
off_t psize = hole_size ? *hole_size : 0; |
|
|
bool make_hole = !!psize; |
|
|
|
|
|
while (0 < max_n_read) |
|
|
{ |
|
|
if (!*abuf) |
|
|
*abuf = xalignalloc (getpagesize (), buf_size); |
|
|
char *buf = *abuf; |
|
|
ssize_t n_read = read (src_fd, buf, MIN (max_n_read, buf_size)); |
|
|
if (n_read < 0) |
|
|
{ |
|
|
if (errno == EINTR) |
|
|
continue; |
|
|
error (0, errno, _("error reading %s"), quoteaf (src_name)); |
|
|
return -1; |
|
|
} |
|
|
if (n_read == 0) |
|
|
break; |
|
|
max_n_read -= n_read; |
|
|
total_n_read += n_read; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
idx_t csize = hole_size ? ST_NBLOCKSIZE : buf_size; |
|
|
char *cbuf = buf; |
|
|
char *pbuf = buf; |
|
|
|
|
|
while (n_read) |
|
|
{ |
|
|
bool prev_hole = make_hole; |
|
|
csize = MIN (csize, n_read); |
|
|
|
|
|
if (hole_size) |
|
|
make_hole = is_nul (cbuf, csize); |
|
|
|
|
|
bool transition = (make_hole != prev_hole) && psize; |
|
|
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, psize) < 0) |
|
|
return false; |
|
|
pbuf = cbuf; |
|
|
psize = csize; |
|
|
} |
|
|
|
|
|
if (!prev_hole || (transition && last_chunk)) |
|
|
{ |
|
|
if (full_write (dest_fd, pbuf, psize) != psize) |
|
|
{ |
|
|
error (0, errno, _("error writing %s"), |
|
|
quoteaf (dst_name)); |
|
|
return -1; |
|
|
} |
|
|
psize = !prev_hole && transition ? csize : 0; |
|
|
} |
|
|
} |
|
|
else |
|
|
{ |
|
|
if (ckd_add (&psize, psize, csize)) |
|
|
{ |
|
|
error (0, 0, _("overflow reading %s"), quoteaf (src_name)); |
|
|
return -1; |
|
|
} |
|
|
} |
|
|
|
|
|
n_read -= csize; |
|
|
cbuf += csize; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
if (hole_size) |
|
|
*hole_size = make_hole ? psize : 0; |
|
|
return total_n_read; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static bool |
|
|
write_zeros (int fd, off_t n_bytes, char **abuf, idx_t buf_size) |
|
|
{ |
|
|
char *zeros = nullptr; |
|
|
while (n_bytes) |
|
|
{ |
|
|
idx_t n = MIN (buf_size, n_bytes); |
|
|
if (!zeros) |
|
|
{ |
|
|
if (!*abuf) |
|
|
*abuf = xalignalloc (getpagesize (), buf_size); |
|
|
zeros = memset (*abuf, 0, n); |
|
|
} |
|
|
if (full_write (fd, zeros, n) != n) |
|
|
return false; |
|
|
n_bytes -= n; |
|
|
} |
|
|
|
|
|
return true; |
|
|
} |
|
|
|
|
|
#ifdef SEEK_HOLE |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static off_t |
|
|
lseek_copy (int src_fd, int dest_fd, char **abuf, idx_t buf_size, |
|
|
off_t src_pos, count_t ibytes, |
|
|
struct scan_inference const *scan_inference, off_t src_total_size, |
|
|
enum Sparse_type sparse_mode, |
|
|
bool allow_reflink, |
|
|
char const *src_name, char const *dst_name, |
|
|
off_t *hole_size, struct copy_debug *debug) |
|
|
{ |
|
|
off_t last_ext_start = src_pos; |
|
|
off_t last_ext_len = 0; |
|
|
off_t max_ipos = ckd_add (&max_ipos, src_pos, ibytes) ? OFF_T_MAX : max_ipos; |
|
|
|
|
|
|
|
|
src_total_size = MIN (src_total_size, max_ipos); |
|
|
|
|
|
|
|
|
|
|
|
off_t ipos = src_pos; |
|
|
|
|
|
debug->sparse_detection = COPY_DEBUG_EXTERNAL; |
|
|
|
|
|
for (off_t ext_start = scan_inference->ext_start; |
|
|
0 <= ext_start && ext_start < max_ipos; ) |
|
|
{ |
|
|
off_t ext_end = (ext_start == 0 |
|
|
? scan_inference->hole_start |
|
|
: lseek (src_fd, ext_start, SEEK_HOLE)); |
|
|
if (0 <= ext_end) |
|
|
ext_end = MIN (ext_end, max_ipos); |
|
|
else |
|
|
{ |
|
|
if (errno != ENXIO) |
|
|
goto cannot_lseek; |
|
|
ext_end = src_total_size; |
|
|
if (ext_end <= ext_start) |
|
|
{ |
|
|
|
|
|
src_total_size = lseek (src_fd, 0, SEEK_END); |
|
|
if (src_total_size < 0) |
|
|
goto cannot_lseek; |
|
|
src_total_size = MIN (src_total_size, max_ipos); |
|
|
|
|
|
|
|
|
if (src_total_size <= ext_start) |
|
|
break; |
|
|
|
|
|
ext_end = src_total_size; |
|
|
} |
|
|
} |
|
|
|
|
|
if (src_total_size < ext_end) |
|
|
src_total_size = ext_end; |
|
|
|
|
|
if (lseek (src_fd, ext_start, SEEK_SET) < 0) |
|
|
goto cannot_lseek; |
|
|
|
|
|
off_t ext_hole_size = ext_start - last_ext_start - last_ext_len; |
|
|
|
|
|
if (ext_hole_size) |
|
|
{ |
|
|
if (sparse_mode == SPARSE_ALWAYS) |
|
|
*hole_size += ext_hole_size; |
|
|
else if (sparse_mode != SPARSE_NEVER) |
|
|
{ |
|
|
off_t epos = create_hole (dest_fd, dst_name, ext_hole_size); |
|
|
if (epos < 0) |
|
|
return epos; |
|
|
} |
|
|
else |
|
|
{ |
|
|
|
|
|
|
|
|
|
|
|
if (! write_zeros (dest_fd, ext_hole_size, abuf, buf_size)) |
|
|
{ |
|
|
error (0, errno, _("%s: write failed"), |
|
|
quotef (dst_name)); |
|
|
return -1; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
off_t ext_len = ext_end - ext_start; |
|
|
last_ext_start = ext_start; |
|
|
last_ext_len = ext_len; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
off_t n_read |
|
|
= sparse_copy (src_fd, dest_fd, abuf, buf_size, |
|
|
allow_reflink, src_name, dst_name, |
|
|
ext_len, |
|
|
sparse_mode == SPARSE_ALWAYS ? hole_size : nullptr, |
|
|
debug); |
|
|
if (n_read < 0) |
|
|
return -1; |
|
|
|
|
|
ipos = ext_start + n_read; |
|
|
if (n_read < ext_len) |
|
|
{ |
|
|
|
|
|
src_total_size = ipos; |
|
|
break; |
|
|
} |
|
|
|
|
|
ext_start = lseek (src_fd, ipos, SEEK_DATA); |
|
|
if (ext_start < 0 && errno != ENXIO) |
|
|
goto cannot_lseek; |
|
|
} |
|
|
|
|
|
*hole_size += src_total_size - (last_ext_start + last_ext_len); |
|
|
return src_total_size - src_pos; |
|
|
|
|
|
cannot_lseek: |
|
|
error (0, errno, _("cannot lseek %s"), quoteaf (src_name)); |
|
|
return -1; |
|
|
} |
|
|
#endif |
|
|
|
|
|
#ifndef HAVE_STRUCT_STAT_ST_BLOCKS |
|
|
# define HAVE_STRUCT_STAT_ST_BLOCKS 0 |
|
|
#endif |
|
|
|
|
|
|
|
|
enum scantype |
|
|
{ |
|
|
|
|
|
ERROR_SCANTYPE, |
|
|
|
|
|
|
|
|
PLAIN_SCANTYPE, |
|
|
|
|
|
|
|
|
|
|
|
ZERO_SCANTYPE, |
|
|
|
|
|
|
|
|
LSEEK_SCANTYPE, |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static enum scantype |
|
|
infer_scantype (int fd, struct stat const *sb, off_t pos, |
|
|
struct scan_inference *scan_inference) |
|
|
{ |
|
|
|
|
|
|
|
|
if (! (HAVE_STRUCT_STAT_ST_BLOCKS |
|
|
&& S_ISREG (sb->st_mode) |
|
|
&& STP_NBLOCKS (sb) < sb->st_size / ST_NBLOCKSIZE)) |
|
|
return PLAIN_SCANTYPE; |
|
|
|
|
|
#ifdef SEEK_HOLE |
|
|
scan_inference->ext_start = lseek (fd, pos, SEEK_DATA); |
|
|
if (scan_inference->ext_start == pos) |
|
|
{ |
|
|
scan_inference->hole_start = lseek (fd, pos, SEEK_HOLE); |
|
|
if (0 <= scan_inference->hole_start) |
|
|
{ |
|
|
if (scan_inference->hole_start < sb->st_size) |
|
|
return LSEEK_SCANTYPE; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (lseek (fd, pos, SEEK_SET) < 0) |
|
|
return ERROR_SCANTYPE; |
|
|
} |
|
|
} |
|
|
else if (pos < scan_inference->ext_start || errno == ENXIO) |
|
|
{ |
|
|
scan_inference->hole_start = 0; |
|
|
return LSEEK_SCANTYPE; |
|
|
} |
|
|
else if (errno != EINVAL && !is_ENOTSUP (errno)) |
|
|
return ERROR_SCANTYPE; |
|
|
#endif |
|
|
|
|
|
return ZERO_SCANTYPE; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
extern intmax_t |
|
|
copy_file_data (int ifd, struct stat const *ist, off_t ipos, char const *iname, |
|
|
int ofd, struct stat const *ost, off_t opos, char const *oname, |
|
|
count_t ibytes, struct cp_options const *x, |
|
|
struct copy_debug *debug) |
|
|
{ |
|
|
|
|
|
idx_t buf_size = io_blksize (ost); |
|
|
|
|
|
|
|
|
struct scan_inference scan_inference; |
|
|
enum scantype scantype = infer_scantype (ifd, ist, ipos, &scan_inference); |
|
|
if (scantype == ERROR_SCANTYPE) |
|
|
{ |
|
|
error (0, errno, _("cannot lseek %s"), quoteaf (iname)); |
|
|
return -1; |
|
|
} |
|
|
bool make_holes |
|
|
= (S_ISREG (ost->st_mode) |
|
|
&& (x->sparse_mode == SPARSE_ALWAYS |
|
|
|| (x->sparse_mode == SPARSE_AUTO |
|
|
&& scantype != PLAIN_SCANTYPE))); |
|
|
|
|
|
|
|
|
|
|
|
if (IO_BUFSIZE < ibytes) |
|
|
fdadvise (ifd, ipos, ibytes <= OFF_T_MAX - ipos ? ibytes : 0, |
|
|
FADVISE_SEQUENTIAL); |
|
|
|
|
|
|
|
|
|
|
|
if (! make_holes) |
|
|
{ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
idx_t blcm_max = MIN (MIN (IDX_MAX - 1, SSIZE_MAX), SIZE_MAX); |
|
|
idx_t blcm = buffer_lcm (io_blksize (ist), buf_size, |
|
|
blcm_max); |
|
|
|
|
|
|
|
|
|
|
|
if (S_ISREG (ist->st_mode) && 0 <= ist->st_size |
|
|
&& ist->st_size < buf_size) |
|
|
buf_size = ist->st_size + 1; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
buf_size = ckd_add (&buf_size, buf_size, blcm - 1) ? IDX_MAX : buf_size; |
|
|
buf_size -= buf_size % blcm; |
|
|
} |
|
|
|
|
|
char *buf = nullptr; |
|
|
intmax_t result; |
|
|
off_t hole_size = 0; |
|
|
|
|
|
if (scantype == LSEEK_SCANTYPE) |
|
|
{ |
|
|
#ifdef SEEK_HOLE |
|
|
result = lseek_copy (ifd, ofd, &buf, buf_size, |
|
|
ipos, ibytes, &scan_inference, ist->st_size, |
|
|
make_holes ? x->sparse_mode : SPARSE_NEVER, |
|
|
x->reflink_mode != REFLINK_NEVER, |
|
|
iname, oname, &hole_size, debug); |
|
|
#else |
|
|
unreachable (); |
|
|
#endif |
|
|
} |
|
|
else |
|
|
result = sparse_copy (ifd, ofd, &buf, buf_size, |
|
|
x->reflink_mode != REFLINK_NEVER, |
|
|
iname, oname, ibytes, |
|
|
make_holes ? &hole_size : nullptr, |
|
|
debug); |
|
|
|
|
|
if (0 <= result && 0 < hole_size) |
|
|
{ |
|
|
off_t oend; |
|
|
if (ckd_add (&oend, opos, result) |
|
|
? (errno = EOVERFLOW, true) |
|
|
: make_holes |
|
|
? ftruncate (ofd, oend) < 0 |
|
|
: !write_zeros (ofd, hole_size, &buf, buf_size)) |
|
|
{ |
|
|
error (0, errno, _("failed to extend %s"), quoteaf (oname)); |
|
|
result = -1; |
|
|
} |
|
|
else if (make_holes |
|
|
&& punch_hole (ofd, oend - hole_size, hole_size) < 0) |
|
|
{ |
|
|
error (0, errno, _("error deallocating %s"), quoteaf (oname)); |
|
|
result = -1; |
|
|
} |
|
|
} |
|
|
|
|
|
alignfree (buf); |
|
|
return result; |
|
|
} |
|
|
|