//! Low-level filesystem operation request.
//!
//! A request represents information about a filesystem operation the kernel driver wants us to
//! perform.

use std::convert::TryFrom;
use std::error;
use std::fmt;
use std::fmt::Display;
use std::fmt::Formatter;
use std::path::Path;

use log::warn;
use nix::unistd::Gid;
use nix::unistd::Pid;
use nix::unistd::Uid;
#[cfg(feature = "serializable")]
use serde::Deserialize;
#[cfg(feature = "serializable")]
use serde::Serialize;

use crate::Errno;
use crate::ll::argument::ArgumentIterator;
use crate::ll::fuse_abi as abi;
use crate::ll::fuse_abi::fuse_in_header;
use crate::ll::fuse_abi::fuse_opcode;

/// Error that may occur while reading and parsing a request from the kernel driver.
#[derive(Debug)]
pub(crate) enum RequestError {
    /// Not enough data for parsing header (short read).
    ShortReadHeader(usize),
    /// Kernel requested an unknown operation.
    UnknownOperation(u32),
    /// Not enough data for arguments (short read).
    ShortRead(usize, usize),
    /// Insufficient argument data.
    InsufficientData,
}

/// Unique ID for a request from the kernel
///
/// The FUSE kernel driver assigns a unique id to every concurrent request. This allows to
/// distinguish between multiple concurrent requests. The unique id of a request may be
/// reused in later requests after it has completed.
///
/// This can be retrieve for any request using `Request::unique`.  The kernel
/// will send an `Interrupt` request to cancel requests in progress.  It's
/// important to handle this for any requests that may block indefinitely, like
/// `SetLkW`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))]
pub struct RequestId(pub u64);
impl From<RequestId> for u64 {
    fn from(fh: RequestId) -> Self {
        fh.0
    }
}

/// A newtype for inode numbers
///
/// These are generated by the filesystem implementation and returned to the
/// kernel in response to a call to `Lookup`, `Create`, `MkNod`, `MkDir` or
/// `SymLink`.  The kernel will then pass these numbers back to the filesystem
/// implementation when it needs to refer to a given file.  Every request has
/// an associated [`INodeNo`], accessible as `Request::nodeid`.
///
/// Reference Counting
/// ------------------
///
/// Every time the kernel receives a given inode number in a response to a
/// `Lookup`, `Create`, `MkNod`, `MkDir` or `SymLink` request it increments an
/// internal counter for that inode.  The filesystem implementation should do
/// the same.  When the kernel is no longer interested in this inode it will
/// send a `Forget` message with that counter.  The filesystem implementation
/// should decrement its own counter and if it reaches 0 then the inode number
/// may be recycled and your filesystem implementation may clean up its
/// internal data-structures relating to that inode.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))]
pub struct INodeNo(pub u64);

impl INodeNo {
    /// Filesystem root inode number.
    pub const ROOT: INodeNo = INodeNo(1);
}

impl From<INodeNo> for u64 {
    fn from(fh: INodeNo) -> Self {
        fh.0
    }
}

impl Display for INodeNo {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        Display::fmt(&self.0, f)
    }
}

/// A newtype for file handles
///
/// This corresponds to a single file description in a client program.  These
/// are generated by the filesystem implementation in replies to `Open`,
/// `OpenDir` and `Create` requests.  It's used as a correlation id across
/// `Read`, `Write`, `FSync`, `IoCtl`, `Poll`, `FAllocate`, `ReadDir`,
/// `FSyncDir`, `GetLk`, `SetLk`, `SetLkW`, `ReadDirPlus`, `Lseek` and
/// `CopyFileRange` requests.
///
/// A filesystem implementation may store arbitrary data as the [`FileHandle`], as
/// long as it fits into 64-bits and doesn't need to change for over the lifetime
/// of the [`FileHandle`].  Typically this might consist of an index into an array
/// of [`FileHandle`]s that the filesystem implementation maintains.
///
/// Filesystems may instead implement stateless file I/O and use `0` as the
/// [`FileHandle`] - although this makes it impossible to correctly implement
/// resumable `ReadDir` in the presence of mutable directories (see `OpenDir`).
///
/// Lifecycle
/// ---------
///
/// A [`FileHandle`] is owned by one or more file-descriptors (or memory
/// mappings) in the client program.  Multiple file descriptors can point to
/// the same [`FileHandle`], just as a single `INode` can have multiple
/// [`FileHandle`]s open at one time.  Every time a single file-descriptor is
/// closed a `Flush` request is made.  This gives filesystem implementations
/// an opportunity to return an error message from that `close()` call.  After
/// all the file-descriptors are closed that own a given [`FileHandle`] the
/// `Release`/`ReleaseDir` request will be made.  This is an opportunity for
/// the filesystem implementation to free any internal per-FileHandle data
/// structures it has allocated.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))]
pub struct FileHandle(pub u64);

impl From<FileHandle> for u64 {
    fn from(fh: FileHandle) -> Self {
        fh.0
    }
}

impl Display for FileHandle {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        Display::fmt(&self.0, f)
    }
}

/// A newtype for lock owners
///
/// TODO: Document lock lifecycle and how and when to implement file locking.
///
/// See `Read`, `Write`, `Release`, `Flush`, `GetLk`, `SetLk`, `SetLkW`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))]
pub struct LockOwner(pub u64);

impl Display for LockOwner {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        Display::fmt(&self.0, f)
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) struct Lock {
    // Unfortunately this can't be a std::ops::Range because Range is not Copy:
    // https://github.com/rust-lang/rfcs/issues/2848
    pub(crate) range: (u64, u64),
    // TODO: Make typ an enum
    pub(crate) typ: i32,
    pub(crate) pid: u32,
}
impl Lock {
    fn from_abi(x: &abi::fuse_file_lock) -> Lock {
        Lock {
            range: (x.start, x.end),
            typ: x.typ,
            pid: x.pid,
        }
    }
}

/// A newtype for ABI version
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))]
pub struct Version(
    /// Major version number.
    pub u32,
    /// Minor version number.
    pub u32,
);

impl Display for Version {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}.{}", self.0, self.1)
    }
}

/// Represents a filename in a directory
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
pub(crate) struct FilenameInDir<'a> {
    /// The Inode number of the directory
    pub(crate) dir: INodeNo,
    /// Name of the file. This refers to a name directly in this directory, rather than any
    /// subdirectory so is guaranteed not to contain '\0' or '/'.  It may be literally "." or ".."
    /// however.
    pub(crate) name: &'a Path,
}

impl fmt::Display for RequestError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            RequestError::ShortReadHeader(len) => write!(
                f,
                "Short read of FUSE request header ({len} < {})",
                size_of::<fuse_in_header>()
            ),
            RequestError::UnknownOperation(opcode) => write!(f, "Unknown FUSE opcode ({opcode})"),
            RequestError::ShortRead(len, total) => {
                write!(f, "Short read of FUSE request ({len} < {total})")
            }
            RequestError::InsufficientData => write!(f, "Insufficient argument data"),
        }
    }
}

impl error::Error for RequestError {}

fn validate_off_t(name: &'static str, value: u64) -> Result<(), Errno> {
    // Value is unsigned, and libfuse silently casts it to signed `off_t`.
    // The value is supposed to be in range `0..=i64::MAX`, so validate.
    if value > i64::MAX as u64 {
        warn!("{name}={value} is out of range");
        return Err(Errno::EINVAL);
    }
    Ok(())
}

mod op {
    use std::cmp;
    use std::convert::TryInto;
    use std::ffi::OsStr;
    use std::fmt::Display;
    use std::mem::offset_of;
    use std::num::NonZeroU32;
    use std::path::Path;
    use std::time::SystemTime;

    use zerocopy::FromZeros;
    use zerocopy::IntoBytes;

    use crate::AccessFlags;
    use crate::CopyFileRangeFlags;
    use crate::Errno;
    use crate::IoctlFlags;
    use crate::OpenFlags;
    use crate::PollEvents;
    use crate::PollFlags;
    use crate::PollHandle;
    use crate::WriteFlags;
    use crate::bsd_file_flags::BsdFileFlags;
    use crate::ll::ResponseData;
    use crate::ll::TimeOrNow;
    use crate::ll::argument::ArgumentIterator;
    use crate::ll::flags::fattr_flags::FattrFlags;
    use crate::ll::flags::fsync_flags::FsyncFlags;
    use crate::ll::flags::getattr_flags::GetattrFlags;
    use crate::ll::flags::init_flags::InitFlags;
    use crate::ll::flags::read_flags::ReadFlags;
    use crate::ll::flags::release_flags::ReleaseFlags;
    use crate::ll::request::FileHandle;
    use crate::ll::request::FilenameInDir;
    use crate::ll::request::INodeNo;
    use crate::ll::request::Lock;
    use crate::ll::request::LockOwner;
    use crate::ll::request::Operation;
    use crate::ll::request::RequestId;
    use crate::ll::request::abi::*;
    use crate::ll::request::validate_off_t;
    use crate::time::system_time_from_time;

