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>
This commit is contained in:
Miklos Szeredi 2025-08-05 17:10:56 +02:00
parent 9d81ba6d49
commit 7a37f55af7
3 changed files with 46 additions and 13 deletions

View 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);

View 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;

View File

@ -235,6 +235,10 @@
*
* 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
@ -270,7 +274,7 @@
#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 {