// FUSE wire-format constants + structs. Field ordering matches
// `include/uapi/linux/fuse.h` exactly so a guest-RAM byte slice can be
// reinterpreted via `pointer::read_unaligned::<T>()` without manual
// per-field decode.
//
// Sources of truth:
//   Linux kernel 6.12: include/uapi/linux/fuse.h
//   FUSE protocol version: 7.36 (negotiated at FUSE_INIT)
//
// Naming: we keep the FUSE_* / fuse_* identifiers from the C header so
// cross-references stay obvious. Rust idioms (UpperCamelCase types,
// snake_case fields) apply.

use std::mem::size_of;

pub const FUSE_KERNEL_VERSION: u32 = 7;
pub const FUSE_KERNEL_MINOR_VERSION: u32 = 36;
pub const FUSE_ROOT_ID: u64 = 1;

// === Opcodes ===========================================================
//
// We list the full upstream set so unknown opcodes are at least
// recognizable when they show up in tracing. The dispatcher only needs
// to handle the ones we choose to implement.

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum Opcode {
    Lookup = 1,
    Forget = 2,
    Getattr = 3,
    Setattr = 4,
    Readlink = 5,
    Symlink = 6,
    Mknod = 8,
    Mkdir = 9,
    Unlink = 10,
    Rmdir = 11,
    Rename = 12,
    Link = 13,
    Open = 14,
    Read = 15,
    Write = 16,
    Statfs = 17,
    Release = 18,
    Fsync = 20,
    Setxattr = 21,
    Getxattr = 22,
    Listxattr = 23,
    Removexattr = 24,
    Flush = 25,
    Init = 26,
    Opendir = 27,
    Readdir = 28,
    Releasedir = 29,
    Fsyncdir = 30,
    Getlk = 31,
    Setlk = 32,
    Setlkw = 33,
    Access = 34,
    Create = 35,
    Interrupt = 36,
    Bmap = 37,
    Destroy = 38,
    Ioctl = 39,
    Poll = 40,
    NotifyReply = 41,
    BatchForget = 42,
    Fallocate = 43,
    Readdirplus = 44,
    Rename2 = 45,
    Lseek = 46,
    CopyFileRange = 47,
    SetupMapping = 48,
    RemoveMapping = 49,
    SyncFs = 50,
    Tmpfile = 51,
    Statx = 52,
}

impl Opcode {
    pub fn from_u32(v: u32) -> Option<Self> {
        // Hand-rolled; std doesn't generate this for `repr(u32)` enums.
        match v {
            1 => Some(Self::Lookup),
            2 => Some(Self::Forget),
            3 => Some(Self::Getattr),
            4 => Some(Self::Setattr),
            5 => Some(Self::Readlink),
            6 => Some(Self::Symlink),
            8 => Some(Self::Mknod),
            9 => Some(Self::Mkdir),
            10 => Some(Self::Unlink),
            11 => Some(Self::Rmdir),
            12 => Some(Self::Rename),
            13 => Some(Self::Link),
            14 => Some(Self::Open),
            15 => Some(Self::Read),
            16 => Some(Self::Write),
            17 => Some(Self::Statfs),
            18 => Some(Self::Release),
            20 => Some(Self::Fsync),
            21 => Some(Self::Setxattr),
            22 => Some(Self::Getxattr),
            23 => Some(Self::Listxattr),
            24 => Some(Self::Removexattr),
            25 => Some(Self::Flush),
            26 => Some(Self::Init),
            27 => Some(Self::Opendir),
            28 => Some(Self::Readdir),
            29 => Some(Self::Releasedir),
            30 => Some(Self::Fsyncdir),
            31 => Some(Self::Getlk),
            32 => Some(Self::Setlk),
            33 => Some(Self::Setlkw),
            34 => Some(Self::Access),
            35 => Some(Self::Create),
            36 => Some(Self::Interrupt),
            37 => Some(Self::Bmap),
            38 => Some(Self::Destroy),
            39 => Some(Self::Ioctl),
            40 => Some(Self::Poll),
            41 => Some(Self::NotifyReply),
            42 => Some(Self::BatchForget),
            43 => Some(Self::Fallocate),
            44 => Some(Self::Readdirplus),
            45 => Some(Self::Rename2),
            46 => Some(Self::Lseek),
            47 => Some(Self::CopyFileRange),
            48 => Some(Self::SetupMapping),
            49 => Some(Self::RemoveMapping),
            50 => Some(Self::SyncFs),
            51 => Some(Self::Tmpfile),
            52 => Some(Self::Statx),
            _ => None,
        }
    }
}

