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