Skip to content

Add ReplyIoctl::retry for variable-size ioctls#676

Open
xfbs wants to merge 2 commits intocberner:masterfrom
xfbs:add-ioctl-retry-api
Open

Add ReplyIoctl::retry for variable-size ioctls#676
xfbs wants to merge 2 commits intocberner:masterfrom
xfbs:add-ioctl-retry-api

Conversation

@xfbs
Copy link
Copy Markdown

@xfbs xfbs commented Apr 27, 2026

Hey y'all, I'm dropping a small PR here that I'm hoping could be useful to others. The TL;DR is that fuser currently 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 via fuser. This PR adds the mechanism that would unblock me. It's a breaking change. But I noticed mount2 was renamed to mount on master, so I figured it might be okay to land another break — and this one takes fuser from "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:

ioctl why it doesn't fit
BTRFS_IOC_TREE_SEARCH_V2 flexible buf[0] at end of args
BTRFS_IOC_LOGICAL_INO_V2 inodes[] buffer
BTRFS_IOC_INO_PATHS paths[] buffer
BTRFS_IOC_GET_SUBVOL_ROOTREF 69 KiB fixed struct

This PR exposes the existing kernel/protocol mechanism through a clean reply API.

API additions

  • IoctlIovec { base: u64, len: u64 } — public mirror of struct fuse_ioctl_iovec. Drivers describe ranges in the caller's userspace memory.
  • ReplyIoctl::retry(in_iovs, out_iovs) — serialises the iovec arrays, sets FUSE_IOCTL_RETRY in the response flags, and sends. The kernel re-issues the ioctl with FUSE_IOCTL_UNRESTRICTED set, the new in_data populated from in_iovs, and out_size matching out_iovs.

Breaking changes

  1. Filesystem::ioctl gains arg: 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.

  2. 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 override Filesystem::ioctl should return ENOTTY for unrecognised cmds (matches the convention for all other ioctl error paths).

Implementation

  • fuse_ioctl_iovec gains IntoBytes so the iovec array can be serialised into the response payload via the existing IoSlice path. No new sender machinery.
  • IoCtl::arg_ptr exposes fuse_ioctl_in.arg so the dispatch site can pass it through.
  • consts::FUSE_IOCTL_{COMPAT,UNRESTRICTED,RETRY,DIR} exposed; previously only FUSE_IOCTL_MAX_IOV was there.

Tests

  • reply_ioctl — sanity check on the existing ReplyIoctl::ioctl path (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 (with flags=FUSE_IOCTL_RETRY) + concatenated fuse_ioctl_iovec entries.

Both pass; existing 53 reply tests remain green.

Used by

The btrfsutils project's btrfs-fuse driver, 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.

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.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread src/lib.rs
fh: FileHandle,
flags: IoctlFlags,
cmd: u32,
arg: u64,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Comment thread src/reply.rs Outdated
Comment on lines +686 to +687
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");
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread src/reply.rs
Comment on lines +709 to +713
self.reply.send_ll(&ll::ResponseIoctl::new_retry(
in_count,
out_count,
&[IoSlice::new(&payload)],
));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant