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 /lib/fuse_lowlevel.c | |
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>
Diffstat (limited to 'lib/fuse_lowlevel.c')
-rw-r--r-- | lib/fuse_lowlevel.c | 66 |
1 files changed, 61 insertions, 5 deletions
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" }, |