Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

fuse: add COPY_FILE_RANGE_64 that allows large copies

The FUSE protocol uses struct fuse_write_out to convey the return value of
copy_file_range, which is restricted to uint32_t. But the COPY_FILE_RANGE
interface supports a 64-bit size copies and there's no reason why copies
should be limited to 32-bit.

Introduce a new op COPY_FILE_RANGE_64, which is identical, except the
number of bytes copied is returned in a 64-bit value.

If the fuse server does not support COPY_FILE_RANGE_64, fall back to
COPY_FILE_RANGE.

Reported-by: Florian Weimer <fweimer@redhat.com>
Closes: https://lore.kernel.org/all/lhuh5ynl8z5.fsf@oldenburg.str.redhat.com/
Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>

+46 -13
+32 -12
fs/fuse/file.c
··· 2960 2960 .nodeid_out = ff_out->nodeid, 2961 2961 .fh_out = ff_out->fh, 2962 2962 .off_out = pos_out, 2963 - .len = min_t(size_t, len, UINT_MAX & PAGE_MASK), 2963 + .len = len, 2964 2964 .flags = flags 2965 2965 }; 2966 2966 struct fuse_write_out outarg; 2967 + struct fuse_copy_file_range_out outarg_64; 2968 + u64 bytes_copied; 2967 2969 ssize_t err; 2968 2970 /* mark unstable when write-back is not used, and file_out gets 2969 2971 * extended */ ··· 3015 3013 if (is_unstable) 3016 3014 set_bit(FUSE_I_SIZE_UNSTABLE, &fi_out->state); 3017 3015 3018 - args.opcode = FUSE_COPY_FILE_RANGE; 3016 + args.opcode = FUSE_COPY_FILE_RANGE_64; 3019 3017 args.nodeid = ff_in->nodeid; 3020 3018 args.in_numargs = 1; 3021 3019 args.in_args[0].size = sizeof(inarg); 3022 3020 args.in_args[0].value = &inarg; 3023 3021 args.out_numargs = 1; 3024 - args.out_args[0].size = sizeof(outarg); 3025 - args.out_args[0].value = &outarg; 3022 + args.out_args[0].size = sizeof(outarg_64); 3023 + args.out_args[0].value = &outarg_64; 3024 + if (fc->no_copy_file_range_64) { 3025 + fallback: 3026 + /* Fall back to old op that can't handle large copy length */ 3027 + args.opcode = FUSE_COPY_FILE_RANGE; 3028 + args.out_args[0].size = sizeof(outarg); 3029 + args.out_args[0].value = &outarg; 3030 + inarg.len = len = min_t(size_t, len, UINT_MAX & PAGE_MASK); 3031 + } 3026 3032 err = fuse_simple_request(fm, &args); 3027 3033 if (err == -ENOSYS) { 3028 - fc->no_copy_file_range = 1; 3029 - err = -EOPNOTSUPP; 3034 + if (fc->no_copy_file_range_64) { 3035 + fc->no_copy_file_range = 1; 3036 + err = -EOPNOTSUPP; 3037 + } else { 3038 + fc->no_copy_file_range_64 = 1; 3039 + goto fallback; 3040 + } 3030 3041 } 3031 - if (!err && outarg.size > len) 3032 - err = -EIO; 3033 - 3034 3042 if (err) 3035 3043 goto out; 3036 3044 3045 + bytes_copied = fc->no_copy_file_range_64 ? 3046 + outarg.size : outarg_64.bytes_copied; 3047 + 3048 + if (bytes_copied > len) { 3049 + err = -EIO; 3050 + goto out; 3051 + } 3052 + 3037 3053 truncate_inode_pages_range(inode_out->i_mapping, 3038 3054 ALIGN_DOWN(pos_out, PAGE_SIZE), 3039 - ALIGN(pos_out + outarg.size, PAGE_SIZE) - 1); 3055 + ALIGN(pos_out + bytes_copied, PAGE_SIZE) - 1); 3040 3056 3041 3057 file_update_time(file_out); 3042 - fuse_write_update_attr(inode_out, pos_out + outarg.size, outarg.size); 3058 + fuse_write_update_attr(inode_out, pos_out + bytes_copied, bytes_copied); 3043 3059 3044 - err = outarg.size; 3060 + err = bytes_copied; 3045 3061 out: 3046 3062 if (is_unstable) 3047 3063 clear_bit(FUSE_I_SIZE_UNSTABLE, &fi_out->state);
+3
fs/fuse/fuse_i.h
··· 856 856 /** Does the filesystem support copy_file_range? */ 857 857 unsigned no_copy_file_range:1; 858 858 859 + /** Does the filesystem support copy_file_range_64? */ 860 + unsigned no_copy_file_range_64:1; 861 + 859 862 /* Send DESTROY request */ 860 863 unsigned int destroy:1; 861 864
+11 -1
include/uapi/linux/fuse.h
··· 235 235 * 236 236 * 7.44 237 237 * - add FUSE_NOTIFY_INC_EPOCH 238 + * 239 + * 7.45 240 + * - add FUSE_COPY_FILE_RANGE_64 241 + * - add struct fuse_copy_file_range_out 238 242 */ 239 243 240 244 #ifndef _LINUX_FUSE_H ··· 274 270 #define FUSE_KERNEL_VERSION 7 275 271 276 272 /** Minor version number of this interface */ 277 - #define FUSE_KERNEL_MINOR_VERSION 44 273 + #define FUSE_KERNEL_MINOR_VERSION 45 278 274 279 275 /** The node ID of the root inode */ 280 276 #define FUSE_ROOT_ID 1 ··· 661 657 FUSE_SYNCFS = 50, 662 658 FUSE_TMPFILE = 51, 663 659 FUSE_STATX = 52, 660 + FUSE_COPY_FILE_RANGE_64 = 53, 664 661 665 662 /* CUSE specific operations */ 666 663 CUSE_INIT = 4096, ··· 1151 1146 uint64_t off_out; 1152 1147 uint64_t len; 1153 1148 uint64_t flags; 1149 + }; 1150 + 1151 + /* For FUSE_COPY_FILE_RANGE_64 */ 1152 + struct fuse_copy_file_range_out { 1153 + uint64_t bytes_copied; 1154 1154 }; 1155 1155 1156 1156 #define FUSE_SETUPMAPPING_FLAG_WRITE (1ull << 0)