blown-fuse 0.3.1

Async filesystem in userspace (FUSE) daemon library
Documentation
// Based on libfuse/include/fuse_kernel.h

use crate::{util::display_or, FuseError, FuseResult};
use bitflags::bitflags;
use bytemuck::{self, try_cast_slice, try_from_bytes, Pod};
use bytemuck_derive::{Pod, Zeroable};
use num_enum::TryFromPrimitive;
use std::{convert::TryFrom, ffi::CStr, fmt};

pub const ROOT_ID: u64 = 1;
pub const MAJOR_VERSION: u32 = 7;
pub const TARGET_MINOR_VERSION: u32 = 32;
pub const REQUIRED_MINOR_VERSION: u32 = 31;

pub const MIN_READ_SIZE: usize = 8192;
pub const DIRENT_ALIGNMENT_BITS: usize = 3;

pub trait Structured<'o>: Sized {
    fn split_from(bytes: &'o [u8], header: &InHeader, last: bool) -> FuseResult<(Self, &'o [u8])>;

    fn toplevel_from(bytes: &'o [u8], header: &InHeader) -> FuseResult<Self> {
        match Self::split_from(bytes, header, true)? {
            (ok, end) if end.is_empty() => Ok(ok),
            _ => Err(FuseError::BadLength),
        }
    }
}

pub enum OpcodeSelect<L, R, const OP: u32> {
    Match(L),
    Alt(R),
}

#[derive(Pod, Zeroable, Copy, Clone)]
#[repr(C)]
pub struct InHeader {
    pub len: u32,
    pub opcode: u32,
    pub unique: u64,
    pub ino: u64,
    pub uid: u32,
    pub gid: u32,
    pub pid: u32,
    pub padding: u32,
}

#[derive(Pod, Zeroable, Copy, Clone)]
#[repr(C)]
pub struct OutHeader {
    pub len: u32,
    pub error: i32,
    pub unique: u64,
}

#[derive(TryFromPrimitive, Copy, Clone, Debug)]
#[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,
}

#[derive(TryFromPrimitive, Copy, Clone)]
#[repr(i32)]
pub enum NotifyCode {
    Poll = 1,
    InvalInode = 2,
    InvalEntry = 3,
    Store = 4,
    Retrieve = 5,
    Delete = 6,
}

#[derive(Pod, Zeroable, Copy, Clone)]
#[repr(C)]
pub struct Attrs {
    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 padding: u32,
}

#[derive(Pod, Zeroable, Copy, Clone)]
#[repr(C)]
pub struct FileLock {
    pub start: u64,
    pub end: u64,
    pub lock_type: u32,
    pub pid: u32,
}

#[derive(Pod, Zeroable, Copy, Clone)]
#[repr(C)]
pub struct EntryOut {
    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: Attrs,
}

#[derive(Pod, Zeroable, Copy, Clone)]
#[repr(C)]
pub struct Dirent {
    pub ino: u64,
    pub off: u64,
    pub namelen: u32,
    pub entry_type: u32,
}

#[derive(Pod, Zeroable, Copy, Clone)]
#[repr(C)]
pub struct DirentPlus {
    pub entry_out: EntryOut,
    pub dirent: Dirent,
}

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

#[derive(Pod, Zeroable, Copy, Clone)]
#[repr(C)]
pub struct GetattrIn {
    pub flags: u32,
    pub dummy: u32,
    pub fh: u64,
}

#[derive(Pod, Zeroable, Copy, Clone)]
#[repr(C)]
pub struct AttrOut {
    pub attr_valid: u64,
    pub attr_valid_nsec: u32,
    pub dummy: u32,
    pub attr: Attrs,
}

#[derive(Pod, Zeroable, Copy, Clone)]
#[repr(C)]
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 unused: u32,
    pub uid: u32,
    pub gid: u32,
    pub unused2: u32,
}

#[derive(Pod, Zeroable, Copy, Clone)]
#[repr(C)]
pub struct MknodIn {
    pub mode: u32,
    pub device: u32,
    pub umask: u32,
    pub padding: u32,
}

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

#[derive(Pod, Zeroable, Copy, Clone)]
#[repr(C)]
pub struct RenameIn {
    pub new_dir: u64,
}

#[derive(Pod, Zeroable, Copy, Clone)]
#[repr(C)]
pub struct LinkIn {
    pub old_ino: u64,
}

