From 5efd4688a9b0541ab0453c415d72ee5c1accec83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 15 Jul 2026 12:39:30 +0200 Subject: [PATCH 1/2] shared/copy: simplify return convention for try_reflink_copy_bytes Follow-up for 5a12005c834eab92d7c76d61c7090aa78b61cdfb. Also a rescue a comment from the discussion in the PR. --- src/repart/repart.c | 6 ++++++ src/shared/copy.c | 33 +++++++++++---------------------- 2 files changed, 17 insertions(+), 22 deletions(-) diff --git a/src/repart/repart.c b/src/repart/repart.c index 7eadfe4a6a..002fb782be 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -6371,6 +6371,12 @@ static int context_copy_blocks(Context *context) { return log_error_errno(errno, "Failed to seek to copy blocks offset in %s: %m", p->copy_blocks_path); } + /* We call copy_bytes_full() instead of copy_file_range() directly, because copy_file_range() + * needs to be called with a size limit to allow for progress updates. But we don't want that + * for cloning, we want one big massive reflink if possible, and unfortunately we can't know + * if copy_file_range() will do reflink or not, so we can't call it without the size limit. + * Hence, call copy_bytes_full(), which tries FICLONE/BTRFS_IOC_CLONE first to do one big + * clone, and then falls back to copying in chunks. */ r = copy_bytes_full( p->copy_blocks_fd, partition_target_fd(t), diff --git a/src/shared/copy.c b/src/shared/copy.c index 7a87842a34..ded40b524e 100644 --- a/src/shared/copy.c +++ b/src/shared/copy.c @@ -130,36 +130,31 @@ static int create_hole(int fd, off_t size) { return 0; } -/* Returns positive if the range was cloned, zero if cloning is unavailable, and negative if finalizing a - * successful clone failed. If the range was cloned, returns the copy_bytes_full() result in ret_copy_result. */ static int try_reflink_copy_bytes( int fdf, int fdt, uint64_t max_bytes, - CopyFlags copy_flags, - int *ret_copy_result) { + CopyFlags copy_flags) { - off_t foffset, toffset; int r; assert(fdf >= 0); assert(fdt >= 0); - assert(ret_copy_result); if (max_bytes == 0) - return 0; + return -EOPNOTSUPP; - foffset = FLAGS_SET(copy_flags, COPY_SEEK0_SOURCE) ? 0 : lseek(fdf, 0, SEEK_CUR); + off_t foffset = FLAGS_SET(copy_flags, COPY_SEEK0_SOURCE) ? 0 : lseek(fdf, 0, SEEK_CUR); if (foffset < 0) - return 0; + return -EOPNOTSUPP; - toffset = FLAGS_SET(copy_flags, COPY_SEEK0_TARGET) ? 0 : lseek(fdt, 0, SEEK_CUR); + off_t toffset = FLAGS_SET(copy_flags, COPY_SEEK0_TARGET) ? 0 : lseek(fdt, 0, SEEK_CUR); if (toffset < 0) - return 0; + return -EOPNOTSUPP; r = reflink_range(fdf, foffset, fdt, toffset, max_bytes == UINT64_MAX ? 0 : max_bytes); if (r < 0) - return 0; + return -EOPNOTSUPP; if (max_bytes == UINT64_MAX) { off_t end; @@ -172,15 +167,11 @@ static int try_reflink_copy_bytes( if (lseek(fdt, toffset + (end - foffset), SEEK_SET) < 0) return -errno; - - *ret_copy_result = 0; /* We copied to EOF. */ } else { if (lseek(fdf, foffset + max_bytes, SEEK_SET) < 0) return -errno; if (lseek(fdt, toffset + max_bytes, SEEK_SET) < 0) return -errno; - - *ret_copy_result = 1; /* We copied the requested range. */ } if (FLAGS_SET(copy_flags, COPY_VERIFY_LINKED)) { @@ -189,7 +180,8 @@ static int try_reflink_copy_bytes( return r; } - return 1; + return max_bytes == UINT64_MAX ? 0 /* we copied until EOF */ + : 1 /* we copied the requested range */; } int copy_bytes_full( @@ -239,12 +231,9 @@ int copy_bytes_full( lseek(fdt, 0, SEEK_SET) < 0) return -errno; - int reflink_result = 0; - r = try_reflink_copy_bytes(fdf, fdt, max_bytes, copy_flags, &reflink_result); - if (r < 0) + r = try_reflink_copy_bytes(fdf, fdt, max_bytes, copy_flags); + if (r != -EOPNOTSUPP) return r; - if (r > 0) - return reflink_result; usec_t start_timestamp = USEC_INFINITY; if (progress) From 8437a7605ad12797722ebe6bd2c8cb0fed1d096d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 15 Jul 2026 12:59:45 +0200 Subject: [PATCH 2/2] shared/copy: simplify argument convention for reflink_range reflink_range() would treat size==UINT64_MAX as "copy everything", but only if the offsets were 0. There are only two callers: one always passes a fixed size, the other translated size==UINT64_MAX to 0. Make things more uniform by always treating size==UINT64_MAX the same as size==0. --- src/shared/copy.c | 25 ++++++++++++++----------- src/shared/copy.h | 2 +- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/shared/copy.c b/src/shared/copy.c index ded40b524e..208e3fd52d 100644 --- a/src/shared/copy.c +++ b/src/shared/copy.c @@ -152,7 +152,7 @@ static int try_reflink_copy_bytes( if (toffset < 0) return -EOPNOTSUPP; - r = reflink_range(fdf, foffset, fdt, toffset, max_bytes == UINT64_MAX ? 0 : max_bytes); + r = reflink_range(fdf, foffset, fdt, toffset, max_bytes); if (r < 0) return -EOPNOTSUPP; @@ -1748,22 +1748,19 @@ int reflink(int infd, int outfd) { assert_cc(sizeof(struct file_clone_range) == sizeof(struct btrfs_ioctl_clone_range_args)); -int reflink_range(int infd, uint64_t in_offset, int outfd, uint64_t out_offset, uint64_t sz) { - struct file_clone_range args = { - .src_fd = infd, - .src_offset = in_offset, - .src_length = sz, - .dest_offset = out_offset, - }; +int reflink_range(int infd, uint64_t in_offset, int outfd, uint64_t out_offset, uint64_t size) { int r; assert(infd >= 0); assert(outfd >= 0); + /* size==UINT64_MAX mean "clone everything". Translate to 0 for the kernel. */ + if (size == UINT64_MAX) + size = 0; + /* Inside the kernel, FICLONE is identical to FICLONERANGE with offsets and size set to zero, let's - * simplify things and use the simple ioctl in that case. Also, do the same if the size is - * UINT64_MAX, which is how we usually encode "everything". */ - if (in_offset == 0 && out_offset == 0 && IN_SET(sz, 0, UINT64_MAX)) + * simplify things and use the simple ioctl in that case. */ + if (in_offset == 0 && out_offset == 0 && size == 0) return reflink(infd, outfd); r = fd_verify_regular(outfd); @@ -1772,5 +1769,11 @@ int reflink_range(int infd, uint64_t in_offset, int outfd, uint64_t out_offset, assert_cc(FICLONERANGE == BTRFS_IOC_CLONE_RANGE); + struct file_clone_range args = { + .src_fd = infd, + .src_offset = in_offset, + .src_length = size, + .dest_offset = out_offset, + }; return RET_NERRNO(ioctl(outfd, FICLONERANGE, &args)); } diff --git a/src/shared/copy.h b/src/shared/copy.h index c5ced00bc9..975b1e9942 100644 --- a/src/shared/copy.h +++ b/src/shared/copy.h @@ -109,4 +109,4 @@ static inline int copy_rights(int fdf, int fdt) { int copy_xattr(int df, const char *from, int dt, const char *to, CopyFlags copy_flags); int reflink(int infd, int outfd); -int reflink_range(int infd, uint64_t in_offset, int outfd, uint64_t out_offset, uint64_t sz); +int reflink_range(int infd, uint64_t in_offset, int outfd, uint64_t out_offset, uint64_t size);