// === Feature flags exchanged at FUSE_INIT ==============================

/// Don't apply umask to file mode on create operations.
pub const FUSE_DONT_MASK: u32 = 1 << 6;
/// Server supports SETUPMAPPING / REMOVEMAPPING for DAX. The CRITICAL
/// flag for virtio-fs — without it the guest will not request DAX
/// mappings.
pub const FUSE_MAP_ALIGNMENT: u32 = 1 << 26;
/// Server supports submounts. virtio-fs uses this when one virtio-fs
/// device exposes multiple host bind-mounts.
pub const FUSE_SUBMOUNTS: u32 = 1 << 27;
/// Server handles `kill priv` (suid drop) itself.
pub const FUSE_HANDLE_KILLPRIV_V2: u32 = 1 << 28;

// === Common headers ====================================================

/// Header of every FUSE request from guest to device.
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct InHeader {
    /// Total length of the request (header + opcode-specific in-args).
    pub len: u32,
    /// FUSE opcode (matches `Opcode`).
    pub opcode: u32,
    /// Unique request id; the device must echo it in the response.
    pub unique: u64,
    /// Inode the request targets (or 0 for opcodes that don't take an inode).
    pub nodeid: u64,
    pub uid: u32,
    pub gid: u32,
    pub pid: u32,
    pub padding: u32,
}

/// Header of every FUSE response from device to guest.
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct OutHeader {
    /// Total response length, including this header.
    pub len: u32,
    /// 0 on success; negated errno on failure (e.g. -2 for ENOENT).
    /// `i32` so it can be negative.
    pub error: i32,
    pub unique: u64,
}

// === FUSE_INIT — first request, negotiates protocol version + features ==

#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct InitIn {
    pub major: u32,
    pub minor: u32,
    pub max_readahead: u32,
    pub flags: u32,
    /// `flags2` was added in 7.36; pre-7.36 init-in's stop at `flags`.
    /// Newer guests send `flags2` + reserved words to grow flag space.
    pub flags2: u32,
    pub unused: [u32; 11],
}

#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct InitOut {
    pub major: u32,
    pub minor: u32,
    pub max_readahead: u32,
    pub flags: u32,
    pub max_background: u16,
    pub congestion_threshold: u16,
    /// Largest single FUSE_WRITE payload the device accepts.
    pub max_write: u32,
    /// Native filesystem block granularity hint.
    pub time_gran: u32,
    pub max_pages: u16,
    /// Granularity required for SETUPMAPPING file offsets, in bytes.
    /// Reported to the guest so its DAX driver knows the host-side
    /// alignment for SETUPMAPPING slots. We'll set this to PAGE_SIZE
    /// (16 KiB on Apple Silicon).
    pub map_alignment: u16,
    pub flags2: u32,
    pub max_stack_depth: u32,
    pub unused: [u32; 6],
}

// === FUSE_SETUPMAPPING / REMOVEMAPPING — the DAX critical path =========
//
// SETUPMAPPING asks the device to map [foffset, foffset+len) of the
// file owned by `fh` into the device's DAX shared-memory region at
// offset `moffset`. The device returns success; the guest then
// performs loads/stores at (DAX_BASE + moffset) and they Just Work.
//
// REMOVEMAPPING tears that down. Multiple submapping descriptors can
// be sent in one request via `count`.

pub const FUSE_SETUPMAPPING_FLAG_WRITE: u64 = 1 << 0;
pub const FUSE_SETUPMAPPING_FLAG_READ: u64 = 1 << 1;

