From fc6f4052648a77a66f6bd50ffd1647992cb68b10 Mon Sep 17 00:00:00 2001 From: Alejandro Soto Date: Thu, 23 Dec 2021 18:57:18 -0600 Subject: Initial commit I started this project on February 2021, but postponed further development until now. The only modification introduced since then is try_trait_v2 (try_trait no longer exists). --- src/proto.rs | 884 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 884 insertions(+) create mode 100644 src/proto.rs (limited to 'src/proto.rs') diff --git a/src/proto.rs b/src/proto.rs new file mode 100644 index 0000000..c6b9925 --- /dev/null +++ b/src/proto.rs @@ -0,0 +1,884 @@ +// Based on libfuse/include/fuse_kernel.h + +use bitflags::bitflags; +use bytemuck::{self, Pod}; +use bytemuck_derive::{Pod, Zeroable}; +use num_enum::TryFromPrimitive; +use std::{convert::TryFrom, ffi::CStr, fmt, mem::replace}; + +use crate::{util::display_or, FuseError, FuseResult}; + +pub const ROOT_ID: u64 = 1; +pub const MIN_READ_SIZE: usize = 8192; +pub const MAJOR_VERSION: u32 = 7; +pub const TARGET_MINOR_VERSION: u32 = 32; +pub const REQUIRED_MINOR_VERSION: u32 = 31; + +pub struct Request<'a> { + header: &'a InHeader, + body: RequestBody<'a>, +} + +#[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, +} + +pub enum RequestBody<'a> { + Lookup { + name: &'a CStr, + }, + Forget(&'a ForgetIn), + Getattr(&'a GetattrIn), + Setattr(&'a SetattrIn), + Readlink, + Symlink { + name: &'a CStr, + target: &'a CStr, + }, + Mknod { + prefix: &'a MknodIn, + name: &'a CStr, + }, + Mkdir { + prefix: &'a MkdirIn, + target: &'a CStr, + }, + Unlink { + name: &'a CStr, + }, + Rmdir { + name: &'a CStr, + }, + Rename { + prefix: &'a RenameIn, + old: &'a CStr, + new: &'a CStr, + }, + Link(&'a LinkIn), + Open(&'a OpenIn), + Read(&'a ReadIn), + Write { + prefix: &'a WriteIn, + data: &'a [u8], + }, + Statfs, + Release(&'a ReleaseIn), + Fsync(&'a FsyncIn), + Setxattr { + prefix: &'a SetxattrIn, + name: &'a CStr, + value: &'a CStr, + }, + Getxattr { + prefix: &'a GetxattrIn, + name: &'a CStr, + }, + Listxattr(&'a ListxattrIn), + Removexattr { + name: &'a CStr, + }, + Flush(&'a FlushIn), + Init(&'a InitIn), + Opendir(&'a OpendirIn), + Readdir(&'a ReaddirIn), + Releasedir(&'a ReleasedirIn), + Fsyncdir(&'a FsyncdirIn), + Getlk(&'a GetlkIn), + Setlk(&'a SetlkIn), + Setlkw(&'a SetlkwIn), + Access(&'a AccessIn), + Create { + prefix: &'a CreateIn, + name: &'a CStr, + }, + Interrupt(&'a InterruptIn), + Bmap(&'a BmapIn), + Destroy, + Ioctl { + prefix: &'a IoctlIn, + data: &'a [u8], + }, + Poll(&'a PollIn), + NotifyReply, + BatchForget { + prefix: &'a BatchForgetIn, + forgets: &'a [ForgetOne], + }, + Fallocate(&'a FallocateIn), + ReaddirPlus(&'a ReaddirPlusIn), + Rename2 { + prefix: &'a Rename2In, + old: &'a CStr, + new: &'a CStr, + }, + Lseek(&'a LseekIn), + CopyFileRange(&'a CopyFileRangeIn), +} + +#[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 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, +} + +#[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 ListxattrIn { + pub getxattr_in: GetxattrIn, +} + +#[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 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 Request<'_> { + pub fn header(&self) -> &InHeader { + self.header + } + + pub fn body(&self) -> &RequestBody { + &self.body + } +} + +impl<'a> TryFrom<&'a [u8]> for Request<'a> { + type Error = FuseError; + + fn try_from(bytes: &'a [u8]) -> FuseResult { + use FuseError::*; + + fn split_from_bytes(bytes: &[u8]) -> FuseResult<(&T, &[u8])> { + let (bytes, next_bytes) = bytes.split_at(bytes.len().min(std::mem::size_of::())); + match bytemuck::try_from_bytes(bytes) { + Ok(t) => Ok((t, next_bytes)), + Err(_) => Err(Truncated), + } + } + + let full_bytes = bytes; + let (header, mut bytes) = split_from_bytes::(full_bytes)?; + + if header.len as usize != full_bytes.len() { + return Err(BadLength); + } + + let opcode = match Opcode::try_from(header.opcode) { + Ok(opcode) => opcode, + Err(_) => return Err(BadOpcode), + }; + + macro_rules! prefix { + ($op:ident, $ident:ident, $is_last:expr) => { + prefix!($op, $ident); + }; + + ($op:ident, $ident:ident) => { + let ($ident, after_prefix) = split_from_bytes::(bytes)?; + bytes = after_prefix; + }; + } + + fn cstr_from_bytes(bytes: &[u8], is_last: bool) -> FuseResult<(&CStr, &[u8])> { + let (cstr, after_cstr): (&[u8], &[u8]) = if is_last { + (bytes, &[]) + } else { + match bytes.iter().position(|byte| *byte == b'\0') { + Some(nul) => bytes.split_at(nul + 1), + None => return Err(Truncated), + } + }; + + let cstr = CStr::from_bytes_with_nul(cstr).map_err(|_| BadLength)?; + Ok((cstr, after_cstr)) + } + + macro_rules! cstr { + ($op:ident, $ident:ident, $is_last:expr) => { + let ($ident, next_bytes) = cstr_from_bytes(bytes, $is_last)?; + bytes = next_bytes; + }; + } + + macro_rules! name { + ($op:ident, $ident:ident, $is_last:expr) => { + cstr!($op, $ident, $is_last); + }; + } + + macro_rules! value { + ($op:ident, $ident:ident, $is_last:expr) => { + cstr!($op, $ident, $is_last); + }; + } + + macro_rules! target { + ($op:ident, $ident:ident, $is_last:expr) => { + cstr!($op, $ident, $is_last); + }; + } + + macro_rules! old { + ($op:ident, $ident:ident, $is_last:expr) => { + cstr!($op, $ident, $is_last); + }; + } + + macro_rules! new { + ($op:ident, $ident:ident, $is_last:expr) => { + cstr!($op, $ident, $is_last); + }; + } + + macro_rules! build_body { + ($op:ident, $last:ident) => { + $last!($op, $last, true); + }; + + ($op:ident, $field:ident, $($next:ident),+) => { + $field!($op, $field, false); + build_body!($op, $($next),+); + }; + } + + macro_rules! body { + ($op:ident) => { + RequestBody::$op + }; + + ($op:ident, prefix) => { + { + prefix!($op, prefix); + RequestBody::$op(prefix) + } + }; + + ($op:ident, prefix, data where len == $size_field:ident) => { + { + prefix!($op, prefix); + if prefix.$size_field as usize != bytes.len() { + return Err(BadLength); + } + + RequestBody::$op { prefix, data: replace(&mut bytes, &[]) } + } + }; + + /*($op:ident, $($field:ident),+) => { + { + $($field!($op, $field));+; + RequestBody::$op { $($field),+ } + } + };*/ + + ($op:ident, $($fields:ident),+) => { + { + build_body!($op, $($fields),+); + RequestBody::$op { $($fields),+ } + } + }; + } + + use Opcode::*; + let body = match opcode { + Lookup => body!(Lookup, name), + Forget => body!(Forget, prefix), + Getattr => body!(Getattr, prefix), + Setattr => body!(Setattr, prefix), + Readlink => body!(Readlink), + Symlink => body!(Symlink, name, target), + Mknod => body!(Mknod, prefix, name), + Mkdir => body!(Mkdir, prefix, target), + Unlink => body!(Unlink, name), + Rmdir => body!(Rmdir, name), + Rename => body!(Rename, prefix, old, new), + Link => body!(Link, prefix), + Open => body!(Open, prefix), + Read => body!(Read, prefix), + Write => body!(Write, prefix, data where len == size), + Statfs => body!(Statfs), + Release => body!(Release, prefix), + Fsync => body!(Fsync, prefix), + Setxattr => body!(Setxattr, prefix, name, value), + Getxattr => body!(Getxattr, prefix, name), + Listxattr => body!(Listxattr, prefix), + Removexattr => body!(Removexattr, name), + Flush => body!(Flush, prefix), + Init => body!(Init, prefix), + Opendir => body!(Opendir, prefix), + Readdir => body!(Readdir, prefix), + Releasedir => body!(Releasedir, prefix), + Fsyncdir => body!(Fsyncdir, prefix), + Getlk => body!(Getlk, prefix), + Setlk => body!(Setlk, prefix), + Setlkw => body!(Setlkw, prefix), + Access => body!(Access, prefix), + Create => body!(Create, prefix, name), + Interrupt => body!(Interrupt, prefix), + Bmap => body!(Bmap, prefix), + Destroy => body!(Destroy), + Ioctl => body!(Ioctl, prefix, data where len == in_size), + Poll => body!(Poll, prefix), + NotifyReply => body!(NotifyReply), + BatchForget => { + prefix!(BatchForget, prefix); + + let forgets = replace(&mut bytes, &[]); + let forgets = bytemuck::try_cast_slice(forgets).map_err(|_| Truncated)?; + + if prefix.count as usize != forgets.len() { + return Err(BadLength); + } + + RequestBody::BatchForget { prefix, forgets } + } + Fallocate => body!(Fallocate, prefix), + ReaddirPlus => body!(ReaddirPlus, prefix), + Rename2 => body!(Rename2, prefix, old, new), + Lseek => body!(Lseek, prefix), + CopyFileRange => body!(CopyFileRange, prefix), + }; + + if bytes.is_empty() { + Ok(Request { header, body }) + } else { + Err(BadLength) + } + } +} + +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) + } +} -- cgit v1.2.3