-
Notifications
You must be signed in to change notification settings - Fork 183
Add ReplyIoctl::retry for variable-size ioctls #676
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -655,12 +655,85 @@ impl ReplyIoctl { | |
| .send_ll(&ll::ResponseIoctl::new_ioctl(result, &[IoSlice::new(data)])); | ||
| } | ||
|
|
||
| /// Ask the kernel to retry the ioctl with the given userspace | ||
| /// iovecs. | ||
| /// | ||
| /// This is the FUSE_IOCTL_RETRY mechanism: when the size encoded | ||
| /// in an ioctl's `cmd` is too small to describe its real input or | ||
| /// output buffer (typically because the struct embeds a flexible | ||
| /// array, or the buffer is otherwise dynamically sized), the | ||
| /// driver responds with the iovecs describing what it actually | ||
| /// wants. The kernel re-issues the ioctl with | ||
| /// `FUSE_IOCTL_UNRESTRICTED` set; the new request's `in_data` is | ||
| /// the concatenation of `in_iovs` and the new `out_size` covers | ||
| /// `out_iovs`. | ||
| /// | ||
| /// `in_iovs` and `out_iovs` describe ranges in the *caller's* | ||
| /// userspace memory. The starting pointer is typically the `arg` | ||
| /// value passed to [`Filesystem::ioctl`](crate::Filesystem::ioctl), | ||
| /// plus any offset into the struct. | ||
| /// | ||
| /// The total number of entries (in_iovs + out_iovs) must be at | ||
| /// most [`FUSE_IOCTL_MAX_IOV`](crate::consts::FUSE_IOCTL_MAX_IOV). | ||
| /// | ||
| /// # Panics | ||
| /// | ||
| /// Panics if `in_iovs.len() + out_iovs.len() > FUSE_IOCTL_MAX_IOV`. | ||
| /// The kernel rejects oversized iovec arrays at runtime, so the | ||
| /// panic surfaces the same bug eagerly with a clearer message. | ||
| pub fn retry(self, in_iovs: &[IoctlIovec], out_iovs: &[IoctlIovec]) { | ||
| let total = in_iovs.len() + out_iovs.len(); | ||
| let max = crate::consts::FUSE_IOCTL_MAX_IOV as usize; | ||
| assert!( | ||
| total <= max, | ||
| "ReplyIoctl::retry: in_iovs ({}) + out_iovs ({}) = {} exceeds \ | ||
| FUSE_IOCTL_MAX_IOV ({max})", | ||
| in_iovs.len(), | ||
| out_iovs.len(), | ||
| total, | ||
| ); | ||
|
|
||
| let mut payload: Vec<u8> = Vec::with_capacity( | ||
| total * std::mem::size_of::<IoctlIovec>(), | ||
| ); | ||
| for iov in in_iovs.iter().chain(out_iovs.iter()) { | ||
| payload.extend_from_slice(&iov.base.to_ne_bytes()); | ||
| payload.extend_from_slice(&iov.len.to_ne_bytes()); | ||
| } | ||
| // Bounded by FUSE_IOCTL_MAX_IOV (256) above — the casts are | ||
| // infallible and clippy is wrong about them. | ||
| #[allow(clippy::cast_possible_truncation)] | ||
| let in_count = in_iovs.len() as u32; | ||
| #[allow(clippy::cast_possible_truncation)] | ||
| let out_count = out_iovs.len() as u32; | ||
| self.reply.send_ll(&ll::ResponseIoctl::new_retry( | ||
| in_count, | ||
| out_count, | ||
| &[IoSlice::new(&payload)], | ||
| )); | ||
|
Comment on lines
+709
to
+713
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Useful? React with 👍 / 👎. |
||
| } | ||
|
|
||
| /// Reply to a request with the given error code | ||
| pub fn error(self, err: Errno) { | ||
| self.reply.error(err); | ||
| } | ||
| } | ||
|
|
||
| /// Userspace memory range, used by [`ReplyIoctl::retry`] to describe | ||
| /// the buffers the FUSE driver wants the kernel to copy on retry. | ||
| /// | ||
| /// Mirrors the kernel's `struct fuse_ioctl_iovec`. `base` is a raw | ||
| /// pointer-as-u64 in the caller's address space; `len` is the byte | ||
| /// length. | ||
| #[derive(Debug, Clone, Copy)] | ||
| #[repr(C)] | ||
| pub struct IoctlIovec { | ||
| /// User-space pointer (as a `u64`) marking the start of the range. | ||
| pub base: u64, | ||
| /// Length of the range in bytes. | ||
| pub len: u64, | ||
| } | ||
|
|
||
| /// | ||
| /// Poll Reply | ||
| /// | ||
|
|
@@ -1258,6 +1331,66 @@ mod test { | |
| reply.data(&[0x11, 0x22, 0x33, 0x44]); | ||
| } | ||
|
|
||
| #[test] | ||
| fn reply_ioctl() { | ||
| // fuse_out_header(16) + fuse_ioctl_out(16) + 4-byte payload. | ||
| // Header length = 36 = 0x24. | ||
| let sender = ReplySender::Assert(AssertSender { | ||
| expected: vec![ | ||
| 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, | ||
| 0x00, 0x00, // fuse_out_header | ||
| 0x00, 0x00, 0x00, 0x00, // result = 0 | ||
| 0x00, 0x00, 0x00, 0x00, // flags = 0 | ||
| 0x01, 0x00, 0x00, 0x00, // in_iovs = 1 | ||
| 0x01, 0x00, 0x00, 0x00, // out_iovs = 1 (one IoSlice) | ||
| 0xde, 0xad, 0xbe, 0xef, // payload | ||
| ], | ||
| }); | ||
| let reply: ReplyIoctl = Reply::new(ll::RequestId(0xdeadbeef), sender); | ||
| reply.ioctl(0, &[0xde, 0xad, 0xbe, 0xef]); | ||
| } | ||
|
|
||
| #[test] | ||
| fn reply_ioctl_retry() { | ||
| // Retry with one in_iov and one out_iov, both pointing at | ||
| // the same address with len=0x1000. Header length = 16 (out | ||
| // header) + 16 (fuse_ioctl_out) + 2*16 (two iovecs) = 64 = 0x40. | ||
| let sender = ReplySender::Assert(AssertSender { | ||
| expected: vec![ | ||
| 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, | ||
| 0x00, 0x00, // fuse_out_header | ||
| 0x00, 0x00, 0x00, 0x00, // result = 0 | ||
| 0x04, 0x00, 0x00, 0x00, // flags = FUSE_IOCTL_RETRY (1 << 2) | ||
| 0x01, 0x00, 0x00, 0x00, // in_iovs = 1 | ||
| 0x01, 0x00, 0x00, 0x00, // out_iovs = 1 | ||
| // in_iov: base = 0xdeadbeef00, len = 0x1000 | ||
| 0x00, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, | ||
| 0x00, 0x00, // out_iov: base = 0xdeadbeef00, len = 0x1000 | ||
| 0x00, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, | ||
| 0x00, 0x00, | ||
| ], | ||
| }); | ||
| let reply: ReplyIoctl = Reply::new(ll::RequestId(0xdeadbeef), sender); | ||
| let iov = IoctlIovec { | ||
| base: 0xdead_beef_00, | ||
| len: 0x1000, | ||
| }; | ||
| reply.retry(&[iov], &[iov]); | ||
| } | ||
|
|
||
| #[test] | ||
| #[should_panic(expected = "exceeds FUSE_IOCTL_MAX_IOV")] | ||
| fn reply_ioctl_retry_panics_on_too_many_iovs() { | ||
| // Sender doesn't matter — we panic before any bytes are sent. | ||
| let (tx, _rx) = sync_channel::<()>(1); | ||
| let reply: ReplyIoctl = | ||
| Reply::new(ll::RequestId(0xdeadbeef), ReplySender::Sync(tx)); | ||
| // 257 in_iovs + 0 out_iovs > FUSE_IOCTL_MAX_IOV (256). | ||
| let iov = IoctlIovec { base: 0, len: 0 }; | ||
| let too_many = vec![iov; 257]; | ||
| reply.retry(&too_many, &[]); | ||
| } | ||
|
|
||
| #[test] | ||
| fn async_reply() { | ||
| let (tx, rx) = sync_channel::<()>(1); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding
arg: u64toFilesystem::ioctlchanges the trait method arity, but the bundled implementation inexamples/ioctl.rsstill uses the old signature (missingargbetweencmdandin_data). As a result,cargo build --exampleswill 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 👍 / 👎.