#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct SetupMappingIn {
    /// File handle (from a prior FUSE_OPEN).
    pub fh: u64,
    /// Offset into the host file.
    pub foffset: u64,
    /// Length of the mapping.
    pub len: u64,
    /// `FUSE_SETUPMAPPING_FLAG_*`.
    pub flags: u64,
    /// Offset within the device's DAX window (the moffset).
    pub moffset: u64,
}

#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct RemoveMappingIn {
    /// Count of `RemoveMappingOne` entries that follow.
    pub count: u32,
    pub _pad: u32,
}

#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct RemoveMappingOne {
    pub moffset: u64,
    pub len: u64,
}

// === Common attribute struct ===========================================

#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct Attr {
    pub ino: u64,
    pub size: u64,
    pub blocks: u64,
    pub atime: u64,
    pub mtime: u64,
    pub ctime: u64,
    pub atimensec: u32,
    pub mtimensec: u32,
    pub ctimensec: u32,
    pub mode: u32,
    pub nlink: u32,
    pub uid: u32,
    pub gid: u32,
    pub rdev: u32,
    pub blksize: u32,
    pub flags: u32,
}

// === FUSE_LOOKUP / FUSE_GETATTR ========================================

#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct EntryOut {
    /// New inode the lookup resolved to (or 0 on negative-cache).
    pub nodeid: u64,
    pub generation: u64,
    pub entry_valid: u64,
    pub attr_valid: u64,
    pub entry_valid_nsec: u32,
    pub attr_valid_nsec: u32,
    pub attr: Attr,
}

#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct GetattrIn {
    pub flags: u32,
    pub _dummy: u32,
    pub fh: u64,
}

#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct AttrOut {
    pub attr_valid: u64,
    pub attr_valid_nsec: u32,
    pub _pad: u32,
    pub attr: Attr,
}

// === FUSE_OPEN / FUSE_READ / FUSE_WRITE ================================

#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct OpenIn {
    pub flags: u32,
    pub open_flags: u32,
}

#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct OpenOut {
    pub fh: u64,
    pub open_flags: u32,
    pub _pad: u32,
}

#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct ReadIn {
    pub fh: u64,
    pub offset: u64,
    pub size: u32,
    pub read_flags: u32,
    pub lock_owner: u64,
    pub flags: u32,
    pub _pad: u32,
}

#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct WriteIn {
    pub fh: u64,
    pub offset: u64,
    pub size: u32,
    pub write_flags: u32,
    pub lock_owner: u64,
    pub flags: u32,
    pub _pad: u32,
}

#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct WriteOut {
    pub size: u32,
    pub _pad: u32,
}

// === FUSE_RELEASE / FUSE_RELEASEDIR ====================================

#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct ReleaseIn {
    pub fh: u64,
    pub flags: u32,
    pub release_flags: u32,
    pub lock_owner: u64,
}

// === FUSE_FSYNC / FUSE_FSYNCDIR ========================================

#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct FsyncIn {
    pub fh: u64,
    /// bit 0 = datasync (no metadata).
    pub fsync_flags: u32,
    pub _pad: u32,
}

// === FUSE_FLUSH ========================================================
//
// Sent by the kernel on close(2). The server should commit any
// state associated with `fh` to durable storage; the call must NOT
// release `fh` (a separate FUSE_RELEASE does that). No reply body.

#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct FlushIn {
    pub fh: u64,
    pub unused: u32,
    pub padding: u32,
    pub lock_owner: u64,
}

// === FUSE_SETATTR ======================================================
//
// `valid` is a bitmask of `FATTR_*` flags indicating which fields the
// kernel wants applied; everything else MUST be ignored. The reply is
// a fresh `AttrOut` carrying the post-change attributes.
//
// Layout matches `struct fuse_setattr_in` from `include/uapi/linux/fuse.h`
// (FUSE 7.36) exactly. Total size is 88 bytes (verified by the size_of
// assertion at the bottom of this module).

