| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | |
| |
|
| | #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; |
| | } |
| |
|