Add ReplyIoctl::retry for variable-size ioctls#676
Add ReplyIoctl::retry for variable-size ioctls#676xfbs wants to merge 2 commits intocberner:masterfrom
Conversation
Exposes the FUSE_IOCTL_RETRY response so drivers can describe input/output buffers larger than the 14-bit size field in the ioctl number can encode (e.g. BTRFS_IOC_TREE_SEARCH_V2). Adds a public IoctlIovec type, ReplyIoctl::retry(in_iovs, out_iovs), and an arg: u64 parameter on Filesystem::ioctl carrying the userspace pointer the iovecs are described relative to. The session no longer rejects unrestricted ioctls upfront — the driver decides.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 37bfb7fdc7
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| fh: FileHandle, | ||
| flags: IoctlFlags, | ||
| cmd: u32, | ||
| arg: u64, |
There was a problem hiding this comment.
Update in-tree ioctl example for new arg parameter
Adding arg: u64 to Filesystem::ioctl changes the trait method arity, but the bundled implementation in examples/ioctl.rs still uses the old signature (missing arg between cmd and in_data). As a result, cargo build --examples will fail with a trait-method mismatch (E0050), so the project’s own ioctl example no longer compiles or serves as a valid migration reference.
Useful? React with 👍 / 👎.
| let in_count = in_iovs.len().try_into().expect("Too many in_iovs"); | ||
| let out_count = out_iovs.len().try_into().expect("Too many out_iovs"); |
There was a problem hiding this comment.
Enforce FUSE_IOCTL_MAX_IOV bound in retry replies
ReplyIoctl::retry documents that in_iovs + out_iovs must not exceed FUSE_IOCTL_MAX_IOV, but the implementation only converts each length to u32 and never checks the protocol limit. This allows callers to emit an invalid retry response with too many iovecs, which will fail at runtime even though the API contract says this bound is required.
Useful? React with 👍 / 👎.
Update examples/ioctl.rs for the new arg parameter so the in-tree example compiles and serves as a migration reference. Enforce the FUSE_IOCTL_MAX_IOV bound in ReplyIoctl::retry with an assertion that names the offending counts when violated. Remove the now-dead IoCtl::unrestricted helper that became unused when the upfront ENOSYS reject was dropped.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e7626f31d4
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| self.reply.send_ll(&ll::ResponseIoctl::new_retry( | ||
| in_count, | ||
| out_count, | ||
| &[IoSlice::new(&payload)], | ||
| )); |
There was a problem hiding this comment.
Gate retry replies to unrestricted ioctl mode
ReplyIoctl::retry always emits FUSE_IOCTL_RETRY, but Linux only accepts retry when the request came in with FUSE_IOCTL_UNRESTRICTED; otherwise the kernel aborts with -EIO instead of reissuing the ioctl. In the normal FUSE path, ioctl requests are restricted (kernel fuse_file_ioctl/fuse_file_compat_ioctl do not set UNRESTRICTED), and this repo still does not handle CUSE_INIT (src/request.rs returns ENOSYS), so users following this new API for filesystem ioctls will get runtime failures rather than a retry round-trip.
Useful? React with 👍 / 👎.
Hey y'all, I'm dropping a small PR here that I'm hoping could be useful to others. The TL;DR is that
fusercurrently doesn't handle variably-sized ioctls, but I need that because I'm implementing a userspace btrfs driver in Rust (btrfsutils) and want it to mount viafuser. This PR adds the mechanism that would unblock me. It's a breaking change. But I noticedmount2was renamed tomounton master, so I figured it might be okay to land another break — and this one takesfuserfrom "we can implement any fixed-size ioctl" to "we can implement any ioctl at all", which sounds like a good thing.Motivation
The FUSE_IOCTL_RETRY response mechanism lets a driver tell the kernel "the size encoded in the ioctl number doesn't describe my real input/output buffers, please re-issue with these userspace ranges". Without it, FUSE silently truncates input and output to the cap that fits in the ioctl number's 14-bit size field (~16 KiB), which breaks any ioctl whose struct embeds a flexible array or otherwise exceeds that cap.
Btrfs alone has four:
BTRFS_IOC_TREE_SEARCH_V2buf[0]at end of argsBTRFS_IOC_LOGICAL_INO_V2inodes[]bufferBTRFS_IOC_INO_PATHSpaths[]bufferBTRFS_IOC_GET_SUBVOL_ROOTREFThis PR exposes the existing kernel/protocol mechanism through a clean reply API.
API additions
IoctlIovec { base: u64, len: u64 }— public mirror ofstruct fuse_ioctl_iovec. Drivers describe ranges in the caller's userspace memory.ReplyIoctl::retry(in_iovs, out_iovs)— serialises the iovec arrays, setsFUSE_IOCTL_RETRYin the response flags, and sends. The kernel re-issues the ioctl withFUSE_IOCTL_UNRESTRICTEDset, the newin_datapopulated fromin_iovs, andout_sizematchingout_iovs.Breaking changes
Filesystem::ioctlgainsarg: u64— the userspace pointer the ioctl was called with (fuse_ioctl_in.arg). Drivers describe their iovec base pointers as offsets into this. Migration is one parameter added in the right place; the default impl gained the same parameter.Unrestricted-ioctl ENOSYS reject removed. Previously the session short-circuited unrestricted ioctls before calling the driver. With
retry()available the driver itself decides — drivers that overrideFilesystem::ioctlshould returnENOTTYfor unrecognised cmds (matches the convention for all other ioctl error paths).Implementation
fuse_ioctl_iovecgainsIntoBytesso the iovec array can be serialised into the response payload via the existingIoSlicepath. No new sender machinery.IoCtl::arg_ptrexposesfuse_ioctl_in.argso the dispatch site can pass it through.consts::FUSE_IOCTL_{COMPAT,UNRESTRICTED,RETRY,DIR}exposed; previously onlyFUSE_IOCTL_MAX_IOVwas there.Tests
reply_ioctl— sanity check on the existingReplyIoctl::ioctlpath (didn't have a dedicated test before).reply_ioctl_retry— asserts the retry response wire bytes match the kernel's expected layout:fuse_out_header+fuse_ioctl_out(withflags=FUSE_IOCTL_RETRY) + concatenatedfuse_ioctl_iovecentries.Both pass; existing 53 reply tests remain green.
Used by
The btrfsutils project's
btrfs-fusedriver, which uses this to expose btrfs's read-only ioctls (TREE_SEARCH_V2,INO_PATHS,GET_SUBVOL_ROOTREF,LOGICAL_INO_V2) to userspace tools running against a fuse mount.