#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct SetattrIn {
    pub valid: u32,
    pub padding: u32,
    pub fh: u64,
    pub size: u64,
    pub lock_owner: u64,
    pub atime: u64,
    pub mtime: u64,
    pub ctime: u64,
    pub atimensec: u32,
    pub mtimensec: u32,
    pub ctimensec: u32,
    pub mode: u32,
    pub unused4: u32,
    pub uid: u32,
    pub gid: u32,
    pub unused5: u32,
}

/// Validity bitmask flags for `SetattrIn::valid`. The kernel sets
/// exactly one bit per attribute the request modifies; the rest of
/// the corresponding fields are undefined.
pub const FATTR_MODE: u32 = 1 << 0;
pub const FATTR_UID: u32 = 1 << 1;
pub const FATTR_GID: u32 = 1 << 2;
pub const FATTR_SIZE: u32 = 1 << 3;
pub const FATTR_ATIME: u32 = 1 << 4;
pub const FATTR_MTIME: u32 = 1 << 5;
pub const FATTR_FH: u32 = 1 << 6;
pub const FATTR_ATIME_NOW: u32 = 1 << 7;
pub const FATTR_MTIME_NOW: u32 = 1 << 8;
pub const FATTR_LOCKOWNER: u32 = 1 << 9;
pub const FATTR_CTIME: u32 = 1 << 10;

// === FUSE_RENAME / FUSE_RENAME2 ========================================
//
// Payload after the in-header is: `RenameIn` (or `Rename2In` for the
// flag-bearing variant), then `old_name\0new_name\0`.
//
// Implementing both is ideal; npm/pnpm/tar only need the vanilla
// `Rename` op (the kernel falls back to it when the server doesn't
// advertise `Rename2` support).

#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct RenameIn {
    /// nodeid of the destination directory.
    pub newdir: u64,
}

#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct Rename2In {
    /// nodeid of the destination directory.
    pub newdir: u64,
    /// `RENAME_NOREPLACE` (1), `RENAME_EXCHANGE` (2), etc. — Linux
    /// uapi/linux/fs.h constants. 0 = vanilla rename.
    pub flags: u32,
    pub padding: u32,
}

// === FUSE notifications (server → guest, unsolicited) ==================
//
// Notifications travel on the same wire shape as a reply, but with
// `unique = 0` and `error` set to the negated notification opcode.
// Guest sees unique=0 in the OutHeader and dispatches based on error.

#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct NotifyInvalInodeOut {
    pub ino: i64,
    pub off: i64,
    pub len: i64,
}

#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct NotifyInvalEntryOut {
    pub parent: u64,
    pub namelen: u32,
    pub _pad: u32,
}

/// Notification "opcode" — encoded as the NEGATED value in OutHeader.error
/// for server-initiated messages. NOT in the same enum as request Opcodes.
pub const FUSE_NOTIFY_INVAL_INODE: i32 = 2;
pub const FUSE_NOTIFY_INVAL_ENTRY: i32 = 3;
pub const FUSE_NOTIFY_DELETE: i32 = 6;

// === FUSE_CREATE / FUSE_UNLINK / FUSE_MKDIR / FUSE_RMDIR ================

#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct CreateIn {
    pub flags: u32,
    pub mode: u32,
    pub umask: u32,
    pub open_flags: u32,
}

#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct MkdirIn {
    pub mode: u32,
    pub umask: u32,
}

// === FUSE_LINK =========================================================
//
// Payload: `LinkIn` header (the source nodeid) + NUL-terminated `name`.
// Reply: `EntryOut` for the new link.

#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct LinkIn {
    /// Source inode the new name will hard-link to.
    pub old_nodeid: u64,
}

// === FUSE_FORGET / FUSE_BATCH_FORGET ===================================

#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct ForgetIn {
    pub nlookup: u64,
}

// === FUSE_STATFS — filesystem stats ====================================

#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct Kstatfs {
    pub blocks: u64,
    pub bfree: u64,
    pub bavail: u64,
    pub files: u64,
    pub ffree: u64,
    pub bsize: u32,
    pub namelen: u32,
    pub frsize: u32,
    pub _pad: u32,
    pub spare: [u32; 6],
}

