diff options
author | Miklos Szeredi <mszeredi@redhat.com> | 2025-08-05 20:31:53 +0200 |
---|---|---|
committer | Bernd Schubert <bernd@bsbernd.com> | 2025-09-13 16:16:06 +0200 |
commit | 2317d8602334093ea0c88efee2e6ac326d42adc7 (patch) | |
tree | e22290de3d73692f9bebec77282ff17a96703e9c | |
parent | 3ff309ea9efa8fc95274d35a96aa5700f0a0d6c1 (diff) | |
download | libfuse-2317d8602334093ea0c88efee2e6ac326d42adc7.tar.gz |
libfuse: fix COPY_FILE_RANGE interface
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.
Currently the number of bytes copied is silently truncated to 32-bit, which
is unfortunate at best.
Implement the COPY_FILE_RANGE_64 interface which is identical to the old
one, except the number of bytes copied is returned in a 64-bit value.
The library interface remains the same.
If the kernel does not support the new interface or the server is running
as a 32-bit process, limit the copy size to size to UINT_MAX - 4096.
Edit by Bernd:
Keep ioctl_64bit and add use new bit is_copy_file_range_64 to keep
flags separated from each other - easier code readability IMO.
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>
Signed-off-by: Bernd Schubert <bschubert@ddn.com>
-rw-r--r-- | include/fuse_kernel.h | 12 | ||||
-rw-r--r-- | lib/fuse_i.h | 1 | ||||
-rw-r--r-- | lib/fuse_lowlevel.c | 66 |
3 files changed, 73 insertions, 6 deletions
diff --git a/include/fuse_kernel.h b/include/fuse_kernel.h index 122d658..94621f6 100644 --- a/include/fuse_kernel.h +++ b/include/fuse_kernel.h @@ -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 { diff --git a/lib/fuse_i.h b/lib/fuse_i.h index 0d0e637..d3d86d4 100644 --- a/lib/fuse_i.h +++ b/lib/fuse_i.h @@ -40,6 +40,7 @@ struct fuse_req { int interrupted; unsigned int ioctl_64bit : 1; unsigned int is_uring : 1; + unsigned int is_copy_file_range_64 : 1; union { struct { uint64_t unique; diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index e4544df..1e45315 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -589,7 +589,7 @@ int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *f) return send_reply_ok(req, &arg, sizeof(arg)); } -int fuse_reply_write(fuse_req_t req, size_t count) +static int do_fuse_reply_write(fuse_req_t req, size_t count) { struct fuse_write_out arg; @@ -599,6 +599,28 @@ int fuse_reply_write(fuse_req_t req, size_t count) return send_reply_ok(req, &arg, sizeof(arg)); } +static int do_fuse_reply_copy(fuse_req_t req, size_t count) +{ + struct fuse_copy_file_range_out arg; + + memset(&arg, 0, sizeof(arg)); + arg.bytes_copied = count; + + return send_reply_ok(req, &arg, sizeof(arg)); +} + +int fuse_reply_write(fuse_req_t req, size_t count) +{ + /* + * This function is also used by FUSE_COPY_FILE_RANGE and its 64-bit + * variant. + */ + if (req->is_copy_file_range_64) + return do_fuse_reply_copy(req, count); + else + return do_fuse_reply_write(req, count); +} + int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size) { return send_reply_ok(req, buf, size); @@ -2403,11 +2425,9 @@ static void do_fallocate(fuse_req_t req, const fuse_ino_t nodeid, _do_fallocate(req, nodeid, inarg, NULL); } -static void _do_copy_file_range(fuse_req_t req, const fuse_ino_t nodeid_in, - const void *op_in, const void *in_payload) +static void copy_file_range_common(fuse_req_t req, const fuse_ino_t nodeid_in, + const struct fuse_copy_file_range_in *arg) { - (void)in_payload; - const struct fuse_copy_file_range_in *arg = op_in; struct fuse_file_info fi_in, fi_out; memset(&fi_in, 0, sizeof(fi_in)); @@ -2424,12 +2444,46 @@ static void _do_copy_file_range(fuse_req_t req, const fuse_ino_t nodeid_in, fuse_reply_err(req, ENOSYS); } +static void _do_copy_file_range(fuse_req_t req, const fuse_ino_t nodeid_in, + const void *op_in, const void *in_payload) +{ + const struct fuse_copy_file_range_in *arg = op_in; + struct fuse_copy_file_range_in arg_tmp; + + (void) in_payload; + /* fuse_write_out can only handle 32bit copy size */ + if (arg->len > 0xfffff000) { + arg_tmp = *arg; + arg_tmp.len = 0xfffff000; + arg = &arg_tmp; + } + copy_file_range_common(req, nodeid_in, arg); +} + static void do_copy_file_range(fuse_req_t req, const fuse_ino_t nodeid_in, const void *inarg) { _do_copy_file_range(req, nodeid_in, inarg, NULL); } +static void _do_copy_file_range_64(fuse_req_t req, const fuse_ino_t nodeid_in, + const void *op_in, const void *in_payload) +{ + (void) in_payload; + req->is_copy_file_range_64 = 1; + /* Limit size on 32bit userspace to avoid conversion overflow */ + if (sizeof(size_t) == 4) + _do_copy_file_range(req, nodeid_in, op_in, NULL); + else + copy_file_range_common(req, nodeid_in, op_in); +} + +static void do_copy_file_range_64(fuse_req_t req, const fuse_ino_t nodeid_in, + const void *inarg) +{ + _do_copy_file_range_64(req, nodeid_in, inarg, NULL); +} + /* * Note that the uint64_t offset in struct fuse_lseek_in is derived from * linux kernel loff_t and is therefore signed. @@ -3378,6 +3432,7 @@ static struct { [FUSE_READDIRPLUS] = { do_readdirplus, "READDIRPLUS"}, [FUSE_RENAME2] = { do_rename2, "RENAME2" }, [FUSE_COPY_FILE_RANGE] = { do_copy_file_range, "COPY_FILE_RANGE" }, + [FUSE_COPY_FILE_RANGE_64] = { do_copy_file_range_64, "COPY_FILE_RANGE_64" }, [FUSE_LSEEK] = { do_lseek, "LSEEK" }, [FUSE_STATX] = { do_statx, "STATX" }, [CUSE_INIT] = { cuse_lowlevel_init, "CUSE_INIT" }, @@ -3433,6 +3488,7 @@ static struct { [FUSE_READDIRPLUS] = { _do_readdirplus, "READDIRPLUS" }, [FUSE_RENAME2] = { _do_rename2, "RENAME2" }, [FUSE_COPY_FILE_RANGE] = { _do_copy_file_range, "COPY_FILE_RANGE" }, + [FUSE_COPY_FILE_RANGE_64] = { _do_copy_file_range_64, "COPY_FILE_RANGE_64" }, [FUSE_LSEEK] = { _do_lseek, "LSEEK" }, [FUSE_STATX] = { _do_statx, "STATX" }, [CUSE_INIT] = { _cuse_lowlevel_init, "CUSE_INIT" }, |