]> www.infradead.org Git - users/jedix/linux-maple.git/commitdiff
fuse: add COPY_FILE_RANGE_64 that allows large copies
authorMiklos Szeredi <mszeredi@redhat.com>
Tue, 5 Aug 2025 15:10:56 +0000 (17:10 +0200)
committerMiklos Szeredi <mszeredi@redhat.com>
Wed, 27 Aug 2025 12:29:43 +0000 (14:29 +0200)
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>
fs/fuse/file.c
fs/fuse/fuse_i.h
include/uapi/linux/fuse.h

index 4adcf09d4b01a6a5522563315c3da2e767a7e341..867b5fde1237f692452d57070359397ec6b26b66 100644 (file)
@@ -2960,10 +2960,12 @@ static ssize_t __fuse_copy_file_range(struct file *file_in, loff_t pos_in,
                .nodeid_out = ff_out->nodeid,
                .fh_out = ff_out->fh,
                .off_out = pos_out,
-               .len = min_t(size_t, len, UINT_MAX & PAGE_MASK),
+               .len = len,
                .flags = flags
        };
        struct fuse_write_out outarg;
+       struct fuse_copy_file_range_out outarg_64;
+       u64 bytes_copied;
        ssize_t err;
        /* mark unstable when write-back is not used, and file_out gets
         * extended */
@@ -3013,33 +3015,51 @@ static ssize_t __fuse_copy_file_range(struct file *file_in, loff_t pos_in,
        if (is_unstable)
                set_bit(FUSE_I_SIZE_UNSTABLE, &fi_out->state);
 
-       args.opcode = FUSE_COPY_FILE_RANGE;
+       args.opcode = FUSE_COPY_FILE_RANGE_64;
        args.nodeid = ff_in->nodeid;
        args.in_numargs = 1;
        args.in_args[0].size = sizeof(inarg);
        args.in_args[0].value = &inarg;
        args.out_numargs = 1;
-       args.out_args[0].size = sizeof(outarg);
-       args.out_args[0].value = &outarg;
+       args.out_args[0].size = sizeof(outarg_64);
+       args.out_args[0].value = &outarg_64;
+       if (fc->no_copy_file_range_64) {
+fallback:
+               /* Fall back to old op that can't handle large copy length */
+               args.opcode = FUSE_COPY_FILE_RANGE;
+               args.out_args[0].size = sizeof(outarg);
+               args.out_args[0].value = &outarg;
+               inarg.len = len = min_t(size_t, len, UINT_MAX & PAGE_MASK);
+       }
        err = fuse_simple_request(fm, &args);
        if (err == -ENOSYS) {
-               fc->no_copy_file_range = 1;
-               err = -EOPNOTSUPP;
+               if (fc->no_copy_file_range_64) {
+                       fc->no_copy_file_range = 1;
+                       err = -EOPNOTSUPP;
+               } else {
+                       fc->no_copy_file_range_64 = 1;
+                       goto fallback;
+               }
        }
-       if (!err && outarg.size > len)
-               err = -EIO;
-
        if (err)
                goto out;
 
+       bytes_copied = fc->no_copy_file_range_64 ?
+               outarg.size : outarg_64.bytes_copied;
+
+       if (bytes_copied > len) {
+               err = -EIO;
+               goto out;
+       }
+
        truncate_inode_pages_range(inode_out->i_mapping,
                                   ALIGN_DOWN(pos_out, PAGE_SIZE),
-                                  ALIGN(pos_out + outarg.size, PAGE_SIZE) - 1);
+                                  ALIGN(pos_out + bytes_copied, PAGE_SIZE) - 1);
 
        file_update_time(file_out);
-       fuse_write_update_attr(inode_out, pos_out + outarg.size, outarg.size);
+       fuse_write_update_attr(inode_out, pos_out + bytes_copied, bytes_copied);
 
-       err = outarg.size;
+       err = bytes_copied;
 out:
        if (is_unstable)
                clear_bit(FUSE_I_SIZE_UNSTABLE, &fi_out->state);
index cc428d04be3e14e93c407fa2f2b6cae8e8cb3b7a..d68eea4dd743ecdd1df632be781fed34ae122461 100644 (file)
@@ -856,6 +856,9 @@ struct fuse_conn {
        /** Does the filesystem support copy_file_range? */
        unsigned no_copy_file_range:1;
 
+       /** Does the filesystem support copy_file_range_64? */
+       unsigned no_copy_file_range_64:1;
+
        /* Send DESTROY request */
        unsigned int destroy:1;
 
index 122d6586e8d4da0cb2a35a4ee80d231ac8762345..94621f68a5cc8dd2d9eedbf2f0080e53b4cd4161 100644 (file)
  *
  *  7.44
  *  - add FUSE_NOTIFY_INC_EPOCH
+ *
+ *  7.45
+ *  - add FUSE_COPY_FILE_RANGE_64
+ *  - add struct fuse_copy_file_range_out
  */
 
 #ifndef _LINUX_FUSE_H
 #define FUSE_KERNEL_VERSION 7
 
 /** Minor version number of this interface */
-#define FUSE_KERNEL_MINOR_VERSION 44
+#define FUSE_KERNEL_MINOR_VERSION 45
 
 /** The node ID of the root inode */
 #define FUSE_ROOT_ID 1
@@ -657,6 +661,7 @@ enum fuse_opcode {
        FUSE_SYNCFS             = 50,
        FUSE_TMPFILE            = 51,
        FUSE_STATX              = 52,
+       FUSE_COPY_FILE_RANGE_64 = 53,
 
        /* CUSE specific operations */
        CUSE_INIT               = 4096,
@@ -1148,6 +1153,11 @@ struct fuse_copy_file_range_in {
        uint64_t        flags;
 };
 
+/* For FUSE_COPY_FILE_RANGE_64 */
+struct fuse_copy_file_range_out {
+       uint64_t        bytes_copied;
+};
+
 #define FUSE_SETUPMAPPING_FLAG_WRITE (1ull << 0)
 #define FUSE_SETUPMAPPING_FLAG_READ (1ull << 1)
 struct fuse_setupmapping_in {