    /// Look up a directory entry by name and get its attributes.
    ///
    /// Implementations allocate and assign [`INodeNo`]s in this request.  Learn more
    /// about `INode` lifecycle and the relationship between [`Lookup`] and [`Forget`] in the
    /// documentation for [`INodeNo`].
    #[derive(Debug)]
    pub(crate) struct Lookup<'a> {
        #[expect(dead_code)]
        header: &'a fuse_in_header,
        name: &'a OsStr,
    }
    impl<'a> Lookup<'a> {
        pub(crate) fn name(&self) -> &'a Path {
            self.name.as_ref()
        }
    }
    /// Forget about an inode.
    ///
    /// The `nlookup` parameter indicates the number of lookups previously performed on
    /// this inode. If the filesystem implements inode lifetimes, it is recommended that
    /// inodes acquire a single reference on each lookup, and lose nlookup references on
    /// each forget. The filesystem may ignore forget calls, if the inodes don't need to
    /// have a limited lifetime.
    ///
    /// Learn more about `INode` lifecycle in the documentation for [`INodeNo`].
    ///
    /// On unmount it is not guaranteed, that all referenced inodes will receive a forget
    /// message.
    #[derive(Debug)]
    pub(crate) struct Forget<'a> {
        #[expect(dead_code)]
        header: &'a fuse_in_header,
        arg: &'a fuse_forget_in,
    }
    impl Forget<'_> {
        /// The number of lookups previously performed on this inode
        pub(crate) fn nlookup(&self) -> u64 {
            self.arg.nlookup
        }
    }

    /// Get file attributes.
    #[derive(Debug)]
    pub(crate) struct GetAttr<'a> {
        #[expect(dead_code)]
        header: &'a fuse_in_header,
        arg: &'a fuse_getattr_in,
    }

    impl GetAttr<'_> {
        /// Returns the getattr flags.
        pub(crate) fn getattr_flags(&self) -> GetattrFlags {
            GetattrFlags::from_bits_retain(self.arg.getattr_flags)
        }

        pub(crate) fn file_handle(&self) -> Option<FileHandle> {
            if self.getattr_flags().contains(GetattrFlags::FUSE_GETATTR_FH) {
                Some(FileHandle(self.arg.fh))
            } else {
                None
            }
        }
    }

    /// Set file attributes.
    #[derive(Debug)]
    pub(crate) struct SetAttr<'a> {
        #[expect(dead_code)]
        header: &'a fuse_in_header,
        arg: &'a fuse_setattr_in,
    }
    impl SetAttr<'_> {
        fn valid(&self) -> FattrFlags {
            FattrFlags::from_bits_retain(self.arg.valid)
        }
        pub(crate) fn mode(&self) -> Option<u32> {
            if self.valid().contains(FattrFlags::FATTR_MODE) {
                Some(self.arg.mode)
            } else {
                None
            }
        }
        pub(crate) fn uid(&self) -> Option<u32> {
            if self.valid().contains(FattrFlags::FATTR_UID) {
                Some(self.arg.uid)
            } else {
                None
            }
        }
        pub(crate) fn gid(&self) -> Option<u32> {
            if self.valid().contains(FattrFlags::FATTR_GID) {
                Some(self.arg.gid)
            } else {
                None
            }
        }
        pub(crate) fn size(&self) -> Option<u64> {
            if self.valid().contains(FattrFlags::FATTR_SIZE) {
                Some(self.arg.size)
            } else {
                None
            }
        }
        pub(crate) fn atime(&self) -> Option<TimeOrNow> {
            if self.valid().contains(FattrFlags::FATTR_ATIME) {
                Some(if self.arg.atime_now() {
                    TimeOrNow::Now
                } else {
                    TimeOrNow::SpecificTime(system_time_from_time(
                        self.arg.atime,
                        self.arg.atimensec,
                    ))
                })
            } else {
                None
            }
        }
        pub(crate) fn mtime(&self) -> Option<TimeOrNow> {
            if self.valid().contains(FattrFlags::FATTR_MTIME) {
                Some(if self.arg.mtime_now() {
                    TimeOrNow::Now
                } else {
                    TimeOrNow::SpecificTime(system_time_from_time(
                        self.arg.mtime,
                        self.arg.mtimensec,
                    ))
                })
            } else {
                None
            }
        }
        pub(crate) fn ctime(&self) -> Option<SystemTime> {
            if self.valid().contains(FattrFlags::FATTR_CTIME) {
                Some(system_time_from_time(self.arg.ctime, self.arg.ctimensec))
            } else {
                None
            }
        }
        /// The value set by the [`Open`] method. See [`FileHandle`].
        ///
        /// This will only be set if the user passed a file-descriptor to set the
        /// attributes - i.e. they used [`libc::fchmod`] rather than [`libc::chmod`].
        pub(crate) fn file_handle(&self) -> Option<FileHandle> {
            if self.valid().contains(FattrFlags::FATTR_FH) {
                Some(FileHandle(self.arg.fh))
            } else {
                None
            }
        }
        pub(crate) fn crtime(&self) -> Option<SystemTime> {
            #[cfg(target_os = "macos")]
            if self.valid().contains(FattrFlags::FATTR_CRTIME) {
                // During certain operation, macOS use some helper that send request to the mountpoint with `crtime` set to 0xffffffff83da4f80.
                // 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.
                // https://github.com/macfuse/macfuse/issues/1042
                if self.arg.crtime == 0xffffffff83da4f80 {
                    None
                } else {
                    Some(
                        SystemTime::UNIX_EPOCH
                            + std::time::Duration::new(self.arg.crtime, self.arg.crtimensec),
                    )
                }
            } else {
                None
            }
            #[cfg(not(target_os = "macos"))]
            None
        }
        pub(crate) fn chgtime(&self) -> Option<SystemTime> {
            #[cfg(target_os = "macos")]
            if self.valid().contains(FattrFlags::FATTR_CHGTIME) {
                Some(
                    SystemTime::UNIX_EPOCH
                        + std::time::Duration::new(self.arg.chgtime, self.arg.chgtimensec),
                )
            } else {
                None
            }
            #[cfg(not(target_os = "macos"))]
            None
        }
        pub(crate) fn bkuptime(&self) -> Option<SystemTime> {
            #[cfg(target_os = "macos")]
            if self.valid().contains(FattrFlags::FATTR_BKUPTIME) {
                Some(
                    SystemTime::UNIX_EPOCH
                        + std::time::Duration::new(self.arg.bkuptime, self.arg.bkuptimensec),
                )
            } else {
                None
            }
            #[cfg(not(target_os = "macos"))]
            None
        }
        pub(crate) fn flags(&self) -> Option<BsdFileFlags> {
            #[cfg(target_os = "macos")]
            if self.valid().contains(FattrFlags::FATTR_FLAGS) {
                Some(BsdFileFlags::from_bits_retain(self.arg.flags))
            } else {
                None
            }
            #[cfg(not(target_os = "macos"))]
            None
        }

        // TODO: Why does *set*attr want to have an attr response?
    }
    impl Display for SetAttr<'_> {
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            write!(
                f,
                "SETATTR mode: {:?}, uid: {:?}, gid: {:?}, size: {:?}, atime: {:?}, \
                mtime: {:?}, ctime: {:?}, file_handle: {:?}, crtime: {:?}, chgtime: {:?}, \
                bkuptime: {:?}, flags: {:?}",
                self.mode(),
                self.uid(),
                self.gid(),
                self.size(),
                self.atime(),
                self.mtime(),
                self.ctime(),
                self.file_handle(),
                self.crtime(),
                self.chgtime(),
                self.bkuptime(),
                self.flags()
            )
        }
    }

    /// Read symbolic link.
    #[derive(Debug)]
    pub(crate) struct ReadLink<'a> {
        #[expect(dead_code)]
        header: &'a fuse_in_header,
    }

    /// Create a symbolic link.
    #[derive(Debug)]
    pub(crate) struct SymLink<'a> {
        #[expect(dead_code)]
        header: &'a fuse_in_header,
        target: &'a Path,
        link_name: &'a Path,
    }
    impl<'a> SymLink<'a> {
        pub(crate) fn target(&self) -> &'a Path {
            self.target
        }
        pub(crate) fn link_name(&self) -> &'a Path {
            self.link_name
        }
    }

    /// Create file node.
    /// Create a regular file, character device, block device, fifo or socket node.
    #[derive(Debug)]
    pub(crate) struct MkNod<'a> {
        #[expect(dead_code)]
        header: &'a fuse_in_header,
        arg: &'a fuse_mknod_in,
        name: &'a Path,
    }
    impl<'a> MkNod<'a> {
        pub(crate) fn name(&self) -> &'a Path {
            self.name
        }
        pub(crate) fn mode(&self) -> u32 {
            self.arg.mode
        }
        pub(crate) fn umask(&self) -> u32 {
            self.arg.umask
        }
        pub(crate) fn rdev(&self) -> u32 {
            self.arg.rdev
        }
    }

    /// Create a directory.
    #[derive(Debug)]
    pub(crate) struct MkDir<'a> {
        #[expect(dead_code)]
        header: &'a fuse_in_header,
        arg: &'a fuse_mkdir_in,
        name: &'a Path,
    }
    impl<'a> MkDir<'a> {
        pub(crate) fn name(&self) -> &'a Path {
            self.name
        }
        pub(crate) fn mode(&self) -> u32 {
            self.arg.mode
        }
        pub(crate) fn umask(&self) -> u32 {
            self.arg.umask
        }
    }

    /// Remove a file.
    #[derive(Debug)]
    pub(crate) struct Unlink<'a> {
        #[expect(dead_code)]
        header: &'a fuse_in_header,
        name: &'a Path,
    }
    impl<'a> Unlink<'a> {
        pub(crate) fn name(&self) -> &'a Path {
            self.name
        }
    }

    /// Remove a directory.
    #[derive(Debug)]
    pub(crate) struct RmDir<'a> {
        #[expect(dead_code)]
        header: &'a fuse_in_header,
        pub(crate) name: &'a Path,
    }
    impl<'a> RmDir<'a> {
        pub(crate) fn name(&self) -> &'a Path {
            self.name
        }
    }

    /// Rename a file.
    #[derive(Debug)]
    pub(crate) struct Rename<'a> {
        header: &'a fuse_in_header,
        arg: &'a fuse_rename_in,
        name: &'a Path,
        newname: &'a Path,
    }
    impl<'a> Rename<'a> {
        pub(crate) fn src(&self) -> FilenameInDir<'a> {
            FilenameInDir::<'a> {
                dir: INodeNo(self.header.nodeid),
                name: self.name,
            }
        }
        pub(crate) fn dest(&self) -> FilenameInDir<'a> {
            FilenameInDir::<'a> {
                dir: INodeNo(self.arg.newdir),
                name: self.newname,
            }
        }
    }

    /// Create a hard link.
    #[derive(Debug)]
    pub(crate) struct Link<'a> {
        header: &'a fuse_in_header,
        arg: &'a fuse_link_in,
        name: &'a Path,
    }
    impl<'a> Link<'a> {
        /// This is the inode no of the file to be linked.  The inode number in
        /// the fuse header is of the directory that it will be linked into.
        pub(crate) fn inode_no(&self) -> INodeNo {
            INodeNo(self.arg.oldnodeid)
        }
        pub(crate) fn dest(&self) -> FilenameInDir<'a> {
            FilenameInDir::<'a> {
                dir: INodeNo(self.header.nodeid),
                name: self.name,
            }
        }
    }

    /// Open a file.
    ///
    /// Open flags (with the exception of `O_CREAT`, `O_EXCL`, `O_NOCTTY` and `O_TRUNC`) are
    /// available in flags. Filesystem may store an arbitrary file handle (pointer, index,
    /// etc) in fh, and use this in other all other file operations (read, write, flush,
    /// release, fsync). Filesystem may also implement stateless file I/O and not store
    /// anything in fh. There are also some flags (`direct_io`, `keep_cache`) which the
    /// filesystem may set, to change the way the file is opened. See `fuse_file_info`
    /// structure in <`fuse_common.h`> for more details.
    #[derive(Debug)]
    pub(crate) struct Open<'a> {
        #[expect(dead_code)]
        header: &'a fuse_in_header,
        arg: &'a fuse_open_in,
    }
    impl Open<'_> {
        pub(crate) fn flags(&self) -> OpenFlags {
            OpenFlags(self.arg.flags)
        }
    }

    /// Read data.
    ///
    /// Read should send exactly the number of bytes requested except on EOF or error,
    /// otherwise the rest of the data will be substituted with zeroes. An exception to
    /// this is when the file has been opened in `direct_io` mode, in which case the
    /// return value of the read system call will reflect the return value of this
    /// operation.
    #[derive(Debug)]
    pub(crate) struct Read<'a> {
        #[expect(dead_code)]
        header: &'a fuse_in_header,
        arg: &'a fuse_read_in,
    }
    impl Read<'_> {
        /// The value set by the [Open] method.
        pub(crate) fn file_handle(&self) -> FileHandle {
            FileHandle(self.arg.fh)
        }
        pub(crate) fn offset(&self) -> Result<u64, Errno> {
            validate_off_t("fuse_read_in.arg.offset", self.arg.offset)?;
            Ok(self.arg.offset)
        }
        pub(crate) fn size(&self) -> u32 {
            self.arg.size
        }
        /// Only supported with ABI >= 7.9
        pub(crate) fn lock_owner(&self) -> Option<LockOwner> {
            if self.read_flags().contains(ReadFlags::FUSE_READ_LOCKOWNER) {
                Some(LockOwner(self.arg.lock_owner))
            } else {
                None
            }
        }
        /// The file flags, such as `O_SYNC`. Only supported with ABI >= 7.9
        pub(crate) fn flags(&self) -> OpenFlags {
            OpenFlags(self.arg.flags)
        }
        pub(crate) fn read_flags(&self) -> ReadFlags {
            ReadFlags::from_bits_retain(self.arg.read_flags)
        }
    }

    /// Write data.
    ///
    /// Write should return exactly the number of bytes requested except on error. An
    /// exception to this is when the file has been opened in `direct_io` mode, in
    /// which case the return value of the write system call will reflect the return
    /// value of this operation.
    #[derive(Debug)]
    pub(crate) struct Write<'a> {
        #[expect(dead_code)]
        header: &'a fuse_in_header,
        arg: &'a fuse_write_in,
        data: &'a [u8],
    }
    impl<'a> Write<'a> {
        /// The value set by the [Open] method.
        pub(crate) fn file_handle(&self) -> FileHandle {
            FileHandle(self.arg.fh)
        }
        pub(crate) fn offset(&self) -> Result<u64, Errno> {
            let offset = self.arg.offset as u64;
            validate_off_t("fuse_write_in.arg.offset", offset)?;
            Ok(offset)
        }
        pub(crate) fn data(&self) -> &'a [u8] {
            self.data
        }
        /// Will contain `FUSE_WRITE_CACHE`, if this write is from the page cache. If set,
        /// the pid, uid, gid, and fh may not match the value that would have been sent if write caching
        /// is disabled
        pub(crate) fn write_flags(&self) -> WriteFlags {
            WriteFlags::from_bits_retain(self.arg.write_flags)
        }
        /// `lock_owner`: only supported with ABI >= 7.9
        pub(crate) fn lock_owner(&self) -> Option<LockOwner> {
            if self
                .write_flags()
                .contains(WriteFlags::FUSE_WRITE_LOCKOWNER)
            {
                Some(LockOwner(self.arg.lock_owner))
            } else {
                None
            }
        }
        /// flags: these are the file flags, such as `O_SYNC`. Only supported with ABI >= 7.9
        pub(crate) fn flags(&self) -> OpenFlags {
            OpenFlags(self.arg.flags)
        }
    }

    /// Get file system statistics.
    #[derive(Debug)]
    pub(crate) struct StatFs<'a> {
        #[expect(dead_code)]
        header: &'a fuse_in_header,
    }

    /// Release an open file.
    ///
    /// Release is called when there are no more references to an open file: all file
    /// descriptors are closed and all memory mappings are unmapped. For every [`Open`]
    /// call there will be exactly one release call. The filesystem may reply with an
    /// error, but error values are not returned to `close()` or `munmap()` which
    /// triggered the release.
    #[derive(Debug)]
    pub(crate) struct Release<'a> {
        #[expect(dead_code)]
        header: &'a fuse_in_header,
        arg: &'a fuse_release_in,
    }
    impl Release<'_> {
        /// Release flags.
        pub(crate) fn release_flags(&self) -> ReleaseFlags {
            ReleaseFlags::from_bits_retain(self.arg.release_flags)
        }
        pub(crate) fn flush(&self) -> bool {
            self.release_flags()
                .contains(ReleaseFlags::FUSE_RELEASE_FLUSH)
        }
        /// The value set by the [`Open`] method.
        pub(crate) fn file_handle(&self) -> FileHandle {
            FileHandle(self.arg.fh)
        }
        /// The same flags as for open.
        pub(crate) fn flags(&self) -> OpenFlags {
            OpenFlags(self.arg.flags)
        }
        pub(crate) fn lock_owner(&self) -> Option<LockOwner> {
            if self
                .release_flags()
                .contains(ReleaseFlags::FUSE_RELEASE_FLOCK_UNLOCK)
            {
                Some(LockOwner(self.arg.lock_owner))
            } else {
                None
            }
        }
    }

    /// Synchronize file contents.
    #[derive(Debug)]
    pub(crate) struct FSync<'a> {
        #[expect(dead_code)]
        header: &'a fuse_in_header,
        arg: &'a fuse_fsync_in,
    }
    impl FSync<'_> {
        /// The value set by the [`Open`] method.
        pub(crate) fn file_handle(&self) -> FileHandle {
            FileHandle(self.arg.fh)
        }
        /// If set only the user data should be flushed, not the meta data.
        pub(crate) fn fdatasync(&self) -> bool {
            FsyncFlags::from_bits_retain(self.arg.fsync_flags)
                .contains(FsyncFlags::FUSE_FSYNC_FDATASYNC)
        }
    }

    /// Set an extended attribute.
    #[derive(Debug)]
    pub(crate) struct SetXAttr<'a> {
        #[expect(dead_code)]
        header: &'a fuse_in_header,
        arg: &'a fuse_setxattr_in,
        name: &'a OsStr,
        value: &'a [u8],
    }
    impl<'a> SetXAttr<'a> {
        pub(crate) fn name(&self) -> &'a OsStr {
            self.name
        }
        pub(crate) fn value(&self) -> &'a [u8] {
            self.value
        }
        // TODO: Document what are valid flags
        pub(crate) fn flags(&self) -> i32 {
            self.arg.flags
        }
        /// This will always be 0 except on `MacOS`.  It's recommended that
        /// implementations return EINVAL if this is not 0.
        pub(crate) fn position(&self) -> u32 {
            #[cfg(target_os = "macos")]
            return self.arg.position;
            #[cfg(not(target_os = "macos"))]
            0
        }
    }

    /// Get an extended attribute.
    ///
    /// If the requested `XAttr` doesn't exist, return [`Err(Errno::NO_XATTR)`] which will
    /// map to the right platform-specific error code.
    #[derive(Debug)]
    pub(crate) struct GetXAttr<'a> {
        #[expect(dead_code)]
        header: &'a fuse_in_header,
        arg: &'a fuse_getxattr_in,
        name: &'a OsStr,
    }

    /// Type for [`GetXAttrSizeEnum::GetSize`].
    ///
    /// Represents a request from the user to get the size of the data stored in the `XAttr`.
    #[derive(Debug)]
    pub(crate) struct GetXAttrSize();

    #[derive(Debug)]
    /// Return type for [`GetXAttr::size`].
    pub(crate) enum GetXAttrSizeEnum {
        /// User is requesting the size of the data stored in the `XAttr`
        GetSize(GetXAttrSize),
        /// User is requesting the data stored in the `XAttr`.  If the data will fit
        /// in this number of bytes it should be returned, otherwise return [`Err(Errno::ERANGE)`].
        #[allow(dead_code)]
        Size(NonZeroU32),
    }
    impl<'a> GetXAttr<'a> {
        /// Name of the `XAttr`
        pub(crate) fn name(&self) -> &'a OsStr {
            self.name
        }
        /// See [`GetXAttrSizeEnum`].
        ///
        /// You only need to check this value as an optimisation where there's a
        /// cost difference between checking the size of the data stored in an `XAttr`
        /// and actually providing the data.  Otherwise just call [`reply()`] with the
        /// data and it will do the right thing.
        pub(crate) fn size(&self) -> GetXAttrSizeEnum {
            let s: Result<NonZeroU32, _> = self.arg.size.try_into();
            match s {
                Ok(s) => GetXAttrSizeEnum::Size(s),
                Err(_) => GetXAttrSizeEnum::GetSize(GetXAttrSize()),
            }
        }
        /// The size of the buffer the user has allocated to store the `XAttr` value.
        pub(crate) fn size_u32(&self) -> u32 {
            self.arg.size
        }
    }

    /// List extended attribute names.
    #[derive(Debug)]
    pub(crate) struct ListXAttr<'a> {
        #[expect(dead_code)]
        header: &'a fuse_in_header,
        arg: &'a fuse_getxattr_in,
    }
    impl ListXAttr<'_> {
        /// The size of the buffer the caller has allocated to receive the list of
        /// `XAttrs`.  If this is 0 the user is just probing to find how much space is
        /// required to fit the whole list.
        ///
        /// You don't need to worry about this except as an optimisation.
        pub(crate) fn size(&self) -> u32 {
            self.arg.size
        }
    }

    /// Remove an extended attribute.
    ///
    /// Return [`Err(Errno::NO_XATTR)`] if the xattr doesn't exist
    /// Return [`Err(Errno::ENOTSUP)`] if this filesystem doesn't support `XAttrs`
    #[derive(Debug)]
    pub(crate) struct RemoveXAttr<'a> {
        #[expect(dead_code)]
        header: &'a fuse_in_header,
        name: &'a OsStr,
    }
    impl<'a> RemoveXAttr<'a> {
        /// Name of the `XAttr` to remove
        pub(crate) fn name(&self) -> &'a OsStr {
            self.name
        }
    }

    /// Flush method.
    ///
    /// This is called on each `close()` of the opened file. Since file descriptors can
    /// be duplicated (dup, dup2, fork), for one open call there may be many flush
    /// calls. Filesystems shouldn't assume that flush will always be called after some
    /// writes, or that if will be called at all.
    ///
    /// NOTE: the name of the method is misleading, since (unlike `fsync`) the filesystem
    /// is not forced to flush pending writes. One reason to flush data, is if the
    /// filesystem wants to return write errors. If the filesystem supports file locking
    /// operations (`setlk`, `getlk`) it should remove all locks belonging to `lock_owner`.
    #[derive(Debug)]
    pub(crate) struct Flush<'a> {
        #[expect(dead_code)]
        header: &'a fuse_in_header,
        arg: &'a fuse_flush_in,
    }
    impl Flush<'_> {
        /// The value set by the open method
        pub(crate) fn file_handle(&self) -> FileHandle {
            FileHandle(self.arg.fh)
        }
        pub(crate) fn lock_owner(&self) -> LockOwner {
            LockOwner(self.arg.lock_owner)
        }
    }

    #[derive(Debug)]
    pub(crate) struct Init<'a> {
        #[expect(dead_code)]
        header: &'a fuse_in_header,
        /// Unlike other operation we put this in a box,
        /// because we memcopy prefix into it instead of casting.
        arg: Box<fuse_init_in>,
    }
    impl Init<'_> {
        pub(crate) fn capabilities(&self) -> InitFlags {
            let flags = InitFlags::from_bits_retain(u64::from(self.arg.flags));
            if flags.contains(InitFlags::FUSE_INIT_EXT) {
                return InitFlags::from_bits_retain(
                    u64::from(self.arg.flags) | (u64::from(self.arg.flags2) << 32),
                );
            }
            flags
        }
        pub(crate) fn max_readahead(&self) -> u32 {
            self.arg.max_readahead
        }
        pub(crate) fn version(&self) -> super::Version {
            super::Version(self.arg.major, self.arg.minor)
        }

        pub(crate) fn reply(&self, config: &crate::KernelConfig) -> ResponseData {
            let flags = config.requested | InitFlags::FUSE_INIT_EXT;
            // use requested features and reported as capable
            let flags = flags & self.capabilities();

            let mut init = fuse_init_out {
                major: FUSE_KERNEL_VERSION,
                minor: FUSE_KERNEL_MINOR_VERSION,
                max_readahead: config.max_readahead,
                flags: flags.pair().0,
                max_background: config.max_background,
                congestion_threshold: config.congestion_threshold(),
                max_write: config.max_write,
                time_gran: config.time_gran.as_nanos() as u32,
                max_pages: config.max_pages(),
                unused2: 0,
                flags2: flags.pair().1,
                max_stack_depth: 0,
                reserved: [0; 6],
            };

            if flags.contains(InitFlags::FUSE_PASSTHROUGH) {
                init.max_stack_depth = config.max_stack_depth;
            }

            let init = init.as_bytes();
            let init = if self.arg.minor < 5 {
                &init[..FUSE_COMPAT_INIT_OUT_SIZE]
            } else if self.arg.minor < 23 {
                &init[..FUSE_COMPAT_22_INIT_OUT_SIZE]
            } else {
                init
            };
            ResponseData::new_data(init)
        }

        /// Reply with only our version, used when kernel major version > our major version.
        /// The kernel will then send a second INIT request with a compatible version.
        pub(crate) fn reply_version_only(&self) -> ResponseData {
            let init = fuse_init_out {
                major: FUSE_KERNEL_VERSION,
                minor: FUSE_KERNEL_MINOR_VERSION,
                max_readahead: 0,
                flags: 0,
                max_background: 0,
                congestion_threshold: 0,
                max_write: 0,
                time_gran: 0,
                max_pages: 0,
                unused2: 0,
                flags2: 0,
                max_stack_depth: 0,
                reserved: [0; 6],
            };
            ResponseData::new_data(init.as_bytes())
        }
    }

    /// Open a directory.
    ///
    /// Filesystem may store an arbitrary file handle (pointer, index, etc) in fh, and
    /// use this in other all other directory stream operations ([`ReadDir`], [`ReleaseDir`],
    /// [`FSyncDir`]). Filesystem may also implement stateless directory I/O and not store
    /// anything in `fh`, though that makes it impossible to implement standard conforming
    /// directory stream operations in case the contents of the directory can change
    /// between [`OpenDir`] and [`ReleaseDir`].
    ///
    /// TODO: Document how to implement "standard conforming directory stream operations"
    #[derive(Debug)]
    pub(crate) struct OpenDir<'a> {
        #[expect(dead_code)]
        header: &'a fuse_in_header,
        arg: &'a fuse_open_in,
    }
    impl OpenDir<'_> {
        /// Flags as passed to open
        pub(crate) fn flags(&self) -> OpenFlags {
            OpenFlags(self.arg.flags)
        }
    }

    /// Read directory.
    #[derive(Debug)]
    pub(crate) struct ReadDir<'a> {
        #[expect(dead_code)]
        header: &'a fuse_in_header,
        arg: &'a fuse_read_in,
    }
    impl ReadDir<'_> {
        /// The value set by the [`OpenDir`] method.
        pub(crate) fn file_handle(&self) -> FileHandle {
            FileHandle(self.arg.fh)
        }
        pub(crate) fn offset(&self) -> u64 {
            self.arg.offset
        }
        pub(crate) fn size(&self) -> u32 {
            self.arg.size
        }
    }

    /// Release an open directory.
    ///
    /// For every [`OpenDir`] call there will be exactly one [`ReleaseDir`] call.
    #[derive(Debug)]
    pub(crate) struct ReleaseDir<'a> {
        #[expect(dead_code)]
        header: &'a fuse_in_header,
        arg: &'a fuse_release_in,
    }
    impl ReleaseDir<'_> {
        /// The value set by the [`OpenDir`] method.
        pub(crate) fn file_handle(&self) -> FileHandle {
            FileHandle(self.arg.fh)
        }
        /// Release flags.
        pub(crate) fn release_flags(&self) -> ReleaseFlags {
            ReleaseFlags::from_bits_retain(self.arg.release_flags)
        }
        pub(crate) fn flush(&self) -> bool {
            self.release_flags()
                .contains(ReleaseFlags::FUSE_RELEASE_FLUSH)
        }
        pub(crate) fn lock_owner(&self) -> Option<LockOwner> {
            if self
                .release_flags()
                .contains(ReleaseFlags::FUSE_RELEASE_FLOCK_UNLOCK)
            {
                Some(LockOwner(self.arg.lock_owner))
            } else {
                None
            }
        }
        pub(crate) fn flags(&self) -> OpenFlags {
            OpenFlags(self.arg.flags)
        }
    }

    /// Synchronize directory contents.
    #[derive(Debug)]
    pub(crate) struct FSyncDir<'a> {
        #[expect(dead_code)]
        header: &'a fuse_in_header,
        arg: &'a fuse_fsync_in,
    }
    impl FSyncDir<'_> {
        /// The value set by the [`OpenDir`] method. See [`FileHandle`].
        pub(crate) fn file_handle(&self) -> FileHandle {
            FileHandle(self.arg.fh)
        }
        /// If set, then only the directory contents should be flushed, not the meta data.
        pub(crate) fn fdatasync(&self) -> bool {
            FsyncFlags::from_bits_retain(self.arg.fsync_flags)
                .contains(FsyncFlags::FUSE_FSYNC_FDATASYNC)
        }
    }

    /// Test for a POSIX file lock.
    #[derive(Debug)]
    pub(crate) struct GetLk<'a> {
        #[expect(dead_code)]
        header: &'a fuse_in_header,
        arg: &'a fuse_lk_in,
    }
    impl GetLk<'_> {
        /// The value set by the [`Open`] method. See [`FileHandle`].
        pub(crate) fn file_handle(&self) -> FileHandle {
            FileHandle(self.arg.fh)
        }
        pub(crate) fn lock(&self) -> Lock {
            Lock::from_abi(&self.arg.lk)
        }
        pub(crate) fn lock_owner(&self) -> LockOwner {
            LockOwner(self.arg.owner)
        }
    }

    /// Acquire, modify or release a POSIX file lock.
    ///
    /// For POSIX threads (NPTL) there's a 1-1 relation between pid and owner, but
    /// otherwise this is not always the case.  For checking lock ownership,
    /// 'fi->owner' must be used. The `l_pid` field in 'struct `flock`' should only be
    /// used to fill in this field in `getlk()`. Note: if the locking methods are not
    /// implemented, the kernel will still allow file locking to work locally.
    /// Hence these are only interesting for network filesystems and similar.
    #[derive(Debug)]
    pub(crate) struct SetLk<'a> {
        #[expect(dead_code)]
        header: &'a fuse_in_header,
        arg: &'a fuse_lk_in,
        /// If true, block until the lock can be acquired (SETLKW).
        /// If false, return immediately if the lock cannot be acquired (SETLK).
        wait: bool,
    }
    impl SetLk<'_> {
        /// The value set by the [`Open`] method. See [`FileHandle`].
        pub(crate) fn file_handle(&self) -> FileHandle {
            FileHandle(self.arg.fh)
        }
        pub(crate) fn lock(&self) -> Lock {
            Lock::from_abi(&self.arg.lk)
        }
        pub(crate) fn lock_owner(&self) -> LockOwner {
            LockOwner(self.arg.owner)
        }
        /// If true, block until the lock can be acquired (SETLKW).
        /// If false, return immediately if the lock cannot be acquired (SETLK).
        pub(crate) fn sleep(&self) -> bool {
            self.wait
        }
    }

    /// Check file access permissions.
    ///
    /// This will be called for the `access()` system call. If the `default_permissions`
    /// mount option is given, this method is not called.
    #[derive(Debug)]
    pub(crate) struct Access<'a> {
        #[expect(dead_code)]
        header: &'a fuse_in_header,
        arg: &'a fuse_access_in,
    }
    impl Access<'_> {
        pub(crate) fn mask(&self) -> AccessFlags {
            AccessFlags::from_bits_retain(self.arg.mask)
        }
    }

    /// Create and open a file.
    ///
    /// If the file does not exist, first create it with the specified mode, and then
    /// open it. Open flags (with the exception of `O_NOCTTY`) are available in flags.
    /// Filesystem may store an arbitrary file handle (pointer, index, etc) in fh,
    /// and use this in other all other file operations ([`Read`], [`Write`], [`Flush`], [`Release`],
    /// [`FSync`]). There are also some flags (`direct_io`, `keep_cache`) which the
    /// filesystem may set, to change the way the file is opened. See `fuse_file_info`
    /// structure in <`fuse_common.h`> for more details. If this method is not
    /// implemented or under Linux kernel versions earlier than 2.6.15, the [`MkNod`]
    /// and [`Open`] methods will be called instead.
    #[derive(Debug)]
    pub(crate) struct Create<'a> {
        #[expect(dead_code)]
        header: &'a fuse_in_header,
        arg: &'a fuse_create_in,
        name: &'a Path,
    }
    impl<'a> Create<'a> {
        pub(crate) fn name(&self) -> &'a Path {
            self.name
        }
        pub(crate) fn mode(&self) -> u32 {
            self.arg.mode
        }
        /// Flags as passed to the `create()` call
        pub(crate) fn flags(&self) -> i32 {
            self.arg.flags
        }
        pub(crate) fn umask(&self) -> u32 {
            self.arg.umask
        }
    }

    /// If a process issuing a FUSE filesystem request is interrupted, the
    /// following will happen:
    ///
    ///   1) If the request is not yet sent to userspace AND the signal is
    ///      fatal (SIGKILL or unhandled fatal signal), then the request is
    ///      dequeued and returns immediately.
    ///
    ///   2) If the request is not yet sent to userspace AND the signal is not
    ///      fatal, then an 'interrupted' flag is set for the request.  When
    ///      the request has been successfully transferred to userspace and
    ///      this flag is set, an INTERRUPT request is queued.
    ///
    ///   3) If the request is already sent to userspace, then an INTERRUPT
    ///      request is queued.
    ///
    /// [`Interrupt`] requests take precedence over other requests, so the
    /// userspace filesystem will receive queued [`Interrupt`]s before any others.
    ///
    /// The userspace filesystem may ignore the [`Interrupt`] requests entirely,
    /// or may honor them by sending a reply to the **original** request, with
    /// the error set to [`Errno::EINTR`].
    ///
    /// It is also possible that there's a race between processing the
    /// original request and its [`Interrupt`] request.  There are two
    /// possibilities:
    ///
    /// 1. The [`Interrupt`] request is processed before the original request is
    ///    processed
    ///
    /// 2. The [`Interrupt`] request is processed after the original request has
    ///    been answered
    ///
    /// If the filesystem cannot find the original request, it should wait for
    /// some timeout and/or a number of new requests to arrive, after which it
    /// should reply to the [`Interrupt`] request with an [`Errno::EAGAIN`] error.
    /// In case (1) the [`Interrupt`] request will be requeued.  In case (2) the
    /// [`Interrupt`] reply will be ignored.
    #[derive(Debug)]
    pub(crate) struct Interrupt<'a> {
        #[expect(dead_code)]
        header: &'a fuse_in_header,
        arg: &'a fuse_interrupt_in,
    }
    impl Interrupt<'_> {
        pub(crate) fn unique(&self) -> RequestId {
            RequestId(self.arg.unique)
        }
    }

    /// Map block index within file to block index within device.
    /// Note: This makes sense only for block device backed filesystems mounted
    /// with the `blkdev` option
    #[derive(Debug)]
    pub(crate) struct BMap<'a> {
        #[expect(dead_code)]
        header: &'a fuse_in_header,
        arg: &'a fuse_bmap_in,
    }
    impl BMap<'_> {
        pub(crate) fn block_size(&self) -> u32 {
            self.arg.blocksize
        }
        pub(crate) fn block(&self) -> u64 {
            self.arg.block
        }
    }

    #[derive(Debug)]
    pub(crate) struct Destroy<'a> {
        #[expect(dead_code)]
        header: &'a fuse_in_header,
    }

    /// Control device
    #[derive(Debug)]
    pub(crate) struct IoCtl<'a> {
        #[expect(dead_code)]
        header: &'a fuse_in_header,
        arg: &'a fuse_ioctl_in,
        data: &'a [u8],
    }
    impl IoCtl<'_> {
        pub(crate) fn in_data(&self) -> &[u8] {
            &self.data[..self.arg.in_size as usize]
        }
        pub(crate) fn unrestricted(&self) -> bool {
            self.flags().contains(IoctlFlags::FUSE_IOCTL_UNRESTRICTED)
        }
        /// The value set by the [`Open`] method. See [`FileHandle`].
        pub(crate) fn file_handle(&self) -> FileHandle {
            FileHandle(self.arg.fh)
        }
        pub(crate) fn flags(&self) -> IoctlFlags {
            IoctlFlags::from_bits_retain(self.arg.flags)
        }
        /// TODO: What does this mean?
        pub(crate) fn command(&self) -> u32 {
            self.arg.cmd
        }
        pub(crate) fn out_size(&self) -> u32 {
            self.arg.out_size
        }
    }

    /// Poll.
    #[derive(Debug)]
    pub(crate) struct Poll<'a> {
        #[expect(dead_code)]
        header: &'a fuse_in_header,
        arg: &'a fuse_poll_in,
    }
    impl Poll<'_> {
        /// The value set by the [`Open`] method. See [`FileHandle`].
        pub(crate) fn file_handle(&self) -> FileHandle {
            FileHandle(self.arg.fh)
        }

        /// The unique id used for the poll context by the kernel
        pub(crate) fn kernel_handle(&self) -> PollHandle {
            PollHandle(self.arg.kh)
        }

        /// The requested poll events
        pub(crate) fn events(&self) -> PollEvents {
            PollEvents::from_bits_retain(self.arg.events)
        }

        /// The poll request's flags
        pub(crate) fn flags(&self) -> PollFlags {
            PollFlags::from_bits_retain(self.arg.flags)
        }
    }

    /// `NotifyReply`.  TODO: currently unsupported by fuser
    #[derive(Debug)]
    pub(crate) struct NotifyReply<'a> {
        #[expect(dead_code)]
        header: &'a fuse_in_header,
        #[expect(dead_code)]
        arg: &'a [u8],
    }

    /// `BatchForget`: TODO: merge with Forget
    #[derive(Debug)]
    pub(crate) struct BatchForget<'a> {
        #[expect(dead_code)]
        header: &'a fuse_in_header,
        #[expect(dead_code)]
        arg: &'a fuse_batch_forget_in,
        nodes: &'a [fuse_forget_one],
    }
    impl<'a> BatchForget<'a> {
        pub(crate) fn nodes(&self) -> &'a [fuse_forget_one] {
            self.nodes
        }
    }

    /// Preallocate or deallocate space to a file
    ///
    /// Implementations should return EINVAL if offset or length are < 0
    #[derive(Debug)]
    pub(crate) struct FAllocate<'a> {
        #[expect(dead_code)]
        header: &'a fuse_in_header,
        arg: &'a fuse_fallocate_in,
    }
    impl FAllocate<'_> {
        /// The value set by the [`Open`] method. See [`FileHandle`].
        pub(crate) fn file_handle(&self) -> FileHandle {
            FileHandle(self.arg.fh)
        }
        pub(crate) fn offset(&self) -> Result<u64, Errno> {
            validate_off_t("fuse_allocate_in.arg.offset", self.arg.offset)?;
            Ok(self.arg.offset)
        }
        pub(crate) fn len(&self) -> Result<u64, Errno> {
            validate_off_t("fuse_allocate_in.arg.length", self.arg.length)?;
            Ok(self.arg.length)
        }
        /// `mode` as passed to fallocate.  See `man 2 fallocate`
        pub(crate) fn mode(&self) -> i32 {
            self.arg.mode
        }
    }

    /// Read directory.
    ///
    /// TODO: Document when this is called rather than `ReadDir`
    #[derive(Debug)]
    pub(crate) struct ReadDirPlus<'a> {
        #[expect(dead_code)]
        header: &'a fuse_in_header,
        arg: &'a fuse_read_in,
    }
    impl ReadDirPlus<'_> {
        /// The value set by the [`Open`] method. See [`FileHandle`].
        pub(crate) fn file_handle(&self) -> FileHandle {
            FileHandle(self.arg.fh)
        }
        pub(crate) fn offset(&self) -> u64 {
            self.arg.offset
        }
        pub(crate) fn size(&self) -> u32 {
            self.arg.size
        }
    }

    /// Rename a file.
    ///
    /// TODO: Document the differences to [`Rename`] and [`Exchange`]
    #[derive(Debug)]
    pub(crate) struct Rename2<'a> {
        #[expect(dead_code)]
        header: &'a fuse_in_header,
        arg: &'a fuse_rename2_in,
        name: &'a Path,
        newname: &'a Path,
        old_parent: INodeNo,
    }
    impl<'a> Rename2<'a> {
        pub(crate) fn from(&self) -> FilenameInDir<'a> {
            FilenameInDir::<'a> {
                dir: self.old_parent,
                name: self.name,
            }
        }
        pub(crate) fn to(&self) -> FilenameInDir<'a> {
            FilenameInDir::<'a> {
                dir: INodeNo(self.arg.newdir),
                name: self.newname,
            }
        }
        /// Flags as passed to renameat2.  As of Linux 3.18 this is
        /// [`libc::RENAME_EXCHANGE`], [`libc::RENAME_NOREPLACE`] and
        /// [`libc::RENAME_WHITEOUT`].  If you don't handle a particular flag
        /// reply with an EINVAL error.
        pub(crate) fn flags(&self) -> crate::RenameFlags {
            crate::RenameFlags::from_bits_retain(self.arg.flags)
        }
    }

    /// Reposition read/write file offset
    ///
    /// TODO: Document when you need to implement this.  Read and Write provide the offset anyway.
    #[derive(Debug)]
    pub(crate) struct Lseek<'a> {
        #[expect(dead_code)]
        header: &'a fuse_in_header,
        arg: &'a fuse_lseek_in,
    }
    impl Lseek<'_> {
        /// The value set by the [`Open`] method. See [`FileHandle`].
        pub(crate) fn file_handle(&self) -> FileHandle {
            FileHandle(self.arg.fh)
        }
        pub(crate) fn offset(&self) -> i64 {
            self.arg.offset
        }
        /// TODO: Make this return an enum
        pub(crate) fn whence(&self) -> i32 {
            self.arg.whence
        }
    }

    /// Copy the specified range from the source inode to the destination inode
    #[derive(Debug, Clone, Copy)]
    pub(crate) struct CopyFileRangeFile {
        pub(crate) inode: INodeNo,
        /// The value set by the [`Open`] method. See [`FileHandle`].
        pub(crate) file_handle: FileHandle,
        pub(crate) offset: u64,
    }
    #[derive(Debug)]
    pub(crate) struct CopyFileRange<'a> {
        header: &'a fuse_in_header,
        arg: &'a fuse_copy_file_range_in,
    }
    impl CopyFileRange<'_> {
        /// File and offset to copy data from
        pub(crate) fn src(&self) -> Result<CopyFileRangeFile, Errno> {
            let offset = self.arg.off_in as u64;
            validate_off_t("fuse_copy_file_range_in.arg.off_in", offset)?;
            Ok(CopyFileRangeFile {
                inode: INodeNo(self.header.nodeid),
                file_handle: FileHandle(self.arg.fh_in),
                offset,
            })
        }
        /// File and offset to copy data to
        pub(crate) fn dest(&self) -> Result<CopyFileRangeFile, Errno> {
            let offset = self.arg.off_out as u64;
            validate_off_t("fuse_copy_file_range_in.arg.off_out", offset)?;
            Ok(CopyFileRangeFile {
                inode: INodeNo(self.arg.nodeid_out),
                file_handle: FileHandle(self.arg.fh_out),
                offset,
            })
        }
        /// Number of bytes to copy
        pub(crate) fn len(&self) -> u64 {
            self.arg.len
        }
        pub(crate) fn flags(&self) -> CopyFileRangeFlags {
            CopyFileRangeFlags::from_bits_retain(self.arg.flags)
        }
    }

    /// `MacOS` only: Rename the volume. Set `fuse_init_out.flags` during init to
    /// `FUSE_VOL_RENAME` to enable
    #[cfg(target_os = "macos")]
    #[derive(Debug)]
    pub(crate) struct SetVolName<'a> {
        #[expect(dead_code)]
        header: &'a fuse_in_header,
        name: &'a OsStr,
    }
    #[cfg(target_os = "macos")]
    impl<'a> SetVolName<'a> {
        pub(crate) fn name(&self) -> &'a OsStr {
            self.name
        }
    }

    /// `MacOS` only: Query extended times (`bkuptime` and `crtime`). Set `fuse_init_out.flags`
    /// during init to `FUSE_XTIMES` to enable
    #[cfg(target_os = "macos")]
    #[derive(Debug)]
    pub(crate) struct GetXTimes<'a> {
        header: &'a fuse_in_header,
    }
    #[cfg(target_os = "macos")]
    impl GetXTimes<'_> {
        pub(crate) fn nodeid(&self) -> INodeNo {
            INodeNo(self.header.nodeid)
        }
    }
    // API TODO: Consider `rename2(RENAME_EXCHANGE)`
    /// `MacOS` only (undocumented)
    #[cfg(target_os = "macos")]
    #[derive(Debug)]
    pub(crate) struct Exchange<'a> {
        #[expect(dead_code)]
        header: &'a fuse_in_header,
        arg: &'a fuse_exchange_in,
        oldname: &'a Path,
        newname: &'a Path,
    }
    #[cfg(target_os = "macos")]
    impl<'a> Exchange<'a> {
        pub(crate) fn from(&self) -> FilenameInDir<'a> {
            FilenameInDir::<'a> {
                dir: INodeNo(self.arg.olddir),
                name: self.oldname,
            }
        }
        pub(crate) fn to(&self) -> FilenameInDir<'a> {
            FilenameInDir::<'a> {
                dir: INodeNo(self.arg.newdir),
                name: self.newname,
            }
        }
        pub(crate) fn options(&self) -> u64 {
            self.arg.options
        }
    }
    /// TODO: Document
    #[derive(Debug)]
    pub(crate) struct CuseInit<'a> {
        #[expect(dead_code)]
        header: &'a fuse_in_header,
        #[expect(dead_code)]
        arg: &'a fuse_init_in,
    }

    pub(crate) fn parse<'a>(
        header: &'a fuse_in_header,
        opcode: &fuse_opcode,
        data: &'a [u8],
    ) -> Option<Operation<'a>> {
        let mut data = ArgumentIterator::new(data);
        Some(match opcode {
            fuse_opcode::FUSE_LOOKUP => Operation::Lookup(Lookup {
                header,
                name: data.fetch_str()?,
            }),
            fuse_opcode::FUSE_FORGET => Operation::Forget(Forget {
                header,
                arg: data.fetch()?,
            }),
            fuse_opcode::FUSE_GETATTR => Operation::GetAttr(GetAttr {
                header,

                arg: data.fetch()?,
            }),
            fuse_opcode::FUSE_SETATTR => Operation::SetAttr(SetAttr {
                header,
                arg: data.fetch()?,
            }),
            fuse_opcode::FUSE_READLINK => Operation::ReadLink(ReadLink { header }),
            fuse_opcode::FUSE_SYMLINK => Operation::SymLink(SymLink {
                header,
                link_name: data.fetch_str()?.as_ref(),
                target: data.fetch_str()?.as_ref(),
            }),
            fuse_opcode::FUSE_MKNOD => Operation::MkNod(MkNod {
                header,
                arg: data.fetch()?,
                name: data.fetch_str()?.as_ref(),
            }),
            fuse_opcode::FUSE_MKDIR => Operation::MkDir(MkDir {
                header,
                arg: data.fetch()?,
                name: data.fetch_str()?.as_ref(),
            }),
            fuse_opcode::FUSE_UNLINK => Operation::Unlink(Unlink {
                header,
                name: data.fetch_str()?.as_ref(),
            }),
            fuse_opcode::FUSE_RMDIR => Operation::RmDir(RmDir {
                header,
                name: data.fetch_str()?.as_ref(),
            }),
            fuse_opcode::FUSE_RENAME => Operation::Rename(Rename {
                header,
                arg: data.fetch()?,
                name: data.fetch_str()?.as_ref(),
                newname: data.fetch_str()?.as_ref(),
            }),
            fuse_opcode::FUSE_LINK => Operation::Link(Link {
                header,
                arg: data.fetch()?,
                name: data.fetch_str()?.as_ref(),
            }),
            fuse_opcode::FUSE_OPEN => Operation::Open(Open {
                header,
                arg: data.fetch()?,
            }),
            fuse_opcode::FUSE_READ => Operation::Read(Read {
                header,
                arg: data.fetch()?,
            }),
            fuse_opcode::FUSE_WRITE => Operation::Write({
                let out = Write {
                    header,
                    arg: data.fetch()?,
                    data: data.fetch_all(),
                };
                assert!(out.data().len() == out.arg.size as usize);
                out
            }),
            fuse_opcode::FUSE_STATFS => Operation::StatFs(StatFs { header }),
            fuse_opcode::FUSE_RELEASE => Operation::Release(Release {
                header,
                arg: data.fetch()?,
            }),
            fuse_opcode::FUSE_FSYNC => Operation::FSync(FSync {
                header,
                arg: data.fetch()?,
            }),
            fuse_opcode::FUSE_SETXATTR => Operation::SetXAttr({
                let out = SetXAttr {
                    header,
                    arg: data.fetch()?,
                    name: data.fetch_str()?,
                    value: data.fetch_all(),
                };
                assert!(out.value.len() == out.arg.size as usize);
                out
            }),
            fuse_opcode::FUSE_GETXATTR => Operation::GetXAttr(GetXAttr {
                header,
                arg: data.fetch()?,
                name: data.fetch_str()?,
            }),
            fuse_opcode::FUSE_LISTXATTR => Operation::ListXAttr(ListXAttr {
                header,
                arg: data.fetch()?,
            }),
            fuse_opcode::FUSE_REMOVEXATTR => Operation::RemoveXAttr(RemoveXAttr {
                header,
                name: data.fetch_str()?,
            }),
            fuse_opcode::FUSE_FLUSH => Operation::Flush(Flush {
                header,
                arg: data.fetch()?,
            }),
            fuse_opcode::FUSE_INIT => Operation::Init(Init {
                header,
                arg: {
                    // fuse_init_in may be truncated in an old version of kernel
                    // or on macOS, or on FreeBSD.
                    // So we copy as much as available, and zerofill the rest.
                    let mut arg = fuse_init_in::new_zeroed();
                    let data = data.fetch_all();
                    // The oldest version of FUSE we support has four fields.
                    if data.len() < offset_of!(fuse_init_in, flags2) {
                        return None;
                    }
                    let prefix = cmp::min(data.len(), arg.as_bytes().len());
                    arg.as_mut_bytes()[..prefix].copy_from_slice(&data[..prefix]);
                    Box::new(arg)
                },
            }),
            fuse_opcode::FUSE_OPENDIR => Operation::OpenDir(OpenDir {
                header,
                arg: data.fetch()?,
            }),
            fuse_opcode::FUSE_READDIR => Operation::ReadDir(ReadDir {
                header,
                arg: data.fetch()?,
            }),
            fuse_opcode::FUSE_RELEASEDIR => Operation::ReleaseDir(ReleaseDir {
                header,
                arg: data.fetch()?,
            }),
            fuse_opcode::FUSE_FSYNCDIR => Operation::FSyncDir(FSyncDir {
                header,
                arg: data.fetch()?,
            }),
            fuse_opcode::FUSE_GETLK => Operation::GetLk(GetLk {
                header,
                arg: data.fetch()?,
            }),
            fuse_opcode::FUSE_SETLK => Operation::SetLk(SetLk {
                header,
                arg: data.fetch()?,
                wait: false,
            }),
            fuse_opcode::FUSE_SETLKW => Operation::SetLk(SetLk {
                header,
                arg: data.fetch()?,
                wait: true,
            }),
            fuse_opcode::FUSE_ACCESS => Operation::Access(Access {
                header,
                arg: data.fetch()?,
            }),
            fuse_opcode::FUSE_CREATE => Operation::Create(Create {
                header,
                arg: data.fetch()?,
                name: data.fetch_str()?.as_ref(),
            }),
            fuse_opcode::FUSE_INTERRUPT => Operation::Interrupt(Interrupt {
                header,
                arg: data.fetch()?,
            }),
            fuse_opcode::FUSE_BMAP => Operation::BMap(BMap {
                header,
                arg: data.fetch()?,
            }),
            fuse_opcode::FUSE_DESTROY => Operation::Destroy(Destroy { header }),
            fuse_opcode::FUSE_IOCTL => Operation::IoCtl(IoCtl {
                header,
                arg: data.fetch()?,
                data: data.fetch_all(),
            }),
            fuse_opcode::FUSE_POLL => Operation::Poll(Poll {
                header,
                arg: data.fetch()?,
            }),
            fuse_opcode::FUSE_NOTIFY_REPLY => Operation::NotifyReply(NotifyReply {
                header,
                arg: data.fetch_all(),
            }),
            fuse_opcode::FUSE_BATCH_FORGET => {
                let arg = data.fetch()?;
                Operation::BatchForget(BatchForget {
                    header,
                    arg,
                    nodes: data.fetch_slice(arg.count as usize)?,
                })
            }
            fuse_opcode::FUSE_FALLOCATE => Operation::FAllocate(FAllocate {
                header,
                arg: data.fetch()?,
            }),
            fuse_opcode::FUSE_READDIRPLUS => Operation::ReadDirPlus(ReadDirPlus {
                header,
                arg: data.fetch()?,
            }),
            fuse_opcode::FUSE_RENAME2 => Operation::Rename2(Rename2 {
                header,
                arg: data.fetch()?,
                name: data.fetch_str()?.as_ref(),
                newname: data.fetch_str()?.as_ref(),
                old_parent: INodeNo(header.nodeid),
            }),
            fuse_opcode::FUSE_LSEEK => Operation::Lseek(Lseek {
                header,
                arg: data.fetch()?,
            }),
            fuse_opcode::FUSE_COPY_FILE_RANGE => Operation::CopyFileRange(CopyFileRange {
                header,
                arg: data.fetch()?,
            }),

            #[cfg(target_os = "macos")]
            fuse_opcode::FUSE_SETVOLNAME => Operation::SetVolName(SetVolName {
                header,
                name: data.fetch_str()?,
            }),
            #[cfg(target_os = "macos")]
            fuse_opcode::FUSE_GETXTIMES => Operation::GetXTimes(GetXTimes { header }),
            #[cfg(target_os = "macos")]
            fuse_opcode::FUSE_EXCHANGE => Operation::Exchange(Exchange {
                header,
                arg: data.fetch()?,
                oldname: data.fetch_str()?.as_ref(),
                newname: data.fetch_str()?.as_ref(),
            }),

            fuse_opcode::CUSE_INIT => Operation::CuseInit(CuseInit {
                header,
                arg: data.fetch()?,
            }),
        })
    }
}
use op::*;