#[derive(Pod, Zeroable, Copy, Clone)]
#[repr(C)]
pub struct OpenIn {
    pub flags: u32,
    pub unused: u32,
}

#[derive(Pod, Zeroable, Copy, Clone)]
#[repr(C)]
pub struct OpenOut {
    pub fh: u64,
    pub open_flags: u32,
    pub padding: u32,
}

bitflags! {
    pub struct OpenOutFlags: u32 {
        const DIRECT_IO   = 1 << 0;
        const KEEP_CACHE  = 1 << 1;
        const NONSEEKABLE = 1 << 2;
        const CACHE_DIR   = 1 << 3;
        const STREAM      = 1 << 4;
    }
}

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

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

#[derive(Pod, Zeroable, Copy, Clone)]
#[repr(C)]
pub struct WriteOut {
    pub size: u32,
    pub padding: u32,
}

#[derive(Pod, Zeroable, Copy, Clone)]
#[repr(C)]
pub struct StatfsOut {
    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 padding: u32,
    pub spare: [u32; 6],
}

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

#[derive(Pod, Zeroable, Copy, Clone)]
#[repr(C)]
pub struct FsyncIn {
    pub fh: u64,
    pub fsync_flags: u32,
    pub padding: u32,
}

bitflags! {
    pub struct FsyncFlags: u32 {
        const FDATASYNC = 1 << 0;
    }
}

#[derive(Pod, Zeroable, Copy, Clone)]
#[repr(C)]
pub struct SetxattrIn {
    pub size: u32,
    pub flags: u32,
}

#[derive(Pod, Zeroable, Copy, Clone)]
#[repr(C)]
pub struct GetxattrIn {
    pub size: u32,
    pub padding: u32,
}

#[derive(Pod, Zeroable, Copy, Clone)]
#[repr(C)]
pub struct GetxattrOut {
    pub size: u32,
    pub padding: u32,
}

#[derive(Pod, Zeroable, Copy, Clone)]
#[repr(C)]
pub struct ListxattrIn {
    pub getxattr_in: GetxattrIn,
}

#[derive(Pod, Zeroable, Copy, Clone)]
#[repr(C)]
pub struct ListxattrOut {
    pub getxattr_out: GetxattrOut,
}

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

#[derive(Pod, Zeroable, Copy, Clone)]
#[repr(C)]
pub struct InitIn {
    pub major: u32,
    pub minor: u32,
    pub max_readahead: u32,
    pub flags: u32,
}

#[derive(Pod, Zeroable, Copy, Clone)]
#[repr(C)]
pub struct InitOut {
    pub major: u32,
    pub minor: u32,
    pub max_readahead: u32,
    pub flags: u32,
    pub max_background: u16,
    pub congestion_threshold: u16,
    pub max_write: u32,
    pub time_gran: u32,
    pub max_pages: u16,
    pub padding: u16,
    pub unused: [u32; 8],
}

bitflags! {
    pub struct InitFlags: u32 {
        const ASYNC_READ          = 1 << 0;
        const POSIX_LOCKS         = 1 << 1;
        const FILE_OPS            = 1 << 2;
        const ATOMIC_O_TRUNC      = 1 << 3;
        const EXPORT_SUPPORT      = 1 << 4;
        const BIG_WRITES          = 1 << 5;
        const DONT_MASK           = 1 << 6;
        const SPLICE_WRITE        = 1 << 7;
        const SPLICE_MOVE         = 1 << 8;
        const SPLICE_READ         = 1 << 9;
        const FLOCK_LOCKS         = 1 << 10;
        const HAS_IOCTL_DIR       = 1 << 11;
        const AUTO_INVAL_DATA     = 1 << 12;
        const DO_READDIRPLUS      = 1 << 13;
        const READDIRPLUS_AUTO    = 1 << 14;
        const ASYNC_DIO           = 1 << 15;
        const WRITEBACK_CACHE     = 1 << 16;
        const NO_OPEN_SUPPOR      = 1 << 17;
        const PARALLEL_DIROPS     = 1 << 18;
        const HANDLE_KILLPRIV     = 1 << 19;
        const POSIX_ACL           = 1 << 20;
        const ABORT_ERROR         = 1 << 21;
        const MAX_PAGES           = 1 << 22;
        const CACHE_SYMLINKS      = 1 << 23;
        const NO_OPENDIR_SUPPORT  = 1 << 24;
        const EXPLICIT_INVAL_DATA = 1 << 25;
    }
}

