Skip to main content

async_fuser/ll/
request.rs

1//! Low-level filesystem operation request.
2//!
3//! A request represents information about a filesystem operation the kernel driver wants us to
4//! perform.
5
6use std::convert::TryFrom;
7use std::error;
8use std::fmt;
9use std::fmt::Display;
10use std::fmt::Formatter;
11use std::path::Path;
12
13use log::warn;
14use nix::unistd::Gid;
15use nix::unistd::Pid;
16use nix::unistd::Uid;
17#[cfg(feature = "serializable")]
18use serde::Deserialize;
19#[cfg(feature = "serializable")]
20use serde::Serialize;
21
22use crate::Errno;
23use crate::ll::argument::ArgumentIterator;
24use crate::ll::fuse_abi as abi;
25use crate::ll::fuse_abi::fuse_in_header;
26use crate::ll::fuse_abi::fuse_opcode;
27
28/// Error that may occur while reading and parsing a request from the kernel driver.
29#[derive(Debug)]
30pub(crate) enum RequestError {
31    /// Not enough data for parsing header (short read).
32    ShortReadHeader(usize),
33    /// Kernel requested an unknown operation.
34    UnknownOperation(u32),
35    /// Not enough data for arguments (short read).
36    ShortRead(usize, usize),
37    /// Insufficient argument data.
38    InsufficientData,
39}
40
41/// Unique ID for a request from the kernel
42///
43/// The FUSE kernel driver assigns a unique id to every concurrent request. This allows to
44/// distinguish between multiple concurrent requests. The unique id of a request may be
45/// reused in later requests after it has completed.
46///
47/// This can be retrieve for any request using `Request::unique`.  The kernel
48/// will send an `Interrupt` request to cancel requests in progress.  It's
49/// important to handle this for any requests that may block indefinitely, like
50/// `SetLkW`.
51#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
52#[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))]
53pub struct RequestId(pub u64);
54impl From<RequestId> for u64 {
55    fn from(fh: RequestId) -> Self {
56        fh.0
57    }
58}
59
60/// A newtype for inode numbers
61///
62/// These are generated by the filesystem implementation and returned to the
63/// kernel in response to a call to `Lookup`, `Create`, `MkNod`, `MkDir` or
64/// `SymLink`.  The kernel will then pass these numbers back to the filesystem
65/// implementation when it needs to refer to a given file.  Every request has
66/// an associated [`INodeNo`], accessible as `Request::nodeid`.
67///
68/// Reference Counting
69/// ------------------
70///
71/// Every time the kernel receives a given inode number in a response to a
72/// `Lookup`, `Create`, `MkNod`, `MkDir` or `SymLink` request it increments an
73/// internal counter for that inode.  The filesystem implementation should do
74/// the same.  When the kernel is no longer interested in this inode it will
75/// send a `Forget` message with that counter.  The filesystem implementation
76/// should decrement its own counter and if it reaches 0 then the inode number
77/// may be recycled and your filesystem implementation may clean up its
78/// internal data-structures relating to that inode.
79#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
80#[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))]
81pub struct INodeNo(pub u64);
82
83impl INodeNo {
84    /// Filesystem root inode number.
85    pub const ROOT: INodeNo = INodeNo(1);
86}
87
88impl From<INodeNo> for u64 {
89    fn from(fh: INodeNo) -> Self {
90        fh.0
91    }
92}
93
94impl Display for INodeNo {
95    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
96        Display::fmt(&self.0, f)
97    }
98}
99
100/// A newtype for file handles
101///
102/// This corresponds to a single file description in a client program.  These
103/// are generated by the filesystem implementation in replies to `Open`,
104/// `OpenDir` and `Create` requests.  It's used as a correlation id across
105/// `Read`, `Write`, `FSync`, `IoCtl`, `Poll`, `FAllocate`, `ReadDir`,
106/// `FSyncDir`, `GetLk`, `SetLk`, `SetLkW`, `ReadDirPlus`, `Lseek` and
107/// `CopyFileRange` requests.
108///
109/// A filesystem implementation may store arbitrary data as the [`FileHandle`], as
110/// long as it fits into 64-bits and doesn't need to change for over the lifetime
111/// of the [`FileHandle`].  Typically this might consist of an index into an array
112/// of [`FileHandle`]s that the filesystem implementation maintains.
113///
114/// Filesystems may instead implement stateless file I/O and use `0` as the
115/// [`FileHandle`] - although this makes it impossible to correctly implement
116/// resumable `ReadDir` in the presence of mutable directories (see `OpenDir`).
117///
118/// Lifecycle
119/// ---------
120///
121/// A [`FileHandle`] is owned by one or more file-descriptors (or memory
122/// mappings) in the client program.  Multiple file descriptors can point to
123/// the same [`FileHandle`], just as a single `INode` can have multiple
124/// [`FileHandle`]s open at one time.  Every time a single file-descriptor is
125/// closed a `Flush` request is made.  This gives filesystem implementations
126/// an opportunity to return an error message from that `close()` call.  After
127/// all the file-descriptors are closed that own a given [`FileHandle`] the
128/// `Release`/`ReleaseDir` request will be made.  This is an opportunity for
129/// the filesystem implementation to free any internal per-FileHandle data
130/// structures it has allocated.
131#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
132#[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))]
133pub struct FileHandle(pub u64);
134
135impl From<FileHandle> for u64 {
136    fn from(fh: FileHandle) -> Self {
137        fh.0
138    }
139}
140
141impl Display for FileHandle {
142    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
143        Display::fmt(&self.0, f)
144    }
145}
146
147/// A newtype for lock owners
148///
149/// TODO: Document lock lifecycle and how and when to implement file locking.
150///
151/// See `Read`, `Write`, `Release`, `Flush`, `GetLk`, `SetLk`, `SetLkW`.
152#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
153#[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))]
154pub struct LockOwner(pub u64);
155
156impl Display for LockOwner {
157    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
158        Display::fmt(&self.0, f)
159    }
160}
161
162#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
163pub(crate) struct Lock {
164    // Unfortunately this can't be a std::ops::Range because Range is not Copy:
165    // https://github.com/rust-lang/rfcs/issues/2848
166    pub(crate) range: (u64, u64),
167    // TODO: Make typ an enum
168    pub(crate) typ: i32,
169    pub(crate) pid: u32,
170}
171impl Lock {
172    fn from_abi(x: &abi::fuse_file_lock) -> Lock {
173        Lock {
174            range: (x.start, x.end),
175            typ: x.typ,
176            pid: x.pid,
177        }
178    }
179}
180
181/// A newtype for ABI version
182#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
183#[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))]
184pub struct Version(
185    /// Major version number.
186    pub u32,
187    /// Minor version number.
188    pub u32,
189);
190
191impl Display for Version {
192    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
193        write!(f, "{}.{}", self.0, self.1)
194    }
195}
196
197/// Represents a filename in a directory
198#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
199pub(crate) struct FilenameInDir<'a> {
200    /// The Inode number of the directory
201    pub(crate) dir: INodeNo,
202    /// Name of the file. This refers to a name directly in this directory, rather than any
203    /// subdirectory so is guaranteed not to contain '\0' or '/'.  It may be literally "." or ".."
204    /// however.
205    pub(crate) name: &'a Path,
206}
207
208impl fmt::Display for RequestError {
209    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
210        match self {
211            RequestError::ShortReadHeader(len) => write!(
212                f,
213                "Short read of FUSE request header ({len} < {})",
214                size_of::<fuse_in_header>()
215            ),
216            RequestError::UnknownOperation(opcode) => write!(f, "Unknown FUSE opcode ({opcode})"),
217            RequestError::ShortRead(len, total) => {
218                write!(f, "Short read of FUSE request ({len} < {total})")
219            }
220            RequestError::InsufficientData => write!(f, "Insufficient argument data"),
221        }
222    }
223}
224
225impl error::Error for RequestError {}
226
227fn validate_off_t(name: &'static str, value: u64) -> Result<(), Errno> {
228    // Value is unsigned, and libfuse silently casts it to signed `off_t`.
229    // The value is supposed to be in range `0..=i64::MAX`, so validate.
230    if value > i64::MAX as u64 {
231        warn!("{name}={value} is out of range");
232        return Err(Errno::EINVAL);
233    }
234    Ok(())
235}
236
237mod op {
238    use std::cmp;
239    use std::convert::TryInto;
240    use std::ffi::OsStr;
241    use std::fmt::Display;
242    use std::mem::offset_of;
243    use std::num::NonZeroU32;
244    use std::path::Path;
245    use std::time::SystemTime;
246
247    use zerocopy::FromZeros;
248    use zerocopy::IntoBytes;
249
250    use crate::AccessFlags;
251    use crate::CopyFileRangeFlags;
252    use crate::Errno;
253    use crate::IoctlFlags;
254    use crate::OpenFlags;
255    use crate::PollEvents;
256    use crate::PollFlags;
257    use crate::PollHandle;
258    use crate::WriteFlags;
259    use crate::bsd_file_flags::BsdFileFlags;
260    use crate::ll::ResponseData;
261    use crate::ll::TimeOrNow;
262    use crate::ll::argument::ArgumentIterator;
263    use crate::ll::flags::fattr_flags::FattrFlags;
264    use crate::ll::flags::fsync_flags::FsyncFlags;
265    use crate::ll::flags::getattr_flags::GetattrFlags;
266    use crate::ll::flags::init_flags::InitFlags;
267    use crate::ll::flags::read_flags::ReadFlags;
268    use crate::ll::flags::release_flags::ReleaseFlags;
269    use crate::ll::request::FileHandle;
270    use crate::ll::request::FilenameInDir;
271    use crate::ll::request::INodeNo;
272    use crate::ll::request::Lock;
273    use crate::ll::request::LockOwner;
274    use crate::ll::request::Operation;
275    use crate::ll::request::RequestId;
276    use crate::ll::request::abi::*;
277    use crate::ll::request::validate_off_t;
278    use crate::time::system_time_from_time;
279
280    /// Look up a directory entry by name and get its attributes.
281    ///
282    /// Implementations allocate and assign [`INodeNo`]s in this request.  Learn more
283    /// about `INode` lifecycle and the relationship between [`Lookup`] and [`Forget`] in the
284    /// documentation for [`INodeNo`].
285    #[derive(Debug)]
286    pub(crate) struct Lookup<'a> {
287        #[expect(dead_code)]
288        header: &'a fuse_in_header,
289        name: &'a OsStr,
290    }
291    impl<'a> Lookup<'a> {
292        pub(crate) fn name(&self) -> &'a Path {
293            self.name.as_ref()
294        }
295    }
296    /// Forget about an inode.
297    ///
298    /// The `nlookup` parameter indicates the number of lookups previously performed on
299    /// this inode. If the filesystem implements inode lifetimes, it is recommended that
300    /// inodes acquire a single reference on each lookup, and lose nlookup references on
301    /// each forget. The filesystem may ignore forget calls, if the inodes don't need to
302    /// have a limited lifetime.
303    ///
304    /// Learn more about `INode` lifecycle in the documentation for [`INodeNo`].
305    ///
306    /// On unmount it is not guaranteed, that all referenced inodes will receive a forget
307    /// message.
308    #[derive(Debug)]
309    pub(crate) struct Forget<'a> {
310        #[expect(dead_code)]
311        header: &'a fuse_in_header,
312        arg: &'a fuse_forget_in,
313    }
314    impl Forget<'_> {
315        /// The number of lookups previously performed on this inode
316        pub(crate) fn nlookup(&self) -> u64 {
317            self.arg.nlookup
318        }
319    }
320
321    /// Get file attributes.
322    #[derive(Debug)]
323    pub(crate) struct GetAttr<'a> {
324        #[expect(dead_code)]
325        header: &'a fuse_in_header,
326        arg: &'a fuse_getattr_in,
327    }
328
329    impl GetAttr<'_> {
330        /// Returns the getattr flags.
331        pub(crate) fn getattr_flags(&self) -> GetattrFlags {
332            GetattrFlags::from_bits_retain(self.arg.getattr_flags)
333        }
334
335        pub(crate) fn file_handle(&self) -> Option<FileHandle> {
336            if self.getattr_flags().contains(GetattrFlags::FUSE_GETATTR_FH) {
337                Some(FileHandle(self.arg.fh))
338            } else {
339                None
340            }
341        }
342    }
343
344    /// Set file attributes.
345    #[derive(Debug)]
346    pub(crate) struct SetAttr<'a> {
347        #[expect(dead_code)]
348        header: &'a fuse_in_header,
349        arg: &'a fuse_setattr_in,
350    }
351    impl SetAttr<'_> {
352        fn valid(&self) -> FattrFlags {
353            FattrFlags::from_bits_retain(self.arg.valid)
354        }
355        pub(crate) fn mode(&self) -> Option<u32> {
356            if self.valid().contains(FattrFlags::FATTR_MODE) {
357                Some(self.arg.mode)
358            } else {
359                None
360            }
361        }
362        pub(crate) fn uid(&self) -> Option<u32> {
363            if self.valid().contains(FattrFlags::FATTR_UID) {
364                Some(self.arg.uid)
365            } else {
366                None
367            }
368        }
369        pub(crate) fn gid(&self) -> Option<u32> {
370            if self.valid().contains(FattrFlags::FATTR_GID) {
371                Some(self.arg.gid)
372            } else {
373                None
374            }
375        }
376        pub(crate) fn size(&self) -> Option<u64> {
377            if self.valid().contains(FattrFlags::FATTR_SIZE) {
378                Some(self.arg.size)
379            } else {
380                None
381            }
382        }
383        pub(crate) fn atime(&self) -> Option<TimeOrNow> {
384            if self.valid().contains(FattrFlags::FATTR_ATIME) {
385                Some(if self.arg.atime_now() {
386                    TimeOrNow::Now
387                } else {
388                    TimeOrNow::SpecificTime(system_time_from_time(
389                        self.arg.atime,
390                        self.arg.atimensec,
391                    ))
392                })
393            } else {
394                None
395            }
396        }
397        pub(crate) fn mtime(&self) -> Option<TimeOrNow> {
398            if self.valid().contains(FattrFlags::FATTR_MTIME) {
399                Some(if self.arg.mtime_now() {
400                    TimeOrNow::Now
401                } else {
402                    TimeOrNow::SpecificTime(system_time_from_time(
403                        self.arg.mtime,
404                        self.arg.mtimensec,
405                    ))
406                })
407            } else {
408                None
409            }
410        }
411        pub(crate) fn ctime(&self) -> Option<SystemTime> {
412            if self.valid().contains(FattrFlags::FATTR_CTIME) {
413                Some(system_time_from_time(self.arg.ctime, self.arg.ctimensec))
414            } else {
415                None
416            }
417        }
418        /// The value set by the [`Open`] method. See [`FileHandle`].
419        ///
420        /// This will only be set if the user passed a file-descriptor to set the
421        /// attributes - i.e. they used [`libc::fchmod`] rather than [`libc::chmod`].
422        pub(crate) fn file_handle(&self) -> Option<FileHandle> {
423            if self.valid().contains(FattrFlags::FATTR_FH) {
424                Some(FileHandle(self.arg.fh))
425            } else {
426                None
427            }
428        }
429        pub(crate) fn crtime(&self) -> Option<SystemTime> {
430            #[cfg(target_os = "macos")]
431            if self.valid().contains(FattrFlags::FATTR_CRTIME) {
432                // During certain operation, macOS use some helper that send request to the mountpoint with `crtime` set to 0xffffffff83da4f80.
433                // That value correspond to `-2_082_844_800u64` which is the difference between the date 1904-01-01 and 1970-01-01 because macOS epoch start at 1904 and not 1970.
434                // https://github.com/macfuse/macfuse/issues/1042
435                if self.arg.crtime == 0xffffffff83da4f80 {
436                    None
437                } else {
438                    Some(
439                        SystemTime::UNIX_EPOCH
440                            + std::time::Duration::new(self.arg.crtime, self.arg.crtimensec),
441                    )
442                }
443            } else {
444                None
445            }
446            #[cfg(not(target_os = "macos"))]
447            None
448        }
449        pub(crate) fn chgtime(&self) -> Option<SystemTime> {
450            #[cfg(target_os = "macos")]
451            if self.valid().contains(FattrFlags::FATTR_CHGTIME) {
452                Some(
453                    SystemTime::UNIX_EPOCH
454                        + std::time::Duration::new(self.arg.chgtime, self.arg.chgtimensec),
455                )
456            } else {
457                None
458            }
459            #[cfg(not(target_os = "macos"))]
460            None
461        }
462        pub(crate) fn bkuptime(&self) -> Option<SystemTime> {
463            #[cfg(target_os = "macos")]
464            if self.valid().contains(FattrFlags::FATTR_BKUPTIME) {
465                Some(
466                    SystemTime::UNIX_EPOCH
467                        + std::time::Duration::new(self.arg.bkuptime, self.arg.bkuptimensec),
468                )
469            } else {
470                None
471            }
472            #[cfg(not(target_os = "macos"))]
473            None
474        }
475        pub(crate) fn flags(&self) -> Option<BsdFileFlags> {
476            #[cfg(target_os = "macos")]
477            if self.valid().contains(FattrFlags::FATTR_FLAGS) {
478                Some(BsdFileFlags::from_bits_retain(self.arg.flags))
479            } else {
480                None
481            }
482            #[cfg(not(target_os = "macos"))]
483            None
484        }
485
486        // TODO: Why does *set*attr want to have an attr response?
487    }
488    impl Display for SetAttr<'_> {
489        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
490            write!(
491                f,
492                "SETATTR mode: {:?}, uid: {:?}, gid: {:?}, size: {:?}, atime: {:?}, \
493                mtime: {:?}, ctime: {:?}, file_handle: {:?}, crtime: {:?}, chgtime: {:?}, \
494                bkuptime: {:?}, flags: {:?}",
495                self.mode(),
496                self.uid(),
497                self.gid(),
498                self.size(),
499                self.atime(),
500                self.mtime(),
501                self.ctime(),
502                self.file_handle(),
503                self.crtime(),
504                self.chgtime(),
505                self.bkuptime(),
506                self.flags()
507            )
508        }
509    }
510
511    /// Read symbolic link.
512    #[derive(Debug)]
513    pub(crate) struct ReadLink<'a> {
514        #[expect(dead_code)]
515        header: &'a fuse_in_header,
516    }
517
518    /// Create a symbolic link.
519    #[derive(Debug)]
520    pub(crate) struct SymLink<'a> {
521        #[expect(dead_code)]
522        header: &'a fuse_in_header,
523        target: &'a Path,
524        link_name: &'a Path,
525    }
526    impl<'a> SymLink<'a> {
527        pub(crate) fn target(&self) -> &'a Path {
528            self.target
529        }
530        pub(crate) fn link_name(&self) -> &'a Path {
531            self.link_name
532        }
533    }
534
535    /// Create file node.
536    /// Create a regular file, character device, block device, fifo or socket node.
537    #[derive(Debug)]
538    pub(crate) struct MkNod<'a> {
539        #[expect(dead_code)]
540        header: &'a fuse_in_header,
541        arg: &'a fuse_mknod_in,
542        name: &'a Path,
543    }
544    impl<'a> MkNod<'a> {
545        pub(crate) fn name(&self) -> &'a Path {
546            self.name
547        }
548        pub(crate) fn mode(&self) -> u32 {
549            self.arg.mode
550        }
551        pub(crate) fn umask(&self) -> u32 {
552            self.arg.umask
553        }
554        pub(crate) fn rdev(&self) -> u32 {
555            self.arg.rdev
556        }
557    }
558
559    /// Create a directory.
560    #[derive(Debug)]
561    pub(crate) struct MkDir<'a> {
562        #[expect(dead_code)]
563        header: &'a fuse_in_header,
564        arg: &'a fuse_mkdir_in,
565        name: &'a Path,
566    }
567    impl<'a> MkDir<'a> {
568        pub(crate) fn name(&self) -> &'a Path {
569            self.name
570        }
571        pub(crate) fn mode(&self) -> u32 {
572            self.arg.mode
573        }
574        pub(crate) fn umask(&self) -> u32 {
575            self.arg.umask
576        }
577    }
578
579    /// Remove a file.
580    #[derive(Debug)]
581    pub(crate) struct Unlink<'a> {
582        #[expect(dead_code)]
583        header: &'a fuse_in_header,
584        name: &'a Path,
585    }
586    impl<'a> Unlink<'a> {
587        pub(crate) fn name(&self) -> &'a Path {
588            self.name
589        }
590    }
591
592    /// Remove a directory.
593    #[derive(Debug)]
594    pub(crate) struct RmDir<'a> {
595        #[expect(dead_code)]
596        header: &'a fuse_in_header,
597        pub(crate) name: &'a Path,
598    }
599    impl<'a> RmDir<'a> {
600        pub(crate) fn name(&self) -> &'a Path {
601            self.name
602        }
603    }
604
605    /// Rename a file.
606    #[derive(Debug)]
607    pub(crate) struct Rename<'a> {
608        header: &'a fuse_in_header,
609        arg: &'a fuse_rename_in,
610        name: &'a Path,
611        newname: &'a Path,
612    }
613    impl<'a> Rename<'a> {
614        pub(crate) fn src(&self) -> FilenameInDir<'a> {
615            FilenameInDir::<'a> {
616                dir: INodeNo(self.header.nodeid),
617                name: self.name,
618            }
619        }
620        pub(crate) fn dest(&self) -> FilenameInDir<'a> {
621            FilenameInDir::<'a> {
622                dir: INodeNo(self.arg.newdir),
623                name: self.newname,
624            }
625        }
626    }
627
628    /// Create a hard link.
629    #[derive(Debug)]
630    pub(crate) struct Link<'a> {
631        header: &'a fuse_in_header,
632        arg: &'a fuse_link_in,
633        name: &'a Path,
634    }
635    impl<'a> Link<'a> {
636        /// This is the inode no of the file to be linked.  The inode number in
637        /// the fuse header is of the directory that it will be linked into.
638        pub(crate) fn inode_no(&self) -> INodeNo {
639            INodeNo(self.arg.oldnodeid)
640        }
641        pub(crate) fn dest(&self) -> FilenameInDir<'a> {
642            FilenameInDir::<'a> {
643                dir: INodeNo(self.header.nodeid),
644                name: self.name,
645            }
646        }
647    }
648
649    /// Open a file.
650    ///
651    /// Open flags (with the exception of `O_CREAT`, `O_EXCL`, `O_NOCTTY` and `O_TRUNC`) are
652    /// available in flags. Filesystem may store an arbitrary file handle (pointer, index,
653    /// etc) in fh, and use this in other all other file operations (read, write, flush,
654    /// release, fsync). Filesystem may also implement stateless file I/O and not store
655    /// anything in fh. There are also some flags (`direct_io`, `keep_cache`) which the
656    /// filesystem may set, to change the way the file is opened. See `fuse_file_info`
657    /// structure in <`fuse_common.h`> for more details.
658    #[derive(Debug)]
659    pub(crate) struct Open<'a> {
660        #[expect(dead_code)]
661        header: &'a fuse_in_header,
662        arg: &'a fuse_open_in,
663    }
664    impl Open<'_> {
665        pub(crate) fn flags(&self) -> OpenFlags {
666            OpenFlags(self.arg.flags)
667        }
668    }
669
670    /// Read data.
671    ///
672    /// Read should send exactly the number of bytes requested except on EOF or error,
673    /// otherwise the rest of the data will be substituted with zeroes. An exception to
674    /// this is when the file has been opened in `direct_io` mode, in which case the
675    /// return value of the read system call will reflect the return value of this
676    /// operation.
677    #[derive(Debug)]
678    pub(crate) struct Read<'a> {
679        #[expect(dead_code)]
680        header: &'a fuse_in_header,
681        arg: &'a fuse_read_in,
682    }
683    impl Read<'_> {
684        /// The value set by the [Open] method.
685        pub(crate) fn file_handle(&self) -> FileHandle {
686            FileHandle(self.arg.fh)
687        }
688        pub(crate) fn offset(&self) -> Result<u64, Errno> {
689            validate_off_t("fuse_read_in.arg.offset", self.arg.offset)?;
690            Ok(self.arg.offset)
691        }
692        pub(crate) fn size(&self) -> u32 {
693            self.arg.size
694        }
695        /// Only supported with ABI >= 7.9
696        pub(crate) fn lock_owner(&self) -> Option<LockOwner> {
697            if self.read_flags().contains(ReadFlags::FUSE_READ_LOCKOWNER) {
698                Some(LockOwner(self.arg.lock_owner))
699            } else {
700                None
701            }
702        }
703        /// The file flags, such as `O_SYNC`. Only supported with ABI >= 7.9
704        pub(crate) fn flags(&self) -> OpenFlags {
705            OpenFlags(self.arg.flags)
706        }
707        pub(crate) fn read_flags(&self) -> ReadFlags {
708            ReadFlags::from_bits_retain(self.arg.read_flags)
709        }
710    }
711
712    /// Write data.
713    ///
714    /// Write should return exactly the number of bytes requested except on error. An
715    /// exception to this is when the file has been opened in `direct_io` mode, in
716    /// which case the return value of the write system call will reflect the return
717    /// value of this operation.
718    #[derive(Debug)]
719    pub(crate) struct Write<'a> {
720        #[expect(dead_code)]
721        header: &'a fuse_in_header,
722        arg: &'a fuse_write_in,
723        data: &'a [u8],
724    }
725    impl<'a> Write<'a> {
726        /// The value set by the [Open] method.
727        pub(crate) fn file_handle(&self) -> FileHandle {
728            FileHandle(self.arg.fh)
729        }
730        pub(crate) fn offset(&self) -> Result<u64, Errno> {
731            let offset = self.arg.offset as u64;
732            validate_off_t("fuse_write_in.arg.offset", offset)?;
733            Ok(offset)
734        }
735        pub(crate) fn data(&self) -> &'a [u8] {
736            self.data
737        }
738        /// Will contain `FUSE_WRITE_CACHE`, if this write is from the page cache. If set,
739        /// the pid, uid, gid, and fh may not match the value that would have been sent if write caching
740        /// is disabled
741        pub(crate) fn write_flags(&self) -> WriteFlags {
742            WriteFlags::from_bits_retain(self.arg.write_flags)
743        }
744        /// `lock_owner`: only supported with ABI >= 7.9
745        pub(crate) fn lock_owner(&self) -> Option<LockOwner> {
746            if self
747                .write_flags()
748                .contains(WriteFlags::FUSE_WRITE_LOCKOWNER)
749            {
750                Some(LockOwner(self.arg.lock_owner))
751            } else {
752                None
753            }
754        }
755        /// flags: these are the file flags, such as `O_SYNC`. Only supported with ABI >= 7.9
756        pub(crate) fn flags(&self) -> OpenFlags {
757            OpenFlags(self.arg.flags)
758        }
759    }
760
761    /// Get file system statistics.
762    #[derive(Debug)]
763    pub(crate) struct StatFs<'a> {
764        #[expect(dead_code)]
765        header: &'a fuse_in_header,
766    }
767
768    /// Release an open file.
769    ///
770    /// Release is called when there are no more references to an open file: all file
771    /// descriptors are closed and all memory mappings are unmapped. For every [`Open`]
772    /// call there will be exactly one release call. The filesystem may reply with an
773    /// error, but error values are not returned to `close()` or `munmap()` which
774    /// triggered the release.
775    #[derive(Debug)]
776    pub(crate) struct Release<'a> {
777        #[expect(dead_code)]
778        header: &'a fuse_in_header,
779        arg: &'a fuse_release_in,
780    }
781    impl Release<'_> {
782        /// Release flags.
783        pub(crate) fn release_flags(&self) -> ReleaseFlags {
784            ReleaseFlags::from_bits_retain(self.arg.release_flags)
785        }
786        pub(crate) fn flush(&self) -> bool {
787            self.release_flags()
788                .contains(ReleaseFlags::FUSE_RELEASE_FLUSH)
789        }
790        /// The value set by the [`Open`] method.
791        pub(crate) fn file_handle(&self) -> FileHandle {
792            FileHandle(self.arg.fh)
793        }
794        /// The same flags as for open.
795        pub(crate) fn flags(&self) -> OpenFlags {
796            OpenFlags(self.arg.flags)
797        }
798        pub(crate) fn lock_owner(&self) -> Option<LockOwner> {
799            if self
800                .release_flags()
801                .contains(ReleaseFlags::FUSE_RELEASE_FLOCK_UNLOCK)
802            {
803                Some(LockOwner(self.arg.lock_owner))
804            } else {
805                None
806            }
807        }
808    }
809
810    /// Synchronize file contents.
811    #[derive(Debug)]
812    pub(crate) struct FSync<'a> {
813        #[expect(dead_code)]
814        header: &'a fuse_in_header,
815        arg: &'a fuse_fsync_in,
816    }
817    impl FSync<'_> {
818        /// The value set by the [`Open`] method.
819        pub(crate) fn file_handle(&self) -> FileHandle {
820            FileHandle(self.arg.fh)
821        }
822        /// If set only the user data should be flushed, not the meta data.
823        pub(crate) fn fdatasync(&self) -> bool {
824            FsyncFlags::from_bits_retain(self.arg.fsync_flags)
825                .contains(FsyncFlags::FUSE_FSYNC_FDATASYNC)
826        }
827    }
828
829    /// Set an extended attribute.
830    #[derive(Debug)]
831    pub(crate) struct SetXAttr<'a> {
832        #[expect(dead_code)]
833        header: &'a fuse_in_header,
834        arg: &'a fuse_setxattr_in,
835        name: &'a OsStr,
836        value: &'a [u8],
837    }
838    impl<'a> SetXAttr<'a> {
839        pub(crate) fn name(&self) -> &'a OsStr {
840            self.name
841        }
842        pub(crate) fn value(&self) -> &'a [u8] {
843            self.value
844        }
845        // TODO: Document what are valid flags
846        pub(crate) fn flags(&self) -> i32 {
847            self.arg.flags
848        }
849        /// This will always be 0 except on `MacOS`.  It's recommended that
850        /// implementations return EINVAL if this is not 0.
851        pub(crate) fn position(&self) -> u32 {
852            #[cfg(target_os = "macos")]
853            return self.arg.position;
854            #[cfg(not(target_os = "macos"))]
855            0
856        }
857    }
858
859    /// Get an extended attribute.
860    ///
861    /// If the requested `XAttr` doesn't exist, return [`Err(Errno::NO_XATTR)`] which will
862    /// map to the right platform-specific error code.
863    #[derive(Debug)]
864    pub(crate) struct GetXAttr<'a> {
865        #[expect(dead_code)]
866        header: &'a fuse_in_header,
867        arg: &'a fuse_getxattr_in,
868        name: &'a OsStr,
869    }
870
871    /// Type for [`GetXAttrSizeEnum::GetSize`].
872    ///
873    /// Represents a request from the user to get the size of the data stored in the `XAttr`.
874    #[derive(Debug)]
875    pub(crate) struct GetXAttrSize();
876
877    #[derive(Debug)]
878    /// Return type for [`GetXAttr::size`].
879    pub(crate) enum GetXAttrSizeEnum {
880        /// User is requesting the size of the data stored in the `XAttr`
881        GetSize(GetXAttrSize),
882        /// User is requesting the data stored in the `XAttr`.  If the data will fit
883        /// in this number of bytes it should be returned, otherwise return [`Err(Errno::ERANGE)`].
884        #[allow(dead_code)]
885        Size(NonZeroU32),
886    }
887    impl<'a> GetXAttr<'a> {
888        /// Name of the `XAttr`
889        pub(crate) fn name(&self) -> &'a OsStr {
890            self.name
891        }
892        /// See [`GetXAttrSizeEnum`].
893        ///
894        /// You only need to check this value as an optimisation where there's a
895        /// cost difference between checking the size of the data stored in an `XAttr`
896        /// and actually providing the data.  Otherwise just call [`reply()`] with the
897        /// data and it will do the right thing.
898        pub(crate) fn size(&self) -> GetXAttrSizeEnum {
899            let s: Result<NonZeroU32, _> = self.arg.size.try_into();
900            match s {
901                Ok(s) => GetXAttrSizeEnum::Size(s),
902                Err(_) => GetXAttrSizeEnum::GetSize(GetXAttrSize()),
903            }
904        }
905        /// The size of the buffer the user has allocated to store the `XAttr` value.
906        pub(crate) fn size_u32(&self) -> u32 {
907            self.arg.size
908        }
909    }
910
911    /// List extended attribute names.
912    #[derive(Debug)]
913    pub(crate) struct ListXAttr<'a> {
914        #[expect(dead_code)]
915        header: &'a fuse_in_header,
916        arg: &'a fuse_getxattr_in,
917    }
918    impl ListXAttr<'_> {
919        /// The size of the buffer the caller has allocated to receive the list of
920        /// `XAttrs`.  If this is 0 the user is just probing to find how much space is
921        /// required to fit the whole list.
922        ///
923        /// You don't need to worry about this except as an optimisation.
924        pub(crate) fn size(&self) -> u32 {
925            self.arg.size
926        }
927    }
928
929    /// Remove an extended attribute.
930    ///
931    /// Return [`Err(Errno::NO_XATTR)`] if the xattr doesn't exist
932    /// Return [`Err(Errno::ENOTSUP)`] if this filesystem doesn't support `XAttrs`
933    #[derive(Debug)]
934    pub(crate) struct RemoveXAttr<'a> {
935        #[expect(dead_code)]
936        header: &'a fuse_in_header,
937        name: &'a OsStr,
938    }
939    impl<'a> RemoveXAttr<'a> {
940        /// Name of the `XAttr` to remove
941        pub(crate) fn name(&self) -> &'a OsStr {
942            self.name
943        }
944    }
945
946    /// Flush method.
947    ///
948    /// This is called on each `close()` of the opened file. Since file descriptors can
949    /// be duplicated (dup, dup2, fork), for one open call there may be many flush
950    /// calls. Filesystems shouldn't assume that flush will always be called after some
951    /// writes, or that if will be called at all.
952    ///
953    /// NOTE: the name of the method is misleading, since (unlike `fsync`) the filesystem
954    /// is not forced to flush pending writes. One reason to flush data, is if the
955    /// filesystem wants to return write errors. If the filesystem supports file locking
956    /// operations (`setlk`, `getlk`) it should remove all locks belonging to `lock_owner`.
957    #[derive(Debug)]
958    pub(crate) struct Flush<'a> {
959        #[expect(dead_code)]
960        header: &'a fuse_in_header,
961        arg: &'a fuse_flush_in,
962    }
963    impl Flush<'_> {
964        /// The value set by the open method
965        pub(crate) fn file_handle(&self) -> FileHandle {
966            FileHandle(self.arg.fh)
967        }
968        pub(crate) fn lock_owner(&self) -> LockOwner {
969            LockOwner(self.arg.lock_owner)
970        }
971    }
972
973    #[derive(Debug)]
974    pub(crate) struct Init<'a> {
975        #[expect(dead_code)]
976        header: &'a fuse_in_header,
977        /// Unlike other operation we put this in a box,
978        /// because we memcopy prefix into it instead of casting.
979        arg: Box<fuse_init_in>,
980    }
981    impl Init<'_> {
982        pub(crate) fn capabilities(&self) -> InitFlags {
983            let flags = InitFlags::from_bits_retain(u64::from(self.arg.flags));
984            if flags.contains(InitFlags::FUSE_INIT_EXT) {
985                return InitFlags::from_bits_retain(
986                    u64::from(self.arg.flags) | (u64::from(self.arg.flags2) << 32),
987                );
988            }
989            flags
990        }
991        pub(crate) fn max_readahead(&self) -> u32 {
992            self.arg.max_readahead
993        }
994        pub(crate) fn version(&self) -> super::Version {
995            super::Version(self.arg.major, self.arg.minor)
996        }
997
998        pub(crate) fn reply(&self, config: &crate::KernelConfig) -> ResponseData {
999            let flags = config.requested | InitFlags::FUSE_INIT_EXT;
1000            // use requested features and reported as capable
1001            let flags = flags & self.capabilities();
1002
1003            let mut init = fuse_init_out {
1004                major: FUSE_KERNEL_VERSION,
1005                minor: FUSE_KERNEL_MINOR_VERSION,
1006                max_readahead: config.max_readahead,
1007                flags: flags.pair().0,
1008                max_background: config.max_background,
1009                congestion_threshold: config.congestion_threshold(),
1010                max_write: config.max_write,
1011                time_gran: config.time_gran.as_nanos() as u32,
1012                max_pages: config.max_pages(),
1013                unused2: 0,
1014                flags2: flags.pair().1,
1015                max_stack_depth: 0,
1016                reserved: [0; 6],
1017            };
1018
1019            if flags.contains(InitFlags::FUSE_PASSTHROUGH) {
1020                init.max_stack_depth = config.max_stack_depth;
1021            }
1022
1023            let init = init.as_bytes();
1024            let init = if self.arg.minor < 5 {
1025                &init[..FUSE_COMPAT_INIT_OUT_SIZE]
1026            } else if self.arg.minor < 23 {
1027                &init[..FUSE_COMPAT_22_INIT_OUT_SIZE]
1028            } else {
1029                init
1030            };
1031            ResponseData::new_data(init)
1032        }
1033
1034        /// Reply with only our version, used when kernel major version > our major version.
1035        /// The kernel will then send a second INIT request with a compatible version.
1036        pub(crate) fn reply_version_only(&self) -> ResponseData {
1037            let init = fuse_init_out {
1038                major: FUSE_KERNEL_VERSION,
1039                minor: FUSE_KERNEL_MINOR_VERSION,
1040                max_readahead: 0,
1041                flags: 0,
1042                max_background: 0,
1043                congestion_threshold: 0,
1044                max_write: 0,
1045                time_gran: 0,
1046                max_pages: 0,
1047                unused2: 0,
1048                flags2: 0,
1049                max_stack_depth: 0,
1050                reserved: [0; 6],
1051            };
1052            ResponseData::new_data(init.as_bytes())
1053        }
1054    }
1055
1056    /// Open a directory.
1057    ///
1058    /// Filesystem may store an arbitrary file handle (pointer, index, etc) in fh, and
1059    /// use this in other all other directory stream operations ([`ReadDir`], [`ReleaseDir`],
1060    /// [`FSyncDir`]). Filesystem may also implement stateless directory I/O and not store
1061    /// anything in `fh`, though that makes it impossible to implement standard conforming
1062    /// directory stream operations in case the contents of the directory can change
1063    /// between [`OpenDir`] and [`ReleaseDir`].
1064    ///
1065    /// TODO: Document how to implement "standard conforming directory stream operations"
1066    #[derive(Debug)]
1067    pub(crate) struct OpenDir<'a> {
1068        #[expect(dead_code)]
1069        header: &'a fuse_in_header,
1070        arg: &'a fuse_open_in,
1071    }
1072    impl OpenDir<'_> {
1073        /// Flags as passed to open
1074        pub(crate) fn flags(&self) -> OpenFlags {
1075            OpenFlags(self.arg.flags)
1076        }
1077    }
1078
1079    /// Read directory.
1080    #[derive(Debug)]
1081    pub(crate) struct ReadDir<'a> {
1082        #[expect(dead_code)]
1083        header: &'a fuse_in_header,
1084        arg: &'a fuse_read_in,
1085    }
1086    impl ReadDir<'_> {
1087        /// The value set by the [`OpenDir`] method.
1088        pub(crate) fn file_handle(&self) -> FileHandle {
1089            FileHandle(self.arg.fh)
1090        }
1091        pub(crate) fn offset(&self) -> u64 {
1092            self.arg.offset
1093        }
1094        pub(crate) fn size(&self) -> u32 {
1095            self.arg.size
1096        }
1097    }
1098
1099    /// Release an open directory.
1100    ///
1101    /// For every [`OpenDir`] call there will be exactly one [`ReleaseDir`] call.
1102    #[derive(Debug)]
1103    pub(crate) struct ReleaseDir<'a> {
1104        #[expect(dead_code)]
1105        header: &'a fuse_in_header,
1106        arg: &'a fuse_release_in,
1107    }
1108    impl ReleaseDir<'_> {
1109        /// The value set by the [`OpenDir`] method.
1110        pub(crate) fn file_handle(&self) -> FileHandle {
1111            FileHandle(self.arg.fh)
1112        }
1113        /// Release flags.
1114        pub(crate) fn release_flags(&self) -> ReleaseFlags {
1115            ReleaseFlags::from_bits_retain(self.arg.release_flags)
1116        }
1117        pub(crate) fn flush(&self) -> bool {
1118            self.release_flags()
1119                .contains(ReleaseFlags::FUSE_RELEASE_FLUSH)
1120        }
1121        pub(crate) fn lock_owner(&self) -> Option<LockOwner> {
1122            if self
1123                .release_flags()
1124                .contains(ReleaseFlags::FUSE_RELEASE_FLOCK_UNLOCK)
1125            {
1126                Some(LockOwner(self.arg.lock_owner))
1127            } else {
1128                None
1129            }
1130        }
1131        pub(crate) fn flags(&self) -> OpenFlags {
1132            OpenFlags(self.arg.flags)
1133        }
1134    }
1135
1136    /// Synchronize directory contents.
1137    #[derive(Debug)]
1138    pub(crate) struct FSyncDir<'a> {
1139        #[expect(dead_code)]
1140        header: &'a fuse_in_header,
1141        arg: &'a fuse_fsync_in,
1142    }
1143    impl FSyncDir<'_> {
1144        /// The value set by the [`OpenDir`] method. See [`FileHandle`].
1145        pub(crate) fn file_handle(&self) -> FileHandle {
1146            FileHandle(self.arg.fh)
1147        }
1148        /// If set, then only the directory contents should be flushed, not the meta data.
1149        pub(crate) fn fdatasync(&self) -> bool {
1150            FsyncFlags::from_bits_retain(self.arg.fsync_flags)
1151                .contains(FsyncFlags::FUSE_FSYNC_FDATASYNC)
1152        }
1153    }
1154
1155    /// Test for a POSIX file lock.
1156    #[derive(Debug)]
1157    pub(crate) struct GetLk<'a> {
1158        #[expect(dead_code)]
1159        header: &'a fuse_in_header,
1160        arg: &'a fuse_lk_in,
1161    }
1162    impl GetLk<'_> {
1163        /// The value set by the [`Open`] method. See [`FileHandle`].
1164        pub(crate) fn file_handle(&self) -> FileHandle {
1165            FileHandle(self.arg.fh)
1166        }
1167        pub(crate) fn lock(&self) -> Lock {
1168            Lock::from_abi(&self.arg.lk)
1169        }
1170        pub(crate) fn lock_owner(&self) -> LockOwner {
1171            LockOwner(self.arg.owner)
1172        }
1173    }
1174
1175    /// Acquire, modify or release a POSIX file lock.
1176    ///
1177    /// For POSIX threads (NPTL) there's a 1-1 relation between pid and owner, but
1178    /// otherwise this is not always the case.  For checking lock ownership,
1179    /// 'fi->owner' must be used. The `l_pid` field in 'struct `flock`' should only be
1180    /// used to fill in this field in `getlk()`. Note: if the locking methods are not
1181    /// implemented, the kernel will still allow file locking to work locally.
1182    /// Hence these are only interesting for network filesystems and similar.
1183    #[derive(Debug)]
1184    pub(crate) struct SetLk<'a> {
1185        #[expect(dead_code)]
1186        header: &'a fuse_in_header,
1187        arg: &'a fuse_lk_in,
1188        /// If true, block until the lock can be acquired (SETLKW).
1189        /// If false, return immediately if the lock cannot be acquired (SETLK).
1190        wait: bool,
1191    }
1192    impl SetLk<'_> {
1193        /// The value set by the [`Open`] method. See [`FileHandle`].
1194        pub(crate) fn file_handle(&self) -> FileHandle {
1195            FileHandle(self.arg.fh)
1196        }
1197        pub(crate) fn lock(&self) -> Lock {
1198            Lock::from_abi(&self.arg.lk)
1199        }
1200        pub(crate) fn lock_owner(&self) -> LockOwner {
1201            LockOwner(self.arg.owner)
1202        }
1203        /// If true, block until the lock can be acquired (SETLKW).
1204        /// If false, return immediately if the lock cannot be acquired (SETLK).
1205        pub(crate) fn sleep(&self) -> bool {
1206            self.wait
1207        }
1208    }
1209
1210    /// Check file access permissions.
1211    ///
1212    /// This will be called for the `access()` system call. If the `default_permissions`
1213    /// mount option is given, this method is not called.
1214    #[derive(Debug)]
1215    pub(crate) struct Access<'a> {
1216        #[expect(dead_code)]
1217        header: &'a fuse_in_header,
1218        arg: &'a fuse_access_in,
1219    }
1220    impl Access<'_> {
1221        pub(crate) fn mask(&self) -> AccessFlags {
1222            AccessFlags::from_bits_retain(self.arg.mask)
1223        }
1224    }
1225
1226    /// Create and open a file.
1227    ///
1228    /// If the file does not exist, first create it with the specified mode, and then
1229    /// open it. Open flags (with the exception of `O_NOCTTY`) are available in flags.
1230    /// Filesystem may store an arbitrary file handle (pointer, index, etc) in fh,
1231    /// and use this in other all other file operations ([`Read`], [`Write`], [`Flush`], [`Release`],
1232    /// [`FSync`]). There are also some flags (`direct_io`, `keep_cache`) which the
1233    /// filesystem may set, to change the way the file is opened. See `fuse_file_info`
1234    /// structure in <`fuse_common.h`> for more details. If this method is not
1235    /// implemented or under Linux kernel versions earlier than 2.6.15, the [`MkNod`]
1236    /// and [`Open`] methods will be called instead.
1237    #[derive(Debug)]
1238    pub(crate) struct Create<'a> {
1239        #[expect(dead_code)]
1240        header: &'a fuse_in_header,
1241        arg: &'a fuse_create_in,
1242        name: &'a Path,
1243    }
1244    impl<'a> Create<'a> {
1245        pub(crate) fn name(&self) -> &'a Path {
1246            self.name
1247        }
1248        pub(crate) fn mode(&self) -> u32 {
1249            self.arg.mode
1250        }
1251        /// Flags as passed to the `create()` call
1252        pub(crate) fn flags(&self) -> i32 {
1253            self.arg.flags
1254        }
1255        pub(crate) fn umask(&self) -> u32 {
1256            self.arg.umask
1257        }
1258    }
1259
1260    /// If a process issuing a FUSE filesystem request is interrupted, the
1261    /// following will happen:
1262    ///
1263    ///   1) If the request is not yet sent to userspace AND the signal is
1264    ///      fatal (SIGKILL or unhandled fatal signal), then the request is
1265    ///      dequeued and returns immediately.
1266    ///
1267    ///   2) If the request is not yet sent to userspace AND the signal is not
1268    ///      fatal, then an 'interrupted' flag is set for the request.  When
1269    ///      the request has been successfully transferred to userspace and
1270    ///      this flag is set, an INTERRUPT request is queued.
1271    ///
1272    ///   3) If the request is already sent to userspace, then an INTERRUPT
1273    ///      request is queued.
1274    ///
1275    /// [`Interrupt`] requests take precedence over other requests, so the
1276    /// userspace filesystem will receive queued [`Interrupt`]s before any others.
1277    ///
1278    /// The userspace filesystem may ignore the [`Interrupt`] requests entirely,
1279    /// or may honor them by sending a reply to the **original** request, with
1280    /// the error set to [`Errno::EINTR`].
1281    ///
1282    /// It is also possible that there's a race between processing the
1283    /// original request and its [`Interrupt`] request.  There are two
1284    /// possibilities:
1285    ///
1286    /// 1. The [`Interrupt`] request is processed before the original request is
1287    ///    processed
1288    ///
1289    /// 2. The [`Interrupt`] request is processed after the original request has
1290    ///    been answered
1291    ///
1292    /// If the filesystem cannot find the original request, it should wait for
1293    /// some timeout and/or a number of new requests to arrive, after which it
1294    /// should reply to the [`Interrupt`] request with an [`Errno::EAGAIN`] error.
1295    /// In case (1) the [`Interrupt`] request will be requeued.  In case (2) the
1296    /// [`Interrupt`] reply will be ignored.
1297    #[derive(Debug)]
1298    pub(crate) struct Interrupt<'a> {
1299        #[expect(dead_code)]
1300        header: &'a fuse_in_header,
1301        arg: &'a fuse_interrupt_in,
1302    }
1303    impl Interrupt<'_> {
1304        pub(crate) fn unique(&self) -> RequestId {
1305            RequestId(self.arg.unique)
1306        }
1307    }
1308
1309    /// Map block index within file to block index within device.
1310    /// Note: This makes sense only for block device backed filesystems mounted
1311    /// with the `blkdev` option
1312    #[derive(Debug)]
1313    pub(crate) struct BMap<'a> {
1314        #[expect(dead_code)]
1315        header: &'a fuse_in_header,
1316        arg: &'a fuse_bmap_in,
1317    }
1318    impl BMap<'_> {
1319        pub(crate) fn block_size(&self) -> u32 {
1320            self.arg.blocksize
1321        }
1322        pub(crate) fn block(&self) -> u64 {
1323            self.arg.block
1324        }
1325    }
1326
1327    #[derive(Debug)]
1328    pub(crate) struct Destroy<'a> {
1329        #[expect(dead_code)]
1330        header: &'a fuse_in_header,
1331    }
1332
1333    /// Control device
1334    #[derive(Debug)]
1335    pub(crate) struct IoCtl<'a> {
1336        #[expect(dead_code)]
1337        header: &'a fuse_in_header,
1338        arg: &'a fuse_ioctl_in,
1339        data: &'a [u8],
1340    }
1341    impl IoCtl<'_> {
1342        pub(crate) fn in_data(&self) -> &[u8] {
1343            &self.data[..self.arg.in_size as usize]
1344        }
1345        pub(crate) fn unrestricted(&self) -> bool {
1346            self.flags().contains(IoctlFlags::FUSE_IOCTL_UNRESTRICTED)
1347        }
1348        /// The value set by the [`Open`] method. See [`FileHandle`].
1349        pub(crate) fn file_handle(&self) -> FileHandle {
1350            FileHandle(self.arg.fh)
1351        }
1352        pub(crate) fn flags(&self) -> IoctlFlags {
1353            IoctlFlags::from_bits_retain(self.arg.flags)
1354        }
1355        /// TODO: What does this mean?
1356        pub(crate) fn command(&self) -> u32 {
1357            self.arg.cmd
1358        }
1359        pub(crate) fn out_size(&self) -> u32 {
1360            self.arg.out_size
1361        }
1362    }
1363
1364    /// Poll.
1365    #[derive(Debug)]
1366    pub(crate) struct Poll<'a> {
1367        #[expect(dead_code)]
1368        header: &'a fuse_in_header,
1369        arg: &'a fuse_poll_in,
1370    }
1371    impl Poll<'_> {
1372        /// The value set by the [`Open`] method. See [`FileHandle`].
1373        pub(crate) fn file_handle(&self) -> FileHandle {
1374            FileHandle(self.arg.fh)
1375        }
1376
1377        /// The unique id used for the poll context by the kernel
1378        pub(crate) fn kernel_handle(&self) -> PollHandle {
1379            PollHandle(self.arg.kh)
1380        }
1381
1382        /// The requested poll events
1383        pub(crate) fn events(&self) -> PollEvents {
1384            PollEvents::from_bits_retain(self.arg.events)
1385        }
1386
1387        /// The poll request's flags
1388        pub(crate) fn flags(&self) -> PollFlags {
1389            PollFlags::from_bits_retain(self.arg.flags)
1390        }
1391    }
1392
1393    /// `NotifyReply`.  TODO: currently unsupported by fuser
1394    #[derive(Debug)]
1395    pub(crate) struct NotifyReply<'a> {
1396        #[expect(dead_code)]
1397        header: &'a fuse_in_header,
1398        #[expect(dead_code)]
1399        arg: &'a [u8],
1400    }
1401
1402    /// `BatchForget`: TODO: merge with Forget
1403    #[derive(Debug)]
1404    pub(crate) struct BatchForget<'a> {
1405        #[expect(dead_code)]
1406        header: &'a fuse_in_header,
1407        #[expect(dead_code)]
1408        arg: &'a fuse_batch_forget_in,
1409        nodes: &'a [fuse_forget_one],
1410    }
1411    impl<'a> BatchForget<'a> {
1412        pub(crate) fn nodes(&self) -> &'a [fuse_forget_one] {
1413            self.nodes
1414        }
1415    }
1416
1417    /// Preallocate or deallocate space to a file
1418    ///
1419    /// Implementations should return EINVAL if offset or length are < 0
1420    #[derive(Debug)]
1421    pub(crate) struct FAllocate<'a> {
1422        #[expect(dead_code)]
1423        header: &'a fuse_in_header,
1424        arg: &'a fuse_fallocate_in,
1425    }
1426    impl FAllocate<'_> {
1427        /// The value set by the [`Open`] method. See [`FileHandle`].
1428        pub(crate) fn file_handle(&self) -> FileHandle {
1429            FileHandle(self.arg.fh)
1430        }
1431        pub(crate) fn offset(&self) -> Result<u64, Errno> {
1432            validate_off_t("fuse_allocate_in.arg.offset", self.arg.offset)?;
1433            Ok(self.arg.offset)
1434        }
1435        pub(crate) fn len(&self) -> Result<u64, Errno> {
1436            validate_off_t("fuse_allocate_in.arg.length", self.arg.length)?;
1437            Ok(self.arg.length)
1438        }
1439        /// `mode` as passed to fallocate.  See `man 2 fallocate`
1440        pub(crate) fn mode(&self) -> i32 {
1441            self.arg.mode
1442        }
1443    }
1444
1445    /// Read directory.
1446    ///
1447    /// TODO: Document when this is called rather than `ReadDir`
1448    #[derive(Debug)]
1449    pub(crate) struct ReadDirPlus<'a> {
1450        #[expect(dead_code)]
1451        header: &'a fuse_in_header,
1452        arg: &'a fuse_read_in,
1453    }
1454    impl ReadDirPlus<'_> {
1455        /// The value set by the [`Open`] method. See [`FileHandle`].
1456        pub(crate) fn file_handle(&self) -> FileHandle {
1457            FileHandle(self.arg.fh)
1458        }
1459        pub(crate) fn offset(&self) -> u64 {
1460            self.arg.offset
1461        }
1462        pub(crate) fn size(&self) -> u32 {
1463            self.arg.size
1464        }
1465    }
1466
1467    /// Rename a file.
1468    ///
1469    /// TODO: Document the differences to [`Rename`] and [`Exchange`]
1470    #[derive(Debug)]
1471    pub(crate) struct Rename2<'a> {
1472        #[expect(dead_code)]
1473        header: &'a fuse_in_header,
1474        arg: &'a fuse_rename2_in,
1475        name: &'a Path,
1476        newname: &'a Path,
1477        old_parent: INodeNo,
1478    }
1479    impl<'a> Rename2<'a> {
1480        pub(crate) fn from(&self) -> FilenameInDir<'a> {
1481            FilenameInDir::<'a> {
1482                dir: self.old_parent,
1483                name: self.name,
1484            }
1485        }
1486        pub(crate) fn to(&self) -> FilenameInDir<'a> {
1487            FilenameInDir::<'a> {
1488                dir: INodeNo(self.arg.newdir),
1489                name: self.newname,
1490            }
1491        }
1492        /// Flags as passed to renameat2.  As of Linux 3.18 this is
1493        /// [`libc::RENAME_EXCHANGE`], [`libc::RENAME_NOREPLACE`] and
1494        /// [`libc::RENAME_WHITEOUT`].  If you don't handle a particular flag
1495        /// reply with an EINVAL error.
1496        pub(crate) fn flags(&self) -> crate::RenameFlags {
1497            crate::RenameFlags::from_bits_retain(self.arg.flags)
1498        }
1499    }
1500
1501    /// Reposition read/write file offset
1502    ///
1503    /// TODO: Document when you need to implement this.  Read and Write provide the offset anyway.
1504    #[derive(Debug)]
1505    pub(crate) struct Lseek<'a> {
1506        #[expect(dead_code)]
1507        header: &'a fuse_in_header,
1508        arg: &'a fuse_lseek_in,
1509    }
1510    impl Lseek<'_> {
1511        /// The value set by the [`Open`] method. See [`FileHandle`].
1512        pub(crate) fn file_handle(&self) -> FileHandle {
1513            FileHandle(self.arg.fh)
1514        }
1515        pub(crate) fn offset(&self) -> i64 {
1516            self.arg.offset
1517        }
1518        /// TODO: Make this return an enum
1519        pub(crate) fn whence(&self) -> i32 {
1520            self.arg.whence
1521        }
1522    }
1523
1524    /// Copy the specified range from the source inode to the destination inode
1525    #[derive(Debug, Clone, Copy)]
1526    pub(crate) struct CopyFileRangeFile {
1527        pub(crate) inode: INodeNo,
1528        /// The value set by the [`Open`] method. See [`FileHandle`].
1529        pub(crate) file_handle: FileHandle,
1530        pub(crate) offset: u64,
1531    }
1532    #[derive(Debug)]
1533    pub(crate) struct CopyFileRange<'a> {
1534        header: &'a fuse_in_header,
1535        arg: &'a fuse_copy_file_range_in,
1536    }
1537    impl CopyFileRange<'_> {
1538        /// File and offset to copy data from
1539        pub(crate) fn src(&self) -> Result<CopyFileRangeFile, Errno> {
1540            let offset = self.arg.off_in as u64;
1541            validate_off_t("fuse_copy_file_range_in.arg.off_in", offset)?;
1542            Ok(CopyFileRangeFile {
1543                inode: INodeNo(self.header.nodeid),
1544                file_handle: FileHandle(self.arg.fh_in),
1545                offset,
1546            })
1547        }
1548        /// File and offset to copy data to
1549        pub(crate) fn dest(&self) -> Result<CopyFileRangeFile, Errno> {
1550            let offset = self.arg.off_out as u64;
1551            validate_off_t("fuse_copy_file_range_in.arg.off_out", offset)?;
1552            Ok(CopyFileRangeFile {
1553                inode: INodeNo(self.arg.nodeid_out),
1554                file_handle: FileHandle(self.arg.fh_out),
1555                offset,
1556            })
1557        }
1558        /// Number of bytes to copy
1559        pub(crate) fn len(&self) -> u64 {
1560            self.arg.len
1561        }
1562        pub(crate) fn flags(&self) -> CopyFileRangeFlags {
1563            CopyFileRangeFlags::from_bits_retain(self.arg.flags)
1564        }
1565    }
1566
1567    /// `MacOS` only: Rename the volume. Set `fuse_init_out.flags` during init to
1568    /// `FUSE_VOL_RENAME` to enable
1569    #[cfg(target_os = "macos")]
1570    #[derive(Debug)]
1571    pub(crate) struct SetVolName<'a> {
1572        #[expect(dead_code)]
1573        header: &'a fuse_in_header,
1574        name: &'a OsStr,
1575    }
1576    #[cfg(target_os = "macos")]
1577    impl<'a> SetVolName<'a> {
1578        pub(crate) fn name(&self) -> &'a OsStr {
1579            self.name
1580        }
1581    }
1582
1583    /// `MacOS` only: Query extended times (`bkuptime` and `crtime`). Set `fuse_init_out.flags`
1584    /// during init to `FUSE_XTIMES` to enable
1585    #[cfg(target_os = "macos")]
1586    #[derive(Debug)]
1587    pub(crate) struct GetXTimes<'a> {
1588        header: &'a fuse_in_header,
1589    }
1590    #[cfg(target_os = "macos")]
1591    impl GetXTimes<'_> {
1592        pub(crate) fn nodeid(&self) -> INodeNo {
1593            INodeNo(self.header.nodeid)
1594        }
1595    }
1596    // API TODO: Consider `rename2(RENAME_EXCHANGE)`
1597    /// `MacOS` only (undocumented)
1598    #[cfg(target_os = "macos")]
1599    #[derive(Debug)]
1600    pub(crate) struct Exchange<'a> {
1601        #[expect(dead_code)]
1602        header: &'a fuse_in_header,
1603        arg: &'a fuse_exchange_in,
1604        oldname: &'a Path,
1605        newname: &'a Path,
1606    }
1607    #[cfg(target_os = "macos")]
1608    impl<'a> Exchange<'a> {
1609        pub(crate) fn from(&self) -> FilenameInDir<'a> {
1610            FilenameInDir::<'a> {
1611                dir: INodeNo(self.arg.olddir),
1612                name: self.oldname,
1613            }
1614        }
1615        pub(crate) fn to(&self) -> FilenameInDir<'a> {
1616            FilenameInDir::<'a> {
1617                dir: INodeNo(self.arg.newdir),
1618                name: self.newname,
1619            }
1620        }
1621        pub(crate) fn options(&self) -> u64 {
1622            self.arg.options
1623        }
1624    }
1625    /// TODO: Document
1626    #[derive(Debug)]
1627    pub(crate) struct CuseInit<'a> {
1628        #[expect(dead_code)]
1629        header: &'a fuse_in_header,
1630        #[expect(dead_code)]
1631        arg: &'a fuse_init_in,
1632    }
1633
1634    pub(crate) fn parse<'a>(
1635        header: &'a fuse_in_header,
1636        opcode: &fuse_opcode,
1637        data: &'a [u8],
1638    ) -> Option<Operation<'a>> {
1639        let mut data = ArgumentIterator::new(data);
1640        Some(match opcode {
1641            fuse_opcode::FUSE_LOOKUP => Operation::Lookup(Lookup {
1642                header,
1643                name: data.fetch_str()?,
1644            }),
1645            fuse_opcode::FUSE_FORGET => Operation::Forget(Forget {
1646                header,
1647                arg: data.fetch()?,
1648            }),
1649            fuse_opcode::FUSE_GETATTR => Operation::GetAttr(GetAttr {
1650                header,
1651
1652                arg: data.fetch()?,
1653            }),
1654            fuse_opcode::FUSE_SETATTR => Operation::SetAttr(SetAttr {
1655                header,
1656                arg: data.fetch()?,
1657            }),
1658            fuse_opcode::FUSE_READLINK => Operation::ReadLink(ReadLink { header }),
1659            fuse_opcode::FUSE_SYMLINK => Operation::SymLink(SymLink {
1660                header,
1661                link_name: data.fetch_str()?.as_ref(),
1662                target: data.fetch_str()?.as_ref(),
1663            }),
1664            fuse_opcode::FUSE_MKNOD => Operation::MkNod(MkNod {
1665                header,
1666                arg: data.fetch()?,
1667                name: data.fetch_str()?.as_ref(),
1668            }),
1669            fuse_opcode::FUSE_MKDIR => Operation::MkDir(MkDir {
1670                header,
1671                arg: data.fetch()?,
1672                name: data.fetch_str()?.as_ref(),
1673            }),
1674            fuse_opcode::FUSE_UNLINK => Operation::Unlink(Unlink {
1675                header,
1676                name: data.fetch_str()?.as_ref(),
1677            }),
1678            fuse_opcode::FUSE_RMDIR => Operation::RmDir(RmDir {
1679                header,
1680                name: data.fetch_str()?.as_ref(),
1681            }),
1682            fuse_opcode::FUSE_RENAME => Operation::Rename(Rename {
1683                header,
1684                arg: data.fetch()?,
1685                name: data.fetch_str()?.as_ref(),
1686                newname: data.fetch_str()?.as_ref(),
1687            }),
1688            fuse_opcode::FUSE_LINK => Operation::Link(Link {
1689                header,
1690                arg: data.fetch()?,
1691                name: data.fetch_str()?.as_ref(),
1692            }),
1693            fuse_opcode::FUSE_OPEN => Operation::Open(Open {
1694                header,
1695                arg: data.fetch()?,
1696            }),
1697            fuse_opcode::FUSE_READ => Operation::Read(Read {
1698                header,
1699                arg: data.fetch()?,
1700            }),
1701            fuse_opcode::FUSE_WRITE => Operation::Write({
1702                let out = Write {
1703                    header,
1704                    arg: data.fetch()?,
1705                    data: data.fetch_all(),
1706                };
1707                assert!(out.data().len() == out.arg.size as usize);
1708                out
1709            }),
1710            fuse_opcode::FUSE_STATFS => Operation::StatFs(StatFs { header }),
1711            fuse_opcode::FUSE_RELEASE => Operation::Release(Release {
1712                header,
1713                arg: data.fetch()?,
1714            }),
1715            fuse_opcode::FUSE_FSYNC => Operation::FSync(FSync {
1716                header,
1717                arg: data.fetch()?,
1718            }),
1719            fuse_opcode::FUSE_SETXATTR => Operation::SetXAttr({
1720                let out = SetXAttr {
1721                    header,
1722                    arg: data.fetch()?,
1723                    name: data.fetch_str()?,
1724                    value: data.fetch_all(),
1725                };
1726                assert!(out.value.len() == out.arg.size as usize);
1727                out
1728            }),
1729            fuse_opcode::FUSE_GETXATTR => Operation::GetXAttr(GetXAttr {
1730                header,
1731                arg: data.fetch()?,
1732                name: data.fetch_str()?,
1733            }),
1734            fuse_opcode::FUSE_LISTXATTR => Operation::ListXAttr(ListXAttr {
1735                header,
1736                arg: data.fetch()?,
1737            }),
1738            fuse_opcode::FUSE_REMOVEXATTR => Operation::RemoveXAttr(RemoveXAttr {
1739                header,
1740                name: data.fetch_str()?,
1741            }),
1742            fuse_opcode::FUSE_FLUSH => Operation::Flush(Flush {
1743                header,
1744                arg: data.fetch()?,
1745            }),
1746            fuse_opcode::FUSE_INIT => Operation::Init(Init {
1747                header,
1748                arg: {
1749                    // fuse_init_in may be truncated in an old version of kernel
1750                    // or on macOS, or on FreeBSD.
1751                    // So we copy as much as available, and zerofill the rest.
1752                    let mut arg = fuse_init_in::new_zeroed();
1753                    let data = data.fetch_all();
1754                    // The oldest version of FUSE we support has four fields.
1755                    if data.len() < offset_of!(fuse_init_in, flags2) {
1756                        return None;
1757                    }
1758                    let prefix = cmp::min(data.len(), arg.as_bytes().len());
1759                    arg.as_mut_bytes()[..prefix].copy_from_slice(&data[..prefix]);
1760                    Box::new(arg)
1761                },
1762            }),
1763            fuse_opcode::FUSE_OPENDIR => Operation::OpenDir(OpenDir {
1764                header,
1765                arg: data.fetch()?,
1766            }),
1767            fuse_opcode::FUSE_READDIR => Operation::ReadDir(ReadDir {
1768                header,
1769                arg: data.fetch()?,
1770            }),
1771            fuse_opcode::FUSE_RELEASEDIR => Operation::ReleaseDir(ReleaseDir {
1772                header,
1773                arg: data.fetch()?,
1774            }),
1775            fuse_opcode::FUSE_FSYNCDIR => Operation::FSyncDir(FSyncDir {
1776                header,
1777                arg: data.fetch()?,
1778            }),
1779            fuse_opcode::FUSE_GETLK => Operation::GetLk(GetLk {
1780                header,
1781                arg: data.fetch()?,
1782            }),
1783            fuse_opcode::FUSE_SETLK => Operation::SetLk(SetLk {
1784                header,
1785                arg: data.fetch()?,
1786                wait: false,
1787            }),
1788            fuse_opcode::FUSE_SETLKW => Operation::SetLk(SetLk {
1789                header,
1790                arg: data.fetch()?,
1791                wait: true,
1792            }),
1793            fuse_opcode::FUSE_ACCESS => Operation::Access(Access {
1794                header,
1795                arg: data.fetch()?,
1796            }),
1797            fuse_opcode::FUSE_CREATE => Operation::Create(Create {
1798                header,
1799                arg: data.fetch()?,
1800                name: data.fetch_str()?.as_ref(),
1801            }),
1802            fuse_opcode::FUSE_INTERRUPT => Operation::Interrupt(Interrupt {
1803                header,
1804                arg: data.fetch()?,
1805            }),
1806            fuse_opcode::FUSE_BMAP => Operation::BMap(BMap {
1807                header,
1808                arg: data.fetch()?,
1809            }),
1810            fuse_opcode::FUSE_DESTROY => Operation::Destroy(Destroy { header }),
1811            fuse_opcode::FUSE_IOCTL => Operation::IoCtl(IoCtl {
1812                header,
1813                arg: data.fetch()?,
1814                data: data.fetch_all(),
1815            }),
1816            fuse_opcode::FUSE_POLL => Operation::Poll(Poll {
1817                header,
1818                arg: data.fetch()?,
1819            }),
1820            fuse_opcode::FUSE_NOTIFY_REPLY => Operation::NotifyReply(NotifyReply {
1821                header,
1822                arg: data.fetch_all(),
1823            }),
1824            fuse_opcode::FUSE_BATCH_FORGET => {
1825                let arg = data.fetch()?;
1826                Operation::BatchForget(BatchForget {
1827                    header,
1828                    arg,
1829                    nodes: data.fetch_slice(arg.count as usize)?,
1830                })
1831            }
1832            fuse_opcode::FUSE_FALLOCATE => Operation::FAllocate(FAllocate {
1833                header,
1834                arg: data.fetch()?,
1835            }),
1836            fuse_opcode::FUSE_READDIRPLUS => Operation::ReadDirPlus(ReadDirPlus {
1837                header,
1838                arg: data.fetch()?,
1839            }),
1840            fuse_opcode::FUSE_RENAME2 => Operation::Rename2(Rename2 {
1841                header,
1842                arg: data.fetch()?,
1843                name: data.fetch_str()?.as_ref(),
1844                newname: data.fetch_str()?.as_ref(),
1845                old_parent: INodeNo(header.nodeid),
1846            }),
1847            fuse_opcode::FUSE_LSEEK => Operation::Lseek(Lseek {
1848                header,
1849                arg: data.fetch()?,
1850            }),
1851            fuse_opcode::FUSE_COPY_FILE_RANGE => Operation::CopyFileRange(CopyFileRange {
1852                header,
1853                arg: data.fetch()?,
1854            }),
1855
1856            #[cfg(target_os = "macos")]
1857            fuse_opcode::FUSE_SETVOLNAME => Operation::SetVolName(SetVolName {
1858                header,
1859                name: data.fetch_str()?,
1860            }),
1861            #[cfg(target_os = "macos")]
1862            fuse_opcode::FUSE_GETXTIMES => Operation::GetXTimes(GetXTimes { header }),
1863            #[cfg(target_os = "macos")]
1864            fuse_opcode::FUSE_EXCHANGE => Operation::Exchange(Exchange {
1865                header,
1866                arg: data.fetch()?,
1867                oldname: data.fetch_str()?.as_ref(),
1868                newname: data.fetch_str()?.as_ref(),
1869            }),
1870
1871            fuse_opcode::CUSE_INIT => Operation::CuseInit(CuseInit {
1872                header,
1873                arg: data.fetch()?,
1874            }),
1875        })
1876    }
1877}
1878use op::*;
1879
1880/// Filesystem operation (and arguments) the kernel driver wants us to perform. The fields of each
1881/// variant needs to match the actual arguments the kernel driver sends for the specific operation.
1882#[derive(Debug)]
1883#[allow(missing_docs)]
1884pub(crate) enum Operation<'a> {
1885    Lookup(Lookup<'a>),
1886    Forget(Forget<'a>),
1887    GetAttr(GetAttr<'a>),
1888    SetAttr(SetAttr<'a>),
1889    ReadLink(#[expect(dead_code)] ReadLink<'a>),
1890    SymLink(SymLink<'a>),
1891    MkNod(MkNod<'a>),
1892    MkDir(MkDir<'a>),
1893    Unlink(Unlink<'a>),
1894    RmDir(RmDir<'a>),
1895    Rename(Rename<'a>),
1896    Link(Link<'a>),
1897    Open(Open<'a>),
1898    Read(Read<'a>),
1899    Write(Write<'a>),
1900    StatFs(#[expect(dead_code)] StatFs<'a>),
1901    Release(Release<'a>),
1902    FSync(FSync<'a>),
1903    SetXAttr(SetXAttr<'a>),
1904    GetXAttr(GetXAttr<'a>),
1905    ListXAttr(ListXAttr<'a>),
1906    RemoveXAttr(RemoveXAttr<'a>),
1907    Flush(Flush<'a>),
1908    Init(Init<'a>),
1909    OpenDir(OpenDir<'a>),
1910    ReadDir(ReadDir<'a>),
1911    ReleaseDir(ReleaseDir<'a>),
1912    FSyncDir(FSyncDir<'a>),
1913    GetLk(GetLk<'a>),
1914    SetLk(SetLk<'a>),
1915    Access(Access<'a>),
1916    Create(Create<'a>),
1917    Interrupt(Interrupt<'a>),
1918    BMap(BMap<'a>),
1919    Destroy(Destroy<'a>),
1920    IoCtl(IoCtl<'a>),
1921    Poll(Poll<'a>),
1922    NotifyReply(#[expect(dead_code)] NotifyReply<'a>),
1923    BatchForget(BatchForget<'a>),
1924    FAllocate(FAllocate<'a>),
1925    ReadDirPlus(ReadDirPlus<'a>),
1926    Rename2(Rename2<'a>),
1927    Lseek(Lseek<'a>),
1928    CopyFileRange(CopyFileRange<'a>),
1929
1930    #[cfg(target_os = "macos")]
1931    SetVolName(SetVolName<'a>),
1932    #[cfg(target_os = "macos")]
1933    GetXTimes(GetXTimes<'a>),
1934    #[cfg(target_os = "macos")]
1935    Exchange(Exchange<'a>),
1936
1937    #[allow(dead_code)]
1938    CuseInit(CuseInit<'a>),
1939}
1940
1941impl fmt::Display for Operation<'_> {
1942    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1943        match self {
1944            Operation::Lookup(x) => write!(f, "LOOKUP name {:?}", x.name()),
1945            Operation::Forget(x) => write!(f, "FORGET nlookup {}", x.nlookup()),
1946            Operation::GetAttr(_) => write!(f, "GETATTR"),
1947            Operation::SetAttr(x) => x.fmt(f),
1948            Operation::ReadLink(_) => write!(f, "READLINK"),
1949            Operation::SymLink(x) => {
1950                write!(
1951                    f,
1952                    "SYMLINK target {:?}, link_name {:?}",
1953                    x.target(),
1954                    x.link_name()
1955                )
1956            }
1957            Operation::MkNod(x) => write!(
1958                f,
1959                "MKNOD name {:?}, mode {:#05o}, rdev {}",
1960                x.name(),
1961                x.mode(),
1962                x.rdev()
1963            ),
1964            Operation::MkDir(x) => write!(f, "MKDIR name {:?}, mode {:#05o}", x.name(), x.mode()),
1965            Operation::Unlink(x) => write!(f, "UNLINK name {:?}", x.name()),
1966            Operation::RmDir(x) => write!(f, "RMDIR name {:?}", x.name),
1967            Operation::Rename(x) => write!(f, "RENAME src {:?}, dest {:?}", x.src(), x.dest()),
1968            Operation::Link(x) => write!(f, "LINK ino {:?}, dest {:?}", x.inode_no(), x.dest()),
1969            Operation::Open(x) => write!(f, "OPEN flags {:#x}", x.flags()),
1970            Operation::Read(x) => write!(
1971                f,
1972                "READ fh {:?}, offset {:?}, size {}",
1973                x.file_handle(),
1974                x.offset(),
1975                x.size()
1976            ),
1977            Operation::Write(x) => write!(
1978                f,
1979                "WRITE fh {:?}, offset {:?}, size {}, write flags {:#x}",
1980                x.file_handle(),
1981                x.offset(),
1982                x.data().len(),
1983                x.write_flags()
1984            ),
1985            Operation::StatFs(_) => write!(f, "STATFS"),
1986            Operation::Release(x) => write!(
1987                f,
1988                "RELEASE fh {:?}, flags {:#x}, flush {}, lock owner {:?}",
1989                x.file_handle(),
1990                x.flags(),
1991                x.flush(),
1992                x.lock_owner()
1993            ),
1994            Operation::FSync(x) => write!(
1995                f,
1996                "FSYNC fh {:?}, fsync fdatasync {}",
1997                x.file_handle(),
1998                x.fdatasync()
1999            ),
2000            Operation::SetXAttr(x) => write!(
2001                f,
2002                "SETXATTR name {:?}, size {}, flags {:#x}",
2003                x.name(),
2004                x.value().len(),
2005                x.flags()
2006            ),
2007            Operation::GetXAttr(x) => {
2008                write!(f, "GETXATTR name {:?}, size {:?}", x.name(), x.size())
2009            }
2010            Operation::ListXAttr(x) => write!(f, "LISTXATTR size {}", x.size()),
2011            Operation::RemoveXAttr(x) => write!(f, "REMOVEXATTR name {:?}", x.name()),
2012            Operation::Flush(x) => write!(
2013                f,
2014                "FLUSH fh {:?}, lock owner {:?}",
2015                x.file_handle(),
2016                x.lock_owner()
2017            ),
2018            Operation::Init(x) => write!(
2019                f,
2020                "INIT kernel ABI {}, capabilities {:#x}, max readahead {}",
2021                x.version(),
2022                x.capabilities(),
2023                x.max_readahead()
2024            ),
2025            Operation::OpenDir(x) => write!(f, "OPENDIR flags {:#x}", x.flags()),
2026            Operation::ReadDir(x) => write!(
2027                f,
2028                "READDIR fh {:?}, offset {}, size {}",
2029                x.file_handle(),
2030                x.offset(),
2031                x.size()
2032            ),
2033            Operation::ReleaseDir(x) => write!(
2034                f,
2035                "RELEASEDIR fh {:?}, flags {:#x}, flush {}, lock owner {:?}",
2036                x.file_handle(),
2037                x.flags(),
2038                x.flush(),
2039                x.lock_owner()
2040            ),
2041            Operation::FSyncDir(x) => write!(
2042                f,
2043                "FSYNCDIR fh {:?}, fsync fdatasync: {}",
2044                x.file_handle(),
2045                x.fdatasync()
2046            ),
2047            Operation::GetLk(x) => write!(
2048                f,
2049                "GETLK fh {:?}, lock owner {:?}",
2050                x.file_handle(),
2051                x.lock_owner()
2052            ),
2053            Operation::SetLk(x) => write!(
2054                f,
2055                "{} fh {:?}, lock owner {:?}",
2056                if x.sleep() { "SETLKW" } else { "SETLK" },
2057                x.file_handle(),
2058                x.lock_owner()
2059            ),
2060            Operation::Access(x) => write!(f, "ACCESS mask {:#05o}", x.mask()),
2061            Operation::Create(x) => write!(
2062                f,
2063                "CREATE name {:?}, mode {:#05o}, flags {:#x}",
2064                x.name(),
2065                x.mode(),
2066                x.flags()
2067            ),
2068            Operation::Interrupt(x) => write!(f, "INTERRUPT unique {:?}", x.unique()),
2069            Operation::BMap(x) => write!(f, "BMAP blocksize {}, ids {}", x.block_size(), x.block()),
2070            Operation::Destroy(_) => write!(f, "DESTROY"),
2071            Operation::IoCtl(x) => write!(
2072                f,
2073                "IOCTL fh {:?}, cmd {}, data size {}, flags {:#x}",
2074                x.file_handle(),
2075                x.command(),
2076                x.in_data().len(),
2077                x.flags()
2078            ),
2079            Operation::Poll(x) => write!(f, "POLL fh {:?}", x.file_handle()),
2080            Operation::NotifyReply(_) => write!(f, "NOTIFYREPLY"),
2081            Operation::BatchForget(x) => write!(f, "BATCHFORGET nodes {:?}", x.nodes()),
2082            Operation::FAllocate(_) => write!(f, "FALLOCATE"),
2083            Operation::ReadDirPlus(x) => write!(
2084                f,
2085                "READDIRPLUS fh {:?}, offset {}, size {}",
2086                x.file_handle(),
2087                x.offset(),
2088                x.size()
2089            ),
2090            Operation::Rename2(x) => write!(f, "RENAME2 from {:?}, to {:?}", x.from(), x.to()),
2091            Operation::Lseek(x) => write!(
2092                f,
2093                "LSEEK fh {:?}, offset {}, whence {}",
2094                x.file_handle(),
2095                x.offset(),
2096                x.whence()
2097            ),
2098            Operation::CopyFileRange(x) => write!(
2099                f,
2100                "COPY_FILE_RANGE src {:?}, dest {:?}, len {}",
2101                x.src(),
2102                x.dest(),
2103                x.len()
2104            ),
2105
2106            #[cfg(target_os = "macos")]
2107            Operation::SetVolName(x) => write!(f, "SETVOLNAME name {:?}", x.name()),
2108            #[cfg(target_os = "macos")]
2109            Operation::GetXTimes(_) => write!(f, "GETXTIMES"),
2110            #[cfg(target_os = "macos")]
2111            Operation::Exchange(x) => write!(
2112                f,
2113                "EXCHANGE from {:?}, to {:?}, options {:#x}",
2114                x.from(),
2115                x.to(),
2116                x.options()
2117            ),
2118
2119            Operation::CuseInit(_) => write!(f, "CUSE_INIT"),
2120        }
2121    }
2122}
2123
2124/// Low-level request of a filesystem operation the kernel driver wants to perform.
2125#[derive(Debug)]
2126pub(crate) struct AnyRequest<'a> {
2127    header: &'a fuse_in_header,
2128    data: &'a [u8],
2129}
2130
2131impl<'a> AnyRequest<'a> {
2132    /// Returns the unique identifier of this request.
2133    ///
2134    /// The FUSE kernel driver assigns a unique id to every concurrent request. This allows to
2135    /// distinguish between multiple concurrent requests. The unique id of a request may be
2136    /// reused in later requests after it has completed.
2137    pub(crate) fn unique(&self) -> RequestId {
2138        RequestId(self.header.unique)
2139    }
2140
2141    /// Returns the node id of the inode this request is targeted to.
2142    pub(crate) fn nodeid(&self) -> INodeNo {
2143        INodeNo(self.header.nodeid)
2144    }
2145
2146    /// Returns the UID that the process that triggered this request runs under.
2147    pub(crate) fn uid(&self) -> Uid {
2148        Uid::from_raw(self.header.uid)
2149    }
2150
2151    /// Returns the GID that the process that triggered this request runs under.
2152    #[cfg_attr(not(test), expect(dead_code))]
2153    fn gid(&self) -> Gid {
2154        Gid::from_raw(self.header.gid)
2155    }
2156
2157    /// Returns the PID of the process that triggered this request.
2158    #[cfg_attr(not(test), expect(dead_code))]
2159    fn pid(&self) -> Pid {
2160        Pid::from_raw(self.header.pid as i32)
2161    }
2162
2163    pub(crate) fn operation(&self) -> Result<Operation<'a>, RequestError> {
2164        // Parse/check opcode
2165        let opcode = fuse_opcode::try_from(self.header.opcode).map_err(
2166            |e: num_enum::TryFromPrimitiveError<_>| RequestError::UnknownOperation(e.number),
2167        )?;
2168        // Parse/check operation arguments
2169        op::parse(self.header, &opcode, self.data).ok_or(RequestError::InsufficientData)
2170    }
2171
2172    pub(crate) fn header(&self) -> &fuse_in_header {
2173        self.header
2174    }
2175}
2176
2177impl fmt::Display for AnyRequest<'_> {
2178    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2179        if let Ok(op) = self.operation() {
2180            write!(
2181                f,
2182                "FUSE({:3}) ino {:#018x} {}",
2183                self.header.unique, self.header.nodeid, op
2184            )
2185        } else {
2186            write!(
2187                f,
2188                "FUSE({:3}) ino {:#018x}",
2189                self.header.unique, self.header.nodeid
2190            )
2191        }
2192    }
2193}
2194
2195impl<'a> TryFrom<&'a [u8]> for AnyRequest<'a> {
2196    type Error = RequestError;
2197
2198    fn try_from(data: &'a [u8]) -> Result<Self, Self::Error> {
2199        // Parse a raw packet as sent by the kernel driver into typed data. Every request always
2200        // begins with a `fuse_in_header` struct followed by arguments depending on the opcode.
2201        let data_len = data.len();
2202        let mut arg_iter = ArgumentIterator::new(data);
2203        // Parse header
2204        let header: &fuse_in_header = arg_iter
2205            .fetch()
2206            .ok_or_else(|| RequestError::ShortReadHeader(arg_iter.len()))?;
2207        // Check data size
2208        if data_len < header.len as usize {
2209            return Err(RequestError::ShortRead(data_len, header.len as usize));
2210        }
2211        Ok(Self {
2212            header,
2213            data: &data[size_of::<fuse_in_header>()..header.len as usize],
2214        })
2215    }
2216}
2217
2218#[cfg(test)]
2219mod tests {
2220    use std::ffi::OsStr;
2221
2222    use crate::ll::request::*;
2223    use crate::ll::test::AlignedData;
2224
2225    #[cfg(target_endian = "big")]
2226    const INIT_REQUEST: AlignedData<[u8; 104]> = AlignedData([
2227        // decimal 104 == hex 0x68
2228        0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x1a, // len, opcode
2229        0xde, 0xad, 0xbe, 0xef, 0xba, 0xad, 0xd0, 0x0d, // unique
2230        0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, // nodeid
2231        0xc0, 0x01, 0xd0, 0x0d, 0xc0, 0x01, 0xca, 0xfe, // uid, gid
2232        0xc0, 0xde, 0xba, 0x5e, 0x00, 0x00, 0x00, 0x00, // pid, padding
2233        0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, // major, minor
2234        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // max_readahead, flags
2235        0x00, 0x00, 0x00, 0x00, // flags2 //TODO: nonzero data
2236        0x00, 0x00, 0x00, 0x00, // eleven unused fields
2237        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2238        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2239        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2240    ]);
2241
2242    #[cfg(target_endian = "little")]
2243    const INIT_REQUEST: AlignedData<[u8; 104]> = AlignedData([
2244        // decimal 104 == hex 0x68
2245        0x68, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, // len, opcode
2246        0x0d, 0xf0, 0xad, 0xba, 0xef, 0xbe, 0xad, 0xde, // unique
2247        0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, // nodeid
2248        0x0d, 0xd0, 0x01, 0xc0, 0xfe, 0xca, 0x01, 0xc0, // uid, gid
2249        0x5e, 0xba, 0xde, 0xc0, 0x00, 0x00, 0x00, 0x00, // pid, padding
2250        0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, // major, minor
2251        0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // max_readahead, flags
2252        0x00, 0x00, 0x00, 0x00, // flags2 //TODO: nonzero data
2253        0x00, 0x00, 0x00, 0x00, // eleven unused fields
2254        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2255        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2256        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2257    ]);
2258
2259    #[cfg(target_endian = "big")]
2260    const MKNOD_REQUEST: AlignedData<[u8; 56]> = [
2261        0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x08, // len, opcode
2262        0xde, 0xad, 0xbe, 0xef, 0xba, 0xad, 0xd0, 0x0d, // unique
2263        0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, // nodeid
2264        0xc0, 0x01, 0xd0, 0x0d, 0xc0, 0x01, 0xca, 0xfe, // uid, gid
2265        0xc0, 0xde, 0xba, 0x5e, 0x00, 0x00, 0x00, 0x00, // pid, padding
2266        0x00, 0x00, 0x01, 0xa4, 0x00, 0x00, 0x00, 0x00, // mode, rdev
2267        0x66, 0x6f, 0x6f, 0x2e, 0x74, 0x78, 0x74, 0x00, // name
2268    ];
2269
2270    #[cfg(target_endian = "little")]
2271    const MKNOD_REQUEST: AlignedData<[u8; 64]> = AlignedData([
2272        0x40, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, // len, opcode
2273        0x0d, 0xf0, 0xad, 0xba, 0xef, 0xbe, 0xad, 0xde, // unique
2274        0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, // nodeid
2275        0x0d, 0xd0, 0x01, 0xc0, 0xfe, 0xca, 0x01, 0xc0, // uid, gid
2276        0x5e, 0xba, 0xde, 0xc0, 0x00, 0x00, 0x00, 0x00, // pid, padding
2277        0xa4, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mode, rdev
2278        0xed, 0x01, 0x00, 0x00, 0xe7, 0x03, 0x00, 0x00, // umask, padding
2279        0x66, 0x6f, 0x6f, 0x2e, 0x74, 0x78, 0x74, 0x00, // name
2280    ]);
2281
2282    #[test]
2283    fn short_read_header() {
2284        match AnyRequest::try_from(&INIT_REQUEST[..20]) {
2285            Err(RequestError::ShortReadHeader(20)) => (),
2286            _ => panic!("Unexpected request parsing result"),
2287        }
2288    }
2289
2290    #[test]
2291    fn short_read() {
2292        match AnyRequest::try_from(&INIT_REQUEST[..48]) {
2293            Err(RequestError::ShortRead(48, 104)) => (),
2294            _ => panic!("Unexpected request parsing result"),
2295        }
2296    }
2297
2298    #[test]
2299    fn init() {
2300        let req = AnyRequest::try_from(&INIT_REQUEST[..]).unwrap();
2301        assert_eq!(req.header.len, 104);
2302        assert_eq!(req.header.opcode, 26);
2303        assert_eq!(req.unique(), RequestId(0xdead_beef_baad_f00d));
2304        assert_eq!(req.nodeid(), INodeNo(0x1122_3344_5566_7788));
2305        assert_eq!(req.uid(), Uid::from_raw(0xc001_d00d));
2306        assert_eq!(req.gid(), Gid::from_raw(0xc001_cafe));
2307        assert_eq!(req.pid(), Pid::from_raw(0xc0de_ba5e_u32 as i32));
2308        match req.operation().unwrap() {
2309            Operation::Init(x) => {
2310                assert_eq!(x.version(), Version(7, 8));
2311                assert_eq!(x.max_readahead(), 4096);
2312            }
2313            _ => panic!("Unexpected request operation"),
2314        }
2315    }
2316
2317    #[test]
2318    fn mknod() {
2319        let req = AnyRequest::try_from(&MKNOD_REQUEST[..]).unwrap();
2320        assert_eq!(req.header.len, 64);
2321        assert_eq!(req.header.opcode, 8);
2322        assert_eq!(req.unique(), RequestId(0xdead_beef_baad_f00d));
2323        assert_eq!(req.nodeid(), INodeNo(0x1122_3344_5566_7788));
2324        assert_eq!(req.uid(), Uid::from_raw(0xc001_d00d));
2325        assert_eq!(req.gid(), Gid::from_raw(0xc001_cafe));
2326        assert_eq!(req.pid(), Pid::from_raw(0xc0de_ba5e_u32 as i32));
2327        match req.operation().unwrap() {
2328            Operation::MkNod(x) => {
2329                assert_eq!(x.mode(), 0o644);
2330                assert_eq!(x.umask(), 0o755);
2331                assert_eq!(x.name(), OsStr::new("foo.txt"));
2332            }
2333            _ => panic!("Unexpected request operation"),
2334        }
2335    }
2336}