/// Filesystem operation (and arguments) the kernel driver wants us to perform. The fields of each
/// variant needs to match the actual arguments the kernel driver sends for the specific operation.
#[derive(Debug)]
#[allow(missing_docs)]
pub(crate) enum Operation<'a> {
    Lookup(Lookup<'a>),
    Forget(Forget<'a>),
    GetAttr(GetAttr<'a>),
    SetAttr(SetAttr<'a>),
    ReadLink(#[expect(dead_code)] ReadLink<'a>),
    SymLink(SymLink<'a>),
    MkNod(MkNod<'a>),
    MkDir(MkDir<'a>),
    Unlink(Unlink<'a>),
    RmDir(RmDir<'a>),
    Rename(Rename<'a>),
    Link(Link<'a>),
    Open(Open<'a>),
    Read(Read<'a>),
    Write(Write<'a>),
    StatFs(#[expect(dead_code)] StatFs<'a>),
    Release(Release<'a>),
    FSync(FSync<'a>),
    SetXAttr(SetXAttr<'a>),
    GetXAttr(GetXAttr<'a>),
    ListXAttr(ListXAttr<'a>),
    RemoveXAttr(RemoveXAttr<'a>),
    Flush(Flush<'a>),
    Init(Init<'a>),
    OpenDir(OpenDir<'a>),
    ReadDir(ReadDir<'a>),
    ReleaseDir(ReleaseDir<'a>),
    FSyncDir(FSyncDir<'a>),
    GetLk(GetLk<'a>),
    SetLk(SetLk<'a>),
    Access(Access<'a>),
    Create(Create<'a>),
    Interrupt(Interrupt<'a>),
    BMap(BMap<'a>),
    Destroy(Destroy<'a>),
    IoCtl(IoCtl<'a>),
    Poll(Poll<'a>),
    NotifyReply(#[expect(dead_code)] NotifyReply<'a>),
    BatchForget(BatchForget<'a>),
    FAllocate(FAllocate<'a>),
    ReadDirPlus(ReadDirPlus<'a>),
    Rename2(Rename2<'a>),
    Lseek(Lseek<'a>),
    CopyFileRange(CopyFileRange<'a>),

    #[cfg(target_os = "macos")]
    SetVolName(SetVolName<'a>),
    #[cfg(target_os = "macos")]
    GetXTimes(GetXTimes<'a>),
    #[cfg(target_os = "macos")]
    Exchange(Exchange<'a>),

    #[allow(dead_code)]
    CuseInit(CuseInit<'a>),
}

impl fmt::Display for Operation<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Operation::Lookup(x) => write!(f, "LOOKUP name {:?}", x.name()),
            Operation::Forget(x) => write!(f, "FORGET nlookup {}", x.nlookup()),
            Operation::GetAttr(_) => write!(f, "GETATTR"),
            Operation::SetAttr(x) => x.fmt(f),
            Operation::ReadLink(_) => write!(f, "READLINK"),
            Operation::SymLink(x) => {
                write!(
                    f,
                    "SYMLINK target {:?}, link_name {:?}",
                    x.target(),
                    x.link_name()
                )
            }
            Operation::MkNod(x) => write!(
                f,
                "MKNOD name {:?}, mode {:#05o}, rdev {}",
                x.name(),
                x.mode(),
                x.rdev()
            ),
            Operation::MkDir(x) => write!(f, "MKDIR name {:?}, mode {:#05o}", x.name(), x.mode()),
            Operation::Unlink(x) => write!(f, "UNLINK name {:?}", x.name()),
            Operation::RmDir(x) => write!(f, "RMDIR name {:?}", x.name),
            Operation::Rename(x) => write!(f, "RENAME src {:?}, dest {:?}", x.src(), x.dest()),
            Operation::Link(x) => write!(f, "LINK ino {:?}, dest {:?}", x.inode_no(), x.dest()),
            Operation::Open(x) => write!(f, "OPEN flags {:#x}", x.flags()),
            Operation::Read(x) => write!(
                f,
                "READ fh {:?}, offset {:?}, size {}",
                x.file_handle(),
                x.offset(),
                x.size()
            ),
            Operation::Write(x) => write!(
                f,
                "WRITE fh {:?}, offset {:?}, size {}, write flags {:#x}",
                x.file_handle(),
                x.offset(),
                x.data().len(),
                x.write_flags()
            ),
            Operation::StatFs(_) => write!(f, "STATFS"),
            Operation::Release(x) => write!(
                f,
                "RELEASE fh {:?}, flags {:#x}, flush {}, lock owner {:?}",
                x.file_handle(),
                x.flags(),
                x.flush(),
                x.lock_owner()
            ),
            Operation::FSync(x) => write!(
                f,
                "FSYNC fh {:?}, fsync fdatasync {}",
                x.file_handle(),
                x.fdatasync()
            ),
            Operation::SetXAttr(x) => write!(
                f,
                "SETXATTR name {:?}, size {}, flags {:#x}",
                x.name(),
                x.value().len(),
                x.flags()
            ),
            Operation::GetXAttr(x) => {
                write!(f, "GETXATTR name {:?}, size {:?}", x.name(), x.size())
            }
            Operation::ListXAttr(x) => write!(f, "LISTXATTR size {}", x.size()),
            Operation::RemoveXAttr(x) => write!(f, "REMOVEXATTR name {:?}", x.name()),
            Operation::Flush(x) => write!(
                f,
                "FLUSH fh {:?}, lock owner {:?}",
                x.file_handle(),
                x.lock_owner()
            ),
            Operation::Init(x) => write!(
                f,
                "INIT kernel ABI {}, capabilities {:#x}, max readahead {}",
                x.version(),
                x.capabilities(),
                x.max_readahead()
            ),
            Operation::OpenDir(x) => write!(f, "OPENDIR flags {:#x}", x.flags()),
            Operation::ReadDir(x) => write!(
                f,
                "READDIR fh {:?}, offset {}, size {}",
                x.file_handle(),
                x.offset(),
                x.size()
            ),
            Operation::ReleaseDir(x) => write!(
                f,
                "RELEASEDIR fh {:?}, flags {:#x}, flush {}, lock owner {:?}",
                x.file_handle(),
                x.flags(),
                x.flush(),
                x.lock_owner()
            ),
            Operation::FSyncDir(x) => write!(
                f,
                "FSYNCDIR fh {:?}, fsync fdatasync: {}",
                x.file_handle(),
                x.fdatasync()
            ),
            Operation::GetLk(x) => write!(
                f,
                "GETLK fh {:?}, lock owner {:?}",
                x.file_handle(),
                x.lock_owner()
            ),
            Operation::SetLk(x) => write!(
                f,
                "{} fh {:?}, lock owner {:?}",
                if x.sleep() { "SETLKW" } else { "SETLK" },
                x.file_handle(),
                x.lock_owner()
            ),
            Operation::Access(x) => write!(f, "ACCESS mask {:#05o}", x.mask()),
            Operation::Create(x) => write!(
                f,
                "CREATE name {:?}, mode {:#05o}, flags {:#x}",
                x.name(),
                x.mode(),
                x.flags()
            ),
            Operation::Interrupt(x) => write!(f, "INTERRUPT unique {:?}", x.unique()),
            Operation::BMap(x) => write!(f, "BMAP blocksize {}, ids {}", x.block_size(), x.block()),
            Operation::Destroy(_) => write!(f, "DESTROY"),
            Operation::IoCtl(x) => write!(
                f,
                "IOCTL fh {:?}, cmd {}, data size {}, flags {:#x}",
                x.file_handle(),
                x.command(),
                x.in_data().len(),
                x.flags()
            ),
            Operation::Poll(x) => write!(f, "POLL fh {:?}", x.file_handle()),
            Operation::NotifyReply(_) => write!(f, "NOTIFYREPLY"),
            Operation::BatchForget(x) => write!(f, "BATCHFORGET nodes {:?}", x.nodes()),
            Operation::FAllocate(_) => write!(f, "FALLOCATE"),
            Operation::ReadDirPlus(x) => write!(
                f,
                "READDIRPLUS fh {:?}, offset {}, size {}",
                x.file_handle(),
                x.offset(),
                x.size()
            ),
            Operation::Rename2(x) => write!(f, "RENAME2 from {:?}, to {:?}", x.from(), x.to()),
            Operation::Lseek(x) => write!(
                f,
                "LSEEK fh {:?}, offset {}, whence {}",
                x.file_handle(),
                x.offset(),
                x.whence()
            ),
            Operation::CopyFileRange(x) => write!(
                f,
                "COPY_FILE_RANGE src {:?}, dest {:?}, len {}",
                x.src(),
                x.dest(),
                x.len()
            ),

            #[cfg(target_os = "macos")]
            Operation::SetVolName(x) => write!(f, "SETVOLNAME name {:?}", x.name()),
            #[cfg(target_os = "macos")]
            Operation::GetXTimes(_) => write!(f, "GETXTIMES"),
            #[cfg(target_os = "macos")]
            Operation::Exchange(x) => write!(
                f,
                "EXCHANGE from {:?}, to {:?}, options {:#x}",
                x.from(),
                x.to(),
                x.options()
            ),

            Operation::CuseInit(_) => write!(f, "CUSE_INIT"),
        }
    }
}

/// Low-level request of a filesystem operation the kernel driver wants to perform.
#[derive(Debug)]
pub(crate) struct AnyRequest<'a> {
    header: &'a fuse_in_header,
    data: &'a [u8],
}

impl<'a> AnyRequest<'a> {
    /// Returns the unique identifier of this request.
    ///
    /// The FUSE kernel driver assigns a unique id to every concurrent request. This allows to
    /// distinguish between multiple concurrent requests. The unique id of a request may be
    /// reused in later requests after it has completed.
    pub(crate) fn unique(&self) -> RequestId {
        RequestId(self.header.unique)
    }

    /// Returns the node id of the inode this request is targeted to.
    pub(crate) fn nodeid(&self) -> INodeNo {
        INodeNo(self.header.nodeid)
    }

    /// Returns the UID that the process that triggered this request runs under.
    pub(crate) fn uid(&self) -> Uid {
        Uid::from_raw(self.header.uid)
    }

    /// Returns the GID that the process that triggered this request runs under.
    #[cfg_attr(not(test), expect(dead_code))]
    fn gid(&self) -> Gid {
        Gid::from_raw(self.header.gid)
    }

    /// Returns the PID of the process that triggered this request.
    #[cfg_attr(not(test), expect(dead_code))]
    fn pid(&self) -> Pid {
        Pid::from_raw(self.header.pid as i32)
    }

    pub(crate) fn operation(&self) -> Result<Operation<'a>, RequestError> {
        // Parse/check opcode
        let opcode = fuse_opcode::try_from(self.header.opcode).map_err(
            |e: num_enum::TryFromPrimitiveError<_>| RequestError::UnknownOperation(e.number),
        )?;
        // Parse/check operation arguments
        op::parse(self.header, &opcode, self.data).ok_or(RequestError::InsufficientData)
    }

    pub(crate) fn header(&self) -> &fuse_in_header {
        self.header
    }
}

impl fmt::Display for AnyRequest<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if let Ok(op) = self.operation() {
            write!(
                f,
                "FUSE({:3}) ino {:#018x} {}",
                self.header.unique, self.header.nodeid, op
            )
        } else {
            write!(
                f,
                "FUSE({:3}) ino {:#018x}",
                self.header.unique, self.header.nodeid
            )
        }
    }
}

impl<'a> TryFrom<&'a [u8]> for AnyRequest<'a> {
    type Error = RequestError;

    fn try_from(data: &'a [u8]) -> Result<Self, Self::Error> {
        // Parse a raw packet as sent by the kernel driver into typed data. Every request always
        // begins with a `fuse_in_header` struct followed by arguments depending on the opcode.
        let data_len = data.len();
        let mut arg_iter = ArgumentIterator::new(data);
        // Parse header
        let header: &fuse_in_header = arg_iter
            .fetch()
            .ok_or_else(|| RequestError::ShortReadHeader(arg_iter.len()))?;
        // Check data size
        if data_len < header.len as usize {
            return Err(RequestError::ShortRead(data_len, header.len as usize));
        }
        Ok(Self {
            header,
            data: &data[size_of::<fuse_in_header>()..header.len as usize],
        })
    }
}

#[cfg(test)]
mod tests {
    use std::ffi::OsStr;

    use crate::ll::request::*;
    use crate::ll::test::AlignedData;

    #[cfg(target_endian = "big")]
    const INIT_REQUEST: AlignedData<[u8; 104]> = AlignedData([
        // decimal 104 == hex 0x68
        0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x1a, // len, opcode
        0xde, 0xad, 0xbe, 0xef, 0xba, 0xad, 0xd0, 0x0d, // unique
        0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, // nodeid
        0xc0, 0x01, 0xd0, 0x0d, 0xc0, 0x01, 0xca, 0xfe, // uid, gid
        0xc0, 0xde, 0xba, 0x5e, 0x00, 0x00, 0x00, 0x00, // pid, padding
        0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, // major, minor
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // max_readahead, flags
        0x00, 0x00, 0x00, 0x00, // flags2 //TODO: nonzero data
        0x00, 0x00, 0x00, 0x00, // eleven unused fields
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    ]);

    #[cfg(target_endian = "little")]
    const INIT_REQUEST: AlignedData<[u8; 104]> = AlignedData([
        // decimal 104 == hex 0x68
        0x68, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, // len, opcode
        0x0d, 0xf0, 0xad, 0xba, 0xef, 0xbe, 0xad, 0xde, // unique
        0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, // nodeid
        0x0d, 0xd0, 0x01, 0xc0, 0xfe, 0xca, 0x01, 0xc0, // uid, gid
        0x5e, 0xba, 0xde, 0xc0, 0x00, 0x00, 0x00, 0x00, // pid, padding
        0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, // major, minor
        0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // max_readahead, flags
        0x00, 0x00, 0x00, 0x00, // flags2 //TODO: nonzero data
        0x00, 0x00, 0x00, 0x00, // eleven unused fields
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    ]);

    #[cfg(target_endian = "big")]
    const MKNOD_REQUEST: AlignedData<[u8; 56]> = [
        0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x08, // len, opcode
        0xde, 0xad, 0xbe, 0xef, 0xba, 0xad, 0xd0, 0x0d, // unique
        0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, // nodeid
        0xc0, 0x01, 0xd0, 0x0d, 0xc0, 0x01, 0xca, 0xfe, // uid, gid
        0xc0, 0xde, 0xba, 0x5e, 0x00, 0x00, 0x00, 0x00, // pid, padding
        0x00, 0x00, 0x01, 0xa4, 0x00, 0x00, 0x00, 0x00, // mode, rdev
        0x66, 0x6f, 0x6f, 0x2e, 0x74, 0x78, 0x74, 0x00, // name
    ];

    #[cfg(target_endian = "little")]
    const MKNOD_REQUEST: AlignedData<[u8; 64]> = AlignedData([
        0x40, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, // len, opcode
        0x0d, 0xf0, 0xad, 0xba, 0xef, 0xbe, 0xad, 0xde, // unique
        0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, // nodeid
        0x0d, 0xd0, 0x01, 0xc0, 0xfe, 0xca, 0x01, 0xc0, // uid, gid
        0x5e, 0xba, 0xde, 0xc0, 0x00, 0x00, 0x00, 0x00, // pid, padding
        0xa4, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mode, rdev
        0xed, 0x01, 0x00, 0x00, 0xe7, 0x03, 0x00, 0x00, // umask, padding
        0x66, 0x6f, 0x6f, 0x2e, 0x74, 0x78, 0x74, 0x00, // name
    ]);

    #[test]
    fn short_read_header() {
        match AnyRequest::try_from(&INIT_REQUEST[..20]) {
            Err(RequestError::ShortReadHeader(20)) => (),
            _ => panic!("Unexpected request parsing result"),
        }
    }

    #[test]
    fn short_read() {
        match AnyRequest::try_from(&INIT_REQUEST[..48]) {
            Err(RequestError::ShortRead(48, 104)) => (),
            _ => panic!("Unexpected request parsing result"),
        }
    }

    #[test]
    fn init() {
        let req = AnyRequest::try_from(&INIT_REQUEST[..]).unwrap();
        assert_eq!(req.header.len, 104);
        assert_eq!(req.header.opcode, 26);
        assert_eq!(req.unique(), RequestId(0xdead_beef_baad_f00d));
        assert_eq!(req.nodeid(), INodeNo(0x1122_3344_5566_7788));
        assert_eq!(req.uid(), Uid::from_raw(0xc001_d00d));
        assert_eq!(req.gid(), Gid::from_raw(0xc001_cafe));
        assert_eq!(req.pid(), Pid::from_raw(0xc0de_ba5e_u32 as i32));
        match req.operation().unwrap() {
            Operation::Init(x) => {
                assert_eq!(x.version(), Version(7, 8));
                assert_eq!(x.max_readahead(), 4096);
            }
            _ => panic!("Unexpected request operation"),
        }
    }

    #[test]
    fn mknod() {
        let req = AnyRequest::try_from(&MKNOD_REQUEST[..]).unwrap();
        assert_eq!(req.header.len, 64);
        assert_eq!(req.header.opcode, 8);
        assert_eq!(req.unique(), RequestId(0xdead_beef_baad_f00d));
        assert_eq!(req.nodeid(), INodeNo(0x1122_3344_5566_7788));
        assert_eq!(req.uid(), Uid::from_raw(0xc001_d00d));
        assert_eq!(req.gid(), Gid::from_raw(0xc001_cafe));
        assert_eq!(req.pid(), Pid::from_raw(0xc0de_ba5e_u32 as i32));
        match req.operation().unwrap() {
            Operation::MkNod(x) => {
                assert_eq!(x.mode(), 0o644);
                assert_eq!(x.umask(), 0o755);
                assert_eq!(x.name(), OsStr::new("foo.txt"));
            }
            _ => panic!("Unexpected request operation"),
        }
    }
}