#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct StatfsOut {
    pub st: Kstatfs,
}

// === FUSE_IOCTL — ioctl forwarding to user-space FUSE server ==========
//
// When a guest userspace process calls ioctl() on a file backed by
// virtio-fs, the kernel forwards the call to us as FUSE_IOCTL.
//
// Wire layout (from include/uapi/linux/fuse.h):
//
//   struct fuse_ioctl_in {
//       u64 fh;          // file handle from FUSE_OPEN
//       u32 flags;       // FUSE_IOCTL_* flags below
//       u32 cmd;          // the ioctl() request number (incl. dir/size/type/nr)
//       u64 arg;         // caller's userland pointer (untrusted; informational)
//       u32 in_size;     // bytes after this struct meant for the server
//       u32 out_size;    // max bytes the server may write back
//   };
//
//   payload[in_size]              ; the input bytes (if cmd has _IOC_WRITE)
//
// Server response:
//
//   struct fuse_ioctl_out {
//       i32 result;       // 0+ = ioctl return value; <0 = -errno
//       u32 flags;        // FUSE_IOCTL_RETRY etc.
//       u32 in_iovs;      // unrestricted ioctl rich-vec count (we don't use)
//       u32 out_iovs;     // ditto
//   };
//
//   payload[out_size]             ; bytes written back to user (when cmd has _IOC_READ)
//
// The kernel sets `out_size` from the ioctl request encoding's size field
// (the middle bits of `cmd`). For `_IOR('a', 35, [128 bytes])`
// = 0x80806123, out_size = 128.

/// `FUSE_IOCTL_*` flag bits — copied verbatim from Linux's UAPI.
pub const FUSE_IOCTL_COMPAT: u32 = 1 << 0;
pub const FUSE_IOCTL_UNRESTRICTED: u32 = 1 << 1;
pub const FUSE_IOCTL_RETRY: u32 = 1 << 2;
pub const FUSE_IOCTL_32BIT: u32 = 1 << 3;
pub const FUSE_IOCTL_DIR: u32 = 1 << 4;
pub const FUSE_IOCTL_COMPAT_X32: u32 = 1 << 5;

/// Helpers for decoding the ioctl command number's direction bits, so
/// the server can know how many bytes the kernel expects to copy in
/// vs. out without trusting `in_size`/`out_size` blindly. Matches
/// `include/uapi/asm-generic/ioctl.h`.
pub const IOC_NRBITS: u32 = 8;
pub const IOC_TYPEBITS: u32 = 8;
pub const IOC_SIZEBITS: u32 = 14;
pub const IOC_DIRBITS: u32 = 2;
pub const IOC_NRSHIFT: u32 = 0;
pub const IOC_TYPESHIFT: u32 = IOC_NRSHIFT + IOC_NRBITS;
pub const IOC_SIZESHIFT: u32 = IOC_TYPESHIFT + IOC_TYPEBITS;
pub const IOC_DIRSHIFT: u32 = IOC_SIZESHIFT + IOC_SIZEBITS;
pub const IOC_NONE: u32 = 0;
pub const IOC_WRITE: u32 = 1;
pub const IOC_READ: u32 = 2;

/// Decode an ioctl request number — primary use is the Rosetta cache-
/// settings query `_IOR('a', 35, [128 bytes])` = `0x80806123`.
#[inline]
pub fn ioc_dir(cmd: u32) -> u32 {
    (cmd >> IOC_DIRSHIFT) & ((1 << IOC_DIRBITS) - 1)
}
#[inline]
pub fn ioc_type(cmd: u32) -> u32 {
    (cmd >> IOC_TYPESHIFT) & ((1 << IOC_TYPEBITS) - 1)
}
#[inline]
pub fn ioc_nr(cmd: u32) -> u32 {
    (cmd >> IOC_NRSHIFT) & ((1 << IOC_NRBITS) - 1)
}
#[inline]
pub fn ioc_size(cmd: u32) -> u32 {
    (cmd >> IOC_SIZESHIFT) & ((1 << IOC_SIZEBITS) - 1)
}

