diff --git a/tests/TEST_posix_copy_file_range b/tests/TEST_posix_copy_file_range index cbd7df21..36920a38 100755 --- a/tests/TEST_posix_copy_file_range +++ b/tests/TEST_posix_copy_file_range @@ -71,6 +71,52 @@ def main(): os.close(mdfd) os.close(ndfd) + # Exercise very large copy lengths to validate the 64-bit copy result + # path when available. Use sparse files to keep runtime and disk usage low. + large_len = (5 << 30) + 4096 + merge_large_src = join(mount, "posix-cfr/src-large") + merge_large_dst = join(mount, "posix-cfr/dst-large") + native_large_src = join(native, "posix-cfr/src-large") + native_large_dst = join(native, "posix-cfr/dst-large") + touch(merge_large_src, b"") + touch(merge_large_dst, b"") + touch(native_large_src, b"") + touch(native_large_dst, b"") + + msfd = os.open(merge_large_src, os.O_RDWR) + mdfd = os.open(merge_large_dst, os.O_RDWR) + nsfd = os.open(native_large_src, os.O_RDWR) + ndfd = os.open(native_large_dst, os.O_RDWR) + try: + # Make sparse files with one non-zero byte at the end so both sides + # have comparable logical contents. + os.ftruncate(msfd, large_len) + os.ftruncate(nsfd, large_len) + os.pwrite(msfd, b"z", large_len - 1) + os.pwrite(nsfd, b"z", large_len - 1) + + err = compare_calls( + "copy_file_range large sparse", + lambda: os.copy_file_range(msfd, mdfd, large_len), + lambda: os.copy_file_range(nsfd, ndfd, large_len), + lambda a, b: a == b, + ) + if err: + return fail(err) + + mstat = os.fstat(mdfd) + nstat = os.fstat(ndfd) + if mstat.st_size != nstat.st_size: + return fail( + "copy_file_range large sparse dst size mismatch " + f"mergerfs={mstat.st_size} native={nstat.st_size}" + ) + finally: + os.close(msfd) + os.close(mdfd) + os.close(nsfd) + os.close(ndfd) + return 0 diff --git a/vendored/libfuse/include/base_types.h b/vendored/libfuse/include/base_types.h index 057a9ac0..851730be 100644 --- a/vendored/libfuse/include/base_types.h +++ b/vendored/libfuse/include/base_types.h @@ -2,11 +2,15 @@ #include +typedef uint16_t u16; +typedef int16_t s16; typedef uint32_t u32; typedef int32_t s32; typedef uint64_t u64; typedef int64_t s64; +typedef const uint16_t cu16; +typedef const int16_t cs16; typedef const uint32_t cu32; typedef const int32_t cs32; typedef const uint64_t cu64; diff --git a/vendored/libfuse/include/fuse_kernel.h b/vendored/libfuse/include/fuse_kernel.h index b4196ca3..bbcf0eec 100644 --- a/vendored/libfuse/include/fuse_kernel.h +++ b/vendored/libfuse/include/fuse_kernel.h @@ -229,6 +229,17 @@ * - FUSE_URING_IN_OUT_HEADER_SZ * - FUSE_URING_OP_IN_OUT_SZ * - enum fuse_uring_cmd + * + * 7.43 + * - add FUSE_REQUEST_TIMEOUT + * + * 7.44 + * - add FUSE_NOTIFY_INC_EPOCH + * + * 7.45 + * - add FUSE_COPY_FILE_RANGE_64 + * - add struct fuse_copy_file_range_out + * - add FUSE_NOTIFY_PRUNE */ #ifndef _LINUX_FUSE_H @@ -264,7 +275,7 @@ #define FUSE_KERNEL_VERSION 7 /** Minor version number of this interface */ -#define FUSE_KERNEL_MINOR_VERSION 42 +#define FUSE_KERNEL_MINOR_VERSION 45 /** The node ID of the root inode */ #define FUSE_ROOT_ID 1 @@ -435,39 +446,41 @@ struct fuse_file_lock { * of the request ID indicates resend requests * FUSE_ALLOW_IDMAP: allow creation of idmapped mounts * FUSE_OVER_IO_URING: Indicate that client supports io-uring + * FUSE_REQUEST_TIMEOUT: kernel supports timing out requests. + * init_out.request_timeout contains the timeout (in secs) */ -#define FUSE_ASYNC_READ (1ULL << 0) -#define FUSE_POSIX_LOCKS (1ULL << 1) -#define FUSE_FILE_OPS (1ULL << 2) -#define FUSE_ATOMIC_O_TRUNC (1ULL << 3) -#define FUSE_EXPORT_SUPPORT (1ULL << 4) -#define FUSE_BIG_WRITES (1ULL << 5) -#define FUSE_DONT_MASK (1ULL << 6) -#define FUSE_SPLICE_WRITE (1ULL << 7) -#define FUSE_SPLICE_MOVE (1ULL << 8) -#define FUSE_SPLICE_READ (1ULL << 9) -#define FUSE_FLOCK_LOCKS (1ULL << 10) -#define FUSE_HAS_IOCTL_DIR (1ULL << 11) -#define FUSE_AUTO_INVAL_DATA (1ULL << 12) -#define FUSE_DO_READDIRPLUS (1ULL << 13) -#define FUSE_READDIRPLUS_AUTO (1ULL << 14) -#define FUSE_ASYNC_DIO (1ULL << 15) -#define FUSE_WRITEBACK_CACHE (1ULL << 16) -#define FUSE_NO_OPEN_SUPPORT (1ULL << 17) -#define FUSE_PARALLEL_DIROPS (1ULL << 18) -#define FUSE_HANDLE_KILLPRIV (1ULL << 19) -#define FUSE_POSIX_ACL (1ULL << 20) -#define FUSE_ABORT_ERROR (1ULL << 21) -#define FUSE_MAX_PAGES (1ULL << 22) -#define FUSE_CACHE_SYMLINKS (1ULL << 23) -#define FUSE_NO_OPENDIR_SUPPORT (1ULL << 24) -#define FUSE_EXPLICIT_INVAL_DATA (1ULL << 25) -#define FUSE_MAP_ALIGNMENT (1ULL << 26) -#define FUSE_SUBMOUNTS (1ULL << 27) -#define FUSE_HANDLE_KILLPRIV_V2 (1ULL << 28) -#define FUSE_SETXATTR_EXT (1ULL << 29) -#define FUSE_INIT_EXT (1ULL << 30) -#define FUSE_INIT_RESERVED (1ULL << 31) +#define FUSE_ASYNC_READ (1 << 0) +#define FUSE_POSIX_LOCKS (1 << 1) +#define FUSE_FILE_OPS (1 << 2) +#define FUSE_ATOMIC_O_TRUNC (1 << 3) +#define FUSE_EXPORT_SUPPORT (1 << 4) +#define FUSE_BIG_WRITES (1 << 5) +#define FUSE_DONT_MASK (1 << 6) +#define FUSE_SPLICE_WRITE (1 << 7) +#define FUSE_SPLICE_MOVE (1 << 8) +#define FUSE_SPLICE_READ (1 << 9) +#define FUSE_FLOCK_LOCKS (1 << 10) +#define FUSE_HAS_IOCTL_DIR (1 << 11) +#define FUSE_AUTO_INVAL_DATA (1 << 12) +#define FUSE_DO_READDIRPLUS (1 << 13) +#define FUSE_READDIRPLUS_AUTO (1 << 14) +#define FUSE_ASYNC_DIO (1 << 15) +#define FUSE_WRITEBACK_CACHE (1 << 16) +#define FUSE_NO_OPEN_SUPPORT (1 << 17) +#define FUSE_PARALLEL_DIROPS (1 << 18) +#define FUSE_HANDLE_KILLPRIV (1 << 19) +#define FUSE_POSIX_ACL (1 << 20) +#define FUSE_ABORT_ERROR (1 << 21) +#define FUSE_MAX_PAGES (1 << 22) +#define FUSE_CACHE_SYMLINKS (1 << 23) +#define FUSE_NO_OPENDIR_SUPPORT (1 << 24) +#define FUSE_EXPLICIT_INVAL_DATA (1 << 25) +#define FUSE_MAP_ALIGNMENT (1 << 26) +#define FUSE_SUBMOUNTS (1 << 27) +#define FUSE_HANDLE_KILLPRIV_V2 (1 << 28) +#define FUSE_SETXATTR_EXT (1 << 29) +#define FUSE_INIT_EXT (1 << 30) +#define FUSE_INIT_RESERVED (1U << 31) /* bits 32..63 get shifted down 32 bits into the flags2 field */ #define FUSE_SECURITY_CTX (1ULL << 32) #define FUSE_HAS_INODE_DAX (1ULL << 33) @@ -477,11 +490,11 @@ struct fuse_file_lock { #define FUSE_PASSTHROUGH (1ULL << 37) #define FUSE_NO_EXPORT_SUPPORT (1ULL << 38) #define FUSE_HAS_RESEND (1ULL << 39) - /* Obsolete alias for FUSE_DIRECT_IO_ALLOW_MMAP */ #define FUSE_DIRECT_IO_RELAX FUSE_DIRECT_IO_ALLOW_MMAP #define FUSE_ALLOW_IDMAP (1ULL << 40) #define FUSE_OVER_IO_URING (1ULL << 41) +#define FUSE_REQUEST_TIMEOUT (1ULL << 42) /** * CUSE INIT request/reply flags @@ -649,6 +662,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, @@ -666,7 +680,8 @@ enum fuse_notify_code { FUSE_NOTIFY_RETRIEVE = 5, FUSE_NOTIFY_DELETE = 6, FUSE_NOTIFY_RESEND = 7, - FUSE_NOTIFY_CODE_MAX, + FUSE_NOTIFY_INC_EPOCH = 8, + FUSE_NOTIFY_PRUNE = 9, }; /* The read buffer is required to be at least 8k, but may be much larger */ @@ -909,7 +924,8 @@ struct fuse_init_out { uint16_t map_alignment; uint32_t flags2; uint32_t max_stack_depth; - uint32_t unused[6]; + uint16_t request_timeout; + uint16_t unused[11]; }; #define CUSE_INIT_INFO_MAX 4096 @@ -1104,6 +1120,12 @@ struct fuse_notify_retrieve_in { uint64_t dummy4; }; +struct fuse_notify_prune_out { + uint32_t count; + uint32_t padding; + uint64_t spare; +}; + struct fuse_backing_map { int32_t fd; uint32_t flags; @@ -1116,6 +1138,7 @@ struct fuse_backing_map { #define FUSE_DEV_IOC_BACKING_OPEN _IOW(FUSE_DEV_IOC_MAGIC, 1, \ struct fuse_backing_map) #define FUSE_DEV_IOC_BACKING_CLOSE _IOW(FUSE_DEV_IOC_MAGIC, 2, uint32_t) +#define FUSE_DEV_IOC_SYNC_INIT _IO(FUSE_DEV_IOC_MAGIC, 3) struct fuse_lseek_in { uint64_t fh; @@ -1138,6 +1161,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/vendored/libfuse/include/fuse_lowlevel.h b/vendored/libfuse/include/fuse_lowlevel.h index 3fe95f4f..f74f5050 100644 --- a/vendored/libfuse/include/fuse_lowlevel.h +++ b/vendored/libfuse/include/fuse_lowlevel.h @@ -271,6 +271,8 @@ int fuse_reply_open(fuse_req_t *req, */ int fuse_reply_write(fuse_req_t *req, size_t count); +int fuse_reply_copy_file_range_64(fuse_req_t *req, size_t count); + /** * Reply with data * diff --git a/vendored/libfuse/lib/debug.cpp b/vendored/libfuse/lib/debug.cpp index 4ea271ff..d9435627 100644 --- a/vendored/libfuse/lib/debug.cpp +++ b/vendored/libfuse/lib/debug.cpp @@ -226,7 +226,7 @@ _open_flag_to_str(const uint64_t offset_) #undef OPEN_FLAG_CASE -#define FUSE_INIT_FLAG_CASE(X) case FUSE_##X: return #X +#define FUSE_INIT_FLAG_CASE(X) case static_cast(FUSE_##X): return #X static const @@ -277,6 +277,7 @@ _fuse_init_flag_name(const uint64_t flag_) FUSE_INIT_FLAG_CASE(HAS_RESEND); FUSE_INIT_FLAG_CASE(ALLOW_IDMAP); FUSE_INIT_FLAG_CASE(OVER_IO_URING); + FUSE_INIT_FLAG_CASE(REQUEST_TIMEOUT); } return nullptr; @@ -1493,7 +1494,8 @@ _opcode_name(enum fuse_opcode op_) "REMOVEMAPPING", "SYNCFS", "TMPFILE", - "STATX" + "STATX", + "COPY_FILE_RANGE_64" }; if(op_ >= (sizeof(names) / sizeof(names[0]))) @@ -1653,6 +1655,7 @@ fuse_debug_in_header(const struct fuse_in_header *hdr_) _debug_fuse_lseek_in(arg); break; case FUSE_COPY_FILE_RANGE: + case FUSE_COPY_FILE_RANGE_64: _debug_fuse_copy_file_range_in(arg); break; case FUSE_TMPFILE: diff --git a/vendored/libfuse/lib/fuse.cpp b/vendored/libfuse/lib/fuse.cpp index a4810655..79993680 100644 --- a/vendored/libfuse/lib/fuse.cpp +++ b/vendored/libfuse/lib/fuse.cpp @@ -2711,8 +2711,17 @@ fuse_lib_copy_file_range(fuse_req_t *req_, fuse_file_info_t ffi_in = {}; fuse_file_info_t ffi_out = {}; const struct fuse_copy_file_range_in *arg; + struct fuse_copy_file_range_in arg_tmp; arg = (fuse_copy_file_range_in*)fuse_hdr_arg(hdr_); + if(((hdr_->opcode == FUSE_COPY_FILE_RANGE) || (sizeof(size_t) == 4)) && + (arg->len > 0xfffff000ULL)) + { + /* Avoid truncation in the old reply format or 32-bit size_t conversion. */ + arg_tmp = *arg; + arg_tmp.len = 0xfffff000ULL; + arg = &arg_tmp; + } ffi_in.fh = arg->fh_in; ffi_out.fh = arg->fh_out; @@ -2725,7 +2734,12 @@ fuse_lib_copy_file_range(fuse_req_t *req_, arg->flags); if(rv >= 0) - fuse_reply_write(req_,rv); + { + if(hdr_->opcode == FUSE_COPY_FILE_RANGE_64) + fuse_reply_copy_file_range_64(req_,rv); + else + fuse_reply_write(req_,rv); + } else fuse_reply_err(req_,rv); } diff --git a/vendored/libfuse/lib/fuse_lowlevel.cpp b/vendored/libfuse/lib/fuse_lowlevel.cpp index fee64814..c140eb5f 100644 --- a/vendored/libfuse/lib/fuse_lowlevel.cpp +++ b/vendored/libfuse/lib/fuse_lowlevel.cpp @@ -395,6 +395,17 @@ fuse_reply_write(fuse_req_t *req, return send_reply_ok(req, &arg, sizeof(arg)); } +int +fuse_reply_copy_file_range_64(fuse_req_t *req, + size_t count) +{ + struct fuse_copy_file_range_out arg = {}; + + arg.bytes_copied = count; + + return send_reply_ok(req, &arg, sizeof(arg)); +} + int fuse_reply_buf(fuse_req_t *req, const char *buf, @@ -1359,6 +1370,14 @@ do_copy_file_range(fuse_req_t *req_, f.op.copy_file_range(req_,hdr_); } +static +void +do_copy_file_range_64(fuse_req_t *req_, + struct fuse_in_header *hdr_) +{ + f.op.copy_file_range(req_,hdr_); +} + static void do_setupmapping(fuse_req_t *req_, @@ -1602,7 +1621,7 @@ fuse_lowlevel_notify_retrieve(struct fuse_session *se, return err; } -#define FUSE_OPCODE_LEN (FUSE_STATX + 1) +#define FUSE_OPCODE_LEN (FUSE_COPY_FILE_RANGE_64 + 1) typedef void (*fuse_ll_func)(fuse_req_t*, struct fuse_in_header *); const @@ -1661,7 +1680,8 @@ fuse_ll_funcs[FUSE_OPCODE_LEN] = do_removemapping, do_syncfs, do_tmpfile, - do_statx + do_statx, + do_copy_file_range_64 }; #define FUSE_MAXOPS (sizeof(fuse_ll_funcs) / sizeof(fuse_ll_funcs[0])) @@ -1757,6 +1777,7 @@ fuse_ll_buf_process_read(struct fuse_session *se_, req->ctx.umask = 0; req->conn = f.conn; req->se = se_; + req->ioctl_64bit = 0; err = ENOSYS; if(in->opcode >= FUSE_MAXOPS) @@ -1795,6 +1816,7 @@ fuse_ll_buf_process_read_init(struct fuse_session *se_, req->ctx.opcode = in->opcode; req->ctx.unique = in->unique; req->ctx.nodeid = in->nodeid; + req->ioctl_64bit = 0; req->ctx.uid = in->uid; req->ctx.gid = in->gid; req->ctx.pid = in->pid;