#[derive(Pod, Zeroable, Copy, Clone)]
#[repr(C)]
pub struct OpendirIn {
    pub open_in: OpenIn,
}

#[derive(Pod, Zeroable, Copy, Clone)]
#[repr(C)]
pub struct ReaddirIn {
    pub read_in: ReadIn,
}

#[derive(Pod, Zeroable, Copy, Clone)]
#[repr(C)]
pub struct ReleasedirIn {
    pub release_in: ReleaseIn,
}

#[derive(Pod, Zeroable, Copy, Clone)]
#[repr(C)]
pub struct FsyncdirIn {
    pub fsync_in: FsyncIn,
}

#[derive(Pod, Zeroable, Copy, Clone)]
#[repr(C)]
pub struct LkIn {
    pub fh: u64,
    pub owner: u64,
    pub lock: FileLock,
    pub lock_flags: u32,
    pub padding: u32,
}

#[derive(Pod, Zeroable, Copy, Clone)]
#[repr(C)]
pub struct GetlkIn {
    pub lk_in: LkIn,
}

#[derive(Pod, Zeroable, Copy, Clone)]
#[repr(C)]
pub struct SetlkIn {
    pub lk_in: LkIn,
}

#[derive(Pod, Zeroable, Copy, Clone)]
#[repr(C)]
pub struct SetlkwIn {
    pub lk_in: LkIn,
}

#[derive(Pod, Zeroable, Copy, Clone)]
#[repr(C)]
pub struct AccessIn {
    pub mask: u32,
    pub padding: u32,
}

#[derive(Pod, Zeroable, Copy, Clone)]
#[repr(C)]
pub struct CreateIn {
    pub flags: u32,
    pub mode: u32,
    pub umask: u32,
    pub padding: u32,
}

#[derive(Pod, Zeroable, Copy, Clone)]
#[repr(C)]
pub struct InterruptIn {
    pub unique: u64,
}

#[derive(Pod, Zeroable, Copy, Clone)]
#[repr(C)]
pub struct BmapIn {
    pub block: u64,
    pub block_size: u32,
    pub padding: u32,
}

#[derive(Pod, Zeroable, Copy, Clone)]
#[repr(C)]
pub struct BmapOut {
    pub block: u64,
}

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

#[derive(Pod, Zeroable, Copy, Clone)]
#[repr(C)]
pub struct PollIn {
    pub fh: u64,
    pub kh: u64,
    pub flags: u32,
    pub events: u32,
}

#[derive(Pod, Zeroable, Copy, Clone)]
#[repr(C)]
pub struct ForgetOne {
    pub ino: u64,
    pub nlookup: u64,
}

#[derive(Pod, Zeroable, Copy, Clone)]
#[repr(C)]
pub struct BatchForgetIn {
    pub count: u32,
    pub dummy: u32,
}

#[derive(Pod, Zeroable, Copy, Clone)]
#[repr(C)]
pub struct FallocateIn {
    pub fh: u64,
    pub offset: u64,
    pub length: u64,
    pub mode: u32,
    pub padding: u32,
}

#[derive(Pod, Zeroable, Copy, Clone)]
#[repr(C)]
pub struct ReaddirPlusIn {
    pub read_in: ReadIn,
}

#[derive(Pod, Zeroable, Copy, Clone)]
#[repr(C)]
pub struct Rename2In {
    pub new_dir: u64,
    pub flags: u32,
    pub padding: u32,
}

#[derive(Pod, Zeroable, Copy, Clone)]
#[repr(C)]
pub struct LseekIn {
    pub fh: u64,
    pub offset: u64,
    pub whence: u32,
    pub padding: u32,
}

#[derive(Pod, Zeroable, Copy, Clone)]
#[repr(C)]
pub struct CopyFileRangeIn {
    pub fh_in: u64,
    pub off_in: u64,
    pub nodeid_out: u64,
    pub fh_out: u64,
    pub off_out: u64,
    pub len: u64,
    pub flags: u64,
}