#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct IoctlIn {
    pub fh: u64,
    pub flags: u32,
    pub cmd: u32,
    pub arg: u64,
    pub in_size: u32,
    pub out_size: u32,
}

#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct IoctlOut {
    pub result: i32,
    pub flags: u32,
    pub in_iovs: u32,
    pub out_iovs: u32,
}

// === Readdir entries — wire format ====================================
//
// READDIR returns a stream of these in the writable buffer:
//
//   struct fuse_dirent {
//       u64 ino;
//       u64 off;
//       u32 namelen;
//       u32 type;
//       char name[];                      // NUL-padded to 8-byte align
//   };
//
// Each entry occupies `align_up(sizeof(fuse_dirent) + namelen, 8)` bytes.

#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct DirentHeader {
    pub ino: u64,
    pub off: u64,
    pub namelen: u32,
    pub typ: u32,
}

/// Standard dt_type values from <dirent.h>. virtio-fs uses these.
pub const DT_UNKNOWN: u32 = 0;
pub const DT_FIFO: u32 = 1;
pub const DT_CHR: u32 = 2;
pub const DT_DIR: u32 = 4;
pub const DT_BLK: u32 = 6;
pub const DT_REG: u32 = 8;
pub const DT_LNK: u32 = 10;
pub const DT_SOCK: u32 = 12;

/// Align `n` up to the nearest multiple of `to` (must be power of 2).
pub const fn align_up(n: usize, to: usize) -> usize {
    (n + to - 1) & !(to - 1)
}

// Standard POSIX mode bits we use to populate Attr.mode.
pub const S_IFMT: u32 = 0o170000;
pub const S_IFREG: u32 = 0o100000;
pub const S_IFDIR: u32 = 0o040000;
pub const S_IFLNK: u32 = 0o120000;
pub const S_IFIFO: u32 = 0o010000;
pub const S_IFBLK: u32 = 0o060000;
pub const S_IFCHR: u32 = 0o020000;
pub const S_IFSOCK: u32 = 0o140000;

// === Sanity: struct sizes must match the C header layout ==============
//
// If a contributor adds/reorders fields these will fail to compile.
// The expected sizes come from `pahole` on a vanilla 6.12 kernel.

const _: () = {
    assert!(size_of::<InHeader>() == 40);
    assert!(size_of::<OutHeader>() == 16);
    assert!(size_of::<InitIn>() == 64);
    assert!(size_of::<InitOut>() == 64);
    assert!(size_of::<SetupMappingIn>() == 40);
    assert!(size_of::<RemoveMappingOne>() == 16);
    assert!(size_of::<Attr>() == 88);
    assert!(size_of::<EntryOut>() == 128);
    assert!(size_of::<AttrOut>() == 104);
    assert!(size_of::<OpenIn>() == 8);
    assert!(size_of::<OpenOut>() == 16);
    assert!(size_of::<ReadIn>() == 40);
    assert!(size_of::<WriteIn>() == 40);
    assert!(size_of::<WriteOut>() == 8);
    assert!(size_of::<ReleaseIn>() == 24);
    assert!(size_of::<FsyncIn>() == 16);
    assert!(size_of::<FlushIn>() == 24);
    assert!(size_of::<SetattrIn>() == 88);
    assert!(size_of::<RenameIn>() == 8);
    assert!(size_of::<Rename2In>() == 16);
    assert!(size_of::<CreateIn>() == 16);
    assert!(size_of::<MkdirIn>() == 8);
    assert!(size_of::<LinkIn>() == 8);
    assert!(size_of::<NotifyInvalInodeOut>() == 24);
    assert!(size_of::<NotifyInvalEntryOut>() == 16);
    assert!(size_of::<ForgetIn>() == 8);
    assert!(size_of::<Kstatfs>() == 80);
    assert!(size_of::<StatfsOut>() == 80);
    assert!(size_of::<DirentHeader>() == 24);
};