impl<'o> Structured<'o> for () {
    fn split_from(bytes: &'o [u8], _: &InHeader, _last: bool) -> FuseResult<(Self, &'o [u8])> {
        Ok(((), bytes))
    }
}

impl<'o, T, U> Structured<'o> for (T, U)
where
    T: Structured<'o>,
    U: Structured<'o>,
{
    fn split_from(bytes: &'o [u8], header: &InHeader, last: bool) -> FuseResult<(Self, &'o [u8])> {
        let (first, bytes) = T::split_from(bytes, header, false)?;
        let (second, end) = U::split_from(bytes, header, last)?;
        Ok(((first, second), end))
    }
}

impl<'o, T, U, V> Structured<'o> for (T, U, V)
where
    T: Structured<'o>,
    U: Structured<'o>,
    V: Structured<'o>,
{
    fn split_from(bytes: &'o [u8], header: &InHeader, last: bool) -> FuseResult<(Self, &'o [u8])> {
        let (first, bytes) = T::split_from(bytes, header, false)?;
        let ((second, third), end) = <(U, V)>::split_from(bytes, header, last)?;
        Ok(((first, second, third), end))
    }
}

impl<'o, T: Pod> Structured<'o> for &'o T {
    fn split_from(bytes: &'o [u8], _: &InHeader, _last: bool) -> FuseResult<(Self, &'o [u8])> {
        let (bytes, next_bytes) = bytes.split_at(bytes.len().min(std::mem::size_of::<T>()));
        match try_from_bytes(bytes) {
            Ok(t) => Ok((t, next_bytes)),
            Err(_) => Err(FuseError::Truncated),
        }
    }
}

impl<'o, T: Pod> Structured<'o> for &'o [T] {
    fn split_from(bytes: &'o [u8], _header: &InHeader, last: bool) -> FuseResult<(Self, &'o [u8])> {
        if !last {
            unimplemented!();
        }

        match try_cast_slice(bytes) {
            Ok(slice) => Ok((slice, &[])),
            Err(_) => Err(FuseError::Truncated),
        }
    }
}

impl<'o> Structured<'o> for &'o CStr {
    fn split_from(bytes: &'o [u8], _header: &InHeader, last: bool) -> FuseResult<(Self, &'o [u8])> {
        let (cstr, after_cstr): (&[u8], &[u8]) = if last {
            (bytes, &[])
        } else {
            match bytes.iter().position(|byte| *byte == b'\0') {
                Some(nul) => bytes.split_at(nul + 1),
                None => return Err(FuseError::Truncated),
            }
        };

        let cstr = CStr::from_bytes_with_nul(cstr).map_err(|_| FuseError::BadLength)?;
        Ok((cstr, after_cstr))
    }
}

impl<'o, L, R, const OP: u32> Structured<'o> for OpcodeSelect<L, R, OP>
where
    L: Structured<'o>,
    R: Structured<'o>,
{
    fn split_from(bytes: &'o [u8], header: &InHeader, last: bool) -> FuseResult<(Self, &'o [u8])> {
        if header.opcode == OP {
            L::split_from(bytes, header, last).map(|(l, end)| (OpcodeSelect::Match(l), end))
        } else {
            R::split_from(bytes, header, last).map(|(r, end)| (OpcodeSelect::Alt(r), end))
        }
    }
}

impl InHeader {
    pub fn from_bytes(bytes: &[u8]) -> FuseResult<(Self, Opcode)> {
        let header_bytes = &bytes[..bytes.len().min(std::mem::size_of::<InHeader>())];
        let header = try_from_bytes::<InHeader>(header_bytes).map_err(|_| FuseError::Truncated)?;

        if header.len as usize != bytes.len() {
            return Err(FuseError::BadLength);
        }

        let opcode = match Opcode::try_from(header.opcode) {
            Ok(opcode) => opcode,
            Err(_) => return Err(FuseError::BadOpcode),
        };

        Ok((*header, opcode))
    }
}

impl fmt::Display for InHeader {
    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
        let opcode = display_or(Opcode::try_from(self.opcode).ok(), "bad opcode");

        write!(
            fmt,
            "<{}> #{} len={} ino={} uid={} gid={} pid={}",
            opcode, self.unique, self.len, self.ino, self.uid, self.gid, self.pid
        )
    }
}

impl fmt::Display for Opcode {
    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(fmt, "{:?} ({})", self, *self as u32)
    }
}