From eeac00b6a61bd4b5173b8a92112593e7f8af58f5 Mon Sep 17 00:00:00 2001 From: Richard Lawrence Date: Wed, 16 Jul 2025 09:53:54 -0500 Subject: [PATCH 01/19] Filesystem methods now return `Result<_, Errno>`. Filesystem methods callback argument `reply` has been removed. reply::ReplyX traits have been combined into a single ReplyHandler type. request::dispatch passes the filesystem's response to an appropriate ReplyHandler method. --- src/lib.rs | 202 ++++++------- src/reply.rs | 769 ++++++++----------------------------------------- src/request.rs | 669 ++++++++++++++++++++++++++++++------------ 3 files changed, 690 insertions(+), 950 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d0e0f493..97eb903a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,7 +6,6 @@ #![warn(missing_docs, missing_debug_implementations, rust_2018_idioms)] -use libc::{c_int, ENOSYS, EPERM}; use log::warn; use mnt::mount_options::parse_options_from_args; #[cfg(feature = "serializable")] @@ -31,16 +30,7 @@ pub use mnt::mount_options::MountOption; pub use notify::{Notifier, PollHandle}; #[cfg(feature = "abi-7-40")] pub use passthrough::BackingId; -#[cfg(feature = "abi-7-11")] -pub use reply::ReplyPoll; -#[cfg(target_os = "macos")] -pub use reply::ReplyXTimes; -pub use reply::ReplyXattr; -pub use reply::{Reply, ReplyAttr, ReplyData, ReplyEmpty, ReplyEntry, ReplyOpen}; -pub use reply::{ - ReplyBmap, ReplyCreate, ReplyDirectory, ReplyDirectoryPlus, ReplyIoctl, ReplyLock, ReplyLseek, - ReplyStatfs, ReplyWrite, -}; +pub use ll::Errno; pub use request::Request; pub use session::{BackgroundSession, Session, SessionACL, SessionUnmounter}; #[cfg(feature = "abi-7-28")] @@ -329,7 +319,7 @@ pub trait Filesystem { /// Initialize filesystem. /// Called before any other filesystem method. /// The kernel module connection can be configured using the KernelConfig object - fn init(&mut self, _req: &Request<'_>, _config: &mut KernelConfig) -> Result<(), c_int> { + fn init(&mut self, _req: &Request<'_>, _config: &mut KernelConfig) -> Result<(), Errno> { Ok(()) } @@ -338,12 +328,12 @@ pub trait Filesystem { fn destroy(&mut self) {} /// Look up a directory entry by name and get its attributes. - fn lookup(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEntry) { + fn lookup(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr) -> Result<(), Errno> { warn!( "[Not Implemented] lookup(parent: {:#x?}, name {:?})", parent, name ); - reply.error(ENOSYS); + Err(Errno::ENOSYS) } /// Forget about an inode. @@ -365,12 +355,12 @@ pub trait Filesystem { } /// Get file attributes. - fn getattr(&mut self, _req: &Request<'_>, ino: u64, fh: Option, reply: ReplyAttr) { + fn getattr(&mut self, _req: &Request<'_>, ino: u64, fh: Option) -> Result<(), Errno> { warn!( "[Not Implemented] getattr(ino: {:#x?}, fh: {:#x?})", ino, fh ); - reply.error(ENOSYS); + Err(Errno::ENOSYS) } /// Set file attributes. @@ -390,20 +380,19 @@ pub trait Filesystem { _chgtime: Option, _bkuptime: Option, flags: Option, - reply: ReplyAttr, - ) { + ) -> Result<(), Errno> { warn!( "[Not Implemented] setattr(ino: {:#x?}, mode: {:?}, uid: {:?}, \ gid: {:?}, size: {:?}, fh: {:?}, flags: {:?})", ino, mode, uid, gid, size, fh, flags ); - reply.error(ENOSYS); + Err(Errno::ENOSYS) } /// Read symbolic link. - fn readlink(&mut self, _req: &Request<'_>, ino: u64, reply: ReplyData) { + fn readlink(&mut self, _req: &Request<'_>, ino: u64) -> Result<(), Errno> { warn!("[Not Implemented] readlink(ino: {:#x?})", ino); - reply.error(ENOSYS); + Err(Errno::ENOSYS) } /// Create file node. @@ -416,14 +405,13 @@ pub trait Filesystem { mode: u32, umask: u32, rdev: u32, - reply: ReplyEntry, - ) { + ) -> Result<(), Errno> { warn!( "[Not Implemented] mknod(parent: {:#x?}, name: {:?}, mode: {}, \ umask: {:#x?}, rdev: {})", parent, name, mode, umask, rdev ); - reply.error(ENOSYS); + Err(Errno::ENOSYS) } /// Create a directory. @@ -434,31 +422,30 @@ pub trait Filesystem { name: &OsStr, mode: u32, umask: u32, - reply: ReplyEntry, - ) { + ) -> Result<(), Errno> { warn!( "[Not Implemented] mkdir(parent: {:#x?}, name: {:?}, mode: {}, umask: {:#x?})", parent, name, mode, umask ); - reply.error(ENOSYS); + Err(Errno::ENOSYS) } /// Remove a file. - fn unlink(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEmpty) { + fn unlink(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr) -> Result<(), Errno> { warn!( "[Not Implemented] unlink(parent: {:#x?}, name: {:?})", parent, name, ); - reply.error(ENOSYS); + Err(Errno::ENOSYS) } /// Remove a directory. - fn rmdir(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEmpty) { + fn rmdir(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr) -> Result<(), Errno> { warn!( "[Not Implemented] rmdir(parent: {:#x?}, name: {:?})", parent, name, ); - reply.error(ENOSYS); + Err(Errno::ENOSYS) } /// Create a symbolic link. @@ -468,13 +455,12 @@ pub trait Filesystem { parent: u64, link_name: &OsStr, target: &Path, - reply: ReplyEntry, - ) { + ) -> Result<(), Errno> { warn!( "[Not Implemented] symlink(parent: {:#x?}, link_name: {:?}, target: {:?})", parent, link_name, target, ); - reply.error(EPERM); + Err(Errno::EPERM) } /// Rename a file. @@ -486,14 +472,13 @@ pub trait Filesystem { newparent: u64, newname: &OsStr, flags: u32, - reply: ReplyEmpty, - ) { + ) -> Result<(), Errno> { warn!( "[Not Implemented] rename(parent: {:#x?}, name: {:?}, newparent: {:#x?}, \ newname: {:?}, flags: {})", parent, name, newparent, newname, flags, ); - reply.error(ENOSYS); + Err(Errno::ENOSYS) } /// Create a hard link. @@ -503,13 +488,12 @@ pub trait Filesystem { ino: u64, newparent: u64, newname: &OsStr, - reply: ReplyEntry, - ) { + ) -> Result<(), Errno> { warn!( "[Not Implemented] link(ino: {:#x?}, newparent: {:#x?}, newname: {:?})", ino, newparent, newname ); - reply.error(EPERM); + Err(Errno::EPERM) } /// Open a file. @@ -520,8 +504,9 @@ pub trait Filesystem { /// 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 for more details. - fn open(&mut self, _req: &Request<'_>, _ino: u64, _flags: i32, reply: ReplyOpen) { - reply.opened(0, 0); + fn open(&mut self, _req: &Request<'_>, _ino: u64, _flags: i32) -> Result<(), Errno> { + warn!("[Not Implemented] open(ino: {:#x?}, flags: {})", _ino, _flags); + Err(Errno::ENOSYS) } /// Read data. @@ -543,14 +528,13 @@ pub trait Filesystem { size: u32, flags: i32, lock_owner: Option, - reply: ReplyData, - ) { + ) -> Result<(), Errno> { warn!( "[Not Implemented] read(ino: {:#x?}, fh: {}, offset: {}, size: {}, \ flags: {:#x?}, lock_owner: {:?})", ino, fh, offset, size, flags, lock_owner ); - reply.error(ENOSYS); + Err(Errno::ENOSYS) } /// Write data. @@ -575,8 +559,7 @@ pub trait Filesystem { write_flags: u32, flags: i32, lock_owner: Option, - reply: ReplyWrite, - ) { + ) -> Result<(), Errno> { warn!( "[Not Implemented] write(ino: {:#x?}, fh: {}, offset: {}, data.len(): {}, \ write_flags: {:#x?}, flags: {:#x?}, lock_owner: {:?})", @@ -588,7 +571,7 @@ pub trait Filesystem { flags, lock_owner ); - reply.error(ENOSYS); + Err(Errno::ENOSYS) } /// Flush method. @@ -601,12 +584,12 @@ pub trait 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'. - fn flush(&mut self, _req: &Request<'_>, ino: u64, fh: u64, lock_owner: u64, reply: ReplyEmpty) { + fn flush(&mut self, _req: &Request<'_>, ino: u64, fh: u64, lock_owner: u64) -> Result<(), Errno> { warn!( "[Not Implemented] flush(ino: {:#x?}, fh: {}, lock_owner: {:?})", ino, fh, lock_owner ); - reply.error(ENOSYS); + Err(Errno::ENOSYS) } /// Release an open file. @@ -625,20 +608,19 @@ pub trait Filesystem { _flags: i32, _lock_owner: Option, _flush: bool, - reply: ReplyEmpty, - ) { - reply.ok(); + ) -> Result<(), Errno> { + Ok(()) } /// Synchronize file contents. /// If the datasync parameter is non-zero, then only the user data should be flushed, /// not the meta data. - fn fsync(&mut self, _req: &Request<'_>, ino: u64, fh: u64, datasync: bool, reply: ReplyEmpty) { + fn fsync(&mut self, _req: &Request<'_>, ino: u64, fh: u64, datasync: bool) -> Result<(), Errno> { warn!( "[Not Implemented] fsync(ino: {:#x?}, fh: {}, datasync: {})", ino, fh, datasync ); - reply.error(ENOSYS); + Err(Errno::ENOSYS) } /// Open a directory. @@ -648,8 +630,9 @@ pub trait Filesystem { /// 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. - fn opendir(&mut self, _req: &Request<'_>, _ino: u64, _flags: i32, reply: ReplyOpen) { - reply.opened(0, 0); + fn opendir(&mut self, _req: &Request<'_>, _ino: u64, _flags: i32) -> Result<(), Errno> { + warn!("[Not Implemented] open(ino: {:#x?}, flags: {})", _ino, _flags); + Err(Errno::ENOSYS) } /// Read directory. @@ -663,13 +646,12 @@ pub trait Filesystem { ino: u64, fh: u64, offset: i64, - reply: ReplyDirectory, - ) { + ) -> Result<(), Errno> { warn!( "[Not Implemented] readdir(ino: {:#x?}, fh: {}, offset: {})", ino, fh, offset ); - reply.error(ENOSYS); + Err(Errno::ENOSYS) } /// Read directory. @@ -683,13 +665,12 @@ pub trait Filesystem { ino: u64, fh: u64, offset: i64, - reply: ReplyDirectoryPlus, - ) { + ) -> Result<(), Errno> { warn!( "[Not Implemented] readdirplus(ino: {:#x?}, fh: {}, offset: {})", ino, fh, offset ); - reply.error(ENOSYS); + Err(Errno::ENOSYS) } /// Release an open directory. @@ -702,9 +683,8 @@ pub trait Filesystem { _ino: u64, _fh: u64, _flags: i32, - reply: ReplyEmpty, - ) { - reply.ok(); + ) -> Result<(), Errno> { + Ok(()) } /// Synchronize directory contents. @@ -717,18 +697,19 @@ pub trait Filesystem { ino: u64, fh: u64, datasync: bool, - reply: ReplyEmpty, - ) { + ) -> Result<(), Errno> { warn!( "[Not Implemented] fsyncdir(ino: {:#x?}, fh: {}, datasync: {})", ino, fh, datasync ); - reply.error(ENOSYS); + Err(Errno::ENOSYS) } /// Get file system statistics. - fn statfs(&mut self, _req: &Request<'_>, _ino: u64, reply: ReplyStatfs) { - reply.statfs(0, 0, 0, 0, 0, 512, 255, 0); + fn statfs(&mut self, _req: &Request<'_>, _ino: u64) -> Result<(), Errno> { + warn!("[Not Implemented] statfs(ino: {:#x?})", _ino); + Err(Errno::ENOSYS) + // TODO: default implementation {0, 0, 0, 0, 0, 512, 255, 0} } /// Set an extended attribute. @@ -740,13 +721,12 @@ pub trait Filesystem { _value: &[u8], flags: i32, position: u32, - reply: ReplyEmpty, - ) { + ) -> Result<(), Errno> { warn!( "[Not Implemented] setxattr(ino: {:#x?}, name: {:?}, flags: {:#x?}, position: {})", ino, name, flags, position ); - reply.error(ENOSYS); + Err(Errno::ENOSYS) } /// Get an extended attribute. @@ -759,43 +739,42 @@ pub trait Filesystem { ino: u64, name: &OsStr, size: u32, - reply: ReplyXattr, - ) { + ) -> Result<(), Errno> { warn!( "[Not Implemented] getxattr(ino: {:#x?}, name: {:?}, size: {})", ino, name, size ); - reply.error(ENOSYS); + Err(Errno::ENOSYS) } /// List extended attribute names. /// If `size` is 0, the size of the value should be sent with `reply.size()`. /// If `size` is not 0, and the value fits, send it with `reply.data()`, or /// `reply.error(ERANGE)` if it doesn't. - fn listxattr(&mut self, _req: &Request<'_>, ino: u64, size: u32, reply: ReplyXattr) { + fn listxattr(&mut self, _req: &Request<'_>, ino: u64, size: u32) -> Result<(), Errno> { warn!( "[Not Implemented] listxattr(ino: {:#x?}, size: {})", ino, size ); - reply.error(ENOSYS); + Err(Errno::ENOSYS) } /// Remove an extended attribute. - fn removexattr(&mut self, _req: &Request<'_>, ino: u64, name: &OsStr, reply: ReplyEmpty) { + fn removexattr(&mut self, _req: &Request<'_>, ino: u64, name: &OsStr) -> Result<(), Errno> { warn!( "[Not Implemented] removexattr(ino: {:#x?}, name: {:?})", ino, name ); - reply.error(ENOSYS); + Err(Errno::ENOSYS) } /// 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. This method is not called /// under Linux kernel versions 2.4.x - fn access(&mut self, _req: &Request<'_>, ino: u64, mask: i32, reply: ReplyEmpty) { + fn access(&mut self, _req: &Request<'_>, ino: u64, mask: i32) -> Result<(), Errno> { warn!("[Not Implemented] access(ino: {:#x?}, mask: {})", ino, mask); - reply.error(ENOSYS); + Err(Errno::ENOSYS) } /// Create and open a file. @@ -816,14 +795,13 @@ pub trait Filesystem { mode: u32, umask: u32, flags: i32, - reply: ReplyCreate, - ) { + ) -> Result<(), Errno> { warn!( "[Not Implemented] create(parent: {:#x?}, name: {:?}, mode: {}, umask: {:#x?}, \ flags: {:#x?})", parent, name, mode, umask, flags ); - reply.error(ENOSYS); + Err(Errno::ENOSYS) } /// Test for a POSIX file lock. @@ -837,14 +815,13 @@ pub trait Filesystem { end: u64, typ: i32, pid: u32, - reply: ReplyLock, - ) { + ) -> Result<(), Errno> { warn!( "[Not Implemented] getlk(ino: {:#x?}, fh: {}, lock_owner: {}, start: {}, \ end: {}, typ: {}, pid: {})", ino, fh, lock_owner, start, end, typ, pid ); - reply.error(ENOSYS); + Err(Errno::ENOSYS) } /// Acquire, modify or release a POSIX file lock. @@ -865,25 +842,24 @@ pub trait Filesystem { typ: i32, pid: u32, sleep: bool, - reply: ReplyEmpty, - ) { + ) -> Result<(), Errno> { warn!( "[Not Implemented] setlk(ino: {:#x?}, fh: {}, lock_owner: {}, start: {}, \ end: {}, typ: {}, pid: {}, sleep: {})", ino, fh, lock_owner, start, end, typ, pid, sleep ); - reply.error(ENOSYS); + Err(Errno::ENOSYS) } /// 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 - fn bmap(&mut self, _req: &Request<'_>, ino: u64, blocksize: u32, idx: u64, reply: ReplyBmap) { + fn bmap(&mut self, _req: &Request<'_>, ino: u64, blocksize: u32, idx: u64) -> Result<(), Errno> { warn!( "[Not Implemented] bmap(ino: {:#x?}, blocksize: {}, idx: {})", ino, blocksize, idx, ); - reply.error(ENOSYS); + Err(Errno::ENOSYS) } /// control device @@ -896,8 +872,7 @@ pub trait Filesystem { cmd: u32, in_data: &[u8], out_size: u32, - reply: ReplyIoctl, - ) { + ) -> Result<(), Errno> { warn!( "[Not Implemented] ioctl(ino: {:#x?}, fh: {}, flags: {}, cmd: {}, \ in_data.len(): {}, out_size: {})", @@ -908,7 +883,7 @@ pub trait Filesystem { in_data.len(), out_size, ); - reply.error(ENOSYS); + Err(Errno::ENOSYS) } /// Poll for events @@ -921,13 +896,12 @@ pub trait Filesystem { ph: PollHandle, events: u32, flags: u32, - reply: ReplyPoll, - ) { + ) -> Result<(), Errno> { warn!( "[Not Implemented] poll(ino: {:#x?}, fh: {}, ph: {:?}, events: {}, flags: {})", ino, fh, ph, events, flags ); - reply.error(ENOSYS); + Err(Errno::ENOSYS) } /// Preallocate or deallocate space to a file @@ -939,14 +913,13 @@ pub trait Filesystem { offset: i64, length: i64, mode: i32, - reply: ReplyEmpty, - ) { + ) -> Result<(), Errno> { warn!( "[Not Implemented] fallocate(ino: {:#x?}, fh: {}, offset: {}, \ length: {}, mode: {})", ino, fh, offset, length, mode ); - reply.error(ENOSYS); + Err(Errno::ENOSYS) } /// Reposition read/write file offset @@ -957,13 +930,12 @@ pub trait Filesystem { fh: u64, offset: i64, whence: i32, - reply: ReplyLseek, - ) { + ) -> Result<(), Errno> { warn!( "[Not Implemented] lseek(ino: {:#x?}, fh: {}, offset: {}, whence: {})", ino, fh, offset, whence ); - reply.error(ENOSYS); + Err(Errno::ENOSYS) } /// Copy the specified range from the source inode to the destination inode @@ -978,23 +950,22 @@ pub trait Filesystem { offset_out: i64, len: u64, flags: u32, - reply: ReplyWrite, - ) { + ) -> Result<(), Errno> { warn!( "[Not Implemented] copy_file_range(ino_in: {:#x?}, fh_in: {}, \ offset_in: {}, ino_out: {:#x?}, fh_out: {}, offset_out: {}, \ len: {}, flags: {})", ino_in, fh_in, offset_in, ino_out, fh_out, offset_out, len, flags ); - reply.error(ENOSYS); + Err(Errno::ENOSYS) } /// macOS only: Rename the volume. Set fuse_init_out.flags during init to /// FUSE_VOL_RENAME to enable #[cfg(target_os = "macos")] - fn setvolname(&mut self, _req: &Request<'_>, name: &OsStr, reply: ReplyEmpty) { + fn setvolname(&mut self, _req: &Request<'_>, name: &OsStr) -> Result<(), Errno> { warn!("[Not Implemented] setvolname(name: {:?})", name); - reply.error(ENOSYS); + Err(Errno::ENOSYS) } /// macOS only (undocumented) @@ -1007,22 +978,21 @@ pub trait Filesystem { newparent: u64, newname: &OsStr, options: u64, - reply: ReplyEmpty, - ) { + ) -> Result<(), Errno> { warn!( "[Not Implemented] exchange(parent: {:#x?}, name: {:?}, newparent: {:#x?}, \ newname: {:?}, options: {})", parent, name, newparent, newname, options ); - reply.error(ENOSYS); + Err(Errno::ENOSYS) } /// macOS only: Query extended times (bkuptime and crtime). Set fuse_init_out.flags /// during init to FUSE_XTIMES to enable #[cfg(target_os = "macos")] - fn getxtimes(&mut self, _req: &Request<'_>, ino: u64, reply: ReplyXTimes) { + fn getxtimes(&mut self, _req: &Request<'_>, ino: u64) -> Result<(), Errno> { warn!("[Not Implemented] getxtimes(ino: {:#x?})", ino); - reply.error(ENOSYS); + Err(Errno::ENOSYS) } } diff --git a/src/reply.rs b/src/reply.rs index 5b779551..0f8bb790 100644 --- a/src/reply.rs +++ b/src/reply.rs @@ -1,22 +1,11 @@ //! Filesystem operation reply //! -//! A reply is passed to filesystem operation implementations and must be used to send back the -//! result of an operation. The reply can optionally be sent to another thread to asynchronously -//! work on an operation and provide the result later. Also it allows replying with a block of -//! data without cloning the data. A reply *must always* be used (by calling either ok() or -//! error() exactly once). - -use crate::ll::{ - self, - reply::{DirEntPlusList, DirEntryPlus}, - Generation, -}; -use crate::ll::{ - reply::{DirEntList, DirEntOffset, DirEntry}, - INodeNo, -}; +//! A reply handler object is created to guarantee that each fuse request receives a reponse exactly once. +//! Either the request logic will call the one of the reply handler's self-destructive methods, +//! or, if the reply handler goes out of scope before that happens, the drop trait will send an error response. #[cfg(feature = "abi-7-40")] use crate::{consts::FOPEN_PASSTHROUGH, passthrough::BackingId}; +use crate::ll; use libc::c_int; use log::{error, warn}; use std::convert::AsRef; @@ -33,7 +22,7 @@ use std::time::SystemTime; use crate::{FileAttr, FileType}; /// Generic reply callback to send data -pub trait ReplySender: Send + Sync + Unpin + 'static { +pub(crate) trait ReplySender: Send + Sync + Unpin + 'static { /// Send data. fn send(&self, data: &[IoSlice<'_>]) -> std::io::Result<()>; /// Open a backing file @@ -47,36 +36,31 @@ impl fmt::Debug for Box { } } -/// Generic reply trait -pub trait Reply { - /// Create a new reply for the given request - fn new(unique: u64, sender: S) -> Self; -} - -/// -/// Raw reply -/// +/// ReplyHander is a struct which holds the unique identifiers needed to reply +/// to a specific request. Traits are implemented on the struct so that ownership +/// of the struct determines whether the identifiers have ever been used. +/// This guarantees that a reply is send at most once per request. #[derive(Debug)] -pub(crate) struct ReplyRaw { +pub(crate) struct ReplyHandler { /// Unique id of the request to reply to unique: ll::RequestId, /// Closure to call for sending the reply sender: Option>, } -impl Reply for ReplyRaw { - fn new(unique: u64, sender: S) -> ReplyRaw { +impl ReplyHandler { + /// Create a reply handler for a specific request identifier + pub(crate) fn new(unique: u64, sender: S) -> ReplyHandler { let sender = Box::new(sender); - ReplyRaw { + ReplyHandler { unique: ll::RequestId(unique), sender: Some(sender), } } -} -impl ReplyRaw { - /// Reply to a request with the given error code and data. Must be called - /// only once (the `ok` and `error` methods ensure this by consuming `self`) + /// Reply to a request with a formatted reponse. Can be called + /// more than once (the `&mut self`` argument does not consume `self`) + /// Avoid using this variant unless you know what you are doing! fn send_ll_mut(&mut self, response: &ll::Response<'_>) { assert!(self.sender.is_some()); let sender = self.sender.take().unwrap(); @@ -85,18 +69,19 @@ impl ReplyRaw { error!("Failed to send FUSE reply: {}", err); } } + /// Reply to a request with a formatted reponse. May be called + /// only once (the `mut self`` argument consumes `self`). + /// Use this variant for general replies. fn send_ll(mut self, response: &ll::Response<'_>) { self.send_ll_mut(response) } - /// Reply to a request with the given error code - pub fn error(self, err: c_int) { - assert_ne!(err, 0); - self.send_ll(&ll::Response::new_error(ll::Errno::from_i32(err))); - } } -impl Drop for ReplyRaw { +/// Drop is implemented on ReplyHandler so that if the program logic fails +/// (for example, due to an interrupt or a panic), +/// a reply will be sent when the Reply Handler falls out of scope. +impl Drop for ReplyHandler { fn drop(&mut self) { if self.sender.is_some() { warn!( @@ -108,255 +93,50 @@ impl Drop for ReplyRaw { } } -/// -/// Empty reply -/// -#[derive(Debug)] -pub struct ReplyEmpty { - reply: ReplyRaw, -} +// +// Methods to reply to a request for each kind of data +// -impl Reply for ReplyEmpty { - fn new(unique: u64, sender: S) -> ReplyEmpty { - ReplyEmpty { - reply: Reply::new(unique, sender), - } - } -} +impl ReplyHandler { -impl ReplyEmpty { - /// Reply to a request with nothing + /// Reply to a general request with Ok pub fn ok(self) { - self.reply.send_ll(&ll::Response::new_empty()); + self.send_ll(&ll::Response::new_empty()); } - /// Reply to a request with the given error code - pub fn error(self, err: c_int) { - self.reply.error(err); + /// Reply to a general request with an error code + pub fn error(self, err: ll::Errno) { + self.send_ll(&ll::Response::new_error(err)); } -} -/// -/// Data reply -/// -#[derive(Debug)] -pub struct ReplyData { - reply: ReplyRaw, -} - -impl Reply for ReplyData { - fn new(unique: u64, sender: S) -> ReplyData { - ReplyData { - reply: Reply::new(unique, sender), - } - } -} - -impl ReplyData { - /// Reply to a request with the given data + /// Reply to a general request with data pub fn data(self, data: &[u8]) { - self.reply.send_ll(&ll::Response::new_slice(data)); - } - - /// Reply to a request with the given error code - pub fn error(self, err: c_int) { - self.reply.error(err); - } -} - -/// -/// Entry reply -/// -#[derive(Debug)] -pub struct ReplyEntry { - reply: ReplyRaw, -} - -impl Reply for ReplyEntry { - fn new(unique: u64, sender: S) -> ReplyEntry { - ReplyEntry { - reply: Reply::new(unique, sender), - } - } -} - -impl ReplyEntry { - /// Reply to a request with the given entry - pub fn entry(self, ttl: &Duration, attr: &FileAttr, generation: u64) { - self.reply.send_ll(&ll::Response::new_entry( - ll::INodeNo(attr.ino), - ll::Generation(generation), - &attr.into(), - *ttl, - *ttl, - )); - } - - /// Reply to a request with the given error code - pub fn error(self, err: c_int) { - self.reply.error(err); - } -} - -/// -/// Attribute Reply -/// -#[derive(Debug)] -pub struct ReplyAttr { - reply: ReplyRaw, -} - -impl Reply for ReplyAttr { - fn new(unique: u64, sender: S) -> ReplyAttr { - ReplyAttr { - reply: Reply::new(unique, sender), - } - } -} - -impl ReplyAttr { - /// Reply to a request with the given attribute - pub fn attr(self, ttl: &Duration, attr: &FileAttr) { - self.reply - .send_ll(&ll::Response::new_attr(ttl, &attr.into())); - } - - /// Reply to a request with the given error code - pub fn error(self, err: c_int) { - self.reply.error(err); - } -} - -/// -/// XTimes Reply -/// -#[cfg(target_os = "macos")] -#[derive(Debug)] -pub struct ReplyXTimes { - reply: ReplyRaw, -} - -#[cfg(target_os = "macos")] -impl Reply for ReplyXTimes { - fn new(unique: u64, sender: S) -> ReplyXTimes { - ReplyXTimes { - reply: Reply::new(unique, sender), - } - } -} - -#[cfg(target_os = "macos")] -impl ReplyXTimes { - /// Reply to a request with the given xtimes - pub fn xtimes(self, bkuptime: SystemTime, crtime: SystemTime) { - self.reply - .send_ll(&ll::Response::new_xtimes(bkuptime, crtime)) - } - - /// Reply to a request with the given error code - pub fn error(self, err: c_int) { - self.reply.error(err); + self.send_ll(&ll::Response::new_slice(data)); } -} + /// Reply to an init request with available features + pub fn config(self) {} -/// -/// Open Reply -/// -#[derive(Debug)] -pub struct ReplyOpen { - reply: ReplyRaw, -} + /// Reply to a request with a file entry + pub fn entry(self) {} -impl Reply for ReplyOpen { - fn new(unique: u64, sender: S) -> ReplyOpen { - ReplyOpen { - reply: Reply::new(unique, sender), - } + /// Reply to a request with a file attributes + pub fn attr(self, attr: FileAttr, ttl: Duration) { + self.send_ll(&ll::Response::new_attr(&ttl, &attr.into())); } -} -impl ReplyOpen { - /// Reply to a request with the given open result - pub fn opened(self, fh: u64, flags: u32) { - #[cfg(feature = "abi-7-40")] - assert_eq!(flags & FOPEN_PASSTHROUGH, 0); - self.reply - .send_ll(&ll::Response::new_open(ll::FileHandle(fh), flags, 0)) - } - - /// Registers a fd for passthrough, returning a `BackingId`. Once you have the backing ID, - /// you can pass it as the 3rd parameter of `OpenReply::opened_passthrough()`. This is done in - /// two separate steps because it may make sense to reuse backing IDs (to avoid having to - /// repeatedly reopen the underlying file or potentially keep thousands of fds open). - #[cfg(feature = "abi-7-40")] - pub fn open_backing(&self, fd: impl std::os::fd::AsFd) -> std::io::Result { - self.reply.sender.as_ref().unwrap().open_backing(fd.as_fd()) - } - - /// Reply to a request with an opened backing id. Call ReplyOpen::open_backing() to get one of - /// these. - #[cfg(feature = "abi-7-40")] - pub fn opened_passthrough(self, fh: u64, flags: u32, backing_id: &BackingId) { - self.reply.send_ll(&ll::Response::new_open( - ll::FileHandle(fh), - flags | FOPEN_PASSTHROUGH, - backing_id.backing_id, - )) - } - - /// Reply to a request with the given error code - pub fn error(self, err: c_int) { - self.reply.error(err); - } -} - -/// -/// Write Reply -/// -#[derive(Debug)] -pub struct ReplyWrite { - reply: ReplyRaw, -} + #[cfg(target_os = "macos")] + /// Reply to a request with xtimes attributes + pub fn xtimes(self) {} -impl Reply for ReplyWrite { - fn new(unique: u64, sender: S) -> ReplyWrite { - ReplyWrite { - reply: Reply::new(unique, sender), - } - } -} + /// Reply to a request with a newly opened file handle + pub fn opened(self) {} -impl ReplyWrite { - /// Reply to a request with the given open result + /// Reply to a request with the number of bytes written pub fn written(self, size: u32) { - self.reply.send_ll(&ll::Response::new_write(size)) - } - - /// Reply to a request with the given error code - pub fn error(self, err: c_int) { - self.reply.error(err); - } -} - -/// -/// Statfs Reply -/// -#[derive(Debug)] -pub struct ReplyStatfs { - reply: ReplyRaw, -} - -impl Reply for ReplyStatfs { - fn new(unique: u64, sender: S) -> ReplyStatfs { - ReplyStatfs { - reply: Reply::new(unique, sender), - } + self.send_ll(&ll::Response::new_write(size)) } -} -impl ReplyStatfs { - /// Reply to a request with the given open result - #[allow(clippy::too_many_arguments)] + /// Reply to a statfs request pub fn statfs( self, blocks: u64, @@ -368,329 +148,48 @@ impl ReplyStatfs { namelen: u32, frsize: u32, ) { - self.reply.send_ll(&ll::Response::new_statfs( + self.send_ll(&ll::Response::new_statfs( blocks, bfree, bavail, files, ffree, bsize, namelen, frsize, )) } - /// Reply to a request with the given error code - pub fn error(self, err: c_int) { - self.reply.error(err); - } -} - -/// -/// Create reply -/// -#[derive(Debug)] -pub struct ReplyCreate { - reply: ReplyRaw, -} - -impl Reply for ReplyCreate { - fn new(unique: u64, sender: S) -> ReplyCreate { - ReplyCreate { - reply: Reply::new(unique, sender), - } - } -} - -impl ReplyCreate { - /// Reply to a request with the given entry - pub fn created(self, ttl: &Duration, attr: &FileAttr, generation: u64, fh: u64, flags: u32) { - #[cfg(feature = "abi-7-40")] - assert_eq!(flags & FOPEN_PASSTHROUGH, 0); - self.reply.send_ll(&ll::Response::new_create( - ttl, - &attr.into(), - ll::Generation(generation), - ll::FileHandle(fh), - flags, - 0, - )) - } - - /// Reply to a request with the given error code - pub fn error(self, err: c_int) { - self.reply.error(err); - } -} - -/// -/// Lock Reply -/// -#[derive(Debug)] -pub struct ReplyLock { - reply: ReplyRaw, -} - -impl Reply for ReplyLock { - fn new(unique: u64, sender: S) -> ReplyLock { - ReplyLock { - reply: Reply::new(unique, sender), - } - } -} - -impl ReplyLock { - /// Reply to a request with the given open result - pub fn locked(self, start: u64, end: u64, typ: i32, pid: u32) { - self.reply.send_ll(&ll::Response::new_lock(&ll::Lock { - range: (start, end), - typ, - pid, - })) - } - - /// Reply to a request with the given error code - pub fn error(self, err: c_int) { - self.reply.error(err); - } -} + /// Reply to a request with a newle created file entry and its newly open file handle + pub fn created(self) {} -/// -/// Bmap Reply -/// -#[derive(Debug)] -pub struct ReplyBmap { - reply: ReplyRaw, -} + /// Reply to a request with a file lock + pub fn locked(self) {} -impl Reply for ReplyBmap { - fn new(unique: u64, sender: S) -> ReplyBmap { - ReplyBmap { - reply: Reply::new(unique, sender), - } - } -} - -impl ReplyBmap { - /// Reply to a request with the given open result + /// Reply to a request with a bmap pub fn bmap(self, block: u64) { - self.reply.send_ll(&ll::Response::new_bmap(block)) + self.send_ll(&ll::Response::new_bmap(block)) } - /// Reply to a request with the given error code - pub fn error(self, err: c_int) { - self.reply.error(err); - } -} - -/// -/// Ioctl Reply -/// -#[derive(Debug)] -pub struct ReplyIoctl { - reply: ReplyRaw, -} - -impl Reply for ReplyIoctl { - fn new(unique: u64, sender: S) -> ReplyIoctl { - ReplyIoctl { - reply: Reply::new(unique, sender), - } - } -} - -impl ReplyIoctl { - /// Reply to a request with the given open result - pub fn ioctl(self, result: i32, data: &[u8]) { - self.reply - .send_ll(&ll::Response::new_ioctl(result, &[IoSlice::new(data)])); - } - - /// Reply to a request with the given error code - pub fn error(self, err: c_int) { - self.reply.error(err); - } -} - -/// -/// Poll Reply -/// -#[derive(Debug)] -#[cfg(feature = "abi-7-11")] -pub struct ReplyPoll { - reply: ReplyRaw, -} - -#[cfg(feature = "abi-7-11")] -impl Reply for ReplyPoll { - fn new(unique: u64, sender: S) -> ReplyPoll { - ReplyPoll { - reply: Reply::new(unique, sender), - } - } -} + #[cfg(feature = "abi-7-11")] + /// Reply to a request with an ioctl + pub fn ioctl(self) {} -#[cfg(feature = "abi-7-11")] -impl ReplyPoll { - /// Reply to a request with the given poll result + #[cfg(feature = "abi-7-11")] + /// Reply to a request with a poll result pub fn poll(self, revents: u32) { - self.reply.send_ll(&ll::Response::new_poll(revents)) - } - - /// Reply to a request with the given error code - pub fn error(self, err: c_int) { - self.reply.error(err); - } -} - -/// -/// Directory reply -/// -#[derive(Debug)] -pub struct ReplyDirectory { - reply: ReplyRaw, - data: DirEntList, -} - -impl ReplyDirectory { - /// Creates a new ReplyDirectory with a specified buffer size. - pub fn new(unique: u64, sender: S, size: usize) -> ReplyDirectory { - ReplyDirectory { - reply: Reply::new(unique, sender), - data: DirEntList::new(size), - } - } - - /// Add an entry to the directory reply buffer. Returns true if the buffer is full. - /// A transparent offset value can be provided for each entry. The kernel uses these - /// value to request the next entries in further readdir calls - #[must_use] - pub fn add>(&mut self, ino: u64, offset: i64, kind: FileType, name: T) -> bool { - let name = name.as_ref(); - self.data.push(&DirEntry::new( - INodeNo(ino), - DirEntOffset(offset), - kind, - name, - )) - } - - /// Reply to a request with the filled directory buffer - pub fn ok(self) { - self.reply.send_ll(&self.data.into()); - } - - /// Reply to a request with the given error code - pub fn error(self, err: c_int) { - self.reply.error(err); - } -} - -/// -/// DirectoryPlus reply -/// -#[derive(Debug)] -pub struct ReplyDirectoryPlus { - reply: ReplyRaw, - buf: DirEntPlusList, -} - -impl ReplyDirectoryPlus { - /// Creates a new ReplyDirectory with a specified buffer size. - pub fn new(unique: u64, sender: S, size: usize) -> ReplyDirectoryPlus { - ReplyDirectoryPlus { - reply: Reply::new(unique, sender), - buf: DirEntPlusList::new(size), - } - } - - /// Add an entry to the directory reply buffer. Returns true if the buffer is full. - /// A transparent offset value can be provided for each entry. The kernel uses these - /// value to request the next entries in further readdir calls - pub fn add>( - &mut self, - ino: u64, - offset: i64, - name: T, - ttl: &Duration, - attr: &FileAttr, - generation: u64, - ) -> bool { - let name = name.as_ref(); - self.buf.push(&DirEntryPlus::new( - INodeNo(ino), - Generation(generation), - DirEntOffset(offset), - name, - *ttl, - attr.into(), - *ttl, - )) + self.send_ll(&ll::Response::new_poll(revents)) } - /// Reply to a request with the filled directory buffer - pub fn ok(self) { - self.reply.send_ll(&self.buf.into()); - } + /// Reply to a request with a filled directory buffer + pub fn dir(self) {} - /// Reply to a request with the given error code - pub fn error(self, err: c_int) { - self.reply.error(err); - } -} + #[cfg(feature = "abi-7-21")] + /// Reply to a request with a filled directory plus buffer + pub fn dirplus(self) {} -/// -/// Xattr reply -/// -#[derive(Debug)] -pub struct ReplyXattr { - reply: ReplyRaw, -} + /// Reply to a request with extended attributes. + pub fn xattr(self) {} -impl Reply for ReplyXattr { - fn new(unique: u64, sender: S) -> ReplyXattr { - ReplyXattr { - reply: Reply::new(unique, sender), - } - } -} - -impl ReplyXattr { - /// Reply to a request with the size of the xattr. - pub fn size(self, size: u32) { - self.reply.send_ll(&ll::Response::new_xattr_size(size)) - } - - /// Reply to a request with the data in the xattr. - pub fn data(self, data: &[u8]) { - self.reply.send_ll(&ll::Response::new_slice(data)) - } - - /// Reply to a request with the given error code. - pub fn error(self, err: c_int) { - self.reply.error(err); - } -} - -/// -/// Lseek Reply -/// -#[derive(Debug)] -pub struct ReplyLseek { - reply: ReplyRaw, -} - -impl Reply for ReplyLseek { - fn new(unique: u64, sender: S) -> ReplyLseek { - ReplyLseek { - reply: Reply::new(unique, sender), - } - } -} - -impl ReplyLseek { - /// Reply to a request with seeked offset + #[cfg(feature = "abi-7-24")] + /// Reply to a request with a seeked offset pub fn offset(self, offset: i64) { - self.reply.send_ll(&ll::Response::new_lseek(offset)) + self.send_ll(&ll::Response::new_lseek(offset)) } - /// Reply to a request with the given error code - pub fn error(self, err: c_int) { - self.reply.error(err); - } } #[cfg(test)] @@ -702,6 +201,7 @@ mod test { use std::thread; use std::time::{Duration, UNIX_EPOCH}; use zerocopy::{Immutable, IntoBytes}; + use std::ffi::OsString; #[derive(Debug, IntoBytes, Immutable)] #[repr(C)] @@ -765,8 +265,8 @@ mod test { 0x00, 0x00, 0x12, 0x34, 0x78, 0x56, ], }; - let reply: ReplyRaw = Reply::new(0xdeadbeef, sender); - reply.send_ll(&ll::Response::new_data(data.as_bytes())); + let replyhandler: ReplyHandler = ReplyHandler::new(0xdeadbeef, sender); + replyhandler.send_ll(&ll::Response::new_data(data.as_bytes())); } #[test] @@ -777,8 +277,9 @@ mod test { 0x00, 0x00, ], }; - let reply: ReplyRaw = Reply::new(0xdeadbeef, sender); - reply.error(66); + let replyhandler: ReplyHandler = ReplyHandler::new(0xdeadbeef, sender); + use crate::ll::Errno; + replyhandler.error(Errno::from_i32(66)); } #[test] @@ -789,8 +290,8 @@ mod test { 0x00, 0x00, ], }; - let reply: ReplyEmpty = Reply::new(0xdeadbeef, sender); - reply.ok(); + let replyhandler: ReplyHandler = ReplyHandler::new(0xdeadbeef, sender); + replyhandler.ok(); } #[test] @@ -801,48 +302,17 @@ mod test { 0x00, 0x00, 0xde, 0xad, 0xbe, 0xef, ], }; - let reply: ReplyData = Reply::new(0xdeadbeef, sender); - reply.data(&[0xde, 0xad, 0xbe, 0xef]); + let replyhandler: ReplyHandler = ReplyHandler::new(0xdeadbeef, sender); + replyhandler.data(&[0xde, 0xad, 0xbe, 0xef]); } #[test] fn reply_entry() { - let mut expected = if cfg!(target_os = "macos") { - vec![ - 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, - 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x65, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x87, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, - 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, - 0x00, 0x00, 0xa4, 0x81, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, - 0x77, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x99, 0x00, 0x00, 0x00, - ] - } else { - vec![ - 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, - 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x65, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x87, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, - 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, - 0x78, 0x56, 0x00, 0x00, 0xa4, 0x81, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x66, 0x00, - 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, - ] - }; - - if cfg!(feature = "abi-7-9") { - expected.extend(vec![0xbb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); - } expected[0] = (expected.len()) as u8; - + // test reply will be compare with the expected message let sender = AssertSender { expected }; - let reply: ReplyEntry = Reply::new(0xdeadbeef, sender); + // prepare the test reply + let replyhandler: ReplyHandler = ReplyHandler::new(0xdeadbeef, sender); let time = UNIX_EPOCH + Duration::new(0x1234, 0x5678); let ttl = Duration::new(0x8765, 0x4321); let attr = FileAttr { @@ -862,7 +332,9 @@ mod test { flags: 0x99, blksize: 0xbb, }; - reply.entry(&ttl, &attr, 0xaa); + // send the test reply + replyhandler.entry( + ); } #[test] @@ -899,7 +371,7 @@ mod test { expected[0] = expected.len() as u8; let sender = AssertSender { expected }; - let reply: ReplyAttr = Reply::new(0xdeadbeef, sender); + let replyhandler: ReplyHandler = ReplyHandler::new(0xdeadbeef, sender); let time = UNIX_EPOCH + Duration::new(0x1234, 0x5678); let ttl = Duration::new(0x8765, 0x4321); let attr = FileAttr { @@ -919,7 +391,7 @@ mod test { flags: 0x99, blksize: 0xbb, }; - reply.attr(&ttl, &attr); + replyhandler.attr(attr, ttl); } #[test] @@ -932,9 +404,10 @@ mod test { 0x00, 0x00, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, ], }; - let reply: ReplyXTimes = Reply::new(0xdeadbeef, sender); + let replyhandler: ReplyHandler = ReplyHandler::new(0xdeadbeef, sender); let time = UNIX_EPOCH + Duration::new(0x1234, 0x5678); - reply.xtimes(time, time); + replyhandler.xtimes( + ); } #[test] @@ -946,8 +419,9 @@ mod test { 0x00, 0x00, 0x00, 0x00, ], }; - let reply: ReplyOpen = Reply::new(0xdeadbeef, sender); - reply.opened(0x1122, 0x33); + let replyhandler: ReplyHandler = ReplyHandler::new(0xdeadbeef, sender); + replyhandler.opened( + ); } #[test] @@ -958,8 +432,8 @@ mod test { 0x00, 0x00, 0x22, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ], }; - let reply: ReplyWrite = Reply::new(0xdeadbeef, sender); - reply.written(0x1122); + let replyhandler: ReplyHandler = ReplyHandler::new(0xdeadbeef, sender); + replyhandler.written(0x1122); } #[test] @@ -975,8 +449,9 @@ mod test { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ], }; - let reply: ReplyStatfs = Reply::new(0xdeadbeef, sender); - reply.statfs(0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88); + let replyhandler: ReplyHandler = ReplyHandler::new(0xdeadbeef, sender); + replyhandler.statfs( + ); } #[test] @@ -1022,7 +497,7 @@ mod test { expected[0] = (expected.len()) as u8; let sender = AssertSender { expected }; - let reply: ReplyCreate = Reply::new(0xdeadbeef, sender); + let replyhandler: ReplyHandler = ReplyHandler::new(0xdeadbeef, sender); let time = UNIX_EPOCH + Duration::new(0x1234, 0x5678); let ttl = Duration::new(0x8765, 0x4321); let attr = FileAttr { @@ -1042,7 +517,8 @@ mod test { flags: 0x99, blksize: 0xdd, }; - reply.created(&ttl, &attr, 0xaa, 0xbb, 0xcc); + replyhandler.created( + ); } #[test] @@ -1054,8 +530,9 @@ mod test { 0x00, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, ], }; - let reply: ReplyLock = Reply::new(0xdeadbeef, sender); - reply.locked(0x11, 0x22, 0x33, 0x44); + let replyhandler: ReplyHandler = ReplyHandler::new(0xdeadbeef, sender); + replyhandler.locked( + ); } #[test] @@ -1066,8 +543,8 @@ mod test { 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ], }; - let reply: ReplyBmap = Reply::new(0xdeadbeef, sender); - reply.bmap(0x1234); + let replyhandler: ReplyHandler = ReplyHandler::new(0xdeadbeef, sender); + replyhandler.bmap(0x1234); } #[test] @@ -1082,10 +559,18 @@ mod test { 0x00, 0x00, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x72, 0x73, ], }; - let mut reply = ReplyDirectory::new(0xdeadbeef, sender, 4096); - assert!(!reply.add(0xaabb, 1, FileType::Directory, "hello")); - assert!(!reply.add(0xccdd, 2, FileType::RegularFile, "world.rs")); - reply.ok(); + let replyhandler: ReplyHandler = ReplyHandler::new(0xdeadbeef, sender); + let entries = vec!( + ); + } + + #[test] + #[cfg(feature = "abi-7-24")] + fn reply_directory_plus() { + // prepare the expected file attribute portion of the message + // test reply will be compared to expected + let sender = AssertSender {expected}; + let replyhandler: ReplyHandler = ReplyHandler::new(0xdeadbeef, sender); } #[test] @@ -1096,8 +581,7 @@ mod test { 0x00, 0x00, 0x78, 0x56, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, ], }; - let reply = ReplyXattr::new(0xdeadbeef, sender); - reply.size(0x12345678); + let replyhandler: ReplyHandler = ReplyHandler::new(0xdeadbeef, sender); } #[test] @@ -1108,8 +592,7 @@ mod test { 0x00, 0x00, 0x11, 0x22, 0x33, 0x44, ], }; - let reply = ReplyXattr::new(0xdeadbeef, sender); - reply.data(&[0x11, 0x22, 0x33, 0x44]); + let replyhandler: ReplyHandler = ReplyHandler::new(0xdeadbeef, sender); } impl super::ReplySender for SyncSender<()> { @@ -1127,9 +610,9 @@ mod test { #[test] fn async_reply() { let (tx, rx) = sync_channel::<()>(1); - let reply: ReplyEmpty = Reply::new(0xdeadbeef, tx); + let replyhandler: ReplyHandler = ReplyHandler::new(0xdeadbeef, tx); thread::spawn(move || { - reply.ok(); + replyhandler.ok(); }); rx.recv().unwrap(); } diff --git a/src/request.rs b/src/request.rs index e25a4c64..cfd39815 100644 --- a/src/request.rs +++ b/src/request.rs @@ -14,9 +14,7 @@ use std::path::Path; use crate::channel::ChannelSender; use crate::ll::Request as _; -#[cfg(feature = "abi-7-21")] -use crate::reply::ReplyDirectoryPlus; -use crate::reply::{Reply, ReplyDirectory, ReplySender}; +use crate::reply::{ReplyHandler, ReplySender}; use crate::session::{Session, SessionACL}; use crate::Filesystem; #[cfg(feature = "abi-7-11")] @@ -33,6 +31,8 @@ pub struct Request<'a> { data: &'a [u8], /// Parsed request request: ll::AnyRequest<'a>, + /// Closure-like object to guarantee a response is sent + replyhandler: ReplyHandler, } impl<'a> Request<'a> { @@ -46,7 +46,8 @@ impl<'a> Request<'a> { } }; - Some(Self { ch, data, request }) + let replyhandler = ReplyHandler::new(request.unique().into(), ch.clone()); + Some(Self { ch, data, request, replyhandler }) } /// Dispatch request to the given filesystem. @@ -54,25 +55,13 @@ impl<'a> Request<'a> { /// request and sends back the returned reply to the kernel pub(crate) fn dispatch(&self, se: &mut Session) { debug!("{}", self.request); - let unique = self.request.unique(); + let op_result = self.request.operation().map_err(|_| Errno::ENOSYS); - let res = match self.dispatch_req(se) { - Ok(Some(resp)) => resp, - Ok(None) => return, - Err(errno) => self.request.reply_err(errno), + if let Err(err) = op_result { + self.replyhandler.error(err); + return; } - .with_iovec(unique, |iov| self.ch.send(iov)); - - if let Err(err) = res { - warn!("Request {:?}: Failed to send reply: {}", unique, err) - } - } - - fn dispatch_req( - &self, - se: &mut Session, - ) -> Result>, Errno> { - let op = self.request.operation().map_err(|_| Errno::ENOSYS)?; + let op = op_result.unwrap(); // Implement allow_root & access check for auto_unmount if (se.allowed == SessionACL::RootAndOwner && self.request.uid() != se.session_owner @@ -96,7 +85,7 @@ impl<'a> Request<'a> { | ll::Operation::Release(_) | ll::Operation::ReleaseDir(_) => {} _ => { - return Err(Errno::EACCES); + self.replyhandler.error(Errno::EACCES); } } } @@ -116,7 +105,7 @@ impl<'a> Request<'a> { | ll::Operation::Release(_) | ll::Operation::ReleaseDir(_) => {} _ => { - return Err(Errno::EACCES); + self.replyhandler.error(Errno::EACCES); } } } @@ -135,7 +124,7 @@ impl<'a> Request<'a> { | ll::Operation::Release(_) | ll::Operation::ReleaseDir(_) => {} _ => { - return Err(Errno::EACCES); + self.replyhandler.error(Errno::EACCES); } } } @@ -147,17 +136,22 @@ impl<'a> Request<'a> { let v = x.version(); if v < ll::Version(7, 6) { error!("Unsupported FUSE ABI version {}", v); - return Err(Errno::EPROTO); + self.replyhandler.error(Errno::EPROTO); } // Remember ABI version supported by kernel se.proto_major = v.major(); se.proto_minor = v.minor(); let mut config = KernelConfig::new(x.capabilities(), x.max_readahead()); - // Call filesystem init method and give it a chance to return an error - se.filesystem - .init(self, &mut config) - .map_err(Errno::from_i32)?; + // Call filesystem init method and give it a chance to + // propose a different config or return an error + match se.filesystem.init(&self, &mut config) { + Ok(()) => {}, + Err(errno) => { + self.replyhandler.error(errno); + return; + } + }; // Reply with our desired version and settings. If the kernel supports a // larger major version, it'll re-send a matching init message. If it @@ -171,37 +165,44 @@ impl<'a> Request<'a> { config.max_write ); se.initialized = true; - return Ok(Some(x.reply(&config))); + self.replyhandler.config(x.capabilities(), config); } // Any operation is invalid before initialization _ if !se.initialized => { warn!("Ignoring FUSE operation before init: {}", self.request); - return Err(Errno::EIO); + self.replyhandler.error(Errno::EIO); } // Filesystem destroyed ll::Operation::Destroy(x) => { se.filesystem.destroy(); se.destroyed = true; - return Ok(Some(x.reply())); + self.replyhandler.ok(); } // Any operation is invalid after destroy _ if se.destroyed => { warn!("Ignoring FUSE operation after destroy: {}", self.request); - return Err(Errno::EIO); + self.replyhandler.error(Errno::EIO); } ll::Operation::Interrupt(_) => { // TODO: handle FUSE_INTERRUPT - return Err(Errno::ENOSYS); + self.replyhandler.error(Errno::ENOSYS); } ll::Operation::Lookup(x) => { - se.filesystem.lookup( + let response = se.filesystem.lookup( self, self.request.nodeid().into(), x.name().as_ref(), - self.reply(), ); + match response { + Ok(entry) => { + self.replyhandler.entry(entry) + }, + Err(err)=>{ + self.replyhandler.error(err) + } + } } ll::Operation::Forget(x) => { se.filesystem @@ -209,20 +210,30 @@ impl<'a> Request<'a> { } ll::Operation::GetAttr(_attr) => { #[cfg(feature = "abi-7-9")] - se.filesystem.getattr( + let response = se.filesystem.getattr( self, self.request.nodeid().into(), - _attr.file_handle().map(|fh| fh.into()), - self.reply(), + _attr.file_handle().map(|fh| fh.into()) ); // Pre-abi-7-9 does not support providing a file handle. #[cfg(not(feature = "abi-7-9"))] - se.filesystem - .getattr(self, self.request.nodeid().into(), None, self.reply()); + let response = se.filesystem.getattr( + self, + self.request.nodeid().into(), + None, + ); + match response { + Ok((attr,ttl))=> { + self.replyhandler.attr(attr, ttl) + } + Err(err)=>{ + self.replyhandler.error(err) + } + } } ll::Operation::SetAttr(x) => { - se.filesystem.setattr( + let response = se.filesystem.setattr( self, self.request.nodeid().into(), x.mode(), @@ -236,98 +247,182 @@ impl<'a> Request<'a> { x.crtime(), x.chgtime(), x.bkuptime(), - x.flags(), - self.reply(), + x.flags() ); + match response { + Ok((attr, ttl))=> { + self.replyhandler.attr(attr, ttl) + } + Err(err)=>{ + self.replyhandler.error(err) + } + } } ll::Operation::ReadLink(_) => { - se.filesystem - .readlink(self, self.request.nodeid().into(), self.reply()); + let response = se.filesystem.readlink( + self, + self.request.nodeid().into() + ); + match response { + Ok(data)=> { + self.replyhandler.data(data) + } + Err(err)=>{ + self.replyhandler.error(err) + } + } } ll::Operation::MkNod(x) => { - se.filesystem.mknod( + let response = se.filesystem.mknod( self, self.request.nodeid().into(), x.name().as_ref(), x.mode(), x.umask(), - x.rdev(), - self.reply(), + x.rdev() ); + match response { + Ok(entry)=> { + self.replyhandler.entry(entry) + } + Err(err)=>{ + self.replyhandler.error(err) + } + } } ll::Operation::MkDir(x) => { - se.filesystem.mkdir( + let response = se.filesystem.mkdir( self, self.request.nodeid().into(), x.name().as_ref(), x.mode(), - x.umask(), - self.reply(), + x.umask() ); + match response { + Ok(entry)=> { + self.replyhandler.entry(entry) + } + Err(err)=>{ + self.replyhandler.error(err) + } + } } ll::Operation::Unlink(x) => { - se.filesystem.unlink( + let response = se.filesystem.unlink( self, self.request.nodeid().into(), - x.name().as_ref(), - self.reply(), + x.name().into() ); + match response { + Ok(())=> { + self.replyhandler.ok() + } + Err(err)=>{ + self.replyhandler.error(err) + } + } } ll::Operation::RmDir(x) => { - se.filesystem.rmdir( + let response = se.filesystem.rmdir( self, self.request.nodeid().into(), x.name().as_ref(), - self.reply(), ); + match response { + Ok(())=> { + self.replyhandler.ok() + } + Err(err)=>{ + self.replyhandler.error(err) + } + } } ll::Operation::SymLink(x) => { - se.filesystem.symlink( + let response = se.filesystem.symlink( self, self.request.nodeid().into(), x.link_name().as_ref(), Path::new(x.target()), - self.reply(), ); + match response { + Ok(entry)=> { + self.replyhandler.entry(entry) + } + Err(err)=>{ + self.replyhandler.error(err) + } + } } ll::Operation::Rename(x) => { - se.filesystem.rename( + let response = se.filesystem.rename( self, self.request.nodeid().into(), x.src().name.as_ref(), x.dest().dir.into(), x.dest().name.as_ref(), - 0, - self.reply(), + 0 ); + match response { + Ok(())=> { + self.replyhandler.ok() + } + Err(err)=>{ + self.replyhandler.error(err) + } + } } ll::Operation::Link(x) => { - se.filesystem.link( + let response = se.filesystem.link( self, x.inode_no().into(), self.request.nodeid().into(), x.dest().name.as_ref(), - self.reply(), ); + match response { + Ok(entry)=> { + self.replyhandler.entry(entry) + } + Err(err)=>{ + self.replyhandler.error(err) + } + } } ll::Operation::Open(x) => { - se.filesystem - .open(self, self.request.nodeid().into(), x.flags(), self.reply()); + let response = se.filesystem.open( + self, + self.request.nodeid().into(), + x.flags() + ); + match response { + Ok(open)=> { + self.replyhandler.opened(open) + } + Err(err)=>{ + self.replyhandler.error(err) + } + } } ll::Operation::Read(x) => { - se.filesystem.read( + let response = se.filesystem.read( self, self.request.nodeid().into(), x.file_handle().into(), x.offset(), x.size(), x.flags(), - x.lock_owner().map(|l| l.into()), - self.reply(), + x.lock_owner().map(|l| l.into()) ); + match response { + Ok(data)=> { + self.replyhandler.data(data.as_ref()) + } + Err(err)=>{ + self.replyhandler.error(err) + } + } } ll::Operation::Write(x) => { - se.filesystem.write( + let response = se.filesystem.write( self, self.request.nodeid().into(), x.file_handle().into(), @@ -335,127 +430,244 @@ impl<'a> Request<'a> { x.data(), x.write_flags(), x.flags(), - x.lock_owner().map(|l| l.into()), - self.reply(), + x.lock_owner().map(|l| l.into()) ); + match response { + Ok(size)=> { + self.replyhandler.written(size) + } + Err(err)=>{ + self.replyhandler.error(err) + } + } } ll::Operation::Flush(x) => { - se.filesystem.flush( + let response = se.filesystem.flush( self, self.request.nodeid().into(), x.file_handle().into(), - x.lock_owner().into(), - self.reply(), + x.lock_owner().into() ); + match response { + Ok(())=> { + self.replyhandler.ok() + } + Err(err)=>{ + self.replyhandler.error(err) + } + } } ll::Operation::Release(x) => { - se.filesystem.release( + let response = se.filesystem.release( self, self.request.nodeid().into(), x.file_handle().into(), x.flags(), x.lock_owner().map(|x| x.into()), - x.flush(), - self.reply(), + x.flush() ); + match response { + Ok(())=> { + self.replyhandler.ok() + } + Err(err)=>{ + self.replyhandler.error(err) + } + } } ll::Operation::FSync(x) => { - se.filesystem.fsync( + let response = se.filesystem.fsync( self, self.request.nodeid().into(), x.file_handle().into(), - x.fdatasync(), - self.reply(), + x.fdatasync() ); + match response { + Ok(())=> { + self.replyhandler.ok() + } + Err(err)=>{ + self.replyhandler.error(err) + } + } } ll::Operation::OpenDir(x) => { - se.filesystem - .opendir(self, self.request.nodeid().into(), x.flags(), self.reply()); + let response = se.filesystem.opendir( + self, + self.request.nodeid().into(), + x.flags() + ); + match response { + Ok(open)=> { + self.replyhandler.opened(open) + } + Err(err)=>{ + self.replyhandler.error(err) + } + } } ll::Operation::ReadDir(x) => { - se.filesystem.readdir( + let response = se.filesystem.readdir( self, self.request.nodeid().into(), x.file_handle().into(), x.offset(), - ReplyDirectory::new( - self.request.unique().into(), - self.ch.clone(), - x.size() as usize, - ), ); + match response { + Ok(entries_list_result)=> { + self.replyhandler.dir( + ) + } + Err(err)=>{ + self.replyhandler.error(err) + } + } } ll::Operation::ReleaseDir(x) => { - se.filesystem.releasedir( + let response = se.filesystem.releasedir( self, self.request.nodeid().into(), x.file_handle().into(), - x.flags(), - self.reply(), + x.flags() ); + match response { + Ok(())=> { + self.replyhandler.ok() + } + Err(err)=>{ + self.replyhandler.error(err) + } + } } ll::Operation::FSyncDir(x) => { - se.filesystem.fsyncdir( + let response = se.filesystem.fsyncdir( self, self.request.nodeid().into(), x.file_handle().into(), - x.fdatasync(), - self.reply(), + x.fdatasync() ); + match response { + Ok(())=> { + self.replyhandler.ok() + } + Err(err)=>{ + self.replyhandler.error(err) + } + } } ll::Operation::StatFs(_) => { - se.filesystem - .statfs(self, self.request.nodeid().into(), self.reply()); + let response = se.filesystem.statfs( + self, + self.request.nodeid().into() + ); + match response { + Ok(statfs)=> { + self.replyhandler.statfs(statfs) + } + Err(err)=>{ + self.replyhandler.error(err) + } + } } ll::Operation::SetXAttr(x) => { - se.filesystem.setxattr( + let response = se.filesystem.setxattr( self, self.request.nodeid().into(), x.name(), x.value(), x.flags(), - x.position(), - self.reply(), + x.position() ); + match response { + Ok(())=> { + self.replyhandler.ok() + } + Err(err)=>{ + self.replyhandler.error(err) + } + } } ll::Operation::GetXAttr(x) => { - se.filesystem.getxattr( + let response = se.filesystem.getxattr( self, self.request.nodeid().into(), x.name(), - x.size_u32(), - self.reply(), + x.size_u32() ); + match response { + Ok(xattr)=> { + self.replyhandler.xattr(xattr) + } + Err(err)=>{ + self.replyhandler.error(err) + } + } } ll::Operation::ListXAttr(x) => { - se.filesystem - .listxattr(self, self.request.nodeid().into(), x.size(), self.reply()); + let response = se.filesystem.listxattr( + self, + self.request.nodeid().into(), + x.size() + ); + match response { + Ok(xattr)=> { + self.replyhandler.xattr(xattr) + } + Err(err)=>{ + self.replyhandler.error(err) + } + } } ll::Operation::RemoveXAttr(x) => { - se.filesystem.removexattr( + let response = se.filesystem.removexattr( self, self.request.nodeid().into(), x.name(), - self.reply(), ); + match response { + Ok(())=> { + self.replyhandler.ok() + } + Err(err)=>{ + self.replyhandler.error(err) + } + } } ll::Operation::Access(x) => { - se.filesystem - .access(self, self.request.nodeid().into(), x.mask(), self.reply()); + let response = se.filesystem.access( + self, + self.request.nodeid().into(), + x.mask() + ); + match response { + Ok(())=> { + self.replyhandler.ok() + } + Err(err)=>{ + self.replyhandler.error(err) + } + } } ll::Operation::Create(x) => { - se.filesystem.create( + let response = se.filesystem.create( self, self.request.nodeid().into(), x.name().as_ref(), x.mode(), x.umask(), - x.flags(), - self.reply(), + x.flags() ); + match response { + Ok((entry, open))=> { + self.replyhandler.created(entry, open) + } + Err(err)=>{ + self.replyhandler.error(err) + } + } } ll::Operation::GetLk(x) => { - se.filesystem.getlk( + let response = se.filesystem.getlk( self, self.request.nodeid().into(), x.file_handle().into(), @@ -463,12 +675,19 @@ impl<'a> Request<'a> { x.lock().range.0, x.lock().range.1, x.lock().typ, - x.lock().pid, - self.reply(), + x.lock().pid ); + match response { + Ok(lock)=> { + self.replyhandler.locked(lock) + } + Err(err)=>{ + self.replyhandler.error(err) + } + } } ll::Operation::SetLk(x) => { - se.filesystem.setlk( + let response = se.filesystem.setlk( self, self.request.nodeid().into(), x.file_handle().into(), @@ -477,12 +696,19 @@ impl<'a> Request<'a> { x.lock().range.1, x.lock().typ, x.lock().pid, - false, - self.reply(), + false ); + match response { + Ok(())=> { + self.replyhandler.ok() + } + Err(err)=>{ + self.replyhandler.error(err) + } + } } ll::Operation::SetLkW(x) => { - se.filesystem.setlk( + let response = se.filesystem.setlk( self, self.request.nodeid().into(), x.file_handle().into(), @@ -491,55 +717,86 @@ impl<'a> Request<'a> { x.lock().range.1, x.lock().typ, x.lock().pid, - true, - self.reply(), + true ); + match response { + Ok(())=> { + self.replyhandler.ok() + } + Err(err)=>{ + self.replyhandler.error(err) + } + } } ll::Operation::BMap(x) => { - se.filesystem.bmap( + let response = se.filesystem.bmap( self, self.request.nodeid().into(), x.block_size(), - x.block(), - self.reply(), + x.block() ); + match response { + Ok(block)=> { + self.replyhandler.bmap(block) + } + Err(err)=>{ + self.replyhandler.error(err) + } + } } #[cfg(feature = "abi-7-11")] ll::Operation::IoCtl(x) => { if x.unrestricted() { - return Err(Errno::ENOSYS); + self.replyhandler.error(Errno::ENOSYS); } else { - se.filesystem.ioctl( + let response = se.filesystem.ioctl( self, self.request.nodeid().into(), x.file_handle().into(), x.flags(), x.command(), x.in_data(), - x.out_size(), - self.reply(), + x.out_size() ); + match response { + Ok(ioctl)=> { + self.replyhandler.ioctl(ioctl) + } + Err(err)=>{ + self.replyhandler.error(err) + } + } } } #[cfg(feature = "abi-7-11")] ll::Operation::Poll(x) => { let ph = PollHandle::new(se.ch.sender(), x.kernel_handle()); - se.filesystem.poll( + let response = se.filesystem.poll( self, self.request.nodeid().into(), x.file_handle().into(), ph, x.events(), - x.flags(), - self.reply(), + x.flags() ); + match response { + Ok(revents)=> { + self.replyhandler.poll(revents) + } + Err(err)=>{ + self.replyhandler.error(err) + } + } + // TODO: register the poll handler + // TODO: receive poll data from the application + // TODO: use the poll handler to send the data } #[cfg(feature = "abi-7-15")] ll::Operation::NotifyReply(_) => { // TODO: handle FUSE_NOTIFY_REPLY - return Err(Errno::ENOSYS); + self.replyhandler.error(Errno::ENOSYS); } #[cfg(feature = "abi-7-16")] ll::Operation::BatchForget(x) => { @@ -547,57 +804,82 @@ impl<'a> Request<'a> { } #[cfg(feature = "abi-7-19")] ll::Operation::FAllocate(x) => { - se.filesystem.fallocate( + let response = se.filesystem.fallocate( self, self.request.nodeid().into(), x.file_handle().into(), x.offset(), x.len(), - x.mode(), - self.reply(), + x.mode() ); + match response { + Ok(())=> { + self.replyhandler.ok() + } + Err(err)=>{ + self.replyhandler.error(err) + } + } } #[cfg(feature = "abi-7-21")] ll::Operation::ReadDirPlus(x) => { - se.filesystem.readdirplus( + let response = se.filesystem.readdirplus( self, self.request.nodeid().into(), x.file_handle().into(), x.offset(), - ReplyDirectoryPlus::new( - self.request.unique().into(), - self.ch.clone(), - x.size() as usize, - ), ); + match response { + Ok(plus_entries_list_result)=> { + self.replyhandler.dirplus( + ) + } + Err(err)=>{ + self.replyhandler.error(err) + } + } } #[cfg(feature = "abi-7-23")] ll::Operation::Rename2(x) => { - se.filesystem.rename( + let response = se.filesystem.rename( self, x.from().dir.into(), x.from().name.as_ref(), x.to().dir.into(), x.to().name.as_ref(), - x.flags(), - self.reply(), + x.flags() ); + match response { + Ok(())=> { + self.replyhandler.ok() + } + Err(err)=>{ + self.replyhandler.error(err) + } + } } #[cfg(feature = "abi-7-24")] ll::Operation::Lseek(x) => { - se.filesystem.lseek( + let response = se.filesystem.lseek( self, self.request.nodeid().into(), x.file_handle().into(), x.offset(), - x.whence(), - self.reply(), + x.whence() ); + match response { + Ok(offset)=> { + self.replyhandler.offset(offset) + } + Err(err)=>{ + self.replyhandler.error(err) + } + } } #[cfg(feature = "abi-7-28")] ll::Operation::CopyFileRange(x) => { let (i, o) = (x.src(), x.dest()); - se.filesystem.copy_file_range( + let response = se.filesystem.copy_file_range( self, i.inode.into(), i.file_handle.into(), @@ -606,68 +888,73 @@ impl<'a> Request<'a> { o.file_handle.into(), o.offset, x.len(), - x.flags().try_into().unwrap(), - self.reply(), + x.flags().try_into().unwrap() ); + match response { + Ok(written)=> { + self.replyhandler.written(written) + } + Err(err)=>{ + self.replyhandler.error(err) + } + } } #[cfg(target_os = "macos")] ll::Operation::SetVolName(x) => { - se.filesystem.setvolname(self, x.name(), self.reply()); + let response = se.filesystem.setvolname( + self.meta, + x.name() + ); + match response { + Ok(())=> { + self.replyhandler.ok() + } + Err(err)=>{ + self.replyhandler.error(err) + } + } } #[cfg(target_os = "macos")] ll::Operation::GetXTimes(x) => { - se.filesystem - .getxtimes(self, x.nodeid().into(), self.reply()); + let response = se.filesystem.getxtimes( + self.meta, + x.nodeid().into() + ); + match response { + Ok(xtimes)=> { + self.replyhandler.xtimes(xtimes) + } + Err(err)=>{ + self.replyhandler.error(err) + } + + } } #[cfg(target_os = "macos")] ll::Operation::Exchange(x) => { - se.filesystem.exchange( - self, + let response = se.filesystem.exchange( + self.meta, x.from().dir.into(), - x.from().name.as_ref(), + x.from().name.into(), x.to().dir.into(), - x.to().name.as_ref(), - x.options(), - self.reply(), + x.to().name.into(), + x.options() ); + match response { + Ok(())=> { + self.replyhandler.ok() + } + Err(err)=>{ + self.replyhandler.error(err) + } + } } #[cfg(feature = "abi-7-12")] ll::Operation::CuseInit(_) => { // TODO: handle CUSE_INIT - return Err(Errno::ENOSYS); + self.replyhandler.error(Errno::ENOSYS); } } - Ok(None) - } - - /// Create a reply object for this request that can be passed to the filesystem - /// implementation and makes sure that a request is replied exactly once - fn reply(&self) -> T { - Reply::new(self.request.unique().into(), self.ch.clone()) - } - - /// Returns the unique identifier of this request - #[inline] - pub fn unique(&self) -> u64 { - self.request.unique().into() - } - - /// Returns the uid of this request - #[inline] - pub fn uid(&self) -> u32 { - self.request.uid() - } - - /// Returns the gid of this request - #[inline] - pub fn gid(&self) -> u32 { - self.request.gid() - } - - /// Returns the pid of this request - #[inline] - pub fn pid(&self) -> u32 { - self.request.pid() } } From d6a1d06a20d271a4992dcc2ec8ff291ca96cf6e4 Mon Sep 17 00:00:00 2001 From: Richard Lawrence Date: Wed, 16 Jul 2025 12:13:50 -0500 Subject: [PATCH 02/19] Filesystem method `req` argument simplified from borrowed `Request` to an owned `RequestMeta`. Replies to Init and Destroy operations are now handled by ReplyHandler rather than ll::request. Defined struct Forget to unify signatures of forget and batch_forget. --- src/lib.rs | 116 ++++++++++++++++++--------------- src/ll/fuse_abi.rs | 7 +- src/ll/request.rs | 73 ++++++--------------- src/reply.rs | 44 ++++++++++++- src/request.rs | 157 ++++++++++++++++++++++++++------------------- 5 files changed, 221 insertions(+), 176 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 97eb903a..d248f810 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,15 +23,13 @@ pub use crate::ll::fuse_abi::FUSE_ROOT_ID; pub use crate::ll::{fuse_abi::consts, TimeOrNow}; use crate::mnt::mount_options::check_option_conflicts; use crate::session::MAX_WRITE_SIZE; -#[cfg(feature = "abi-7-16")] -pub use ll::fuse_abi::fuse_forget_one; pub use mnt::mount_options::MountOption; #[cfg(feature = "abi-7-11")] pub use notify::{Notifier, PollHandle}; #[cfg(feature = "abi-7-40")] pub use passthrough::BackingId; pub use ll::Errno; -pub use request::Request; +pub use request::RequestMeta; pub use session::{BackgroundSession, Session, SessionACL, SessionUnmounter}; #[cfg(feature = "abi-7-28")] use std::cmp::max; @@ -133,6 +131,16 @@ pub struct FileAttr { /// Flags (macOS only, see chflags(2)) pub flags: u32, } +#[derive(Debug)] +/// Target of a `forget` or `batch_forget` operation. +pub struct Forget { + /// Inode of the file to be forgotten. + pub ino: u64, + /// The number of times the file has been looked up (and not yet forgotten). + /// When a `forget` operation is received, the filesystem should typically + /// decrement its internal reference count for the inode by `nlookup`. + pub nlookup: u64 +} /// Configuration of the fuse kernel module connection #[derive(Debug)] @@ -315,12 +323,14 @@ impl KernelConfig { /// implementations are provided here to get a mountable filesystem that does /// nothing. #[allow(clippy::too_many_arguments)] +#[allow(unused_variables)] // This is the main API, so variables are named without the underscore even though the defaults may not use them. pub trait Filesystem { /// Initialize filesystem. /// Called before any other filesystem method. - /// The kernel module connection can be configured using the KernelConfig object - fn init(&mut self, _req: &Request<'_>, _config: &mut KernelConfig) -> Result<(), Errno> { - Ok(()) + /// The kernel module connection can be configured using the KernelConfig object. + /// The method should return `Ok(KernelConfig)` to accept the connection, or `Err(Errno)` to reject it. + fn init(&mut self, req: RequestMeta, config: KernelConfig) -> Result { + Ok(config) } /// Clean up filesystem. @@ -328,7 +338,7 @@ pub trait Filesystem { fn destroy(&mut self) {} /// Look up a directory entry by name and get its attributes. - fn lookup(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr) -> Result<(), Errno> { + fn lookup(&mut self, _req: RequestMeta, parent: u64, name: &OsStr) -> Result<(), Errno> { warn!( "[Not Implemented] lookup(parent: {:#x?}, name {:?})", parent, name @@ -337,25 +347,25 @@ pub trait Filesystem { } /// Forget about an inode. - /// The nlookup parameter indicates the number of lookups previously performed on + /// The `target.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 + /// inodes acquire a single reference on each lookup, and lose `target.nlookup` references on /// each forget. The filesystem may ignore forget calls, if the inodes don't need to /// have a limited lifetime. On unmount it is not guaranteed, that all referenced - /// inodes will receive a forget message. - fn forget(&mut self, _req: &Request<'_>, _ino: u64, _nlookup: u64) {} + /// inodes will receive a forget message. This operation does not return a result. + fn forget(&mut self, req: RequestMeta, target: Forget) {} /// Like forget, but take multiple forget requests at once for performance. The default - /// implementation will fallback to forget. + /// implementation will fallback to `forget` for each node. This operation does not return a result. #[cfg(feature = "abi-7-16")] - fn batch_forget(&mut self, req: &Request<'_>, nodes: &[fuse_forget_one]) { + fn batch_forget(&mut self, req: RequestMeta, nodes: Vec) { for node in nodes { - self.forget(req, node.nodeid, node.nlookup); + self.forget(req, node); } } /// Get file attributes. - fn getattr(&mut self, _req: &Request<'_>, ino: u64, fh: Option) -> Result<(), Errno> { + fn getattr(&mut self, _req: RequestMeta, ino: u64, fh: Option) -> Result<(), Errno> { warn!( "[Not Implemented] getattr(ino: {:#x?}, fh: {:#x?})", ino, fh @@ -366,7 +376,7 @@ pub trait Filesystem { /// Set file attributes. fn setattr( &mut self, - _req: &Request<'_>, + _req: RequestMeta, ino: u64, mode: Option, uid: Option, @@ -390,7 +400,7 @@ pub trait Filesystem { } /// Read symbolic link. - fn readlink(&mut self, _req: &Request<'_>, ino: u64) -> Result<(), Errno> { + fn readlink(&mut self, _req: RequestMeta, ino: u64) -> Result<(), Errno> { warn!("[Not Implemented] readlink(ino: {:#x?})", ino); Err(Errno::ENOSYS) } @@ -399,7 +409,7 @@ pub trait Filesystem { /// Create a regular file, character device, block device, fifo or socket node. fn mknod( &mut self, - _req: &Request<'_>, + _req: RequestMeta, parent: u64, name: &OsStr, mode: u32, @@ -417,7 +427,7 @@ pub trait Filesystem { /// Create a directory. fn mkdir( &mut self, - _req: &Request<'_>, + _req: RequestMeta, parent: u64, name: &OsStr, mode: u32, @@ -431,7 +441,7 @@ pub trait Filesystem { } /// Remove a file. - fn unlink(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr) -> Result<(), Errno> { + fn unlink(&mut self, _req: RequestMeta, parent: u64, name: &OsStr) -> Result<(), Errno> { warn!( "[Not Implemented] unlink(parent: {:#x?}, name: {:?})", parent, name, @@ -440,7 +450,7 @@ pub trait Filesystem { } /// Remove a directory. - fn rmdir(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr) -> Result<(), Errno> { + fn rmdir(&mut self, _req: RequestMeta, parent: u64, name: &OsStr) -> Result<(), Errno> { warn!( "[Not Implemented] rmdir(parent: {:#x?}, name: {:?})", parent, name, @@ -451,7 +461,7 @@ pub trait Filesystem { /// Create a symbolic link. fn symlink( &mut self, - _req: &Request<'_>, + _req: RequestMeta, parent: u64, link_name: &OsStr, target: &Path, @@ -466,7 +476,7 @@ pub trait Filesystem { /// Rename a file. fn rename( &mut self, - _req: &Request<'_>, + _req: RequestMeta, parent: u64, name: &OsStr, newparent: u64, @@ -484,7 +494,7 @@ pub trait Filesystem { /// Create a hard link. fn link( &mut self, - _req: &Request<'_>, + _req: RequestMeta, ino: u64, newparent: u64, newname: &OsStr, @@ -504,7 +514,7 @@ pub trait Filesystem { /// 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 for more details. - fn open(&mut self, _req: &Request<'_>, _ino: u64, _flags: i32) -> Result<(), Errno> { + fn open(&mut self, _req: RequestMeta, _ino: u64, _flags: i32) -> Result<(), Errno> { warn!("[Not Implemented] open(ino: {:#x?}, flags: {})", _ino, _flags); Err(Errno::ENOSYS) } @@ -521,7 +531,7 @@ pub trait Filesystem { /// lock_owner: only supported with ABI >= 7.9 fn read( &mut self, - _req: &Request<'_>, + _req: RequestMeta, ino: u64, fh: u64, offset: i64, @@ -551,7 +561,7 @@ pub trait Filesystem { /// lock_owner: only supported with ABI >= 7.9 fn write( &mut self, - _req: &Request<'_>, + _req: RequestMeta, ino: u64, fh: u64, offset: i64, @@ -584,7 +594,7 @@ pub trait 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'. - fn flush(&mut self, _req: &Request<'_>, ino: u64, fh: u64, lock_owner: u64) -> Result<(), Errno> { + fn flush(&mut self, _req: RequestMeta, ino: u64, fh: u64, lock_owner: u64) -> Result<(), Errno> { warn!( "[Not Implemented] flush(ino: {:#x?}, fh: {}, lock_owner: {:?})", ino, fh, lock_owner @@ -602,7 +612,7 @@ pub trait Filesystem { /// open. fn release( &mut self, - _req: &Request<'_>, + _req: RequestMeta, _ino: u64, _fh: u64, _flags: i32, @@ -615,7 +625,7 @@ pub trait Filesystem { /// Synchronize file contents. /// If the datasync parameter is non-zero, then only the user data should be flushed, /// not the meta data. - fn fsync(&mut self, _req: &Request<'_>, ino: u64, fh: u64, datasync: bool) -> Result<(), Errno> { + fn fsync(&mut self, _req: RequestMeta, ino: u64, fh: u64, datasync: bool) -> Result<(), Errno> { warn!( "[Not Implemented] fsync(ino: {:#x?}, fh: {}, datasync: {})", ino, fh, datasync @@ -630,7 +640,7 @@ pub trait Filesystem { /// 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. - fn opendir(&mut self, _req: &Request<'_>, _ino: u64, _flags: i32) -> Result<(), Errno> { + fn opendir(&mut self, _req: RequestMeta, _ino: u64, _flags: i32) -> Result<(), Errno> { warn!("[Not Implemented] open(ino: {:#x?}, flags: {})", _ino, _flags); Err(Errno::ENOSYS) } @@ -642,7 +652,7 @@ pub trait Filesystem { /// didn't set any value. fn readdir( &mut self, - _req: &Request<'_>, + _req: RequestMeta, ino: u64, fh: u64, offset: i64, @@ -661,7 +671,7 @@ pub trait Filesystem { /// didn't set any value. fn readdirplus( &mut self, - _req: &Request<'_>, + _req: RequestMeta, ino: u64, fh: u64, offset: i64, @@ -679,7 +689,7 @@ pub trait Filesystem { /// opendir method didn't set any value. fn releasedir( &mut self, - _req: &Request<'_>, + _req: RequestMeta, _ino: u64, _fh: u64, _flags: i32, @@ -693,7 +703,7 @@ pub trait Filesystem { /// method, or will be undefined if the opendir method didn't set any value. fn fsyncdir( &mut self, - _req: &Request<'_>, + _req: RequestMeta, ino: u64, fh: u64, datasync: bool, @@ -706,7 +716,7 @@ pub trait Filesystem { } /// Get file system statistics. - fn statfs(&mut self, _req: &Request<'_>, _ino: u64) -> Result<(), Errno> { + fn statfs(&mut self, _req: RequestMeta, _ino: u64) -> Result<(), Errno> { warn!("[Not Implemented] statfs(ino: {:#x?})", _ino); Err(Errno::ENOSYS) // TODO: default implementation {0, 0, 0, 0, 0, 512, 255, 0} @@ -715,7 +725,7 @@ pub trait Filesystem { /// Set an extended attribute. fn setxattr( &mut self, - _req: &Request<'_>, + _req: RequestMeta, ino: u64, name: &OsStr, _value: &[u8], @@ -735,7 +745,7 @@ pub trait Filesystem { /// `reply.error(ERANGE)` if it doesn't. fn getxattr( &mut self, - _req: &Request<'_>, + _req: RequestMeta, ino: u64, name: &OsStr, size: u32, @@ -751,7 +761,7 @@ pub trait Filesystem { /// If `size` is 0, the size of the value should be sent with `reply.size()`. /// If `size` is not 0, and the value fits, send it with `reply.data()`, or /// `reply.error(ERANGE)` if it doesn't. - fn listxattr(&mut self, _req: &Request<'_>, ino: u64, size: u32) -> Result<(), Errno> { + fn listxattr(&mut self, _req: RequestMeta, ino: u64, size: u32) -> Result<(), Errno> { warn!( "[Not Implemented] listxattr(ino: {:#x?}, size: {})", ino, size @@ -760,7 +770,7 @@ pub trait Filesystem { } /// Remove an extended attribute. - fn removexattr(&mut self, _req: &Request<'_>, ino: u64, name: &OsStr) -> Result<(), Errno> { + fn removexattr(&mut self, _req: RequestMeta, ino: u64, name: &OsStr) -> Result<(), Errno> { warn!( "[Not Implemented] removexattr(ino: {:#x?}, name: {:?})", ino, name @@ -772,7 +782,7 @@ pub trait Filesystem { /// This will be called for the access() system call. If the 'default_permissions' /// mount option is given, this method is not called. This method is not called /// under Linux kernel versions 2.4.x - fn access(&mut self, _req: &Request<'_>, ino: u64, mask: i32) -> Result<(), Errno> { + fn access(&mut self, _req: RequestMeta, ino: u64, mask: i32) -> Result<(), Errno> { warn!("[Not Implemented] access(ino: {:#x?}, mask: {})", ino, mask); Err(Errno::ENOSYS) } @@ -789,7 +799,7 @@ pub trait Filesystem { /// 2.6.15, the mknod() and open() methods will be called instead. fn create( &mut self, - _req: &Request<'_>, + _req: RequestMeta, parent: u64, name: &OsStr, mode: u32, @@ -807,7 +817,7 @@ pub trait Filesystem { /// Test for a POSIX file lock. fn getlk( &mut self, - _req: &Request<'_>, + _req: RequestMeta, ino: u64, fh: u64, lock_owner: u64, @@ -833,7 +843,7 @@ pub trait Filesystem { /// Hence these are only interesting for network filesystems and similar. fn setlk( &mut self, - _req: &Request<'_>, + _req: RequestMeta, ino: u64, fh: u64, lock_owner: u64, @@ -854,7 +864,7 @@ pub trait Filesystem { /// 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 - fn bmap(&mut self, _req: &Request<'_>, ino: u64, blocksize: u32, idx: u64) -> Result<(), Errno> { + fn bmap(&mut self, _req: RequestMeta, ino: u64, blocksize: u32, idx: u64) -> Result<(), Errno> { warn!( "[Not Implemented] bmap(ino: {:#x?}, blocksize: {}, idx: {})", ino, blocksize, idx, @@ -865,7 +875,7 @@ pub trait Filesystem { /// control device fn ioctl( &mut self, - _req: &Request<'_>, + _req: RequestMeta, ino: u64, fh: u64, flags: u32, @@ -890,7 +900,7 @@ pub trait Filesystem { #[cfg(feature = "abi-7-11")] fn poll( &mut self, - _req: &Request<'_>, + _req: RequestMeta, ino: u64, fh: u64, ph: PollHandle, @@ -907,7 +917,7 @@ pub trait Filesystem { /// Preallocate or deallocate space to a file fn fallocate( &mut self, - _req: &Request<'_>, + _req: RequestMeta, ino: u64, fh: u64, offset: i64, @@ -925,7 +935,7 @@ pub trait Filesystem { /// Reposition read/write file offset fn lseek( &mut self, - _req: &Request<'_>, + _req: RequestMeta, ino: u64, fh: u64, offset: i64, @@ -941,7 +951,7 @@ pub trait Filesystem { /// Copy the specified range from the source inode to the destination inode fn copy_file_range( &mut self, - _req: &Request<'_>, + _req: RequestMeta, ino_in: u64, fh_in: u64, offset_in: i64, @@ -963,7 +973,7 @@ pub trait Filesystem { /// macOS only: Rename the volume. Set fuse_init_out.flags during init to /// FUSE_VOL_RENAME to enable #[cfg(target_os = "macos")] - fn setvolname(&mut self, _req: &Request<'_>, name: &OsStr) -> Result<(), Errno> { + fn setvolname(&mut self, _req: RequestMeta, name: &OsStr) -> Result<(), Errno> { warn!("[Not Implemented] setvolname(name: {:?})", name); Err(Errno::ENOSYS) } @@ -972,7 +982,7 @@ pub trait Filesystem { #[cfg(target_os = "macos")] fn exchange( &mut self, - _req: &Request<'_>, + _req: RequestMeta, parent: u64, name: &OsStr, newparent: u64, @@ -990,7 +1000,7 @@ pub trait Filesystem { /// macOS only: Query extended times (bkuptime and crtime). Set fuse_init_out.flags /// during init to FUSE_XTIMES to enable #[cfg(target_os = "macos")] - fn getxtimes(&mut self, _req: &Request<'_>, ino: u64) -> Result<(), Errno> { + fn getxtimes(&mut self, _req: RequestMeta, ino: u64) -> Result<(), Errno> { warn!("[Not Implemented] getxtimes(ino: {:#x?})", ino); Err(Errno::ENOSYS) } diff --git a/src/ll/fuse_abi.rs b/src/ll/fuse_abi.rs index 96cea927..33f4dbe9 100644 --- a/src/ll/fuse_abi.rs +++ b/src/ll/fuse_abi.rs @@ -136,10 +136,15 @@ pub struct fuse_kstatfs { #[repr(C)] #[derive(Debug, IntoBytes, FromBytes, KnownLayout, Immutable)] pub struct fuse_file_lock { + /// start of locked byte range pub start: u64, + /// end of locked byte range pub end: u64, // NOTE: this field is defined as u32 in fuse_kernel.h in libfuse. However, it is treated as signed + // TODO enum {F_RDLCK, F_WRLCK, F_UNLCK} + /// kind of lock (read and/or write) pub typ: i32, + /// PID of process blocking our lock pub pid: u32, } @@ -530,7 +535,7 @@ pub struct fuse_forget_in { #[cfg(feature = "abi-7-16")] #[repr(C)] -#[derive(Debug, FromBytes, KnownLayout, Immutable)] +#[derive(Debug, FromBytes, KnownLayout, Immutable, Clone)] pub struct fuse_forget_one { pub nodeid: u64, pub nlookup: u64, diff --git a/src/ll/request.rs b/src/ll/request.rs index 01c28eda..cbbd87a5 100644 --- a/src/ll/request.rs +++ b/src/ll/request.rs @@ -5,7 +5,7 @@ use super::fuse_abi::{fuse_in_header, fuse_opcode, InvalidOpcodeError}; -use super::{fuse_abi as abi, Errno, Response}; +use super::{fuse_abi as abi, Errno}; #[cfg(feature = "serializable")] use serde::{Deserialize, Serialize}; use std::{convert::TryFrom, fmt::Display, path::Path}; @@ -226,11 +226,6 @@ pub trait Request: Sized { /// Returns the PID of the process that triggered this request. fn pid(&self) -> u32; - - /// Create an error response for this Request - fn reply_err(&self, errno: Errno) -> Response<'_> { - Response::new_error(errno) - } } macro_rules! impl_request { @@ -265,8 +260,6 @@ macro_rules! impl_request { } mod op { - use crate::ll::Response; - use super::{ super::{argument::ArgumentIterator, TimeOrNow}, FilenameInDir, Request, @@ -987,46 +980,6 @@ mod op { pub fn version(&self) -> super::Version { super::Version(self.arg.major, self.arg.minor) } - - pub fn reply(&self, config: &crate::KernelConfig) -> Response<'a> { - let flags = self.capabilities() & config.requested; // use requested features and reported as capable - - let init = fuse_init_out { - major: FUSE_KERNEL_VERSION, - minor: FUSE_KERNEL_MINOR_VERSION, - max_readahead: config.max_readahead, - #[cfg(not(feature = "abi-7-36"))] - flags: flags as u32, - #[cfg(feature = "abi-7-36")] - flags: (flags | FUSE_INIT_EXT) as u32, - #[cfg(not(feature = "abi-7-13"))] - unused: 0, - #[cfg(feature = "abi-7-13")] - max_background: config.max_background, - #[cfg(feature = "abi-7-13")] - congestion_threshold: config.congestion_threshold(), - max_write: config.max_write, - #[cfg(feature = "abi-7-23")] - time_gran: config.time_gran.as_nanos() as u32, - #[cfg(all(feature = "abi-7-23", not(feature = "abi-7-28")))] - reserved: [0; 9], - #[cfg(feature = "abi-7-28")] - max_pages: config.max_pages(), - #[cfg(feature = "abi-7-28")] - unused2: 0, - #[cfg(all(feature = "abi-7-28", not(feature = "abi-7-36")))] - reserved: [0; 8], - #[cfg(feature = "abi-7-36")] - flags2: (flags >> 32) as u32, - #[cfg(all(feature = "abi-7-36", not(feature = "abi-7-40")))] - reserved: [0; 7], - #[cfg(feature = "abi-7-40")] - max_stack_depth: config.max_stack_depth, - #[cfg(feature = "abi-7-40")] - reserved: [0; 6], - }; - Response::new_data(init.as_bytes()) - } } /// Open a directory. @@ -1313,11 +1266,6 @@ mod op { header: &'a fuse_in_header, } impl_request!(Destroy<'a>); - impl<'a> Destroy<'a> { - pub fn reply(&self) -> Response<'a> { - Response::new_empty() - } - } /// Control device #[cfg(feature = "abi-7-11")] @@ -1418,6 +1366,25 @@ mod op { self.nodes } } + #[cfg(feature = "abi-7-16")] + use crate::Forget as ForgetAPI; // to distinguish from op::Forget (above) + #[cfg(feature = "abi-7-16")] + #[allow(clippy::from_over_into)] + /// just a convenience function + impl Into> for BatchForget<'_> { + fn into(self) -> Vec { + let mut buf = Vec::new(); + for node in self.nodes { + buf.push({ + ForgetAPI{ + ino: node.nodeid, + nlookup: node.nlookup + } + }) + } + buf + } + } /// Preallocate or deallocate space to a file /// diff --git a/src/reply.rs b/src/reply.rs index 0f8bb790..b6433268 100644 --- a/src/reply.rs +++ b/src/reply.rs @@ -3,6 +3,7 @@ //! A reply handler object is created to guarantee that each fuse request receives a reponse exactly once. //! Either the request logic will call the one of the reply handler's self-destructive methods, //! or, if the reply handler goes out of scope before that happens, the drop trait will send an error response. +use crate::KernelConfig; #[cfg(feature = "abi-7-40")] use crate::{consts::FOPEN_PASSTHROUGH, passthrough::BackingId}; use crate::ll; @@ -113,8 +114,47 @@ impl ReplyHandler { pub fn data(self, data: &[u8]) { self.send_ll(&ll::Response::new_slice(data)); } - /// Reply to an init request with available features - pub fn config(self) {} + + // Reply to an init request with available features + pub fn config(self, capabilities: u64, config: KernelConfig) { + let flags = capabilities & config.requested; // use features requested by fs and reported as capable by kernel + + let init = ll::fuse_abi::fuse_init_out { + major: ll::fuse_abi::FUSE_KERNEL_VERSION, + minor: ll::fuse_abi::FUSE_KERNEL_MINOR_VERSION, + max_readahead: config.max_readahead, + #[cfg(not(feature = "abi-7-36"))] + flags: flags as u32, + #[cfg(feature = "abi-7-36")] + flags: (flags | ll::fuse_abi::consts::FUSE_INIT_EXT) as u32, + #[cfg(not(feature = "abi-7-13"))] + unused: 0, + #[cfg(feature = "abi-7-13")] + max_background: config.max_background, + #[cfg(feature = "abi-7-13")] + congestion_threshold: config.congestion_threshold(), + max_write: config.max_write, + #[cfg(feature = "abi-7-23")] + time_gran: config.time_gran.as_nanos() as u32, + #[cfg(all(feature = "abi-7-23", not(feature = "abi-7-28")))] + reserved: [0; 9], + #[cfg(feature = "abi-7-28")] + max_pages: config.max_pages(), + #[cfg(feature = "abi-7-28")] + unused2: 0, + #[cfg(all(feature = "abi-7-28", not(feature = "abi-7-36")))] + reserved: [0; 8], + #[cfg(feature = "abi-7-36")] + flags2: (flags >> 32) as u32, + #[cfg(all(feature = "abi-7-36", not(feature = "abi-7-40")))] + reserved: [0; 7], + #[cfg(feature = "abi-7-40")] + max_stack_depth: config.max_stack_depth, + #[cfg(feature = "abi-7-40")] + reserved: [0; 6], + }; + self.send_ll(&ll::Response::new_data(init.as_bytes())); + } /// Reply to a request with a file entry pub fn entry(self) {} diff --git a/src/request.rs b/src/request.rs index cfd39815..95cddca4 100644 --- a/src/request.rs +++ b/src/request.rs @@ -5,12 +5,11 @@ //! //! TODO: This module is meant to go away soon in favor of `ll::Request`. -use crate::ll::{fuse_abi as abi, Errno, Response}; +use crate::ll::{fuse_abi as abi, Errno}; use log::{debug, error, warn}; use std::convert::TryFrom; #[cfg(feature = "abi-7-28")] use std::convert::TryInto; -use std::path::Path; use crate::channel::ChannelSender; use crate::ll::Request as _; @@ -19,7 +18,7 @@ use crate::session::{Session, SessionACL}; use crate::Filesystem; #[cfg(feature = "abi-7-11")] use crate::PollHandle; -use crate::{ll, KernelConfig}; +use crate::{ll, KernelConfig, Forget}; /// Request data structure #[derive(Debug)] @@ -31,10 +30,25 @@ pub struct Request<'a> { data: &'a [u8], /// Parsed request request: ll::AnyRequest<'a>, + /// Request metadata + meta: RequestMeta, /// Closure-like object to guarantee a response is sent replyhandler: ReplyHandler, } +/// Request metadata structure +#[derive(Copy, Clone, Debug)] +pub struct RequestMeta { + /// The unique identifier of this request + pub unique: u64, + /// The uid of this request + pub uid: u32, + /// The gid of this request + pub gid: u32, + /// The pid of this request + pub pid: u32 +} + impl<'a> Request<'a> { /// Create a new request from the given data pub(crate) fn new(ch: ChannelSender, data: &'a [u8]) -> Option> { @@ -46,14 +60,20 @@ impl<'a> Request<'a> { } }; + let meta = RequestMeta { + unique: request.unique().into(), + uid: request.uid(), + gid: request.gid(), + pid: request.pid() + }; let replyhandler = ReplyHandler::new(request.unique().into(), ch.clone()); - Some(Self { ch, data, request, replyhandler }) + Some(Self { ch, data, request, meta, replyhandler }) } /// Dispatch request to the given filesystem. /// This calls the appropriate filesystem operation method for the /// request and sends back the returned reply to the kernel - pub(crate) fn dispatch(&self, se: &mut Session) { + pub(crate) fn dispatch(self, se: &mut Session) { debug!("{}", self.request); let op_result = self.request.operation().map_err(|_| Errno::ENOSYS); @@ -137,35 +157,36 @@ impl<'a> Request<'a> { if v < ll::Version(7, 6) { error!("Unsupported FUSE ABI version {}", v); self.replyhandler.error(Errno::EPROTO); + return; } // Remember ABI version supported by kernel se.proto_major = v.major(); se.proto_minor = v.minor(); - let mut config = KernelConfig::new(x.capabilities(), x.max_readahead()); + let config = KernelConfig::new(x.capabilities(), x.max_readahead()); // Call filesystem init method and give it a chance to // propose a different config or return an error - match se.filesystem.init(&self, &mut config) { - Ok(()) => {}, + match se.filesystem.init(self.meta, config) { + Ok(config) => { + // Reply with our desired version and settings. If the kernel supports a + // larger major version, it'll re-send a matching init message. If it + // supports only lower major versions, we replied with an error above. + debug!( + "INIT response: ABI {}.{}, flags {:#x}, max readahead {}, max write {}", + abi::FUSE_KERNEL_VERSION, + abi::FUSE_KERNEL_MINOR_VERSION, + x.capabilities() & config.requested, + config.max_readahead, + config.max_write + ); + se.initialized = true; + self.replyhandler.config(x.capabilities(), config); + }, Err(errno) => { + // Filesystem refused the config. self.replyhandler.error(errno); - return; } }; - - // Reply with our desired version and settings. If the kernel supports a - // larger major version, it'll re-send a matching init message. If it - // supports only lower major versions, we replied with an error above. - debug!( - "INIT response: ABI {}.{}, flags {:#x}, max readahead {}, max write {}", - abi::FUSE_KERNEL_VERSION, - abi::FUSE_KERNEL_MINOR_VERSION, - x.capabilities() & config.requested, - config.max_readahead, - config.max_write - ); - se.initialized = true; - self.replyhandler.config(x.capabilities(), config); } // Any operation is invalid before initialization _ if !se.initialized => { @@ -191,7 +212,7 @@ impl<'a> Request<'a> { ll::Operation::Lookup(x) => { let response = se.filesystem.lookup( - self, + self.meta, self.request.nodeid().into(), x.name().as_ref(), ); @@ -205,21 +226,24 @@ impl<'a> Request<'a> { } } ll::Operation::Forget(x) => { + let target = Forget { + ino: self.request.nodeid().into(), + nlookup: x.nlookup() + }; se.filesystem - .forget(self, self.request.nodeid().into(), x.nlookup()); // no reply + .forget(self.meta, target); // no reply } ll::Operation::GetAttr(_attr) => { #[cfg(feature = "abi-7-9")] let response = se.filesystem.getattr( - self, + self.meta, self.request.nodeid().into(), _attr.file_handle().map(|fh| fh.into()) ); - // Pre-abi-7-9 does not support providing a file handle. #[cfg(not(feature = "abi-7-9"))] let response = se.filesystem.getattr( - self, + self.meta, self.request.nodeid().into(), None, ); @@ -234,7 +258,7 @@ impl<'a> Request<'a> { } ll::Operation::SetAttr(x) => { let response = se.filesystem.setattr( - self, + self.meta, self.request.nodeid().into(), x.mode(), x.uid(), @@ -260,7 +284,7 @@ impl<'a> Request<'a> { } ll::Operation::ReadLink(_) => { let response = se.filesystem.readlink( - self, + self.meta, self.request.nodeid().into() ); match response { @@ -274,7 +298,7 @@ impl<'a> Request<'a> { } ll::Operation::MkNod(x) => { let response = se.filesystem.mknod( - self, + self.meta, self.request.nodeid().into(), x.name().as_ref(), x.mode(), @@ -292,7 +316,7 @@ impl<'a> Request<'a> { } ll::Operation::MkDir(x) => { let response = se.filesystem.mkdir( - self, + self.meta, self.request.nodeid().into(), x.name().as_ref(), x.mode(), @@ -309,7 +333,7 @@ impl<'a> Request<'a> { } ll::Operation::Unlink(x) => { let response = se.filesystem.unlink( - self, + self.meta, self.request.nodeid().into(), x.name().into() ); @@ -324,7 +348,7 @@ impl<'a> Request<'a> { } ll::Operation::RmDir(x) => { let response = se.filesystem.rmdir( - self, + self.meta, self.request.nodeid().into(), x.name().as_ref(), ); @@ -339,7 +363,7 @@ impl<'a> Request<'a> { } ll::Operation::SymLink(x) => { let response = se.filesystem.symlink( - self, + self.meta, self.request.nodeid().into(), x.link_name().as_ref(), Path::new(x.target()), @@ -355,7 +379,7 @@ impl<'a> Request<'a> { } ll::Operation::Rename(x) => { let response = se.filesystem.rename( - self, + self.meta, self.request.nodeid().into(), x.src().name.as_ref(), x.dest().dir.into(), @@ -373,7 +397,7 @@ impl<'a> Request<'a> { } ll::Operation::Link(x) => { let response = se.filesystem.link( - self, + self.meta, x.inode_no().into(), self.request.nodeid().into(), x.dest().name.as_ref(), @@ -389,7 +413,7 @@ impl<'a> Request<'a> { } ll::Operation::Open(x) => { let response = se.filesystem.open( - self, + self.meta, self.request.nodeid().into(), x.flags() ); @@ -404,7 +428,7 @@ impl<'a> Request<'a> { } ll::Operation::Read(x) => { let response = se.filesystem.read( - self, + self.meta, self.request.nodeid().into(), x.file_handle().into(), x.offset(), @@ -423,7 +447,7 @@ impl<'a> Request<'a> { } ll::Operation::Write(x) => { let response = se.filesystem.write( - self, + self.meta, self.request.nodeid().into(), x.file_handle().into(), x.offset(), @@ -443,7 +467,7 @@ impl<'a> Request<'a> { } ll::Operation::Flush(x) => { let response = se.filesystem.flush( - self, + self.meta, self.request.nodeid().into(), x.file_handle().into(), x.lock_owner().into() @@ -459,7 +483,7 @@ impl<'a> Request<'a> { } ll::Operation::Release(x) => { let response = se.filesystem.release( - self, + self.meta, self.request.nodeid().into(), x.file_handle().into(), x.flags(), @@ -477,7 +501,7 @@ impl<'a> Request<'a> { } ll::Operation::FSync(x) => { let response = se.filesystem.fsync( - self, + self.meta, self.request.nodeid().into(), x.file_handle().into(), x.fdatasync() @@ -493,7 +517,7 @@ impl<'a> Request<'a> { } ll::Operation::OpenDir(x) => { let response = se.filesystem.opendir( - self, + self.meta, self.request.nodeid().into(), x.flags() ); @@ -508,7 +532,7 @@ impl<'a> Request<'a> { } ll::Operation::ReadDir(x) => { let response = se.filesystem.readdir( - self, + self.meta, self.request.nodeid().into(), x.file_handle().into(), x.offset(), @@ -525,7 +549,7 @@ impl<'a> Request<'a> { } ll::Operation::ReleaseDir(x) => { let response = se.filesystem.releasedir( - self, + self.meta, self.request.nodeid().into(), x.file_handle().into(), x.flags() @@ -541,7 +565,7 @@ impl<'a> Request<'a> { } ll::Operation::FSyncDir(x) => { let response = se.filesystem.fsyncdir( - self, + self.meta, self.request.nodeid().into(), x.file_handle().into(), x.fdatasync() @@ -557,7 +581,7 @@ impl<'a> Request<'a> { } ll::Operation::StatFs(_) => { let response = se.filesystem.statfs( - self, + self.meta, self.request.nodeid().into() ); match response { @@ -571,7 +595,7 @@ impl<'a> Request<'a> { } ll::Operation::SetXAttr(x) => { let response = se.filesystem.setxattr( - self, + self.meta, self.request.nodeid().into(), x.name(), x.value(), @@ -589,7 +613,7 @@ impl<'a> Request<'a> { } ll::Operation::GetXAttr(x) => { let response = se.filesystem.getxattr( - self, + self.meta, self.request.nodeid().into(), x.name(), x.size_u32() @@ -605,7 +629,7 @@ impl<'a> Request<'a> { } ll::Operation::ListXAttr(x) => { let response = se.filesystem.listxattr( - self, + self.meta, self.request.nodeid().into(), x.size() ); @@ -620,7 +644,7 @@ impl<'a> Request<'a> { } ll::Operation::RemoveXAttr(x) => { let response = se.filesystem.removexattr( - self, + self.meta, self.request.nodeid().into(), x.name(), ); @@ -635,7 +659,7 @@ impl<'a> Request<'a> { } ll::Operation::Access(x) => { let response = se.filesystem.access( - self, + self.meta, self.request.nodeid().into(), x.mask() ); @@ -650,7 +674,7 @@ impl<'a> Request<'a> { } ll::Operation::Create(x) => { let response = se.filesystem.create( - self, + self.meta, self.request.nodeid().into(), x.name().as_ref(), x.mode(), @@ -668,7 +692,7 @@ impl<'a> Request<'a> { } ll::Operation::GetLk(x) => { let response = se.filesystem.getlk( - self, + self.meta, self.request.nodeid().into(), x.file_handle().into(), x.lock_owner().into(), @@ -688,7 +712,7 @@ impl<'a> Request<'a> { } ll::Operation::SetLk(x) => { let response = se.filesystem.setlk( - self, + self.meta, self.request.nodeid().into(), x.file_handle().into(), x.lock_owner().into(), @@ -709,7 +733,7 @@ impl<'a> Request<'a> { } ll::Operation::SetLkW(x) => { let response = se.filesystem.setlk( - self, + self.meta, self.request.nodeid().into(), x.file_handle().into(), x.lock_owner().into(), @@ -730,7 +754,7 @@ impl<'a> Request<'a> { } ll::Operation::BMap(x) => { let response = se.filesystem.bmap( - self, + self.meta, self.request.nodeid().into(), x.block_size(), x.block() @@ -751,7 +775,7 @@ impl<'a> Request<'a> { self.replyhandler.error(Errno::ENOSYS); } else { let response = se.filesystem.ioctl( - self, + self.meta, self.request.nodeid().into(), x.file_handle().into(), x.flags(), @@ -772,9 +796,8 @@ impl<'a> Request<'a> { #[cfg(feature = "abi-7-11")] ll::Operation::Poll(x) => { let ph = PollHandle::new(se.ch.sender(), x.kernel_handle()); - let response = se.filesystem.poll( - self, + self.meta, self.request.nodeid().into(), x.file_handle().into(), ph, @@ -800,12 +823,12 @@ impl<'a> Request<'a> { } #[cfg(feature = "abi-7-16")] ll::Operation::BatchForget(x) => { - se.filesystem.batch_forget(self, x.nodes()); // no reply + se.filesystem.batch_forget(self.meta, x.into()); // no reply } #[cfg(feature = "abi-7-19")] ll::Operation::FAllocate(x) => { let response = se.filesystem.fallocate( - self, + self.meta, self.request.nodeid().into(), x.file_handle().into(), x.offset(), @@ -824,7 +847,7 @@ impl<'a> Request<'a> { #[cfg(feature = "abi-7-21")] ll::Operation::ReadDirPlus(x) => { let response = se.filesystem.readdirplus( - self, + self.meta, self.request.nodeid().into(), x.file_handle().into(), x.offset(), @@ -842,7 +865,7 @@ impl<'a> Request<'a> { #[cfg(feature = "abi-7-23")] ll::Operation::Rename2(x) => { let response = se.filesystem.rename( - self, + self.meta, x.from().dir.into(), x.from().name.as_ref(), x.to().dir.into(), @@ -861,7 +884,7 @@ impl<'a> Request<'a> { #[cfg(feature = "abi-7-24")] ll::Operation::Lseek(x) => { let response = se.filesystem.lseek( - self, + self.meta, self.request.nodeid().into(), x.file_handle().into(), x.offset(), @@ -880,7 +903,7 @@ impl<'a> Request<'a> { ll::Operation::CopyFileRange(x) => { let (i, o) = (x.src(), x.dest()); let response = se.filesystem.copy_file_range( - self, + self.meta, i.inode.into(), i.file_handle.into(), i.offset, From c6e6e4e6d3c2d12c9205657bb0b5cde7c226d3df Mon Sep 17 00:00:00 2001 From: Richard Lawrence Date: Wed, 16 Jul 2025 14:02:25 -0500 Subject: [PATCH 03/19] Struct-based return types for most Filesystem methods. Created new structs: Entry, Open, Statfs, Lock, XTimes. Reorganized existing structs: FileType, FileAttr. Some borrowed arguments have been converted to owned arguments. --- src/lib.rs | 264 +++++++++++++++++++++---------------------- src/ll/reply.rs | 12 +- src/reply.rs | 289 ++++++++++++++++++++++++++++++++++++++++++++---- src/request.rs | 2 +- 4 files changed, 400 insertions(+), 167 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d248f810..5ee4cc99 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,14 +8,16 @@ use log::warn; use mnt::mount_options::parse_options_from_args; +/* +#[cfg(feature = "serializable")] +use serde::de::value::F64Deserializer; #[cfg(feature = "serializable")] use serde::{Deserialize, Serialize}; -use std::ffi::OsStr; +*/ +use std::ffi::{OsStr, OsString}; use std::io; -use std::path::Path; -#[cfg(feature = "abi-7-23")] -use std::time::Duration; -use std::time::SystemTime; +use std::path::{Path, PathBuf}; +use std::time::{Duration, SystemTime}; use std::{convert::AsRef, io::ErrorKind}; use crate::ll::fuse_abi::consts::*; @@ -26,8 +28,13 @@ use crate::session::MAX_WRITE_SIZE; pub use mnt::mount_options::MountOption; #[cfg(feature = "abi-7-11")] pub use notify::{Notifier, PollHandle}; +#[cfg(feature = "abi-7-11")] +pub use reply::Ioctl; #[cfg(feature = "abi-7-40")] pub use passthrough::BackingId; +#[cfg(target_os = "macos")] +pub use reply::XTimes; +pub use reply::{Entry, FileAttr, FileType, Open, Statfs, Lock}; pub use ll::Errno; pub use request::RequestMeta; pub use session::{BackgroundSession, Session, SessionACL, SessionUnmounter}; @@ -76,61 +83,6 @@ const fn default_init_flags(#[allow(unused_variables)] capabilities: u64) -> u64 } } -/// File types -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -#[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))] -pub enum FileType { - /// Named pipe (S_IFIFO) - NamedPipe, - /// Character device (S_IFCHR) - CharDevice, - /// Block device (S_IFBLK) - BlockDevice, - /// Directory (S_IFDIR) - Directory, - /// Regular file (S_IFREG) - RegularFile, - /// Symbolic link (S_IFLNK) - Symlink, - /// Unix domain socket (S_IFSOCK) - Socket, -} - -/// File attributes -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -#[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))] -pub struct FileAttr { - /// Inode number - pub ino: u64, - /// Size in bytes - pub size: u64, - /// Size in blocks - pub blocks: u64, - /// Time of last access - pub atime: SystemTime, - /// Time of last modification - pub mtime: SystemTime, - /// Time of last change - pub ctime: SystemTime, - /// Time of creation (macOS only) - pub crtime: SystemTime, - /// Kind of file (directory, file, pipe, etc) - pub kind: FileType, - /// Permissions - pub perm: u16, - /// Number of hard links - pub nlink: u32, - /// User id - pub uid: u32, - /// Group id - pub gid: u32, - /// Rdev - pub rdev: u32, - /// Block size - pub blksize: u32, - /// Flags (macOS only, see chflags(2)) - pub flags: u32, -} #[derive(Debug)] /// Target of a `forget` or `batch_forget` operation. pub struct Forget { @@ -338,7 +290,8 @@ pub trait Filesystem { fn destroy(&mut self) {} /// Look up a directory entry by name and get its attributes. - fn lookup(&mut self, _req: RequestMeta, parent: u64, name: &OsStr) -> Result<(), Errno> { + /// The method should return `Ok(Entry)` if the entry is found, or `Err(Errno)` otherwise. + fn lookup(&mut self, req: RequestMeta, parent: u64, name: OsString) -> Result { warn!( "[Not Implemented] lookup(parent: {:#x?}, name {:?})", parent, name @@ -365,7 +318,8 @@ pub trait Filesystem { } /// Get file attributes. - fn getattr(&mut self, _req: RequestMeta, ino: u64, fh: Option) -> Result<(), Errno> { + /// The method should return `Ok(Attr)` with the file attributes, or `Err(Errno)` otherwise. + fn getattr(&mut self, req: RequestMeta, ino: u64, fh: Option) -> Result<(FileAttr, Duration), Errno> { warn!( "[Not Implemented] getattr(ino: {:#x?}, fh: {:#x?})", ino, fh @@ -374,6 +328,7 @@ pub trait Filesystem { } /// Set file attributes. + /// The method should return `Ok(Attr)` with the updated file attributes, or `Err(Errno)` otherwise. fn setattr( &mut self, _req: RequestMeta, @@ -390,7 +345,7 @@ pub trait Filesystem { _chgtime: Option, _bkuptime: Option, flags: Option, - ) -> Result<(), Errno> { + ) -> Result<(FileAttr, std::time::Duration), Errno> { warn!( "[Not Implemented] setattr(ino: {:#x?}, mode: {:?}, uid: {:?}, \ gid: {:?}, size: {:?}, fh: {:?}, flags: {:?})", @@ -400,22 +355,26 @@ pub trait Filesystem { } /// Read symbolic link. - fn readlink(&mut self, _req: RequestMeta, ino: u64) -> Result<(), Errno> { + /// The method should return `Ok(Bytes<'a>)` with the link target (an OS native string), + /// or `Err(Errno)` otherwise. + /// `Bytes` allows for returning data under various ownership models potentially avoiding a copy. + fn readlink<'a>(&mut self, req: RequestMeta, ino: u64) -> Result, Errno> { warn!("[Not Implemented] readlink(ino: {:#x?})", ino); Err(Errno::ENOSYS) } /// Create file node. /// Create a regular file, character device, block device, fifo or socket node. + /// The method should return `Ok(Entry)` with the new entry's attributes, or `Err(Errno)` otherwise. fn mknod( &mut self, - _req: RequestMeta, + req: RequestMeta, parent: u64, - name: &OsStr, + name: OsString, mode: u32, umask: u32, rdev: u32, - ) -> Result<(), Errno> { + ) -> Result { warn!( "[Not Implemented] mknod(parent: {:#x?}, name: {:?}, mode: {}, \ umask: {:#x?}, rdev: {})", @@ -425,14 +384,15 @@ pub trait Filesystem { } /// Create a directory. + /// The method should return `Ok(Entry)` with the new directory's attributes, or `Err(Errno)` otherwise. fn mkdir( &mut self, - _req: RequestMeta, + req: RequestMeta, parent: u64, - name: &OsStr, + name: OsString, mode: u32, umask: u32, - ) -> Result<(), Errno> { + ) -> Result { warn!( "[Not Implemented] mkdir(parent: {:#x?}, name: {:?}, mode: {}, umask: {:#x?})", parent, name, mode, umask @@ -441,7 +401,8 @@ pub trait Filesystem { } /// Remove a file. - fn unlink(&mut self, _req: RequestMeta, parent: u64, name: &OsStr) -> Result<(), Errno> { + /// The method should return `Ok(())` on success, or `Err(Errno)` otherwise. + fn unlink(&mut self, req: RequestMeta, parent: u64, name: OsString) -> Result<(), Errno> { warn!( "[Not Implemented] unlink(parent: {:#x?}, name: {:?})", parent, name, @@ -450,7 +411,8 @@ pub trait Filesystem { } /// Remove a directory. - fn rmdir(&mut self, _req: RequestMeta, parent: u64, name: &OsStr) -> Result<(), Errno> { + /// The method should return `Ok(())` on success, or `Err(Errno)` otherwise. + fn rmdir(&mut self, req: RequestMeta, parent: u64, name: OsString) -> Result<(), Errno> { warn!( "[Not Implemented] rmdir(parent: {:#x?}, name: {:?})", parent, name, @@ -459,30 +421,33 @@ pub trait Filesystem { } /// Create a symbolic link. + /// The method should return `Ok(Entry)` with the new link's attributes, or `Err(Errno)` otherwise. fn symlink( &mut self, _req: RequestMeta, parent: u64, - link_name: &OsStr, - target: &Path, - ) -> Result<(), Errno> { + link_name: OsString, + target: PathBuf, + ) -> Result { warn!( "[Not Implemented] symlink(parent: {:#x?}, link_name: {:?}, target: {:?})", parent, link_name, target, ); - Err(Errno::EPERM) + Err(Errno::EPERM) // why isn't this ENOSYS? } /// Rename a file. + /// The method should return `Ok(())` on success, or `Err(Errno)` otherwise. + /// `flags` may be `RENAME_EXCHANGE` or `RENAME_NOREPLACE`. fn rename( &mut self, _req: RequestMeta, parent: u64, - name: &OsStr, + name: OsString, newparent: u64, - newname: &OsStr, + newname: OsString, flags: u32, - ) -> Result<(), Errno> { + ) -> Result<(), Errno> { warn!( "[Not Implemented] rename(parent: {:#x?}, name: {:?}, newparent: {:#x?}, \ newname: {:?}, flags: {})", @@ -492,30 +457,32 @@ pub trait Filesystem { } /// Create a hard link. + /// The method should return `Ok(Entry)` with the new link's attributes, or `Err(Errno)` otherwise. fn link( &mut self, _req: RequestMeta, ino: u64, newparent: u64, - newname: &OsStr, - ) -> Result<(), Errno> { + newname: OsString, + ) -> Result { warn!( "[Not Implemented] link(ino: {:#x?}, newparent: {:#x?}, newname: {:?})", ino, newparent, newname ); - Err(Errno::EPERM) + Err(Errno::EPERM) // why isn't this ENOSYS? } /// 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, + /// available in `flags`. Filesystem may store an arbitrary file handle (pointer, index, + /// etc) in `Open.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 for more details. - fn open(&mut self, _req: RequestMeta, _ino: u64, _flags: i32) -> Result<(), Errno> { - warn!("[Not Implemented] open(ino: {:#x?}, flags: {})", _ino, _flags); + /// anything in `Open.fh`. There are also some flags (direct_io, keep_cache) which the + /// filesystem may set in `Open.flags`, to change the way the file is opened. See fuse_file_info + /// structure in `` for more details. + /// The method should return `Ok(Open)` on success, or `Err(Errno)` otherwise. + fn open(&mut self, req: RequestMeta, ino: u64, flags: i32) -> Result { + warn!("[Not Implemented] open(ino: {:#x?}, flags: {})", ino, flags); Err(Errno::ENOSYS) } @@ -551,17 +518,18 @@ pub trait Filesystem { /// 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. fh will contain the value set by the open method, or + /// value of this operation. `fh` will contain the value set by the open method, or /// will be undefined if the open method didn't set any value. + /// The method should return `Ok(u32)` with the number of bytes written, or `Err(Errno)` otherwise. /// - /// write_flags: 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 cachin - /// is disabled - /// flags: these are the file flags, such as O_SYNC. Only supported with ABI >= 7.9 - /// lock_owner: only supported with ABI >= 7.9 + /// `write_flags`: 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. + /// `flags`: these are the file flags, such as O_SYNC. Only supported with ABI >= 7.9. + /// `lock_owner`: only supported with ABI >= 7.9. fn write( &mut self, - _req: RequestMeta, + req: RequestMeta, ino: u64, fh: u64, offset: i64, @@ -569,7 +537,7 @@ pub trait Filesystem { write_flags: u32, flags: i32, lock_owner: Option, - ) -> Result<(), Errno> { + ) -> Result { warn!( "[Not Implemented] write(ino: {:#x?}, fh: {}, offset: {}, data.len(): {}, \ write_flags: {:#x?}, flags: {:#x?}, lock_owner: {:?})", @@ -634,15 +602,17 @@ pub trait Filesystem { } /// Open a directory. - /// Filesystem may store an arbitrary file handle (pointer, index, etc) in fh, and + /// Filesystem may store an arbitrary file handle (pointer, index, etc) in `Open.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 + /// anything in `Open.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. - fn opendir(&mut self, _req: RequestMeta, _ino: u64, _flags: i32) -> Result<(), Errno> { - warn!("[Not Implemented] open(ino: {:#x?}, flags: {})", _ino, _flags); + /// The method should return `Ok(Open)` on success, or `Err(Errno)` otherwise. + fn opendir(&mut self, req: RequestMeta, ino: u64, flags: i32) -> Result { + warn!("[Not Implemented] open(ino: {:#x?}, flags: {})", ino, flags); Err(Errno::ENOSYS) + // TODO: Open{0,0} } /// Read directory. @@ -684,9 +654,10 @@ pub trait Filesystem { } /// Release an open directory. - /// For every opendir call there will be exactly one releasedir call. fh will + /// For every opendir call there will be exactly one releasedir call. `fh` will /// contain the value set by the opendir method, or will be undefined if the /// opendir method didn't set any value. + /// The method should return `Ok(())` on success, or `Err(Errno)` otherwise. fn releasedir( &mut self, _req: RequestMeta, @@ -716,13 +687,15 @@ pub trait Filesystem { } /// Get file system statistics. - fn statfs(&mut self, _req: RequestMeta, _ino: u64) -> Result<(), Errno> { - warn!("[Not Implemented] statfs(ino: {:#x?})", _ino); + /// The method should return `Ok(Statfs)` with the filesystem statistics, or `Err(Errno)` otherwise. + fn statfs(&mut self, req: RequestMeta, ino: u64) -> Result { + warn!("[Not Implemented] statfs(ino: {:#x?})", ino); Err(Errno::ENOSYS) - // TODO: default implementation {0, 0, 0, 0, 0, 512, 255, 0} + // TODO: Statfs{0, 0, 0, 0, 0, 512, 255, 0} } /// Set an extended attribute. + /// The method should return `Ok(())` on success, or `Err(Errno)` otherwise. fn setxattr( &mut self, _req: RequestMeta, @@ -789,23 +762,24 @@ pub trait Filesystem { /// Create and open a file. /// If the file does not exist, first create it with the specified mode, and then - /// open it. You can use any open flags in the flags parameter except O_NOCTTY. + /// open it. You can use any open flags in the `flags` parameter except `O_NOCTTY`. /// The filesystem can store any type of file handle (such as a pointer or index) - /// in fh, which can then be used across all subsequent file operations including + /// in `Open.fh`, which can then be used across all subsequent file operations including /// read, write, flush, release, and fsync. Additionally, the filesystem may set - /// certain flags like direct_io and keep_cache to change the way the file is - /// opened. See fuse_file_info structure in for more details. If + /// certain flags like `direct_io` and `keep_cache` in `Open.flags` to change the way the file is + /// opened. See `fuse_file_info` structure in `` 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. + /// 2.6.15, the `mknod()` and `open()` methods will be called instead. + /// The method should return `Ok((Entry, Open))` with the new entry's attributes and open file information, or `Err(Errno)` otherwise. fn create( &mut self, - _req: RequestMeta, + req: RequestMeta, parent: u64, - name: &OsStr, + name: OsString, mode: u32, umask: u32, flags: i32, - ) -> Result<(), Errno> { + ) -> Result<(Entry,Open), Errno> { warn!( "[Not Implemented] create(parent: {:#x?}, name: {:?}, mode: {}, umask: {:#x?}, \ flags: {:#x?})", @@ -815,9 +789,10 @@ pub trait Filesystem { } /// Test for a POSIX file lock. + /// The method should return `Ok(Lock)` with the lock information, or `Err(Errno)` otherwise. fn getlk( &mut self, - _req: RequestMeta, + req: RequestMeta, ino: u64, fh: u64, lock_owner: u64, @@ -825,7 +800,7 @@ pub trait Filesystem { end: u64, typ: i32, pid: u32, - ) -> Result<(), Errno> { + ) -> Result { warn!( "[Not Implemented] getlk(ino: {:#x?}, fh: {}, lock_owner: {}, start: {}, \ end: {}, typ: {}, pid: {})", @@ -863,8 +838,9 @@ pub trait Filesystem { /// 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 - fn bmap(&mut self, _req: RequestMeta, ino: u64, blocksize: u32, idx: u64) -> Result<(), Errno> { + /// with the 'blkdev' option. + /// The method should return `Ok(u64)` with the device block index, or `Err(Errno)` otherwise. + fn bmap(&mut self, req: RequestMeta, ino: u64, blocksize: u32, idx: u64) -> Result { warn!( "[Not Implemented] bmap(ino: {:#x?}, blocksize: {}, idx: {})", ino, blocksize, idx, @@ -872,7 +848,9 @@ pub trait Filesystem { Err(Errno::ENOSYS) } - /// control device + /// Control device. + /// + #[cfg(feature = "abi-7-11")] fn ioctl( &mut self, _req: RequestMeta, @@ -896,7 +874,8 @@ pub trait Filesystem { Err(Errno::ENOSYS) } - /// Poll for events + /// Poll for events. + /// The method should return `Ok(u32)` with the poll events, or `Err(Errno)` otherwise. #[cfg(feature = "abi-7-11")] fn poll( &mut self, @@ -906,7 +885,7 @@ pub trait Filesystem { ph: PollHandle, events: u32, flags: u32, - ) -> Result<(), Errno> { + ) -> Result { warn!( "[Not Implemented] poll(ino: {:#x?}, fh: {}, ph: {:?}, events: {}, flags: {})", ino, fh, ph, events, flags @@ -914,10 +893,11 @@ pub trait Filesystem { Err(Errno::ENOSYS) } - /// Preallocate or deallocate space to a file + /// Preallocate or deallocate space to a file. + /// The method should return `Ok(())` on success, or `Err(Errno)` otherwise. fn fallocate( &mut self, - _req: RequestMeta, + req: RequestMeta, ino: u64, fh: u64, offset: i64, @@ -932,15 +912,17 @@ pub trait Filesystem { Err(Errno::ENOSYS) } - /// Reposition read/write file offset + /// Reposition read/write file offset. + /// The method should return `Ok(i64)` with the new offset, or `Err(Errno)` otherwise. + #[cfg(feature = "abi-7-24")] fn lseek( &mut self, - _req: RequestMeta, + req: RequestMeta, ino: u64, fh: u64, offset: i64, whence: i32, - ) -> Result<(), Errno> { + ) -> Result { warn!( "[Not Implemented] lseek(ino: {:#x?}, fh: {}, offset: {}, whence: {})", ino, fh, offset, whence @@ -948,10 +930,11 @@ pub trait Filesystem { Err(Errno::ENOSYS) } - /// Copy the specified range from the source inode to the destination inode + /// Copy the specified range from the source inode to the destination inode. + /// The method should return `Ok(u32)` with the number of bytes copied, or `Err(Errno)` otherwise. fn copy_file_range( &mut self, - _req: RequestMeta, + req: RequestMeta, ino_in: u64, fh_in: u64, offset_in: i64, @@ -960,7 +943,7 @@ pub trait Filesystem { offset_out: i64, len: u64, flags: u32, - ) -> Result<(), Errno> { + ) -> Result { warn!( "[Not Implemented] copy_file_range(ino_in: {:#x?}, fh_in: {}, \ offset_in: {}, ino_out: {:#x?}, fh_out: {}, offset_out: {}, \ @@ -970,24 +953,26 @@ pub trait Filesystem { Err(Errno::ENOSYS) } - /// macOS only: Rename the volume. Set fuse_init_out.flags during init to - /// FUSE_VOL_RENAME to enable + /// macOS only: Rename the volume. Set `fuse_init_out.flags` during init to + /// `FUSE_VOL_RENAME` to enable. + /// The method should return `Ok(())` on success, or `Err(Errno)` otherwise. #[cfg(target_os = "macos")] - fn setvolname(&mut self, _req: RequestMeta, name: &OsStr) -> Result<(), Errno> { + fn setvolname(&mut self, req: RequestMeta, name: OsStr) -> Result<(), Errno> { warn!("[Not Implemented] setvolname(name: {:?})", name); Err(Errno::ENOSYS) } - /// macOS only (undocumented) + /// macOS only (undocumented). + /// The method should return `Ok(())` on success, or `Err(Errno)` otherwise. #[cfg(target_os = "macos")] fn exchange( &mut self, - _req: RequestMeta, + req: RequestMeta, parent: u64, - name: &OsStr, + name: OsString, newparent: u64, - newname: &OsStr, - options: u64, + newname: OsString, + options: u64 ) -> Result<(), Errno> { warn!( "[Not Implemented] exchange(parent: {:#x?}, name: {:?}, newparent: {:#x?}, \ @@ -997,10 +982,11 @@ pub trait Filesystem { Err(Errno::ENOSYS) } - /// macOS only: Query extended times (bkuptime and crtime). Set fuse_init_out.flags - /// during init to FUSE_XTIMES to enable + /// macOS only: Query extended times (bkuptime and crtime). Set `fuse_init_out.flags` + /// during init to `FUSE_XTIMES` to enable. + /// The method should return `Ok(XTimes)` with the extended times, or `Err(Errno)` otherwise. #[cfg(target_os = "macos")] - fn getxtimes(&mut self, _req: RequestMeta, ino: u64) -> Result<(), Errno> { + fn getxtimes(&mut self, req: RequestMeta, ino: u64) -> Result { warn!("[Not Implemented] getxtimes(ino: {:#x?})", ino); Err(Errno::ENOSYS) } diff --git a/src/ll/reply.rs b/src/ll/reply.rs index cada68ca..93377ac3 100644 --- a/src/ll/reply.rs +++ b/src/ll/reply.rs @@ -79,18 +79,18 @@ impl<'a> Response<'a> { pub(crate) fn new_entry( ino: INodeNo, generation: Generation, - attr: &Attr, + file_ttl: Duration, + attr: &crate::FileAttr, attr_ttl: Duration, - entry_ttl: Duration, ) -> Self { let d = abi::fuse_entry_out { nodeid: ino.into(), generation: generation.into(), - entry_valid: entry_ttl.as_secs(), + entry_valid: file_ttl.as_secs(), attr_valid: attr_ttl.as_secs(), - entry_valid_nsec: entry_ttl.subsec_nanos(), + entry_valid_nsec: file_ttl.subsec_nanos(), attr_valid_nsec: attr_ttl.subsec_nanos(), - attr: attr.attr, + attr: fuse_attr_from_attr(&attr), }; Self::from_struct(d.as_bytes()) } @@ -611,7 +611,7 @@ mod test { flags: 0x99, blksize: 0xbb, }; - let r = Response::new_entry(INodeNo(0x11), Generation(0xaa), &attr.into(), ttl, ttl); + let r = Response::new_entry(INodeNo(0x11), Generation(0xaa), ttl, &attr.into(), ttl); assert_eq!( r.with_iovec(RequestId(0xdeadbeef), ioslice_to_vec), expected diff --git a/src/reply.rs b/src/reply.rs index b6433268..f4e5de68 100644 --- a/src/reply.rs +++ b/src/reply.rs @@ -15,12 +15,10 @@ use std::fmt; use std::io::IoSlice; #[cfg(feature = "abi-7-40")] use std::os::fd::BorrowedFd; -use std::time::Duration; - -#[cfg(target_os = "macos")] -use std::time::SystemTime; - -use crate::{FileAttr, FileType}; +use std::time::{Duration, SystemTime}; +use zerocopy::IntoBytes; +#[cfg(feature = "serializable")] +use serde::{Deserialize, Serialize}; /// Generic reply callback to send data pub(crate) trait ReplySender: Send + Sync + Unpin + 'static { @@ -94,6 +92,134 @@ impl Drop for ReplyHandler { } } +/// File types +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))] +pub enum FileType { + /// Named pipe (S_IFIFO) + NamedPipe, + /// Character device (S_IFCHR) + CharDevice, + /// Block device (S_IFBLK) + BlockDevice, + /// Directory (S_IFDIR) + Directory, + /// Regular file (S_IFREG) + RegularFile, + /// Symbolic link (S_IFLNK) + Symlink, + /// Unix domain socket (S_IFSOCK) + Socket, +} + +/// File attributes +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))] +pub struct FileAttr { + /// Unique number for this file + pub ino: u64, + /// Size in bytes + pub size: u64, + /// Size in blocks + pub blocks: u64, + /// Time of last access + pub atime: SystemTime, + /// Time of last modification + pub mtime: SystemTime, + /// Time of last change + pub ctime: SystemTime, + /// Time of creation (macOS only) + pub crtime: SystemTime, + /// Kind of file (directory, file, pipe, etc) + pub kind: FileType, + /// Permissions + pub perm: u16, + /// Number of hard links + pub nlink: u32, + /// User id + pub uid: u32, + /// Group id + pub gid: u32, + /// Rdev + pub rdev: u32, + /// Block size + pub blksize: u32, + /// Flags (macOS only, see chflags(2)) + pub flags: u32, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))] +/// An entry in the kernel's file cache +pub struct Entry { + /// file inode number + pub ino: u64, + /// file generation number + pub generation: Option, + /// duration to cache file identity + pub file_ttl: Duration, + /// file attributes + pub attr: FileAttr, + /// duration to cache file attributes + pub attr_ttl: Duration, +} + +#[derive(Debug, Clone)] //TODO #[derive(Copy)] +#[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))] +/// Open file handle response data +pub struct Open { + /// File handle for the opened file + pub fh: u64, + /// Flags for the opened file + pub flags: u32 +} + +#[cfg(target_os = "macos")] +#[derive(Debug)] +/// Xtimes response data +pub struct XTimes { + /// Backup time + pub bkuptime: SystemTime, + /// Creation time + pub crtime: SystemTime +} + +#[derive(Copy, Clone, Debug)] +/// Statfs response data +pub struct Statfs { + /// Total blocks (in units of frsize) + pub blocks: u64, + /// Free blocks + pub bfree: u64, + /// Free blocks for unprivileged users + pub bavail: u64, + /// Total inodes + pub files: u64, + /// Free inodes + pub ffree: u64, + /// Filesystem block size + pub bsize: u32, + /// Maximum filename length + pub namelen: u32, + /// Fundamental file system block size + pub frsize: u32 +} + +#[derive(Copy, Clone, Debug)] +/// File lock response data +pub struct Lock { + /// start of locked byte range + pub start: u64, + /// end of locked byte range + pub end: u64, + // NOTE: lock field is defined as u32 in fuse_kernel.h in libfuse. However, it is treated as signed + // TODO enum {F_RDLCK, F_WRLCK, F_UNLCK} + /// kind of lock (read and/or write) + pub typ: i32, + /// PID of process blocking our lock + pub pid: u32, +} + // // Methods to reply to a request for each kind of data // @@ -157,7 +283,16 @@ impl ReplyHandler { } /// Reply to a request with a file entry - pub fn entry(self) {} + pub fn entry(self, entry: Entry) { + self.send_ll(&ll::Response::new_entry( + ll::INodeNo(entry.ino), + ll::Generation(entry.generation.unwrap_or(1)), + entry.file_ttl, + &entry.attr.into(), + entry.attr_ttl, + + )); + } /// Reply to a request with a file attributes pub fn attr(self, attr: FileAttr, ttl: Duration) { @@ -166,10 +301,16 @@ impl ReplyHandler { #[cfg(target_os = "macos")] /// Reply to a request with xtimes attributes - pub fn xtimes(self) {} + pub fn xtimes(self, xtimes: XTimes) { + self.send_ll(&ll::Response::new_xtimes(xtimes.bkuptime, xtimes.crtime)) + } /// Reply to a request with a newly opened file handle - pub fn opened(self) {} + pub fn opened(self, open: Open) { + #[cfg(feature = "abi-7-40")] + assert_eq!(open.flags & FOPEN_PASSTHROUGH, 0); + self.send_ll(&ll::Response::new_open(ll::FileHandle(open.fh), open.flags, 0)) + } /// Reply to a request with the number of bytes written pub fn written(self, size: u32) { @@ -179,25 +320,35 @@ impl ReplyHandler { /// Reply to a statfs request pub fn statfs( self, - blocks: u64, - bfree: u64, - bavail: u64, - files: u64, - ffree: u64, - bsize: u32, - namelen: u32, - frsize: u32, + statfs: Statfs ) { self.send_ll(&ll::Response::new_statfs( - blocks, bfree, bavail, files, ffree, bsize, namelen, frsize, + statfs.blocks, statfs.bfree, statfs.bavail, statfs.files, statfs.ffree, statfs.bsize, statfs.namelen, statfs.frsize, )) } /// Reply to a request with a newle created file entry and its newly open file handle - pub fn created(self) {} + pub fn created(self, entry: Entry, open: Open) { + #[cfg(feature = "abi-7-40")] + assert_eq!(open.flags & FOPEN_PASSTHROUGH, 0); + self.send_ll(&ll::Response::new_create( + &entry.file_ttl, + &entry.attr.into(), + ll::Generation(entry.generation.unwrap_or(1)), + ll::FileHandle(open.fh), + open.flags, + 0, + )) + } /// Reply to a request with a file lock - pub fn locked(self) {} + pub fn locked(self, lock: Lock) { + self.send_ll(&ll::Response::new_lock(&ll::Lock{ + range: (lock.start, lock.end), + typ: lock.typ, + pid: lock.pid, + })) + } /// Reply to a request with a bmap pub fn bmap(self, block: u64) { @@ -348,6 +499,63 @@ mod test { #[test] fn reply_entry() { + // prepare the expected message + let mut expected = Vec::new(); + expected.extend_from_slice(&[ + // header + 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00, + // ino + 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // generation + 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // ttl + 0x65, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x65, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x21, 0x43, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, + // file attributes + 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // file times (s) + 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]); + #[cfg(target_os = "macos")] + expected.extend_from_slice(&[ + // crtime (s) + 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]); + expected.extend_from_slice(&[ + // file times (ns) + 0x78, 0x56, 0x00, 0x00, + 0x78, 0x56, 0x00, 0x00, + 0x78, 0x56, 0x00, 0x00, + ]); + #[cfg(target_os = "macos")] + expected.extend_from_slice([ + // crtime (ns) + 0x78, 0x56, 0x00, 0x00, + ]); + expected.extend_from_slice(&[ + // file permissions + 0xa4, 0x81, 0x00, 0x00, + // file owners + 0x55, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, + 0x77, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, + ]); + #[cfg(target_os = "macos")] + expected.extend_from_slice(&[ + // flags + 0x99, 0x00, 0x00, 0x00, + ]); + #[cfg(feature = "abi-7-9")] + expected.extend_from_slice(&[ + // blksize + 0xbb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + ]); + // correct the header expected[0] = (expected.len()) as u8; // test reply will be compare with the expected message let sender = AssertSender { expected }; @@ -374,6 +582,13 @@ mod test { }; // send the test reply replyhandler.entry( + Entry{ + ino: attr.ino, + generation: Some(0xaa), + file_ttl: ttl, + attr: attr, + attr_ttl: ttl, + } ); } @@ -447,6 +662,10 @@ mod test { let replyhandler: ReplyHandler = ReplyHandler::new(0xdeadbeef, sender); let time = UNIX_EPOCH + Duration::new(0x1234, 0x5678); replyhandler.xtimes( + XTimes{ + bkuptime: time, + crtime: time, + } ); } @@ -461,6 +680,7 @@ mod test { }; let replyhandler: ReplyHandler = ReplyHandler::new(0xdeadbeef, sender); replyhandler.opened( + Open { fh: 0x1122, flags: 0x33} ); } @@ -491,6 +711,16 @@ mod test { }; let replyhandler: ReplyHandler = ReplyHandler::new(0xdeadbeef, sender); replyhandler.statfs( + Statfs{ + blocks: 0x11, + bfree: 0x22, + bavail: 0x33, + files: 0x44, + ffree: 0x55, + bsize: 0x66, + namelen: 0x77, + frsize: 0x88 + } ); } @@ -523,7 +753,7 @@ mod test { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0xa4, 0x81, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0xbb, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ] }; @@ -558,6 +788,17 @@ mod test { blksize: 0xdd, }; replyhandler.created( + Entry { + ino: attr.ino, + generation: Some(0xaa), + file_ttl: ttl, + attr: attr, + attr_ttl: ttl, + }, + Open { + fh: 0xbb, + flags: 0x0f + } ); } @@ -572,6 +813,12 @@ mod test { }; let replyhandler: ReplyHandler = ReplyHandler::new(0xdeadbeef, sender); replyhandler.locked( + Lock { + start: 0x11, + end: 0x22, + typ: 0x33, + pid: 0x44 + } ); } diff --git a/src/request.rs b/src/request.rs index 95cddca4..ec79de7c 100644 --- a/src/request.rs +++ b/src/request.rs @@ -13,7 +13,7 @@ use std::convert::TryInto; use crate::channel::ChannelSender; use crate::ll::Request as _; -use crate::reply::{ReplyHandler, ReplySender}; +use crate::reply::{ReplyHandler}; use crate::session::{Session, SessionACL}; use crate::Filesystem; #[cfg(feature = "abi-7-11")] From b57b04acd3788505b4785854341d580f9e57a232 Mon Sep 17 00:00:00 2001 From: Richard Lawrence Date: Wed, 16 Jul 2025 15:30:29 -0500 Subject: [PATCH 04/19] Many small changes for style and optimization. Elide unneeded lifetimes. Remove _ from Filesystem parameters (warning has been silenced). Prefer borrowed types for Filesystem parameters. Fixed init bug in ll::request::tests. Useful derives for library-public types. Refactored redundant access logic. Removed publicity from some previously public structs. --- src/channel.rs | 8 +-- src/lib.rs | 164 +++++++++++++++++++++++++-------------------- src/ll/fuse_abi.rs | 3 + src/ll/mod.rs | 3 +- src/ll/request.rs | 111 +++++++++++++++++++++--------- src/request.rs | 123 +++++++++++++--------------------- 6 files changed, 222 insertions(+), 190 deletions(-) diff --git a/src/channel.rs b/src/channel.rs index fbc4ded3..11614224 100644 --- a/src/channel.rs +++ b/src/channel.rs @@ -16,7 +16,7 @@ use crate::reply::ReplySender; /// A raw communication channel to the FUSE kernel driver #[derive(Debug)] -pub struct Channel(Arc); +pub(crate) struct Channel(Arc); impl AsFd for Channel { fn as_fd(&self) -> BorrowedFd<'_> { @@ -33,7 +33,7 @@ impl Channel { } /// Receives data up to the capacity of the given buffer (can block). - pub fn receive(&self, buffer: &mut [u8]) -> io::Result { + pub(crate) fn receive(&self, buffer: &mut [u8]) -> io::Result { let rc = unsafe { libc::read( self.0.as_raw_fd(), @@ -51,7 +51,7 @@ impl Channel { /// Returns a sender object for this channel. The sender object can be /// used to send to the channel. Multiple sender objects can be used /// and they can safely be sent to other threads. - pub fn sender(&self) -> ChannelSender { + pub(crate) fn sender(&self) -> ChannelSender { // Since write/writev syscalls are threadsafe, we can simply create // a sender by using the same file and use it in other threads. ChannelSender(self.0.clone()) @@ -59,7 +59,7 @@ impl Channel { } #[derive(Clone, Debug)] -pub struct ChannelSender(Arc); +pub(crate) struct ChannelSender(Arc); impl ReplySender for ChannelSender { fn send(&self, bufs: &[io::IoSlice<'_>]) -> io::Result<()> { diff --git a/src/lib.rs b/src/lib.rs index 5ee4cc99..ec223ded 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,9 +14,9 @@ use serde::de::value::F64Deserializer; #[cfg(feature = "serializable")] use serde::{Deserialize, Serialize}; */ -use std::ffi::{OsStr, OsString}; +use std::ffi::OsStr; use std::io; -use std::path::{Path, PathBuf}; +use std::path::Path; use std::time::{Duration, SystemTime}; use std::{convert::AsRef, io::ErrorKind}; @@ -67,7 +67,8 @@ const INIT_FLAGS: u64 = FUSE_ASYNC_READ | FUSE_BIG_WRITES; const INIT_FLAGS: u64 = FUSE_ASYNC_READ | FUSE_CASE_INSENSITIVE | FUSE_VOL_RENAME | FUSE_XTIMES; // TODO: Add FUSE_EXPORT_SUPPORT and FUSE_BIG_WRITES (requires ABI 7.10) -const fn default_init_flags(#[allow(unused_variables)] capabilities: u64) -> u64 { +#[allow(unused_variables)] +const fn default_init_flags(capabilities: u64) -> u64 { #[cfg(not(feature = "abi-7-28"))] { INIT_FLAGS @@ -291,7 +292,7 @@ pub trait Filesystem { /// Look up a directory entry by name and get its attributes. /// The method should return `Ok(Entry)` if the entry is found, or `Err(Errno)` otherwise. - fn lookup(&mut self, req: RequestMeta, parent: u64, name: OsString) -> Result { + fn lookup(&mut self, req: RequestMeta, parent: u64, name: &Path) -> Result { warn!( "[Not Implemented] lookup(parent: {:#x?}, name {:?})", parent, name @@ -331,20 +332,20 @@ pub trait Filesystem { /// The method should return `Ok(Attr)` with the updated file attributes, or `Err(Errno)` otherwise. fn setattr( &mut self, - _req: RequestMeta, + req: RequestMeta, ino: u64, mode: Option, uid: Option, gid: Option, size: Option, - _atime: Option, - _mtime: Option, - _ctime: Option, + atime: Option, + mtime: Option, + ctime: Option, fh: Option, - _crtime: Option, - _chgtime: Option, - _bkuptime: Option, - flags: Option, + crtime: Option, + chgtime: Option, + bkuptime: Option, + flags: Option ) -> Result<(FileAttr, std::time::Duration), Errno> { warn!( "[Not Implemented] setattr(ino: {:#x?}, mode: {:?}, uid: {:?}, \ @@ -370,7 +371,7 @@ pub trait Filesystem { &mut self, req: RequestMeta, parent: u64, - name: OsString, + name: &Path, mode: u32, umask: u32, rdev: u32, @@ -389,7 +390,7 @@ pub trait Filesystem { &mut self, req: RequestMeta, parent: u64, - name: OsString, + name: &Path, mode: u32, umask: u32, ) -> Result { @@ -402,7 +403,7 @@ pub trait Filesystem { /// Remove a file. /// The method should return `Ok(())` on success, or `Err(Errno)` otherwise. - fn unlink(&mut self, req: RequestMeta, parent: u64, name: OsString) -> Result<(), Errno> { + fn unlink(&mut self, req: RequestMeta, parent: u64, name: &Path) -> Result<(), Errno> { warn!( "[Not Implemented] unlink(parent: {:#x?}, name: {:?})", parent, name, @@ -412,7 +413,7 @@ pub trait Filesystem { /// Remove a directory. /// The method should return `Ok(())` on success, or `Err(Errno)` otherwise. - fn rmdir(&mut self, req: RequestMeta, parent: u64, name: OsString) -> Result<(), Errno> { + fn rmdir(&mut self, req: RequestMeta, parent: u64, name: &Path) -> Result<(), Errno> { warn!( "[Not Implemented] rmdir(parent: {:#x?}, name: {:?})", parent, name, @@ -424,10 +425,10 @@ pub trait Filesystem { /// The method should return `Ok(Entry)` with the new link's attributes, or `Err(Errno)` otherwise. fn symlink( &mut self, - _req: RequestMeta, + req: RequestMeta, parent: u64, - link_name: OsString, - target: PathBuf, + link_name: &Path, + target: &Path, ) -> Result { warn!( "[Not Implemented] symlink(parent: {:#x?}, link_name: {:?}, target: {:?})", @@ -441,11 +442,11 @@ pub trait Filesystem { /// `flags` may be `RENAME_EXCHANGE` or `RENAME_NOREPLACE`. fn rename( &mut self, - _req: RequestMeta, + req: RequestMeta, parent: u64, - name: OsString, + name: &Path, newparent: u64, - newname: OsString, + newname: &Path, flags: u32, ) -> Result<(), Errno> { warn!( @@ -460,10 +461,10 @@ pub trait Filesystem { /// The method should return `Ok(Entry)` with the new link's attributes, or `Err(Errno)` otherwise. fn link( &mut self, - _req: RequestMeta, + req: RequestMeta, ino: u64, newparent: u64, - newname: OsString, + newname: &Path, ) -> Result { warn!( "[Not Implemented] link(ino: {:#x?}, newparent: {:#x?}, newname: {:?})", @@ -487,18 +488,18 @@ pub trait Filesystem { } /// Read data. - /// Read should send exactly the number of bytes requested except on EOF or error, + /// Read should return 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. fh will contain the value set by the open method, or will be undefined + /// operation. `fh` will contain the value set by the open method, or will be undefined /// if the open method didn't set any value. /// - /// flags: these are the file flags, such as O_SYNC. Only supported with ABI >= 7.9 - /// lock_owner: only supported with ABI >= 7.9 - fn read( + /// `flags`: these are the file flags, such as O_SYNC. Only supported with ABI >= 7.9 + /// `lock_owner`: only supported with ABI >= 7.9 + fn read<'a>( &mut self, - _req: RequestMeta, + req: RequestMeta, ino: u64, fh: u64, offset: i64, @@ -553,16 +554,17 @@ pub trait Filesystem { } /// Flush method. - /// This is called on each close() of the opened file. Since file descriptors can + /// 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. fh will contain the value set by the + /// writes, or that it will be called at all. `fh` will contain the value set by the /// open method, or will be undefined if the open method didn't set any value. /// 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 + /// 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'. - fn flush(&mut self, _req: RequestMeta, ino: u64, fh: u64, lock_owner: u64) -> Result<(), Errno> { + /// operations (setlk, getlk) it should remove all locks belonging to `lock_owner`. + /// The method should return `Ok(())` on success, or `Err(Errno)` otherwise. + fn flush(&mut self, req: RequestMeta, ino: u64, fh: u64, lock_owner: u64) -> Result<(), Errno> { warn!( "[Not Implemented] flush(ino: {:#x?}, fh: {}, lock_owner: {:?})", ino, fh, lock_owner @@ -573,27 +575,29 @@ pub trait Filesystem { /// 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. fh will contain the value set by the open method, or will be undefined - /// if the open method didn't set any value. flags will contain the same flags as for + /// call there will be exactly one release call. The filesystem may return an + /// error, but error values are not returned to `close()` or `munmap()` which triggered + /// the release. `fh` will contain the value set by the open method, or will be undefined + /// if the open method didn't set any value. `flags` will contain the same flags as for /// open. + /// The method should return `Ok(())` on success, or `Err(Errno)` otherwise. fn release( &mut self, - _req: RequestMeta, - _ino: u64, - _fh: u64, - _flags: i32, - _lock_owner: Option, - _flush: bool, - ) -> Result<(), Errno> { + req: RequestMeta, + ino: u64, + fh: u64, + flags: i32, + lock_owner: Option, + flush: bool, + ) -> Result<(), Errno> { Ok(()) } /// Synchronize file contents. - /// If the datasync parameter is non-zero, then only the user data should be flushed, + /// If the `datasync` parameter is non-zero, then only the user data should be flushed, /// not the meta data. - fn fsync(&mut self, _req: RequestMeta, ino: u64, fh: u64, datasync: bool) -> Result<(), Errno> { + /// The method should return `Ok(())` on success, or `Err(Errno)` otherwise. + fn fsync(&mut self, req: RequestMeta, ino: u64, fh: u64, datasync: bool) -> Result<(), Errno> { warn!( "[Not Implemented] fsync(ino: {:#x?}, fh: {}, datasync: {})", ino, fh, datasync @@ -622,7 +626,7 @@ pub trait Filesystem { /// didn't set any value. fn readdir( &mut self, - _req: RequestMeta, + req: RequestMeta, ino: u64, fh: u64, offset: i64, @@ -641,7 +645,7 @@ pub trait Filesystem { /// didn't set any value. fn readdirplus( &mut self, - _req: RequestMeta, + req: RequestMeta, ino: u64, fh: u64, offset: i64, @@ -660,21 +664,22 @@ pub trait Filesystem { /// The method should return `Ok(())` on success, or `Err(Errno)` otherwise. fn releasedir( &mut self, - _req: RequestMeta, - _ino: u64, - _fh: u64, - _flags: i32, + req: RequestMeta, + ino: u64, + fh: u64, + flags: i32, ) -> Result<(), Errno> { Ok(()) } /// Synchronize directory contents. - /// If the datasync parameter is set, then only the directory contents should - /// be flushed, not the meta data. fh will contain the value set by the opendir + /// If the `datasync` parameter is set, then only the directory contents should + /// be flushed, not the meta data. `fh` will contain the value set by the opendir /// method, or will be undefined if the opendir method didn't set any value. + /// The method should return `Ok(())` on success, or `Err(Errno)` otherwise. fn fsyncdir( &mut self, - _req: RequestMeta, + req: RequestMeta, ino: u64, fh: u64, datasync: bool, @@ -698,10 +703,10 @@ pub trait Filesystem { /// The method should return `Ok(())` on success, or `Err(Errno)` otherwise. fn setxattr( &mut self, - _req: RequestMeta, + req: RequestMeta, ino: u64, name: &OsStr, - _value: &[u8], + value: &[u8], flags: i32, position: u32, ) -> Result<(), Errno> { @@ -718,7 +723,7 @@ pub trait Filesystem { /// `reply.error(ERANGE)` if it doesn't. fn getxattr( &mut self, - _req: RequestMeta, + req: RequestMeta, ino: u64, name: &OsStr, size: u32, @@ -731,10 +736,13 @@ pub trait Filesystem { } /// List extended attribute names. - /// If `size` is 0, the size of the value should be sent with `reply.size()`. - /// If `size` is not 0, and the value fits, send it with `reply.data()`, or - /// `reply.error(ERANGE)` if it doesn't. - fn listxattr(&mut self, _req: RequestMeta, ino: u64, size: u32) -> Result<(), Errno> { + /// If the list does not fit, `Err(Errno::ERANGE)` should be returned. + fn listxattr( + &mut self, + req: RequestMeta, + ino: u64, + size: u32, + ) -> Result<(), Errno> { warn!( "[Not Implemented] listxattr(ino: {:#x?}, size: {})", ino, size @@ -743,7 +751,13 @@ pub trait Filesystem { } /// Remove an extended attribute. - fn removexattr(&mut self, _req: RequestMeta, ino: u64, name: &OsStr) -> Result<(), Errno> { + /// The method should return `Ok(())` on success, or `Err(Errno)` otherwise. + fn removexattr( + &mut self, + req: RequestMeta, + ino: u64, + name: &OsStr, + ) -> Result<(), Errno> { warn!( "[Not Implemented] removexattr(ino: {:#x?}, name: {:?})", ino, name @@ -752,10 +766,11 @@ pub trait Filesystem { } /// Check file access permissions. - /// This will be called for the access() system call. If the 'default_permissions' + /// This will be called for the `access()` system call. If the 'default_permissions' /// mount option is given, this method is not called. This method is not called /// under Linux kernel versions 2.4.x - fn access(&mut self, _req: RequestMeta, ino: u64, mask: i32) -> Result<(), Errno> { + /// The method should return `Ok(())` if access is allowed, or `Err(Errno)` otherwise. + fn access(&mut self, req: RequestMeta, ino: u64, mask: i32) -> Result<(), Errno> { warn!("[Not Implemented] access(ino: {:#x?}, mask: {})", ino, mask); Err(Errno::ENOSYS) } @@ -775,7 +790,7 @@ pub trait Filesystem { &mut self, req: RequestMeta, parent: u64, - name: OsString, + name: &Path, mode: u32, umask: u32, flags: i32, @@ -810,15 +825,16 @@ pub trait Filesystem { } /// Acquire, modify or release a POSIX file lock. - /// For POSIX threads (NPTL) there's a 1-1 relation between pid and owner, but + /// 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 + /// `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. + /// The method should return `Ok(())` on success, or `Err(Errno)` otherwise. fn setlk( &mut self, - _req: RequestMeta, + req: RequestMeta, ino: u64, fh: u64, lock_owner: u64, @@ -969,9 +985,9 @@ pub trait Filesystem { &mut self, req: RequestMeta, parent: u64, - name: OsString, + name: &Path, newparent: u64, - newname: OsString, + newname: &Path, options: u64 ) -> Result<(), Errno> { warn!( diff --git a/src/ll/fuse_abi.rs b/src/ll/fuse_abi.rs index 33f4dbe9..d5e9c01c 100644 --- a/src/ll/fuse_abi.rs +++ b/src/ll/fuse_abi.rs @@ -20,6 +20,9 @@ #![warn(missing_debug_implementations)] #![allow(missing_docs)] +// TODO: fix all these non camel case types +#![allow(non_camel_case_types)] + #[cfg(feature = "abi-7-9")] use crate::consts::{FATTR_ATIME_NOW, FATTR_MTIME_NOW}; diff --git a/src/ll/mod.rs b/src/ll/mod.rs index 58855cc3..834c2bab 100644 --- a/src/ll/mod.rs +++ b/src/ll/mod.rs @@ -37,7 +37,7 @@ macro_rules! errno { } /// Represents an error code to be returned to the caller -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub struct Errno(pub NonZeroI32); impl Errno { /// Operation not permitted @@ -225,6 +225,7 @@ impl Errno { #[cfg(not(target_os = "linux"))] pub const NO_XATTR: Errno = Self::ENOATTR; + /// Use this to try to convert an integer error code into a fuser Errno pub fn from_i32(err: i32) -> Errno { err.try_into().ok().map(Errno).unwrap_or(Errno::EIO) } diff --git a/src/ll/request.rs b/src/ll/request.rs index cbbd87a5..819d6c07 100644 --- a/src/ll/request.rs +++ b/src/ll/request.rs @@ -5,7 +5,7 @@ use super::fuse_abi::{fuse_in_header, fuse_opcode, InvalidOpcodeError}; -use super::{fuse_abi as abi, Errno}; +use super::{fuse_abi as abi}; #[cfg(feature = "serializable")] use serde::{Deserialize, Serialize}; use std::{convert::TryFrom, fmt::Display, path::Path}; @@ -275,7 +275,6 @@ mod op { path::Path, time::{Duration, SystemTime}, }; - use zerocopy::IntoBytes; /// Look up a directory entry by name and get its attributes. /// @@ -311,7 +310,7 @@ mod op { arg: &'a fuse_forget_in, } impl_request!(Forget<'_>); - impl<'a> Forget<'a> { + impl Forget<'_> { /// The number of lookups previously performed on this inode pub fn nlookup(&self) -> u64 { self.arg.nlookup @@ -329,7 +328,7 @@ mod op { impl_request!(GetAttr<'_>); #[cfg(feature = "abi-7-9")] - impl<'a> GetAttr<'a> { + impl GetAttr<'_> { pub fn file_handle(&self) -> Option { if self.arg.getattr_flags & crate::FUSE_GETATTR_FH != 0 { Some(FileHandle(self.arg.fh)) @@ -346,7 +345,7 @@ mod op { arg: &'a fuse_setattr_in, } impl_request!(SetAttr<'_>); - impl<'a> SetAttr<'a> { + impl SetAttr<'_> { pub fn mode(&self) -> Option { match self.arg.valid & FATTR_MODE { 0 => None, @@ -466,7 +465,7 @@ mod op { // TODO: Why does *set*attr want to have an attr response? } - impl<'a> Display for SetAttr<'a> { + impl Display for SetAttr<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, @@ -650,7 +649,7 @@ mod op { arg: &'a fuse_open_in, } impl_request!(Open<'_>); - impl<'a> Open<'a> { + impl Open<'_> { pub fn flags(&self) -> i32 { self.arg.flags } @@ -669,7 +668,7 @@ mod op { arg: &'a fuse_read_in, } impl_request!(Read<'_>); - impl<'a> Read<'a> { + impl Read<'_> { /// The value set by the [Open] method. pub fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) @@ -773,7 +772,7 @@ mod op { arg: &'a fuse_release_in, } impl_request!(Release<'_>); - impl<'a> Release<'a> { + impl Release<'_> { pub fn flush(&self) -> bool { self.arg.release_flags & FUSE_RELEASE_FLUSH != 0 } @@ -805,7 +804,7 @@ mod op { arg: &'a fuse_fsync_in, } impl_request!(FSync<'a>); - impl<'a> FSync<'a> { + impl FSync<'_> { /// The value set by the [Open] method. pub fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) @@ -905,7 +904,7 @@ mod op { arg: &'a fuse_getxattr_in, } impl_request!(ListXAttr<'a>); - impl<'a> ListXAttr<'a> { + 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. @@ -950,7 +949,7 @@ mod op { arg: &'a fuse_flush_in, } impl_request!(Flush<'a>); - impl<'a> Flush<'a> { + impl Flush<'_> { /// The value set by the open method pub fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) @@ -966,7 +965,7 @@ mod op { arg: &'a fuse_init_in, } impl_request!(Init<'a>); - impl<'a> Init<'a> { + impl Init<'_> { pub fn capabilities(&self) -> u64 { #[cfg(feature = "abi-7-36")] if self.arg.flags & (FUSE_INIT_EXT as u32) != 0 { @@ -998,7 +997,7 @@ mod op { arg: &'a fuse_open_in, } impl_request!(OpenDir<'a>); - impl<'a> OpenDir<'a> { + impl OpenDir<'_> { /// Flags as passed to open pub fn flags(&self) -> i32 { self.arg.flags @@ -1012,7 +1011,7 @@ mod op { arg: &'a fuse_read_in, } impl_request!(ReadDir<'a>); - impl<'a> ReadDir<'a> { + impl ReadDir<'_> { /// The value set by the [OpenDir] method. pub fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) @@ -1034,7 +1033,7 @@ mod op { arg: &'a fuse_release_in, } impl_request!(ReleaseDir<'a>); - impl<'a> ReleaseDir<'a> { + impl ReleaseDir<'_> { /// The value set by the [OpenDir] method. pub fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) @@ -1065,7 +1064,7 @@ mod op { arg: &'a fuse_fsync_in, } impl_request!(FSyncDir<'a>); - impl<'a> FSyncDir<'a> { + impl FSyncDir<'_> { /// The value set by the [OpenDir] method. See [FileHandle]. pub fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) @@ -1083,7 +1082,7 @@ mod op { arg: &'a fuse_lk_in, } impl_request!(GetLk<'a>); - impl<'a> GetLk<'a> { + impl GetLk<'_> { /// The value set by the [Open] method. See [FileHandle]. pub fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) @@ -1110,7 +1109,7 @@ mod op { arg: &'a fuse_lk_in, } impl_request!(SetLk<'a>); - impl<'a> SetLk<'a> { + impl SetLk<'_> { /// The value set by the [Open] method. See [FileHandle]. pub fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) @@ -1128,7 +1127,7 @@ mod op { arg: &'a fuse_lk_in, } impl_request!(SetLkW<'a>); - impl<'a> SetLkW<'a> { + impl SetLkW<'_> { /// The value set by the [Open] method. See [FileHandle]. pub fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) @@ -1151,7 +1150,7 @@ mod op { arg: &'a fuse_access_in, } impl_request!(Access<'a>); - impl<'a> Access<'a> { + impl Access<'_> { pub fn mask(&self) -> i32 { self.arg.mask } @@ -1237,7 +1236,7 @@ mod op { arg: &'a fuse_interrupt_in, } impl_request!(Interrupt<'a>); - impl<'a> Interrupt<'a> { + impl Interrupt<'_> { pub fn unique(&self) -> RequestId { RequestId(self.arg.unique) } @@ -1252,7 +1251,7 @@ mod op { arg: &'a fuse_bmap_in, } impl_request!(BMap<'a>); - impl<'a> BMap<'a> { + impl BMap<'_> { pub fn block_size(&self) -> u32 { self.arg.blocksize } @@ -1278,7 +1277,7 @@ mod op { #[cfg(feature = "abi-7-11")] impl_request!(IoCtl<'a>); #[cfg(feature = "abi-7-11")] - impl<'a> IoCtl<'a> { + impl IoCtl<'_> { pub fn in_data(&self) -> &[u8] { &self.data[..self.arg.in_size as usize] } @@ -1312,7 +1311,7 @@ mod op { #[cfg(feature = "abi-7-11")] impl_request!(Poll<'a>); #[cfg(feature = "abi-7-11")] - impl<'a> Poll<'a> { + impl Poll<'_> { /// The value set by the [Open] method. See [FileHandle]. pub fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) @@ -1398,7 +1397,7 @@ mod op { #[cfg(feature = "abi-7-19")] impl_request!(FAllocate<'a>); #[cfg(feature = "abi-7-19")] - impl<'a> FAllocate<'a> { + impl FAllocate<'_> { /// The value set by the [Open] method. See [FileHandle]. pub fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) @@ -1427,7 +1426,7 @@ mod op { #[cfg(feature = "abi-7-21")] impl_request!(ReadDirPlus<'a>); #[cfg(feature = "abi-7-21")] - impl<'a> ReadDirPlus<'a> { + impl ReadDirPlus<'_> { /// The value set by the [Open] method. See [FileHandle]. pub fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) @@ -1491,7 +1490,7 @@ mod op { #[cfg(feature = "abi-7-24")] impl_request!(Lseek<'a>); #[cfg(feature = "abi-7-24")] - impl<'a> Lseek<'a> { + impl Lseek<'_> { /// The value set by the [Open] method. See [FileHandle]. pub fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) @@ -1523,7 +1522,7 @@ mod op { #[cfg(feature = "abi-7-28")] impl_request!(CopyFileRange<'a>); #[cfg(feature = "abi-7-28")] - impl<'a> CopyFileRange<'a> { + impl CopyFileRange<'_> { /// File and offset to copy data from pub fn src(&self) -> CopyFileRangeFile { CopyFileRangeFile { @@ -1941,7 +1940,7 @@ pub enum Operation<'a> { CuseInit(CuseInit<'a>), } -impl<'a> fmt::Display for Operation<'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()), @@ -2157,7 +2156,7 @@ impl<'a> AnyRequest<'a> { } } -impl<'a> fmt::Display for AnyRequest<'a> { +impl fmt::Display for AnyRequest<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if let Ok(op) = self.operation() { write!( @@ -2204,8 +2203,9 @@ mod tests { use super::*; use std::ffi::OsStr; - #[cfg(target_endian = "big")] + #[cfg(all(target_endian = "big", not(feature = "abi-7-36")))] const INIT_REQUEST: AlignedData<[u8; 56]> = AlignedData([ + // decimal 56 == hex 0x38 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x1a, // len, opcode 0xde, 0xad, 0xbe, 0xef, 0xba, 0xad, 0xd0, 0x0d, // unique 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, // nodeid @@ -2215,8 +2215,9 @@ mod tests { 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, // max_readahead, flags ]); - #[cfg(target_endian = "little")] + #[cfg(all(target_endian = "little", not(feature = "abi-7-36")))] const INIT_REQUEST: AlignedData<[u8; 56]> = AlignedData([ + // decimal 56 == hex 0x38 0x38, 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 @@ -2226,6 +2227,44 @@ mod tests { 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // max_readahead, flags ]); + #[cfg(all(target_endian = "big", feature = "abi-7-36"))] + 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(all(target_endian = "little", feature = "abi-7-36"))] + 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 @@ -2271,7 +2310,10 @@ mod tests { #[test] fn short_read() { match AnyRequest::try_from(&INIT_REQUEST[..48]) { + #[cfg(not(feature = "abi-7-36"))] Err(RequestError::ShortRead(48, 56)) => (), + #[cfg(feature = "abi-7-36")] + Err(RequestError::ShortRead(48, 104)) => (), _ => panic!("Unexpected request parsing result"), } } @@ -2279,7 +2321,10 @@ mod tests { #[test] fn init() { let req = AnyRequest::try_from(&INIT_REQUEST[..]).unwrap(); + #[cfg(not(feature="abi-7-36"))] assert_eq!(req.header.len, 56); + #[cfg(feature="abi-7-36")] + 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)); diff --git a/src/request.rs b/src/request.rs index ec79de7c..319dc5e1 100644 --- a/src/request.rs +++ b/src/request.rs @@ -82,73 +82,40 @@ impl<'a> Request<'a> { return; } let op = op_result.unwrap(); + // Implement allow_root & access check for auto_unmount - if (se.allowed == SessionACL::RootAndOwner + let access_denied = if (se.allowed == SessionACL::RootAndOwner && self.request.uid() != se.session_owner && self.request.uid() != 0) || (se.allowed == SessionACL::Owner && self.request.uid() != se.session_owner) { - #[cfg(feature = "abi-7-21")] - { - match op { - // Only allow operations that the kernel may issue without a uid set - ll::Operation::Init(_) - | ll::Operation::Destroy(_) - | ll::Operation::Read(_) - | ll::Operation::ReadDir(_) - | ll::Operation::ReadDirPlus(_) - | ll::Operation::BatchForget(_) - | ll::Operation::Forget(_) - | ll::Operation::Write(_) - | ll::Operation::FSync(_) - | ll::Operation::FSyncDir(_) - | ll::Operation::Release(_) - | ll::Operation::ReleaseDir(_) => {} - _ => { - self.replyhandler.error(Errno::EACCES); - } - } - } - #[cfg(all(feature = "abi-7-16", not(feature = "abi-7-21")))] - { - match op { - // Only allow operations that the kernel may issue without a uid set - ll::Operation::Init(_) - | ll::Operation::Destroy(_) - | ll::Operation::Read(_) - | ll::Operation::ReadDir(_) - | ll::Operation::BatchForget(_) - | ll::Operation::Forget(_) - | ll::Operation::Write(_) - | ll::Operation::FSync(_) - | ll::Operation::FSyncDir(_) - | ll::Operation::Release(_) - | ll::Operation::ReleaseDir(_) => {} - _ => { - self.replyhandler.error(Errno::EACCES); - } - } - } - #[cfg(not(feature = "abi-7-16"))] - { - match op { - // Only allow operations that the kernel may issue without a uid set - ll::Operation::Init(_) - | ll::Operation::Destroy(_) - | ll::Operation::Read(_) - | ll::Operation::ReadDir(_) - | ll::Operation::Forget(_) - | ll::Operation::Write(_) - | ll::Operation::FSync(_) - | ll::Operation::FSyncDir(_) - | ll::Operation::Release(_) - | ll::Operation::ReleaseDir(_) => {} - _ => { - self.replyhandler.error(Errno::EACCES); - } - } - } + match op { + // Only allow operations that the kernel may issue without a uid set + ll::Operation::Init(_) + | ll::Operation::Destroy(_) + | ll::Operation::Read(_) + | ll::Operation::ReadDir(_) + | ll::Operation::Forget(_) + | ll::Operation::Write(_) + | ll::Operation::FSync(_) + | ll::Operation::FSyncDir(_) + | ll::Operation::Release(_) + | ll::Operation::ReleaseDir(_) => false, + #[cfg(feature = "abi-7-16")] + ll::Operation::BatchForget(_) => false, + #[cfg(feature = "abi-7-21")] + ll::Operation::ReadDirPlus(_) => false, + _ => true, + } + } else { + false + }; + + if access_denied { + self.replyhandler.error(Errno::EACCES); + return; } + match op { // Filesystem initialization ll::Operation::Init(x) => { @@ -194,7 +161,7 @@ impl<'a> Request<'a> { self.replyhandler.error(Errno::EIO); } // Filesystem destroyed - ll::Operation::Destroy(x) => { + ll::Operation::Destroy(_x) => { se.filesystem.destroy(); se.destroyed = true; self.replyhandler.ok(); @@ -214,7 +181,7 @@ impl<'a> Request<'a> { let response = se.filesystem.lookup( self.meta, self.request.nodeid().into(), - x.name().as_ref(), + x.name() ); match response { Ok(entry) => { @@ -300,7 +267,7 @@ impl<'a> Request<'a> { let response = se.filesystem.mknod( self.meta, self.request.nodeid().into(), - x.name().as_ref(), + x.name(), x.mode(), x.umask(), x.rdev() @@ -318,7 +285,7 @@ impl<'a> Request<'a> { let response = se.filesystem.mkdir( self.meta, self.request.nodeid().into(), - x.name().as_ref(), + x.name(), x.mode(), x.umask() ); @@ -335,7 +302,7 @@ impl<'a> Request<'a> { let response = se.filesystem.unlink( self.meta, self.request.nodeid().into(), - x.name().into() + x.name() ); match response { Ok(())=> { @@ -350,7 +317,7 @@ impl<'a> Request<'a> { let response = se.filesystem.rmdir( self.meta, self.request.nodeid().into(), - x.name().as_ref(), + x.name() ); match response { Ok(())=> { @@ -365,8 +332,8 @@ impl<'a> Request<'a> { let response = se.filesystem.symlink( self.meta, self.request.nodeid().into(), - x.link_name().as_ref(), - Path::new(x.target()), + x.link_name(), + x.target() ); match response { Ok(entry)=> { @@ -381,9 +348,9 @@ impl<'a> Request<'a> { let response = se.filesystem.rename( self.meta, self.request.nodeid().into(), - x.src().name.as_ref(), + x.src().name, x.dest().dir.into(), - x.dest().name.as_ref(), + x.dest().name, 0 ); match response { @@ -400,7 +367,7 @@ impl<'a> Request<'a> { self.meta, x.inode_no().into(), self.request.nodeid().into(), - x.dest().name.as_ref(), + x.dest().name ); match response { Ok(entry)=> { @@ -646,7 +613,7 @@ impl<'a> Request<'a> { let response = se.filesystem.removexattr( self.meta, self.request.nodeid().into(), - x.name(), + x.name() ); match response { Ok(())=> { @@ -676,7 +643,7 @@ impl<'a> Request<'a> { let response = se.filesystem.create( self.meta, self.request.nodeid().into(), - x.name().as_ref(), + x.name(), x.mode(), x.umask(), x.flags() @@ -867,9 +834,9 @@ impl<'a> Request<'a> { let response = se.filesystem.rename( self.meta, x.from().dir.into(), - x.from().name.as_ref(), + x.from().name, x.to().dir.into(), - x.to().name.as_ref(), + x.to().name, x.flags() ); match response { @@ -958,9 +925,9 @@ impl<'a> Request<'a> { let response = se.filesystem.exchange( self.meta, x.from().dir.into(), - x.from().name.into(), + x.from().name, x.to().dir.into(), - x.to().name.into(), + x.to().name, x.options() ); match response { From cebd72b46f9b1343f8c3286054321d70aa9a111f Mon Sep 17 00:00:00 2001 From: Richard Lawrence Date: Wed, 16 Jul 2025 15:45:27 -0500 Subject: [PATCH 05/19] New! Generic enum Container that allows the API to accept unsized data under a wide variety of ownership models. Filesystem methods that previously replied with &[u8] or similar now return Bytes, which is short for Container. --- src/container/core.rs | 192 +++++++++++++++++++++ src/container/mod.rs | 12 ++ src/container/specialized.rs | 101 +++++++++++ src/container/tests.rs | 326 +++++++++++++++++++++++++++++++++++ src/container/utils.rs | 139 +++++++++++++++ src/lib.rs | 38 ++-- src/reply.rs | 126 ++++++++++++-- src/request.rs | 2 +- 8 files changed, 908 insertions(+), 28 deletions(-) create mode 100644 src/container/core.rs create mode 100644 src/container/mod.rs create mode 100644 src/container/specialized.rs create mode 100644 src/container/tests.rs create mode 100644 src/container/utils.rs diff --git a/src/container/core.rs b/src/container/core.rs new file mode 100644 index 00000000..a5eb9390 --- /dev/null +++ b/src/container/core.rs @@ -0,0 +1,192 @@ +use std::sync::{Arc,Mutex,RwLock}; +use std::rc::Rc; +use std::borrow::Cow; +use std::cell::RefCell; +use std::ops::Deref; +use std::sync::{MutexGuard, RwLockReadGuard, PoisonError}; +use std::cell::{Ref}; + +#[derive(Debug)] +/// A generic container enum that provides flexible ownership models for unsized data types. +/// If there is a borrow, its lifetime is 'a (most likely 'static). +pub enum Container<'a, T: Clone> { + // ----- Simple Variants ----- + /// No data. + Empty, + /// An owned, fixed-size, heap-allocated slice. + Box(Box<[T]>), + /// An owned, growable, heap-allocated vector. + Vec(Vec), + /// A borrowed slice. + Ref(&'a [T]), + /// A borrowed slice with copy-on-write. + Cow(Cow<'a, [T]>), + /// A reusable, fixed-size slice. + Rc(Rc<[T]>), + /// A shared, fixed-size slice. + Arc(Arc<[T]>), + // ----- Compount Variants ----- + #[allow(clippy::borrowed_box)] + /// A borrowed, fixed-size, heap-allocated slice. + RefBox(&'a Box<[T]>), + /// A borrowed, immutable, heap-allocated vector. + RefVec(&'a Vec), + /// A borrowed, fixed-size, heap-allocated vector, with copy-on-write. + CowBox(Cow<'a, Box<[T]>>), + /// A borrowed, immutable, heap-allocated vactor, with copy-on-write. + CowVec(Cow<'a, Vec>), + /// A reusable, fixed-size, heap-allocated slice. + RcBox(Rc>), + /// A reusable, immutable, heap-allocated vector. + RcVec(Rc>), + /// A shared, fixed-size, heap-allocated slice. + ArcBox(Arc>), + /// A shared, immutable, heap-allocated vector. + ArcVec(Arc>), + // ----- Locking Variants ----- + /// A reusable, replaceable, heap-allocated slice. + RcRefCellBox(Rc>>), + /// A reusable, growable, heap-allocated vector. + RcRefCellVec(Rc>>), + /// A shared, fixed-size, replacable, heap-allocated slice. + ArcMutexBox(Arc>>), + /// A shared, growable, heap-allocated vector. + ArcMutexVec(Arc>>), + /// A shared, fixed-size, replacable, heap-allocated slide with multiple readers. + ArcRwLockBox(Arc>>), + /// A shared, growable, heap-allocated vector with multiple readers. + ArcRwLockVec(Arc>>), +} + +// ----- Borrow from a Container ----- + +#[derive(Debug)] +/// A value borrowed from a container with flexible ownership models for unsized data types. +pub enum Borrow<'a, T> { + // ----- Simple Variants ----- + /// No data. + Empty, + /// A borrowed reference to a slice. + Slice(&'a [T]), + // ----- Locking Variants ----- + /// A borrowed reference to a reusable, replaceable, heap-allocated slice. + RcRefCellBox(Ref<'a, Box<[T]>>), + /// A borrowed reference to a reusable, growable, heap-allocated vector. + RcRefCellVec(Ref<'a, Vec>), + /// A borrowed reference to a shared, fixed-size, replacable, heap-allocated slice. + ArcMutexBox(MutexGuard<'a, Box<[T]>>), + /// A borrowed reference to a shared, growable, heap-allocated vector. + ArcMutexVec(MutexGuard<'a, Vec>), + /// A borrowed reference to a shared, fixed-size, replacable, heap-allocated slide with multiple readers. + ArcRwLockBox(RwLockReadGuard<'a, Box<[T]>>), + /// A borrowed reference to a shared, growable, heap-allocated vector with multiple readers. + ArcRwLockVec(RwLockReadGuard<'a, Vec>), +} + +impl Deref for Borrow<'_, T> { + type Target = [T]; + + fn deref(&self) -> &Self::Target { + match self { + Borrow::Empty => &[], + Borrow::Slice(value) => value, + Borrow::RcRefCellBox(value) => value, + Borrow::RcRefCellVec(value) => value, + Borrow::ArcMutexBox(value) => value, + Borrow::ArcMutexVec(value) => value, + Borrow::ArcRwLockBox(value) => value, + Borrow::ArcRwLockVec(value) => value, + } + } +} + +// --- Container to Borrow --- +// --- Container to Reference --- + +#[derive(Debug, PartialEq)] +pub enum BorrowError { + Poisoned, +} + +impl From> for BorrowError { + fn from(_: PoisonError) -> Self { + BorrowError::Poisoned + } +} + +impl Container<'_, T> { + /// Borrows a slice-like immutable reference from the container. + /// Will attempt to gain access to a locking variant. + /// Returns an error if the source data is unavailable. + pub fn try_borrow(&self) -> Result, BorrowError> { + match self { + // ----- Simple Variants ----- + Container::Empty => Ok(Borrow::Empty), + Container::Box(value) => Ok(Borrow::Slice(value.as_ref())), + Container::Vec(value) => Ok(Borrow::Slice(value.as_ref())), + Container::Ref(value) => Ok(Borrow::Slice(value)), + Container::Cow(value) => Ok(Borrow::Slice(value.as_ref())), + Container::Rc(value) => Ok(Borrow::Slice(value.as_ref())), + Container::Arc(value) => Ok(Borrow::Slice(value.as_ref())), + // ----- Compound Variants ----- + Container::RefBox(value) => Ok(Borrow::Slice(value.as_ref())), + Container::RefVec(value) => Ok(Borrow::Slice(value.as_ref())), + Container::CowBox(value) => Ok(Borrow::Slice(value.as_ref().as_ref())), + Container::CowVec(value) => Ok(Borrow::Slice(value.as_ref().as_ref())), + Container::RcBox(value) => Ok(Borrow::Slice(value.as_ref().as_ref())), + Container::RcVec(value) => Ok(Borrow::Slice(value.as_ref().as_ref())), + Container::ArcBox(value) => Ok(Borrow::Slice(value.as_ref().as_ref())), + Container::ArcVec(value) => Ok(Borrow::Slice(value.as_ref().as_ref())), + // ----- Locking Variants ----- + Container::RcRefCellBox(value) => Ok(Borrow::RcRefCellBox(value.borrow())), + Container::RcRefCellVec(value) => Ok(Borrow::RcRefCellVec(value.borrow())), + Container::ArcMutexBox(value) => Ok(Borrow::ArcMutexBox(value.lock()?)), + Container::ArcMutexVec(value) => Ok(Borrow::ArcMutexVec(value.lock()?)), + Container::ArcRwLockBox(value) => Ok(Borrow::ArcRwLockBox(value.read()?)), + Container::ArcRwLockVec(value) => Ok(Borrow::ArcRwLockVec(value.read()?)), + } + } + + /// Borrows a slice-like immutable reference from the container. + /// Will attempt to gain access to a locking variant. + /// Panics if the source data is unavailable. + pub fn borrow(&self) -> Borrow<'_, T> { + self.try_borrow().unwrap() + } + + /// Returns a borrowed slice &[] from the container if it is an immutable variant. + /// Returns an error if the container is a locking variant. + /// Hint: use try_borrow() to handle locking variants. + pub fn try_as_ref(&self) -> Result<&[T], &str> { + match self { + // ----- Simple Variants ----- + Container::Empty => Ok(&[]), // the 'static zero-length slice of type T + Container::Box(value) => Ok(value.as_ref()), + Container::Vec(value) => Ok(value.as_ref()), + Container::Ref(value) => Ok(value), + Container::Cow(value) => Ok(value.as_ref()), + Container::Rc(value) => Ok(value.as_ref()), + Container::Arc(value) => Ok(value.as_ref()), + // ----- Compound Variants ----- + Container::RefBox(value) => Ok(value.as_ref()), + Container::RefVec(value) => Ok(value.as_ref()), + Container::CowBox(value) => Ok(value.as_ref().as_ref()), + Container::CowVec(value) => Ok(value.as_ref().as_ref()), + Container::RcBox(value) => Ok(value.as_ref().as_ref()), + Container::RcVec(value) => Ok(value.as_ref().as_ref()), + Container::ArcBox(value) => Ok(value.as_ref().as_ref()), + Container::ArcVec(value) => Ok(value.as_ref().as_ref()), + // ----- Locking Variants ----- + _ => Err("Attempted to get a reference from a locking container without a lock."), + } + } +} + +impl AsRef<[T]> for Container<'_, T> { + /// Returns a borrowed slice &[] from the container. + /// Will panic if the container is a locking variant. + /// Hint: use borrow() to handle locking variants. + fn as_ref(&self) -> &[T] { + self.try_as_ref().unwrap() + } +} \ No newline at end of file diff --git a/src/container/mod.rs b/src/container/mod.rs new file mode 100644 index 00000000..500b8f6c --- /dev/null +++ b/src/container/mod.rs @@ -0,0 +1,12 @@ +// This file orchestrates the container module. +// It declares submodules and re-exports public items. + +pub mod core; +pub mod specialized; +pub mod utils; + +pub use self::core::Container; +pub use self::core::Borrow; + +#[cfg(test)] +mod tests; diff --git a/src/container/specialized.rs b/src/container/specialized.rs new file mode 100644 index 00000000..88e9b5c2 --- /dev/null +++ b/src/container/specialized.rs @@ -0,0 +1,101 @@ +use super::core::{Container, Borrow, BorrowError}; +use std::ffi::{OsStr, OsString}; +#[cfg(unix)] +use std::os::unix::ffi::{OsStrExt, OsStringExt}; +use std::str::Utf8Error; +use std::string::FromUtf8Error; + +// --- u8 & string specializations --- + +#[derive(Debug, PartialEq)] +pub enum ToStringError { + Utf8(Utf8Error), + FromUtf8(FromUtf8Error), + Lock(BorrowError), +} + +impl From for ToStringError { + fn from(err: Utf8Error) -> Self { + ToStringError::Utf8(err) + } +} + +impl From for ToStringError { + fn from(err: FromUtf8Error) -> Self { + ToStringError::FromUtf8(err) + } +} + +impl From for ToStringError { + fn from(err: BorrowError) -> Self { + ToStringError::Lock(err) + } +} + +// TODO: Consider implementing std::error::Error for ToStringError +// impl std::error::Error for ToStringError { ... } + +// --- From String and OsString implementations for Container<'a, u8> --- + +impl From for Container<'_, u8> { + fn from(s: String) -> Self { + Container::Vec(s.into_bytes()) + } +} + +impl<'a> From<&'a str> for Container<'a, u8> { + fn from(s: &'a str) -> Self { + Container::Ref(s.as_bytes()) + } +} + +impl From for Container<'_, u8> { + fn from(s: OsString) -> Self { + Container::Vec(s.into_vec()) + } +} + +impl<'a> From<&'a OsStr> for Container<'a, u8> { + fn from(s: &'a OsStr) -> Self { + Container::Ref(s.as_bytes()) + } +} + +// --- String and OsString methods for Container<'a, u8> --- + +impl Container<'_, u8> { + /// Converts the bytes to an owned UTF-8 String. + /// Most likely, this is a copy. + /// + /// Returns an error if the byte slice is not valid UTF-8. + /// Returns an error if the source data is not available. + pub fn try_to_string(&self) -> Result { + let borrowed = self.try_borrow()?; + Ok(std::str::from_utf8(&borrowed)?.to_string()) + } + + /// Converts the container's content to an owned OsString. + /// Most likely, this is a copy. + /// + /// Returns an error if the source data is not available. + pub fn try_to_os_string(&self) -> Result { + let borrowed = self.try_borrow()?; + Ok(OsStr::from_bytes(&borrowed).to_os_string()) + } +} + +// --- String and OsString methods for Borrow<'a, u8> --- + +impl Borrow<'_, u8> { + /// Converts the borrowed bytes to an borrowed UTF-8 String. + /// + /// Returns an error if the byte slice is not valid UTF-8. + pub fn try_to_str(&self) -> Result<&str, ToStringError> { + Ok(std::str::from_utf8(self)?) + } + + /// Converts the borrowed bytes to a borrowed OsStr. + pub fn to_os_str(&self) -> &OsStr { + OsStr::from_bytes(self) + } +} \ No newline at end of file diff --git a/src/container/tests.rs b/src/container/tests.rs new file mode 100644 index 00000000..4f36db76 --- /dev/null +++ b/src/container/tests.rs @@ -0,0 +1,326 @@ +// This file contains unit tests for the Container enum +// and its trait implementations. + +use crate::container::Container; +use std::sync::Arc; + +#[cfg(test)] +mod u8 { + use super::*; + + #[test] + fn container_clone_slice() { + let data_vec: Vec = vec![1, 2, 3]; + let container1: Container<'_, u8> = Container::from(data_vec.clone()); // This will be Container::Vec + let container2 = container1.clone(); + assert_eq!(&*container1.borrow(), &*container2.borrow()); + match (&container1, &container2) { + (Container::Vec(v1), Container::Vec(v2)) => { // Expect Vec after cloning Vec + assert_ne!(v1.as_ptr(), v2.as_ptr(), "Cloned Vec should have different pointers"); + } + _ => panic!("Expected Vec variants for Container::from(Vec) clone"), + } + + let borrowed_slice: &[u8] = &[4,5,6]; + let c_borrowed1 : Container<'_, u8> = Container::Ref(borrowed_slice); + let c_borrowed2 = c_borrowed1.clone(); + assert_eq!(&*c_borrowed1.borrow(), &*c_borrowed2.borrow()); + assert_eq!((&*c_borrowed1.borrow()).as_ptr(), (&*c_borrowed2.borrow()).as_ptr()); + + + let shared_slice_arc: Arc<[u8]> = Arc::new([7,8,9]); + let c_shared1 : Container<'_, u8> = Container::Arc(shared_slice_arc.clone()); + let c_shared2 = c_shared1.clone(); + if let Container::Arc(s2_arc) = &c_shared2 { + assert!(Arc::ptr_eq(s2_arc, &shared_slice_arc)); + } else { panic!("Expected shared after clone"); } + } + + #[test] + fn container_deref_slice() { + let data: &[u8] = &[1, 2, 3, 4, 5]; + let container: Container<'_, u8> = Container::from(data); + let borrowed = container.borrow(); + assert_eq!(borrowed.len(), 5); // Test Deref to [u8] + assert_eq!(borrowed[0], 1); + } + + #[test] + fn borrowed_variants() { + let data_slice: &'static [u8] = &[1, 2, 3]; + let data_box: Box<[u8]> = Box::new([4, 5, 6]); + let data_vec: Vec = vec![7, 8, 9]; + + // Container::Ref + let container_ref = Container::Ref(data_slice); + assert_eq!(&*container_ref.borrow(), data_slice); + + // Container::Cow (borrowed) + let container_cow_borrowed = Container::Cow(std::borrow::Cow::Borrowed(data_slice)); + assert_eq!(&*container_cow_borrowed.borrow(), data_slice); + + // Container::RefBox + let container_ref_box = Container::RefBox(&data_box); + assert_eq!(&*container_ref_box.borrow(), data_box.as_ref()); + + // Container::RefVec + let container_ref_vec = Container::RefVec(&data_vec); + assert_eq!(&*container_ref_vec.borrow(), data_vec.as_slice()); + + // Container::CowBox (borrowed) + let container_cow_box_borrowed = Container::CowBox(std::borrow::Cow::Borrowed(&data_box)); + assert_eq!(&*container_cow_box_borrowed.borrow(), data_box.as_ref()); + + // Container::CowVec (borrowed) + let container_cow_vec_borrowed = Container::CowVec(std::borrow::Cow::Borrowed(&data_vec)); + assert_eq!(&*container_cow_vec_borrowed.borrow(), data_vec.as_slice()); + } + + #[test] + fn owned_variants() { + let data_box_orig: Box<[u8]> = Box::new([1, 2, 3]); + let data_vec_orig: Vec = vec![4, 5, 6]; + let data_rc_orig: std::rc::Rc<[u8]> = std::rc::Rc::new([7, 8, 9]); + let data_arc_orig: Arc<[u8]> = Arc::new([10, 11, 12]); + + // Container::Box + let container_box = Container::from(data_box_orig.clone()); + assert_eq!(&*container_box.borrow(), data_box_orig.as_ref()); + + // Container::Vec + let container_vec = Container::from(data_vec_orig.clone()); + assert_eq!(&*container_vec.borrow(), data_vec_orig.as_slice()); + + // Container::Cow (owned from Vec) + let container_cow_owned_vec = Container::Cow(std::borrow::Cow::Owned(data_vec_orig.clone())); + assert_eq!(&*container_cow_owned_vec.borrow(), data_vec_orig.as_slice()); + + // Container::Cow (owned from Box) - Note: Cow doesn't directly take Box<[T]>, so we convert to Vec first for owned Cow + let container_cow_owned_box_as_vec = Container::Cow(std::borrow::Cow::Owned(data_box_orig.to_vec())); + assert_eq!(&*container_cow_owned_box_as_vec.borrow(), data_box_orig.as_ref()); + + // Container::Rc + let container_rc = Container::from(data_rc_orig.clone()); + assert_eq!(&*container_rc.borrow(), data_rc_orig.as_ref()); + + // Container::Arc + let container_arc = Container::from(data_arc_orig.clone()); + assert_eq!(&*container_arc.borrow(), data_arc_orig.as_ref()); + + // Container::RcBox + let container_rc_box = Container::RcBox(std::rc::Rc::new(data_box_orig.clone())); + assert_eq!(&*container_rc_box.borrow(), data_box_orig.as_ref()); + + // Container::RcVec + let container_rc_vec = Container::RcVec(std::rc::Rc::new(data_vec_orig.clone())); + assert_eq!(&*container_rc_vec.borrow(), data_vec_orig.as_slice()); + + // Container::ArcBox + let container_arc_box = Container::ArcBox(Arc::new(data_box_orig.clone())); + assert_eq!(&*container_arc_box.borrow(), data_box_orig.as_ref()); + + // Container::ArcVec + let container_arc_vec = Container::ArcVec(Arc::new(data_vec_orig.clone())); + assert_eq!(&*container_arc_vec.borrow(), data_vec_orig.as_slice()); + } + + #[test] + fn locking_variants_read() { + let data_box_orig: Box<[u8]> = Box::new([1, 2, 3]); + let data_vec_orig: Vec = vec![4, 5, 6]; + + // Container::RcRefCellBox + let rc_ref_cell_box = std::rc::Rc::new(std::cell::RefCell::new(data_box_orig.clone())); + let container_rc_ref_cell_box: Container<'static, u8> = Container::from(rc_ref_cell_box.clone()); + match container_rc_ref_cell_box.try_borrow() { + Ok(guard) => assert_eq!(&*guard, data_box_orig.as_ref()), + Err(_) => panic!("Expected Ok for RcRefCellBox get_slice"), + }; + + // Container::RcRefCellVec + let rc_ref_cell_vec = std::rc::Rc::new(std::cell::RefCell::new(data_vec_orig.clone())); + let container_rc_ref_cell_vec: Container<'static, u8> = Container::from(rc_ref_cell_vec.clone()); + match container_rc_ref_cell_vec.try_borrow() { + Ok(guard) => assert_eq!(&*guard, data_vec_orig.as_slice()), + Err(_) => panic!("Expected Ok for RcRefCellVec get_slice"), + }; + + // Container::ArcMutexBox + let arc_mutex_box = Arc::new(std::sync::Mutex::new(data_box_orig.clone())); + let container_arc_mutex_box: Container<'static, u8> = Container::from(arc_mutex_box.clone()); + match container_arc_mutex_box.try_borrow() { + Ok(guard) => assert_eq!(&*guard, data_box_orig.as_ref()), + Err(_) => panic!("Expected Ok for ArcMutexBox get_slice"), + }; + + // Container::ArcMutexVec + let arc_mutex_vec = Arc::new(std::sync::Mutex::new(data_vec_orig.clone())); + let container_arc_mutex_vec: Container<'static, u8> = Container::from(arc_mutex_vec.clone()); + match container_arc_mutex_vec.try_borrow() { + Ok(guard) => assert_eq!(&*guard, data_vec_orig.as_slice()), + Err(_) => panic!("Expected Ok for ArcMutexVec get_slice"), + }; + + // Container::ArcRwLockBox + let arc_rw_lock_box = Arc::new(std::sync::RwLock::new(data_box_orig.clone())); + let container_arc_rw_lock_box: Container<'static, u8> = Container::from(arc_rw_lock_box.clone()); + match container_arc_rw_lock_box.try_borrow() { + Ok(guard) => assert_eq!(&*guard, data_box_orig.as_ref()), + Err(_) => panic!("Expected Ok for ArcRwLockBox get_slice"), + }; + + // Container::ArcRwLockVec + let arc_rw_lock_vec = Arc::new(std::sync::RwLock::new(data_vec_orig.clone())); + let container_arc_rw_lock_vec: Container<'static, u8> = Container::from(arc_rw_lock_vec.clone()); + match container_arc_rw_lock_vec.try_borrow() { + Ok(guard) => assert_eq!(&*guard, data_vec_orig.as_slice()), + Err(_) => panic!("Expected Ok for ArcRwLockVec get_slice"), + }; + } + + #[test] + fn locking_variants_try_as_ref_error() { + let data_box_orig: Box<[u8]> = Box::new([1, 2, 3]); + let data_vec_orig: Vec = vec![4, 5, 6]; + + // Container::RcRefCellBox + let rc_ref_cell_box = std::rc::Rc::new(std::cell::RefCell::new(data_box_orig.clone())); + let container_rc_ref_cell_box: Container<'static, u8> = Container::from(rc_ref_cell_box); + assert!(container_rc_ref_cell_box.try_as_ref().is_err()); + + // Container::RcRefCellVec + let rc_ref_cell_vec = std::rc::Rc::new(std::cell::RefCell::new(data_vec_orig.clone())); + let container_rc_ref_cell_vec: Container<'static, u8> = Container::from(rc_ref_cell_vec); + assert!(container_rc_ref_cell_vec.try_as_ref().is_err()); + + // Container::ArcMutexBox + let arc_mutex_box = Arc::new(std::sync::Mutex::new(data_box_orig.clone())); + let container_arc_mutex_box: Container<'static, u8> = Container::from(arc_mutex_box); + assert!(container_arc_mutex_box.try_as_ref().is_err()); + + // Container::ArcMutexVec + let arc_mutex_vec = Arc::new(std::sync::Mutex::new(data_vec_orig.clone())); + let container_arc_mutex_vec: Container<'static, u8> = Container::from(arc_mutex_vec); + assert!(container_arc_mutex_vec.try_as_ref().is_err()); + + // Container::ArcRwLockBox + let arc_rw_lock_box = Arc::new(std::sync::RwLock::new(data_box_orig.clone())); + let container_arc_rw_lock_box: Container<'static, u8> = Container::from(arc_rw_lock_box); + assert!(container_arc_rw_lock_box.try_as_ref().is_err()); + + // Container::ArcRwLockVec + let arc_rw_lock_vec = Arc::new(std::sync::RwLock::new(data_vec_orig.clone())); + let container_arc_rw_lock_vec: Container<'static, u8> = Container::from(arc_rw_lock_vec); + assert!(container_arc_rw_lock_vec.try_as_ref().is_err()); + } + + #[test] + fn as_ref() { + let data: &[u8] = &[1, 2, 3, 4, 5]; + let container: Container<'_, u8> = Container::from(data); + assert_eq!(container.as_ref(), data); + } +} + +#[cfg(test)] +mod string { + use crate::container::Container; + #[cfg(unix)] + use std::os::unix::ffi::OsStrExt; + use std::ffi::{OsStr, OsString}; + use std::sync::{Arc, Mutex, RwLock}; + use std::cell::RefCell; + use std::rc::Rc; + use std::borrow::Cow; + + // --- Test Data --- + const VALID_UTF8_STR: &str = "hello world"; + const VALID_UTF8_BYTES: &[u8] = b"hello world"; + // Invalid UTF-8 sequence (from Rust docs for from_utf8) + const INVALID_UTF8_BYTES: &[u8] = &[0xf0, 0x90, 0x80]; + + + // --- Tests for From --- + #[test] + fn from_string_etc_for_container_u8() { + let s = String::from(VALID_UTF8_STR); + let container: Container<'_, u8> = Container::from(s.clone()); + assert_eq!(&*container.borrow(), s.as_bytes()); + match container { + Container::Vec(_) => {} // Expected + _ => panic!("Expected Container::Vec from String"), + } + + let s_ref: &str = VALID_UTF8_STR; + let container: Container<'_, u8> = Container::from(s_ref); + assert_eq!(&*container.borrow(), s_ref.as_bytes()); + match container { + Container::Ref(_) => {} // Expected + _ => panic!("Expected Container::Ref from &str"), + } + + let os_string = OsString::from(VALID_UTF8_STR); + let container: Container<'_, u8> = Container::from(os_string.clone()); + assert_eq!(&*container.borrow(), OsStr::new(VALID_UTF8_STR).as_bytes()); + match container { + Container::Vec(_) => {} // Expected + _ => panic!("Expected Container::Vec from OsString"), + } + + let os_str_ref = OsStr::new(VALID_UTF8_STR); + let container: Container<'_, u8> = Container::from(os_str_ref); + assert_eq!(&*container.borrow(), os_str_ref.as_bytes()); + match container { + Container::Ref(_) => {} // Expected + _ => panic!("Expected Container::Ref from &OsStr"), + } + } + + // --- Helper to create containers for testing --- + fn create_non_locking_containers<'a>(bytes: &'a [u8]) -> Vec> { + vec![ + Container::Ref(bytes), + Container::Vec(bytes.to_vec()), + Container::Box(bytes.to_vec().into_boxed_slice()), + Container::Cow(Cow::Borrowed(bytes)), + Container::Cow(Cow::Owned(bytes.to_vec())), + Container::Arc(Arc::from(bytes)), + Container::Rc(Rc::from(bytes)), + ] + } + + fn create_locking_containers<'a>(bytes: &'a [u8]) -> Vec> { + vec![ + Container::ArcMutexVec(Arc::new(Mutex::new(bytes.to_vec()))), + Container::ArcRwLockVec(Arc::new(RwLock::new(bytes.to_vec()))), + Container::RcRefCellVec(Rc::new(RefCell::new(bytes.to_vec()))), + ] + } + + + #[test] + fn to_str_etc() { + // Valid UTF-8 + for container in create_non_locking_containers(VALID_UTF8_BYTES) { + assert_eq!(container.try_to_string().unwrap(), VALID_UTF8_STR); + } + for container in create_locking_containers(VALID_UTF8_BYTES) { + assert_eq!(container.try_to_string().unwrap(), VALID_UTF8_STR); + } + // Invalid UTF-8 + for container in create_non_locking_containers(INVALID_UTF8_BYTES) { + assert!(container.try_to_string().is_err()); + } + for container in create_locking_containers(INVALID_UTF8_BYTES) { + assert!(container.try_to_string().is_err()); + } + + // Valid Os bytes + for container in create_non_locking_containers(VALID_UTF8_BYTES) { + assert_eq!(container.try_to_os_string().unwrap(), OsString::from(VALID_UTF8_STR)); + } + for container in create_locking_containers(VALID_UTF8_BYTES) { + assert_eq!(container.try_to_os_string().unwrap(), OsString::from(VALID_UTF8_STR)); + } + } +} diff --git a/src/container/utils.rs b/src/container/utils.rs new file mode 100644 index 00000000..6e680d96 --- /dev/null +++ b/src/container/utils.rs @@ -0,0 +1,139 @@ +use super::core::Container; +use std::sync::{Arc,Mutex,RwLock}; +use std::rc::Rc; +use std::borrow::Cow; +use std::cell::RefCell; + +// --- From Raw --- + +// ----- Simple Variants ----- +impl From<(/* Empty */)> for Container<'_, T> {fn from(_: ()) -> Self {Container::Empty}} +impl From> for Container<'_, T> {fn from(value: Box<[T]>) -> Self {Container::Box(value)}} +impl From> for Container<'_, T> {fn from(value: Vec) -> Self {Container::Vec(value)}} +impl<'a, T: Clone> From<&'a [T]> for Container<'a, T> {fn from(value: &'a [T]) -> Self {Container::Ref(value)}} +impl<'a, T: Clone> From> for Container<'a, T> {fn from(value: Cow<'a, [T]>) -> Self {Container::Cow(value)}} +impl From> for Container<'_, T> {fn from(value: Rc<[T]>) -> Self {Container::Rc(value)}} +impl From> for Container<'_, T> {fn from(value: Arc<[T]>) -> Self {Container::Arc(value)}} +// ----- Compound Variants ----- +impl<'a, T: Clone> From<&'a Box<[T]>> for Container<'a, T> {fn from(value: &'a Box<[T]>) -> Self {Container::RefBox(value)}} +impl<'a, T: Clone> From<&'a Vec> for Container<'a, T> {fn from(value: &'a Vec) -> Self {Container::RefVec(value)}} +impl<'a, T: Clone> From>> for Container<'a, T> {fn from(value: Cow<'a, Box<[T]>>) -> Self {Container::CowBox(value)}} +impl<'a, T: Clone> From>> for Container<'a, T> {fn from(value: Cow<'a, Vec>) -> Self {Container::CowVec(value)}} +impl From>> for Container<'_, T> {fn from(value: Rc>) -> Self {Container::RcBox(value)}} +impl From>> for Container<'_, T> {fn from(value: Rc>) -> Self {Container::RcVec(value)}} +impl From>> for Container<'_, T> {fn from(value: Arc>) -> Self {Container::ArcBox(value)}} +impl From>> for Container<'_, T> {fn from(value: Arc>) -> Self {Container::ArcVec(value)}} +// ----- Locking Variants ----- +impl From>>> for Container<'_, T> {fn from(value: Rc>>) -> Self {Container::RcRefCellBox(value)}} +impl From>>> for Container<'_, T> {fn from(value: Rc>>) -> Self {Container::RcRefCellVec(value)}} +impl From>>> for Container<'_, T> {fn from(value: Arc>>) -> Self {Container::ArcMutexBox(value)}} +impl From>>> for Container<'_, T> {fn from(value: Arc>>) -> Self {Container::ArcMutexVec(value)}} +impl From>>> for Container<'_, T> {fn from(value: Arc>>) -> Self {Container::ArcRwLockBox(value)}} +impl From>>> for Container<'_, T> {fn from(value: Arc>>) -> Self {Container::ArcRwLockVec(value)}} + + +// --- Clone --- + +// Clone for Container where T is Clone +impl Clone for Container<'_, T> { + /// Creates a new container which wraps the same or identical underyling objects. + /// This method will do its best to avoid a deep copy, but some copies are unavoidable. + fn clone(&self) -> Self { + match self { + // ----- Simple Variants ----- + Container::Empty => Container::Empty, + Container::Box(value) => Container::Box(value.clone()), + Container::Vec(value) => Container::Vec(value.clone()), + Container::Ref(value) => Container::Ref(value), + Container::Cow(value) => Container::Cow(value.clone()), + Container::Rc(value) => Container::Rc(value.clone()), + Container::Arc(value) => Container::Arc(value.clone()), + // ----- Compound Variants ----- + Container::RefBox(value) => Container::RefBox(value), + Container::RefVec(value) => Container::RefVec(value), + Container::CowBox(value) => Container::CowBox(value.clone()), + Container::CowVec(value) => Container::CowVec(value.clone()), + Container::RcBox(value) => Container::RcBox(value.clone()), + Container::RcVec(value) => Container::RcVec(value.clone()), + Container::ArcBox(value) => Container::ArcBox(value.clone()), + Container::ArcVec(value) => Container::ArcVec(value.clone()), + // ----- Locking Variants ----- + Container::RcRefCellBox(value) => Container::RcRefCellBox(value.clone()), + Container::RcRefCellVec(value) => Container::RcRefCellVec(value.clone()), + Container::ArcMutexBox(value) => Container::ArcMutexBox(value.clone()), + Container::ArcMutexVec(value) => Container::ArcMutexVec(value.clone()), + Container::ArcRwLockBox(value) => Container::ArcRwLockBox(value.clone()), + Container::ArcRwLockVec(value) => Container::ArcRwLockVec(value.clone()), + } + } +} + +// --- Additional utility methods --- + +impl Container<'_, T> { + /// Returns the length of the container. + /// Returns zero if the source data is unavailable. + pub fn len(&self) -> usize { + match self.try_borrow(){ + Ok(value) => value.len(), + Err(_)=>0, + } + } + + /// Returns true if the container is empty. + /// Also returns true if the source data is unavailable. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Converts the container to an owned Vec. + /// This will most likely be a copy. + /// Returns an empty vector if the source data is unavailable. + pub fn to_vec(&self) -> Vec { + match self.try_borrow(){ + Ok(value) => value.to_vec(), + Err(_)=>Vec::new(), + } + } +} + +// ----- Serialize ----- +#[cfg(feature = "serializable")] +mod serialize { + use super::*; + use super::super::core::Borrow; + use serde::{Deserialize, Deserializer, Serialize, Serializer, ser::SerializeSeq}; + /// Serialize a Borrow. + impl Serialize for Borrow<'_, T> { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer { + let mut seq = serializer.serialize_seq(Some(self.len()))?; + for e in self.as_ref() { + seq.serialize_element(e)?; + } + seq.end() + } + } + /// Serialize a Container by borrowing. + /// Writes nothing if the borrow fails. + impl Serialize for Container<'_, T> { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer { + let borrow = self.try_borrow().unwrap_or(Borrow::Empty); + let mut seq = serializer.serialize_seq(Some(borrow.len()))?; + for e in borrow.as_ref() { + seq.serialize_element(e)?; + } + seq.end() + } + } + /// Deserialize into a Container. + impl<'de, T: Deserialize<'de> + Clone> Deserialize<'de> for Container<'_, T> { + fn deserialize>(d: D) -> Result { + let v = Vec::::deserialize(d)?; + Ok(Container::Vec(v)) + } + } +} diff --git a/src/lib.rs b/src/lib.rs index ec223ded..0bd083d0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,16 +34,22 @@ pub use reply::Ioctl; pub use passthrough::BackingId; #[cfg(target_os = "macos")] pub use reply::XTimes; -pub use reply::{Entry, FileAttr, FileType, Open, Statfs, Lock}; +pub use reply::{Entry, FileAttr, FileType, Open, Statfs, Xattr, Lock}; pub use ll::Errno; pub use request::RequestMeta; pub use session::{BackgroundSession, Session, SessionACL, SessionUnmounter}; +pub use container::{Container, Borrow}; + +/// A container for bytes, implementing flexible ownership. +pub type Bytes<'a> = Container<'a, u8>; + #[cfg(feature = "abi-7-28")] use std::cmp::max; #[cfg(feature = "abi-7-13")] use std::cmp::min; mod channel; +mod container; mod ll; mod mnt; #[cfg(feature = "abi-7-11")] @@ -494,6 +500,8 @@ pub trait Filesystem { /// return value of the read system call will reflect the return value of this /// operation. `fh` will contain the value set by the open method, or will be undefined /// if the open method didn't set any value. + /// The method should return `Ok(Bytes<'a>)` with the read data, or `Err(Errno)` otherwise. + /// `Bytes` allows for returning borrowed or owned data, potentially avoiding data copies. /// /// `flags`: these are the file flags, such as O_SYNC. Only supported with ABI >= 7.9 /// `lock_owner`: only supported with ABI >= 7.9 @@ -506,7 +514,7 @@ pub trait Filesystem { size: u32, flags: i32, lock_owner: Option, - ) -> Result<(), Errno> { + ) -> Result, Errno> { warn!( "[Not Implemented] read(ino: {:#x?}, fh: {}, offset: {}, size: {}, \ flags: {:#x?}, lock_owner: {:?})", @@ -718,16 +726,18 @@ pub trait Filesystem { } /// Get an extended attribute. - /// If `size` is 0, the size of the value should be sent with `reply.size()`. - /// If `size` is not 0, and the value fits, send it with `reply.data()`, or - /// `reply.error(ERANGE)` if it doesn't. - fn getxattr( + /// If `size` is 0, the size of the value should be returned in `Xattr::Size(u32)`. + /// If `size` is not 0, and the value fits, the value should be returned in `Xattr::Data(Bytes<'a>)`. + /// `Bytes` allows for returning borrowed or owned data for the attribute value. + /// If the value does not fit, `Err(Errno::ERANGE)` should be returned. + /// The method should return `Ok(Xattr<'a>)` on success, or `Err(Errno)` otherwise. + fn getxattr<'a>( &mut self, req: RequestMeta, ino: u64, name: &OsStr, size: u32, - ) -> Result<(), Errno> { + ) -> Result, Errno> { warn!( "[Not Implemented] getxattr(ino: {:#x?}, name: {:?}, size: {})", ino, name, size @@ -736,13 +746,17 @@ pub trait Filesystem { } /// List extended attribute names. + /// If `size` is 0, the size of the names list should be returned in `Xattr::Size(u32)`. + /// If `size` is not 0, and the names list fits, it should be returned in `Xattr::Data(ByteBox<'a>)`. + /// `ByteBox` allows for returning borrowed or owned data for the concatenated list of names. /// If the list does not fit, `Err(Errno::ERANGE)` should be returned. - fn listxattr( + /// The method should return `Ok(Xattr<'a>)` on success, or `Err(Errno)` otherwise. + fn listxattr<'a>( &mut self, req: RequestMeta, ino: u64, size: u32, - ) -> Result<(), Errno> { + ) -> Result, Errno> { warn!( "[Not Implemented] listxattr(ino: {:#x?}, size: {})", ino, size @@ -865,9 +879,9 @@ pub trait Filesystem { } /// Control device. - /// + /// The method should return `Ok(Ioctl)` with the ioctl result, or `Err(Errno)` otherwise. #[cfg(feature = "abi-7-11")] - fn ioctl( + fn ioctl<'a>( &mut self, _req: RequestMeta, ino: u64, @@ -876,7 +890,7 @@ pub trait Filesystem { cmd: u32, in_data: &[u8], out_size: u32, - ) -> Result<(), Errno> { + ) -> Result, Errno> { warn!( "[Not Implemented] ioctl(ino: {:#x?}, fh: {}, flags: {}, cmd: {}, \ in_data.len(): {}, out_size: {})", diff --git a/src/reply.rs b/src/reply.rs index f4e5de68..c48a8aa9 100644 --- a/src/reply.rs +++ b/src/reply.rs @@ -3,14 +3,12 @@ //! A reply handler object is created to guarantee that each fuse request receives a reponse exactly once. //! Either the request logic will call the one of the reply handler's self-destructive methods, //! or, if the reply handler goes out of scope before that happens, the drop trait will send an error response. -use crate::KernelConfig; + +use crate::{Container, Bytes, KernelConfig}; #[cfg(feature = "abi-7-40")] use crate::{consts::FOPEN_PASSTHROUGH, passthrough::BackingId}; -use crate::ll; -use libc::c_int; -use log::{error, warn}; -use std::convert::AsRef; -use std::ffi::OsStr; +#[allow(unused_imports)] +use log::{error, warn, info, debug}; use std::fmt; use std::io::IoSlice; #[cfg(feature = "abi-7-40")] @@ -220,6 +218,29 @@ pub struct Lock { pub pid: u32, } +/// `Xattr` represents the response for extended attribute operations (`getxattr`, `listxattr`). +/// It can either indicate the size of the attribute data or provide the data itself +/// using `Bytes` for flexible ownership. +#[derive(Debug)] +pub enum Xattr<'a> { + /// Indicates the size of the extended attribute data. Used when the caller + /// provides a zero-sized buffer to query the required buffer size. + Size(u32), + /// Contains the extended attribute data. `Bytes` allows this data to be + /// returned in a zero-copy data ownership model. + Data(Bytes<'a>), +} + +#[cfg(feature = "abi-7-11")] +#[derive(Debug)] +/// File io control reponse data +pub struct Ioctl<'a> { + /// Result of the ioctl operation + pub result: i32, + /// Data to be returned with the ioctl operation + pub data: Bytes<'a> +} + // // Methods to reply to a request for each kind of data // @@ -237,8 +258,8 @@ impl ReplyHandler { } /// Reply to a general request with data - pub fn data(self, data: &[u8]) { - self.send_ll(&ll::Response::new_slice(data)); + pub fn data<'a>(self, data: Bytes<'a>) { + self.send_ll(&ll::Response::new_slice(&data.borrow())); } // Reply to an init request with available features @@ -355,9 +376,11 @@ impl ReplyHandler { self.send_ll(&ll::Response::new_bmap(block)) } - #[cfg(feature = "abi-7-11")] + #[cfg(feature = "abi-7-11")] /// Reply to a request with an ioctl - pub fn ioctl(self) {} + pub fn ioctl(self, ioctl: Ioctl<'_>) { + self.send_ll(&ll::Response::new_ioctl(ioctl.result, &ioctl.data.borrow())); + } #[cfg(feature = "abi-7-11")] /// Reply to a request with a poll result @@ -366,14 +389,85 @@ impl ReplyHandler { } /// Reply to a request with a filled directory buffer - pub fn dir(self) {} + pub fn dir( + self, + entries_list: &DirentList<'_, '_>, + size: usize, + min_offset: i64, + ) { + let mut buf = DirentBuf::new(size); + let entries = match entries_list.try_borrow(){ + Ok(entries) => entries, + Err(e) => { + log::error!("ReplyHandler::dir: Borrow Error: {:?}", e); + return; + } + }; + for item in entries.iter() { + if item.offset < min_offset { + log::debug!("ReplyHandler::dir: skipping item with offset #{}", item.offset); + continue; + } else { + log::debug!("ReplyHandler::dir: processing item with offset #{}", item.offset); + } + let full= buf.push(item); + if full { + log::debug!("ReplyHandler::dir: buffer full!"); + break; + } + } + self.send_ll(&buf.into()); + } #[cfg(feature = "abi-7-21")] - /// Reply to a request with a filled directory plus buffer - pub fn dirplus(self) {} + // Reply to a request with a filled directory plus buffer + pub fn dirplus( + self, + entries_plus_list: &DirentPlusList<'_, '_>, + size: usize, + min_offset: i64, + ) { + let mut buf = DirentPlusBuf::new(size); + let entries = match entries_plus_list.try_borrow(){ + Ok(entries) => entries, + Err(e) => { + log::error!("ReplyHandler::dirplus: Borrow Error: {:?}", e); + return; + } + }; + for (dirent, entry) in entries.iter() { + if dirent.offset < min_offset { + log::debug!("ReplyHandler::dirplus: skipping item with offset #{}", dirent.offset); + continue; + } else { + log::debug!("ReplyHandler::dirplus: processing item with offset #{}", dirent.offset); + } + let full = buf.push(&dirent, &entry); + if full { + log::debug!("ReplyHandler::dirplus: buffer full!"); + break; + } + } + self.send_ll(&buf.into()); + } /// Reply to a request with extended attributes. - pub fn xattr(self) {} + pub fn xattr(self, reply: Xattr<'_>) { + match reply { + Xattr::Size(s) => self.xattr_size(s), + Xattr::Data(d) => self.xattr_data(d), + } + } + + /// Reply to a request with the size of an xattr result. + pub fn xattr_size(self, size: u32) { + self.send_ll(&ll::Response::new_xattr_size(size)) + } + + /// Reply to a request with the data in an xattr result. + pub fn xattr_data(self, data: Bytes<'_>) { + self.send_ll(&ll::Response::new_slice(&data.borrow())) + } #[cfg(feature = "abi-7-24")] /// Reply to a request with a seeked offset @@ -494,7 +588,7 @@ mod test { ], }; let replyhandler: ReplyHandler = ReplyHandler::new(0xdeadbeef, sender); - replyhandler.data(&[0xde, 0xad, 0xbe, 0xef]); + replyhandler.data(Bytes::Ref(&[0xde, 0xad, 0xbe, 0xef])); } #[test] @@ -869,6 +963,7 @@ mod test { ], }; let replyhandler: ReplyHandler = ReplyHandler::new(0xdeadbeef, sender); + replyhandler.xattr(Xattr::Size(0x12345678)); } #[test] @@ -880,6 +975,7 @@ mod test { ], }; let replyhandler: ReplyHandler = ReplyHandler::new(0xdeadbeef, sender); + replyhandler.xattr(Xattr::Data(vec![0x11, 0x22, 0x33, 0x44].into())); } impl super::ReplySender for SyncSender<()> { diff --git a/src/request.rs b/src/request.rs index 319dc5e1..8a186d44 100644 --- a/src/request.rs +++ b/src/request.rs @@ -405,7 +405,7 @@ impl<'a> Request<'a> { ); match response { Ok(data)=> { - self.replyhandler.data(data.as_ref()) + self.replyhandler.data(data) } Err(err)=>{ self.replyhandler.error(err) From 52c90f6ab7a1a0a971bb09a943162a4485eac293 Mon Sep 17 00:00:00 2001 From: Richard Lawrence Date: Wed, 16 Jul 2025 15:58:15 -0500 Subject: [PATCH 06/19] Reorganized Dirent(Plus)-related structs from ll -> public. Dirent(Plus)List is an alias for a Container, which enables Filesystem methods to handle and return data under a variety of ownership models. `offset` and `max_bytes` parameters are now enforced by the library, so they can be considered as optional. The library should do its best to avoid repeatedly copying Directory data before constructing the response buffer. --- src/lib.rs | 40 +++++++----- src/ll/reply.rs | 151 ++++++++++++++++--------------------------- src/reply.rs | 166 ++++++++++++++++++++++++++++++++++++++++++++++++ src/request.rs | 8 +++ 4 files changed, 250 insertions(+), 115 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0bd083d0..37970405 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,7 +34,7 @@ pub use reply::Ioctl; pub use passthrough::BackingId; #[cfg(target_os = "macos")] pub use reply::XTimes; -pub use reply::{Entry, FileAttr, FileType, Open, Statfs, Xattr, Lock}; +pub use reply::{Entry, FileAttr, FileType, Dirent, DirentList, DirentPlusList, Open, Statfs, Xattr, Lock}; pub use ll::Errno; pub use request::RequestMeta; pub use session::{BackgroundSession, Session, SessionACL, SessionUnmounter}; @@ -628,39 +628,45 @@ pub trait Filesystem { } /// Read directory. - /// Send a buffer filled using buffer.fill(), with size not exceeding the - /// requested size. Send an empty buffer on end of stream. fh will contain the - /// value set by the opendir method, or will be undefined if the opendir method - /// didn't set any value. - fn readdir( + /// The filesystem should return a list of directory entries. + /// A buffer will be filled with entries from up to `max_bytes`. + /// An empty list indicates the end of the stream. + /// `fh` will contain the value set by the opendir method, or will be undefined if the + /// opendir method didn't set any value. + fn readdir<'dir, 'name>( &mut self, req: RequestMeta, ino: u64, fh: u64, offset: i64, - ) -> Result<(), Errno> { + max_bytes: u32 + ) -> Result, Errno> { warn!( - "[Not Implemented] readdir(ino: {:#x?}, fh: {}, offset: {})", - ino, fh, offset + "[Not Implemented] readdir(ino: {:#x?}, fh: {}, offset: {}, max_bytes: {})", + ino, fh, offset, max_bytes ); Err(Errno::ENOSYS) } /// Read directory. - /// Send a buffer filled using buffer.fill(), with size not exceeding the - /// requested size. Send an empty buffer on end of stream. fh will contain the - /// value set by the opendir method, or will be undefined if the opendir method - /// didn't set any value. - fn readdirplus( + /// Similar to `readdir`, but also returns the attributes of each directory entry. + /// The filesystem should return a list of tuples of directory entries and their attributes. + /// A buffer will be filled with entries and attributes up to `max_bytes`. + /// An empty list indicates the end of the stream. + /// `fh` will contain the value set by the opendir method, or will be + /// undefined if the opendir method didn't set any value. + #[cfg(feature = "abi-7-21")] + fn readdirplus<'dir, 'name>( &mut self, req: RequestMeta, ino: u64, fh: u64, offset: i64, - ) -> Result<(), Errno> { + max_bytes: u32, + ) -> Result, Errno> { warn!( - "[Not Implemented] readdirplus(ino: {:#x?}, fh: {}, offset: {})", - ino, fh, offset + "[Not Implemented] readdirplus(ino: {:#x?}, fh: {}, offset: {}, max_bytes: {})", + ino, fh, offset, max_bytes ); Err(Errno::ENOSYS) } diff --git a/src/ll/reply.rs b/src/ll/reply.rs index 93377ac3..8dee26c3 100644 --- a/src/ll/reply.rs +++ b/src/ll/reply.rs @@ -2,8 +2,6 @@ use std::{ convert::TryInto, io::IoSlice, mem::size_of, - os::unix::prelude::OsStrExt, - path::Path, time::{Duration, SystemTime, UNIX_EPOCH}, }; @@ -222,7 +220,9 @@ impl<'a> Response<'a> { } // TODO: Are you allowed to send data while result != 0? - pub(crate) fn new_ioctl(result: i32, data: &[IoSlice<'_>]) -> Self { + // TODO: This used to be IoSlice -- does that have any additional utility here? + #[cfg(feature = "abi-7-11")] + pub(crate) fn new_ioctl(result: i32, data: &[u8]) -> Self { let r = abi::fuse_ioctl_out { result, // these fields are only needed for unrestricted ioctls @@ -232,9 +232,7 @@ impl<'a> Response<'a> { }; // TODO: Don't copy this data let mut v: ResponseBuf = ResponseBuf::from_slice(r.as_bytes()); - for x in data { - v.extend_from_slice(x) - } + v.extend_from_slice(data); Self::Data(v) } @@ -257,6 +255,7 @@ impl<'a> Response<'a> { Self::from_struct(&r) } + #[cfg(feature = "abi-7-24")] pub(crate) fn new_lseek(offset: i64) -> Self { let r = abi::fuse_lseek_out { offset }; Self::from_struct(&r) @@ -381,6 +380,8 @@ impl EntListBuf { } } +/* +// TODO: strong typing on `offset` values. #[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)] pub struct DirEntOffset(pub i64); impl From for i64 { @@ -388,37 +389,19 @@ impl From for i64 { x.0 } } +*/ +/// Used to respond to [ReaddirPlus] requests. #[derive(Debug)] -pub struct DirEntry> { - ino: INodeNo, - offset: DirEntOffset, - kind: FileType, - name: T, -} - -impl> DirEntry { - pub fn new(ino: INodeNo, offset: DirEntOffset, kind: FileType, name: T) -> DirEntry { - DirEntry:: { - ino, - offset, - kind, - name, - } - } -} - -/// Used to respond to [ReadDirPlus] requests. -#[derive(Debug)] -pub struct DirEntList(EntListBuf); -impl From for Response<'_> { - fn from(l: DirEntList) -> Self { +pub struct DirentBuf(EntListBuf); +impl From for Response<'_> { + fn from(l: DirentBuf) -> Self { assert!(l.0.buf.len() <= l.0.max_size); Response::new_directory(l.0) } } -impl DirEntList { +impl DirentBuf { pub(crate) fn new(max_size: usize) -> Self { Self(EntListBuf::new(max_size)) } @@ -426,63 +409,34 @@ impl DirEntList { /// A transparent offset value can be provided for each entry. The kernel uses these /// value to request the next entries in further readdir calls #[must_use] - pub fn push>(&mut self, ent: &DirEntry) -> bool { - let name = ent.name.as_ref().as_os_str().as_bytes(); + pub fn push(&mut self, ent: &crate::Dirent<'_>) -> bool { + let name = ent.name.try_borrow() + .expect("Borrow name from dirent failed"); let header = abi::fuse_dirent { ino: ent.ino.into(), - off: ent.offset.0, + off: ent.offset, namelen: name.len().try_into().expect("Name too long"), typ: mode_from_kind_and_perm(ent.kind, 0) >> 12, }; - self.0.push([header.as_bytes(), name]) + self.0.push([header.as_bytes(), &name]) } } +/// Used to respond to [Readdir] requests. +#[cfg(feature = "abi-7-21")] #[derive(Debug)] -pub struct DirEntryPlus> { - #[allow(unused)] // We use `attr.ino` instead - ino: INodeNo, - generation: Generation, - offset: DirEntOffset, - name: T, - entry_valid: Duration, - attr: Attr, - attr_valid: Duration, -} +pub struct DirentPlusBuf(EntListBuf); -impl> DirEntryPlus { - pub fn new( - ino: INodeNo, - generation: Generation, - offset: DirEntOffset, - name: T, - entry_valid: Duration, - attr: Attr, - attr_valid: Duration, - ) -> Self { - Self { - ino, - generation, - offset, - name, - entry_valid, - attr, - attr_valid, - } - } -} - -/// Used to respond to [ReadDir] requests. -#[derive(Debug)] -pub struct DirEntPlusList(EntListBuf); -impl From for Response<'_> { - fn from(l: DirEntPlusList) -> Self { +#[cfg(feature = "abi-7-21")] +impl From for Response<'_> { + fn from(l: DirentPlusBuf) -> Self { assert!(l.0.buf.len() <= l.0.max_size); Response::new_directory(l.0) } } -impl DirEntPlusList { +#[cfg(feature = "abi-7-21")] +impl DirentPlusBuf { pub(crate) fn new(max_size: usize) -> Self { Self(EntListBuf::new(max_size)) } @@ -490,26 +444,27 @@ impl DirEntPlusList { /// A transparent offset value can be provided for each entry. The kernel uses these /// value to request the next entries in further readdir calls #[must_use] - pub fn push>(&mut self, x: &DirEntryPlus) -> bool { - let name = x.name.as_ref().as_os_str().as_bytes(); + pub fn push(&mut self, x: &crate::Dirent<'_>, y: &crate::Entry) -> bool { + let name = x.name.try_borrow() + .expect("Borrow name from dirent failed"); let header = abi::fuse_direntplus { entry_out: abi::fuse_entry_out { - nodeid: x.attr.attr.ino, - generation: x.generation.into(), - entry_valid: x.entry_valid.as_secs(), - attr_valid: x.attr_valid.as_secs(), - entry_valid_nsec: x.entry_valid.subsec_nanos(), - attr_valid_nsec: x.attr_valid.subsec_nanos(), - attr: x.attr.attr, + nodeid: y.ino, + generation: y.generation.unwrap_or(1).into(), + entry_valid: y.file_ttl.as_secs(), + attr_valid: y.attr_ttl.as_secs(), + entry_valid_nsec: y.file_ttl.subsec_nanos(), + attr_valid_nsec: y.attr_ttl.subsec_nanos(), + attr: fuse_attr_from_attr(&y.attr), }, dirent: abi::fuse_dirent { - ino: x.attr.attr.ino, - off: x.offset.into(), + ino: x.ino, + off: x.offset, namelen: name.len().try_into().expect("Name too long"), - typ: x.attr.attr.mode >> 12, + typ: mode_from_kind_and_perm(x.kind, 0) >> 12, }, }; - self.0.push([header.as_bytes(), name]) + self.0.push([header.as_bytes(), &name]) } } @@ -880,19 +835,19 @@ mod test { 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x72, 0x73, ]; - let mut buf = DirEntList::new(4096); - assert!(!buf.push(&DirEntry::new( - INodeNo(0xaabb), - DirEntOffset(1), - FileType::Directory, - "hello" - ))); - assert!(!buf.push(&DirEntry::new( - INodeNo(0xccdd), - DirEntOffset(2), - FileType::RegularFile, - "world.rs" - ))); + let mut buf = DirentBuf::new(4096); + assert!(!buf.push(&crate::Dirent { + ino: 0xaabb, + offset: 1, + kind: FileType::Directory, + name: "hello".into() + })); + assert!(!buf.push(&crate::Dirent { + ino: 0xccdd, + offset: 2, + kind: FileType::RegularFile, + name: "world.rs".into() + })); let r: Response<'_> = buf.into(); assert_eq!( r.with_iovec(RequestId(0xdeadbeef), ioslice_to_vec), diff --git a/src/reply.rs b/src/reply.rs index c48a8aa9..0e8c0594 100644 --- a/src/reply.rs +++ b/src/reply.rs @@ -5,6 +5,9 @@ //! or, if the reply handler goes out of scope before that happens, the drop trait will send an error response. use crate::{Container, Bytes, KernelConfig}; +use crate::ll::{self, reply::DirentBuf}; +#[cfg(feature = "abi-7-21")] +use crate::ll::reply::{DirentPlusBuf}; #[cfg(feature = "abi-7-40")] use crate::{consts::FOPEN_PASSTHROUGH, passthrough::BackingId}; #[allow(unused_imports)] @@ -172,6 +175,28 @@ pub struct Open { pub flags: u32 } +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))] +/// A sinegle directory entry. +/// The `'name` lifetime parameter is associated with the `name` field if it is from borrowed Bytes. +pub struct Dirent<'name> { + /// file inode number + pub ino: u64, + /// entry number in directory + pub offset: i64, + /// kind of file + pub kind: FileType, + /// name of file + pub name: Bytes<'name>, +} + +/// A list of directory entries. +pub type DirentList<'dir, 'name> = Container<'dir, Dirent<'name>>; + +/// A list of directory entries, plus additional file data for the kernel cache. +pub type DirentPlusList<'dir, 'name> = Container<'dir, (Dirent<'name>, Entry)>; + + #[cfg(target_os = "macos")] #[derive(Debug)] /// Xtimes response data @@ -942,16 +967,157 @@ mod test { }; let replyhandler: ReplyHandler = ReplyHandler::new(0xdeadbeef, sender); let entries = vec!( + Dirent { + ino: 0xaabb, + offset: 1, + kind: FileType::Directory, + name: OsString::from("hello").into(), + }, + Dirent { + ino: 0xccdd, + offset: 2, + kind: FileType::RegularFile, + name: OsString::from("world.rs").into(), + } ); + replyhandler.dir(&entries.into(), std::mem::size_of::()*128, 0); } #[test] #[cfg(feature = "abi-7-24")] fn reply_directory_plus() { // prepare the expected file attribute portion of the message + // see test::reply_entry() for details + let mut attr_bytes = Vec::new(); + attr_bytes.extend_from_slice(&[ + 0xbb, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x65, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x65, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x21, 0x43, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, + 0xbb, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]); + #[cfg(target_os = "macos")] + attr_bytes.extend_from_slice(&[ + 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]); + attr_bytes.extend_from_slice(&[ + 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, + ]); + #[cfg(target_os = "macos")] + attr_bytes.extend_from_slice([ + 0x78, 0x56, 0x00, 0x00, + ]); + attr_bytes.extend_from_slice(&[ + 0xa4, 0x41, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, + 0x77, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, + ]); + #[cfg(target_os = "macos")] + attr_bytes.extend_from_slice(&[ + 0x99, 0x00, 0x00, 0x00, + ]); + #[cfg(feature = "abi-7-9")] + attr_bytes.extend_from_slice(&[ + 0xbb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + ]); + + let mut expected = Vec::new(); + // header + expected.extend_from_slice(&[ + 0x50, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00, + ]); + // attr 1 + expected.extend_from_slice(&attr_bytes); + // dir entry 1 + expected.extend_from_slice(&[ + 0xbb, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x00, 0x00, 0x00, + ]); + // attr 2 has a different ino value in two positions + attr_bytes[0]=0xdd; + attr_bytes[1]=0xcc; + attr_bytes[40]=0xdd; + attr_bytes[41]=0xcc; + // attr 2 has a different file permission in one position + let i = if cfg!(target_os = "macos") {113} else {101}; + attr_bytes[i]=0x81; + expected.extend_from_slice(&attr_bytes); + // dir entry 2 + expected.extend_from_slice(&[ + 0xdd, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x72, 0x73, + ]); + // correct the header + expected[0] = (expected.len()) as u8; // test reply will be compared to expected let sender = AssertSender {expected}; let replyhandler: ReplyHandler = ReplyHandler::new(0xdeadbeef, sender); + let time = UNIX_EPOCH + Duration::new(0x1234, 0x5678); + let ttl = Duration::new(0x8765, 0x4321); + let attr1 = FileAttr { + ino: 0xaabb, + size: 0x22, + blocks: 0x33, + atime: time, + mtime: time, + ctime: time, + crtime: time, + kind: FileType::Directory, + perm: 0o644, + nlink: 0x55, + uid: 0x66, + gid: 0x77, + rdev: 0x88, + flags: 0x99, + blksize: 0xbb, + }; + let mut attr2 = attr1; //implicit copy + attr2.ino = 0xccdd; + attr2.kind = FileType::RegularFile; + let generation = Some(0xaa); + let entries = vec!( + ( + Dirent { + ino: 0xaabb, + offset: 1, + kind: FileType::Directory, + name: OsString::from("hello").into(), + }, + Entry { + ino: 0xaabb, + generation, + file_ttl: ttl, + attr: attr1, + attr_ttl: ttl, + } + ), + ( + Dirent { + ino: 0xccdd, + offset: 2, + kind: FileType::RegularFile, + name: OsString::from("world.rs").into(), + }, + Entry { + ino:0xccdd, + generation, + file_ttl: ttl, + attr: attr2, + attr_ttl: ttl, + } + ) + ); + replyhandler.dirplus(&entries.into(), std::mem::size_of::()*4096, 0); } #[test] diff --git a/src/request.rs b/src/request.rs index 8a186d44..2b07644e 100644 --- a/src/request.rs +++ b/src/request.rs @@ -503,10 +503,14 @@ impl<'a> Request<'a> { self.request.nodeid().into(), x.file_handle().into(), x.offset(), + x.size() ); match response { Ok(entries_list_result)=> { self.replyhandler.dir( + &entries_list_result, + x.size() as usize, + x.offset(), ) } Err(err)=>{ @@ -818,10 +822,14 @@ impl<'a> Request<'a> { self.request.nodeid().into(), x.file_handle().into(), x.offset(), + x.size() ); match response { Ok(plus_entries_list_result)=> { self.replyhandler.dirplus( + &plus_entries_list_result, + x.size() as usize, + x.offset() ) } Err(err)=>{ From 455b5c9ad8d83e2925edb94d7500144b3205a471 Mon Sep 17 00:00:00 2001 From: Richard Lawrence Date: Wed, 16 Jul 2025 16:43:08 -0500 Subject: [PATCH 07/19] Updated examples to use Filesystem traits that return containers. Reverted PollHandle back to u64. Added documentation throughout examples. Updated documentation for mount functions. --- examples/hello.rs | 237 +++++-- examples/ioctl.rs | 113 ++-- examples/notify_inval_entry.rs | 88 +-- examples/notify_inval_inode.rs | 146 ++-- examples/null.rs | 51 ++ examples/passthrough.rs | 106 +-- examples/poll.rs | 257 ++++--- examples/simple.rs | 1158 ++++++++++++++++++-------------- src/lib.rs | 48 +- src/request.rs | 2 +- 10 files changed, 1362 insertions(+), 844 deletions(-) diff --git a/examples/hello.rs b/examples/hello.rs index c4150495..96d0a7a6 100644 --- a/examples/hello.rs +++ b/examples/hello.rs @@ -1,11 +1,12 @@ use clap::{crate_version, Arg, ArgAction, Command}; use fuser::{ - FileAttr, FileType, Filesystem, MountOption, ReplyAttr, ReplyData, ReplyDirectory, ReplyEntry, - Request, + FileAttr, Bytes, Dirent, DirentList, Entry, Errno, + Filesystem, FileType, MountOption, RequestMeta, }; -use libc::ENOENT; -use std::ffi::OsStr; +use std::ffi::{OsStr, OsString}; +use std::path::Path; use std::time::{Duration, UNIX_EPOCH}; +use std::sync::Arc; const TTL: Duration = Duration::from_secs(1); // 1 second @@ -47,69 +48,146 @@ const HELLO_TXT_ATTR: FileAttr = FileAttr { blksize: 512, }; -struct HelloFS; +// An example of reusable Borrowed data. +// This entry derives its lifetime from string literal, +// which is 'static. +const DOT_ENTRY: Dirent<'static> = Dirent { + ino: 1, + offset: 1, + kind: FileType::Directory, + name: Bytes::Ref(b"."), +}; + +/// Example Filesystem data +struct HelloFS<'a> { + hello_entry: Arc>, +} + +impl HelloFS<'_> { + fn new() -> Self { + HelloFS{ + // An example of reusable Shared data. + // Entry #3 is allocated here once. + // It is persistent until replaced. + hello_entry: Arc::new( + Dirent { + ino: 2, + offset: 3, + kind: FileType::RegularFile, + name: OsString::from("hello.txt").into(), + } + ) + } + } +} -impl Filesystem for HelloFS { - fn lookup(&mut self, _req: &Request, parent: u64, name: &OsStr, reply: ReplyEntry) { - if parent == 1 && name.to_str() == Some("hello.txt") { - reply.entry(&TTL, &HELLO_TXT_ATTR, 0); +impl Filesystem for HelloFS<'static> { + // Must specify HelloFS lifetime ('static) here + // to enable its methods to return borrowed data + fn lookup(&mut self, _req: RequestMeta, parent: u64, name: &Path) -> Result { + if parent == 1 && name == OsStr::new("hello.txt") { + Ok(Entry{ + ino: 2, + generation: None, + file_ttl: TTL, + attr: HELLO_TXT_ATTR, + attr_ttl: TTL, + }) } else { - reply.error(ENOENT); + Err(Errno::ENOENT) } } - fn getattr(&mut self, _req: &Request, ino: u64, _fh: Option, reply: ReplyAttr) { + fn getattr( + &mut self, + _req: RequestMeta, + ino: u64, + _fh: Option, + ) -> Result<(FileAttr, Duration), Errno> { match ino { - 1 => reply.attr(&TTL, &HELLO_DIR_ATTR), - 2 => reply.attr(&TTL, &HELLO_TXT_ATTR), - _ => reply.error(ENOENT), + 1 => Ok((HELLO_DIR_ATTR, TTL)), + 2 => Ok((HELLO_TXT_ATTR, TTL)), + _ => Err(Errno::ENOENT), } } - fn read( + fn read<'a>( &mut self, - _req: &Request, + _req: RequestMeta, ino: u64, _fh: u64, offset: i64, _size: u32, _flags: i32, _lock: Option, - reply: ReplyData, - ) { + ) -> Result, Errno> { if ino == 2 { - reply.data(&HELLO_TXT_CONTENT.as_bytes()[offset as usize..]); + // HELLO_TXT_CONTENT is &'static str, so its bytes are &'static [u8] + let bytes = HELLO_TXT_CONTENT.as_bytes(); + let slice_len = bytes.len(); + let offset = offset as usize; + if offset >= slice_len { + Ok(Bytes::Ref(&[])) + } else { + // Returning as Borrowed to avoid a copy. + Ok(Bytes::Ref(&bytes[offset..])) + } } else { - reply.error(ENOENT); + Err(Errno::ENOENT) } } - fn readdir( + fn readdir<'dir, 'name>( &mut self, - _req: &Request, + _req: RequestMeta, ino: u64, _fh: u64, offset: i64, - mut reply: ReplyDirectory, - ) { + _max_bytes: u32, + ) -> Result, Errno> { if ino != 1 { - reply.error(ENOENT); - return; + return Err(Errno::ENOENT); } - let entries = vec![ - (1, FileType::Directory, "."), - (1, FileType::Directory, ".."), - (2, FileType::RegularFile, "hello.txt"), - ]; + // This example builds the list of 3 directory entries from scratch + // on each call to readdir(). + let mut entries= Vec::new(); - for (i, entry) in entries.into_iter().enumerate().skip(offset as usize) { - // i + 1 means the index of the next entry - if reply.add(entry.0, (i + 1) as i64, entry.1, entry.2) { - break; - } - } - reply.ok(); + // Entry 1: example of borrowed data. + // - name: "." + // - entry is constructed in the global scope. + // - lifetime is 'static. + // - a reference is passed along. + entries.push(DOT_ENTRY.clone()); + + // Entry 2: example of single-use Owned data. + // - name: ".." + // - entry is constructed during each call to readdir(). + let dotdot_entry = Dirent { + ino: 1, // Parent of root is itself for simplicity. + // Note: this can cause some weird behavior for an observer. + offset: 2, + kind: FileType::Directory, + // ownership of the string is moved into the DirEntry + name: OsString::from("..").into() + }; + // Ownership of the entry is passed along + entries.push(dotdot_entry); + + // Entry 3: an example of shared data. + // - name: "hello.txt" + // - entry is constructed in HelloFS::new() + // - Ownership of a smart reference is passed along. + entries.push(self.hello_entry.as_ref().clone()); + + // Slice the collected entries based on the requested offset. + let entries: Vec = entries.into_iter().skip(offset as usize).collect(); + // ( Only references and smart pointers are being reorganized at this time; + // the underlying data should just stay where it is.) + + // Entries may be returned as borrowed, owned, or shared. + // From<...> and Into<...> methods can be used to help construct the return type. + Ok(entries.into()) } } @@ -145,5 +223,86 @@ fn main() { if matches.get_flag("allow-root") { options.push(MountOption::AllowRoot); } - fuser::mount2(HelloFS, mountpoint, &options).unwrap(); + let hellofs = HelloFS::new(); + fuser::mount2(hellofs, mountpoint, &options).unwrap(); +} + +#[cfg(test)] +mod test { + use fuser::{Filesystem, RequestMeta, Errno, FileType}; + use std::ffi::OsStr; + use std::path::PathBuf; + use std::os::unix::ffi::OsStrExt; + + fn dummy_meta() -> RequestMeta { + RequestMeta { unique: 0, uid: 1000, gid: 1000, pid: 2000 } + } + + #[test] + fn test_lookup_hello_txt() { + let mut hellofs = super::HelloFS::new(); + let req = dummy_meta(); + let result = hellofs.lookup(req, 1, &PathBuf::from("hello.txt")); + assert!(result.is_ok(), "Lookup for hello.txt should succeed"); + if let Ok(entry) = result { + assert_eq!(entry.attr.ino, 2, "Lookup should return inode 2 for hello.txt"); + assert_eq!(entry.attr.kind, FileType::RegularFile, "hello.txt should be a regular file"); + assert_eq!(entry.attr.perm, 0o644, "hello.txt should have permissions 0o644"); + } + } + + #[test] + fn test_read_hello_txt() { + let mut hellofs = super::HelloFS::new(); + let req = dummy_meta(); + let result = hellofs.read(req, 2, 0, 0, 13, 0, None); + assert!(result.is_ok(), "Read for hello.txt should succeed"); + if let Ok(content) = result { + assert_eq!(String::from_utf8_lossy(content.as_ref()), "Hello World!\n", "Content of hello.txt should be 'Hello World!\\n'"); + } + } + + #[test] + fn test_readdir_root() { + let mut hellofs = super::HelloFS::new(); + let req = dummy_meta(); + let result = hellofs.readdir(req, 1, 0, 0, 4096); + assert!(result.is_ok(), "Readdir on root should succeed"); + if let Ok(entries_list) = result { + let entries_slice = entries_list.as_ref(); + assert_eq!(entries_slice.len(), 3, "Root directory should contain exactly 3 entries"); + + // Check entry 0: "." + let entry0_data = &entries_slice[0]; + assert_eq!(entry0_data.name.as_ref(), OsStr::new(".").as_bytes(), "First entry should be '.'"); + assert_eq!(entry0_data.ino, 1, "Inode for '.' should be 1"); + assert_eq!(entry0_data.offset, 1, "Offset for '.' should be 1"); + assert_eq!(entry0_data.kind, FileType::Directory, "'.' should be a directory"); + + // Check entry 1: ".." + let entry1_data = &entries_slice[1]; + assert_eq!(entry1_data.name.as_ref(), OsStr::new("..").as_bytes(), "Second entry should be '..'"); + assert_eq!(entry1_data.ino, 1, "Inode for '..' should be 1"); + assert_eq!(entry1_data.offset, 2, "Offset for '..' should be 2"); + assert_eq!(entry1_data.kind, FileType::Directory, "'..' should be a directory"); + + // Check entry 2: "hello.txt" + let entry2_data = &entries_slice[2]; + assert_eq!(entry2_data.name.as_ref(), OsStr::new("hello.txt").as_bytes(), "Third entry should be 'hello.txt'"); + assert_eq!(entry2_data.ino, 2, "Inode for 'hello.txt' should be 2"); + assert_eq!(entry2_data.offset, 3, "Offset for 'hello.txt' should be 3"); + assert_eq!(entry2_data.kind, FileType::RegularFile, "'hello.txt' should be a regular file"); + } + } + + #[test] + fn test_create_fails_readonly() { + let mut hellofs = super::HelloFS::new(); + let req = dummy_meta(); + let result = hellofs.create(req, 1, &PathBuf::from("newfile.txt"), 0o644, 0, 0); + assert!(result.is_err(), "Create should fail for read-only filesystem"); + if let Err(e) = result { + assert_eq!(e, Errno::ENOSYS, "Create should return ENOSYS for unsupported operation"); + } + } } diff --git a/examples/ioctl.rs b/examples/ioctl.rs index d9c7cf34..cac725e3 100644 --- a/examples/ioctl.rs +++ b/examples/ioctl.rs @@ -4,12 +4,12 @@ use clap::{crate_version, Arg, ArgAction, Command}; use fuser::{ - FileAttr, FileType, Filesystem, MountOption, ReplyAttr, ReplyData, ReplyDirectory, ReplyEntry, - Request, + Bytes, Dirent, DirentList, Entry, Errno, FileAttr, + Filesystem, FileType, Ioctl, MountOption, RequestMeta, }; -use libc::{EINVAL, ENOENT}; use log::debug; use std::ffi::OsStr; +use std::path::Path; use std::time::{Duration, UNIX_EPOCH}; const TTL: Duration = Duration::from_secs(1); // 1 second @@ -20,6 +20,13 @@ struct FiocFS { fioc_file_attr: FileAttr, } +// Constant data can be stored as Borrowed to prevent unnecessary copying +const DIR_ENTRIES: [Dirent; 3] = [ + Dirent { ino: 1, offset: 1, kind: FileType::Directory, name: Bytes::Ref(b".") }, + Dirent { ino: 1, offset: 2, kind: FileType::Directory, name: Bytes::Ref(b"..") }, + Dirent { ino: 2, offset: 3, kind: FileType::RegularFile, name: Bytes::Ref(b"fioc")}, +]; + impl FiocFS { fn new() -> Self { let uid = unsafe { libc::getuid() }; @@ -69,83 +76,87 @@ impl FiocFS { } } -impl Filesystem for FiocFS { - fn lookup(&mut self, _req: &Request, parent: u64, name: &OsStr, reply: ReplyEntry) { - if parent == 1 && name.to_str() == Some("fioc") { - reply.entry(&TTL, &self.fioc_file_attr, 0); +impl Filesystem for FiocFS{ + fn lookup(&mut self, _req: RequestMeta, parent: u64, name: &Path) -> Result { + if parent == 1 && name == OsStr::new("fioc") { + Ok(Entry { + ino: self.fioc_file_attr.ino, + generation: None, + attr: self.fioc_file_attr, + attr_ttl: TTL, + file_ttl: TTL, + }) } else { - reply.error(ENOENT); + Err(Errno::ENOENT) } } - fn getattr(&mut self, _req: &Request, ino: u64, _fh: Option, reply: ReplyAttr) { + fn getattr(&mut self, _req: RequestMeta, ino: u64, _fh: Option) -> Result<(FileAttr, Duration), Errno> { match ino { - 1 => reply.attr(&TTL, &self.root_attr), - 2 => reply.attr(&TTL, &self.fioc_file_attr), - _ => reply.error(ENOENT), + 1 => Ok((self.root_attr, TTL)), + 2 => Ok((self.fioc_file_attr, TTL)), + _ => Err(Errno::ENOENT), } } - fn read( + fn read<'a>( &mut self, - _req: &Request, + _req: RequestMeta, ino: u64, _fh: u64, offset: i64, _size: u32, _flags: i32, _lock: Option, - reply: ReplyData, - ) { + ) -> Result, Errno> { if ino == 2 { - reply.data(&self.content[offset as usize..]) + let offset = offset as usize; + if offset >= self.content.len() { + // No need to allocate anything, just borrow from this 'static empty literal. + Ok(Bytes::Ref(&[])) + } else { + /*********** + // Option 1: Copy the bytes into a new boxed slice + let copy_of_bytes = self.content[offset..].to_owned().into_boxed_slice(); + Ok(ByteBox::Owned(copy_of_bytes)) + ***********/ + // Option 2: Using From<...>/Into<...> trait methods + Ok(self.content[offset..].to_vec().into()) + } } else { - reply.error(ENOENT); + Err(Errno::ENOENT) } } - fn readdir( + fn readdir<'dir, 'name>( &mut self, - _req: &Request, + _req: RequestMeta, ino: u64, _fh: u64, - offset: i64, - mut reply: ReplyDirectory, - ) { + // Fuser library will ensure that offset and max_bytes are respected. + _offset: i64, + _max_bytes: u32, + ) -> Result, Errno> { if ino != 1 { - reply.error(ENOENT); - return; - } - - let entries = vec![ - (1, FileType::Directory, "."), - (1, FileType::Directory, ".."), - (2, FileType::RegularFile, "fioc"), - ]; - - for (i, entry) in entries.into_iter().enumerate().skip(offset as usize) { - // i + 1 means the index of the next entry - if reply.add(entry.0, (i + 1) as i64, entry.1, entry.2) { - break; - } + return Err(Errno::ENOENT); } - reply.ok(); + // In this example, return a borrowed reference to the (static) list of dir entries. + Ok(DirentList::Ref(&DIR_ENTRIES)) } - fn ioctl( + #[cfg(feature = "abi-7-11")] + fn ioctl<'a>( &mut self, - _req: &Request<'_>, + _req: RequestMeta, ino: u64, _fh: u64, _flags: u32, cmd: u32, in_data: &[u8], _out_size: u32, - reply: fuser::ReplyIoctl, - ) { + ) -> Result, Errno> { if ino != 2 { - reply.error(EINVAL); - return; + return Err(Errno::EINVAL); } const FIOC_GET_SIZE: u64 = nix::request_code_read!('E', 0, std::mem::size_of::()); @@ -154,16 +165,22 @@ impl Filesystem for FiocFS { match cmd.into() { FIOC_GET_SIZE => { let size_bytes = self.content.len().to_ne_bytes(); - reply.ioctl(0, &size_bytes); + Ok(Ioctl { + result: 0, + data: Bytes::Box(Box::new(size_bytes)), + }) } FIOC_SET_SIZE => { let new_size = usize::from_ne_bytes(in_data.try_into().unwrap()); self.content = vec![0_u8; new_size]; - reply.ioctl(0, &[]); + Ok(Ioctl { + result: 0, + data: Bytes::Empty, + }) } _ => { debug!("unknown ioctl: {}", cmd); - reply.error(EINVAL); + Err(Errno::EINVAL) } } } diff --git a/examples/notify_inval_entry.rs b/examples/notify_inval_entry.rs index e62ea58b..3c483cb9 100644 --- a/examples/notify_inval_entry.rs +++ b/examples/notify_inval_entry.rs @@ -8,7 +8,8 @@ // licensed under the terms of the GNU GPLv2. use std::{ - ffi::OsStr, + ffi::{OsStr, OsString}, + path::Path, sync::{ atomic::{AtomicU64, Ordering::SeqCst}, Arc, Mutex, @@ -17,22 +18,19 @@ use std::{ time::{Duration, SystemTime, UNIX_EPOCH}, }; -use libc::{ENOBUFS, ENOENT, ENOTDIR}; - use clap::Parser; use fuser::{ - FileAttr, FileType, Filesystem, MountOption, ReplyAttr, ReplyDirectory, ReplyEntry, Request, - FUSE_ROOT_ID, + Dirent, DirentList, Entry, Errno, FileAttr, FileType, + Filesystem, Forget, MountOption, RequestMeta, FUSE_ROOT_ID, }; - struct ClockFS<'a> { file_name: Arc>, lookup_cnt: &'a AtomicU64, timeout: Duration, } -impl<'a> ClockFS<'a> { +impl ClockFS<'_> { const FILE_INO: u64 = 2; fn get_filename(&self) -> String { @@ -67,58 +65,70 @@ impl<'a> ClockFS<'a> { } } -impl<'a> Filesystem for ClockFS<'a> { - fn lookup(&mut self, _req: &Request, parent: u64, name: &OsStr, reply: ReplyEntry) { - if parent != FUSE_ROOT_ID || name != AsRef::::as_ref(&self.get_filename()) { - reply.error(ENOENT); - return; +impl Filesystem for ClockFS<'_> { + fn lookup(&mut self, _req: RequestMeta, parent: u64, name: &Path) -> Result { + if parent != FUSE_ROOT_ID || name != OsStr::new(&self.get_filename()) { + return Err(Errno::ENOENT); } self.lookup_cnt.fetch_add(1, SeqCst); - reply.entry(&self.timeout, &ClockFS::stat(ClockFS::FILE_INO).unwrap(), 0); + match ClockFS::stat(ClockFS::FILE_INO) { + Some(attr) => Ok(Entry { + ino: attr.ino, + generation: None, + file_ttl: self.timeout, + attr, + attr_ttl: self.timeout, + }), + None => Err(Errno::EIO), // Should not happen if FILE_INO is valid + } } - fn forget(&mut self, _req: &Request, ino: u64, nlookup: u64) { - if ino == ClockFS::FILE_INO { - let prev = self.lookup_cnt.fetch_sub(nlookup, SeqCst); - assert!(prev >= nlookup); + fn forget(&mut self, _req: RequestMeta, target: Forget) { + if target.ino == ClockFS::FILE_INO { + let prev = self.lookup_cnt.fetch_sub(target.nlookup, SeqCst); + assert!(prev >= target.nlookup); } else { - assert!(ino == FUSE_ROOT_ID); + assert!(target.ino == FUSE_ROOT_ID); } } - fn getattr(&mut self, _req: &Request, ino: u64, _fh: Option, reply: ReplyAttr) { + fn getattr(&mut self, _req: RequestMeta, ino: u64, _fh: Option) -> Result<(FileAttr, Duration), Errno> { match ClockFS::stat(ino) { - Some(a) => reply.attr(&self.timeout, &a), - None => reply.error(ENOENT), + Some(attr) => Ok((attr, self.timeout)), + None => Err(Errno::ENOENT), } } - fn readdir( + fn readdir<'dir, 'name>( &mut self, - _req: &Request, + _req: RequestMeta, ino: u64, _fh: u64, offset: i64, - mut reply: ReplyDirectory, - ) { + _max_bytes: u32, + ) -> Result, Errno> { if ino != FUSE_ROOT_ID { - reply.error(ENOTDIR); - return; + return Err(Errno::ENOTDIR); } - - if offset == 0 - && reply.add( - ClockFS::FILE_INO, - offset + 1, - FileType::RegularFile, - &self.get_filename(), - ) - { - reply.error(ENOBUFS); - } else { - reply.ok(); + // In this example, construct and return an owned vector, + // containing owned bytes. + let mut entries= Vec::new(); + if offset == 0 { + let filename_string = self.get_filename(); // Returns String + let filename_os_string = OsString::from(filename_string); + + let entry = Dirent { + ino: ClockFS::FILE_INO, + offset: 1, // This entry's cookie + kind: FileType::RegularFile, + name: filename_os_string.into(), + }; + entries.push(entry); } + // If offset is > 0, we've already returned the single entry during a previous request, + // so just return the empty vector. + Ok(entries.into()) } } diff --git a/examples/notify_inval_inode.rs b/examples/notify_inval_inode.rs index 84f1418f..1af987ee 100644 --- a/examples/notify_inval_inode.rs +++ b/examples/notify_inval_inode.rs @@ -9,6 +9,7 @@ use std::{ convert::TryInto, ffi::OsStr, + path::Path, sync::{ atomic::{AtomicU64, Ordering::SeqCst}, Arc, Mutex, @@ -17,13 +18,11 @@ use std::{ time::{Duration, SystemTime, UNIX_EPOCH}, }; -use libc::{EACCES, EINVAL, EISDIR, ENOBUFS, ENOENT, ENOTDIR}; - use clap::Parser; use fuser::{ - consts, FileAttr, FileType, Filesystem, MountOption, ReplyAttr, ReplyData, ReplyDirectory, - ReplyEntry, ReplyOpen, Request, FUSE_ROOT_ID, + consts, Bytes, Dirent, DirentList, Entry, Errno, + FileAttr, FileType, Filesystem, Forget, MountOption, Open, RequestMeta, FUSE_ROOT_ID, }; struct ClockFS<'a> { @@ -31,7 +30,7 @@ struct ClockFS<'a> { lookup_cnt: &'a AtomicU64, } -impl<'a> ClockFS<'a> { +impl ClockFS<'_> { const FILE_INO: u64 = 2; const FILE_NAME: &'static str = "current_time"; @@ -66,102 +65,127 @@ impl<'a> ClockFS<'a> { } } -impl<'a> Filesystem for ClockFS<'a> { - fn lookup(&mut self, _req: &Request, parent: u64, name: &OsStr, reply: ReplyEntry) { - if parent != FUSE_ROOT_ID || name != AsRef::::as_ref(&Self::FILE_NAME) { - reply.error(ENOENT); - return; +impl Filesystem for ClockFS<'_> { + fn lookup(&mut self, _req: RequestMeta, parent: u64, name: &Path) -> Result { + if parent != FUSE_ROOT_ID || name != OsStr::new(Self::FILE_NAME) { + return Err(Errno::ENOENT); } self.lookup_cnt.fetch_add(1, SeqCst); - reply.entry(&Duration::MAX, &self.stat(ClockFS::FILE_INO).unwrap(), 0); + match self.stat(ClockFS::FILE_INO) { + Some(attr) => Ok(Entry { + ino: attr.ino, + generation: None, + file_ttl: Duration::MAX, + attr, + attr_ttl: Duration::MAX, + }), + None => Err(Errno::EIO), // Should not happen + } } - fn forget(&mut self, _req: &Request, ino: u64, nlookup: u64) { - if ino == ClockFS::FILE_INO { - let prev = self.lookup_cnt.fetch_sub(nlookup, SeqCst); - assert!(prev >= nlookup); + fn forget(&mut self, _req: RequestMeta, target: Forget) { + if target.ino == ClockFS::FILE_INO { + let prev = self.lookup_cnt.fetch_sub(target.nlookup, SeqCst); + assert!(prev >= target.nlookup); } else { - assert!(ino == FUSE_ROOT_ID); + assert!(target.ino == FUSE_ROOT_ID); } } - fn getattr(&mut self, _req: &Request, ino: u64, _fh: Option, reply: ReplyAttr) { + fn getattr( + &mut self, + _req: RequestMeta, + ino: u64, + _fh: Option, + ) -> Result<(FileAttr, Duration), Errno> { match self.stat(ino) { - Some(a) => reply.attr(&Duration::MAX, &a), - None => reply.error(ENOENT), + Some(attr) => Ok((attr, Duration::MAX)), + None => Err(Errno::ENOENT), } } - fn readdir( + fn readdir<'dir, 'name>( &mut self, - _req: &Request, + _req: RequestMeta, ino: u64, _fh: u64, offset: i64, - mut reply: ReplyDirectory, - ) { + _max_bytes: u32, + ) -> Result, Errno> { if ino != FUSE_ROOT_ID { - reply.error(ENOTDIR); - return; + return Err(Errno::ENOTDIR); } - - if offset == 0 - && reply.add( - ClockFS::FILE_INO, - offset + 1, - FileType::RegularFile, - Self::FILE_NAME, - ) - { - reply.error(ENOBUFS); - } else { - reply.ok(); + // In this example, construct and return an owned vector, + // containing a reference to borrowed ('static) bytes. + let mut entries: Vec = Vec::new(); + if offset == 0 { + let entry_data = Dirent { + ino: ClockFS::FILE_INO, + offset: 1, // This entry's cookie + kind: FileType::RegularFile, + name: Bytes::Ref(Self::FILE_NAME.as_bytes()), + }; + entries.push(entry_data); } + // If offset is > 0, we've already returned the single entry during a previous request, + // so just return the empty vector. + Ok(entries.into()) } - fn open(&mut self, _req: &Request, ino: u64, flags: i32, reply: ReplyOpen) { + fn open(&mut self, _req: RequestMeta, ino: u64, flags: i32) -> Result { if ino == FUSE_ROOT_ID { - reply.error(EISDIR); + Err(Errno::EISDIR) } else if flags & libc::O_ACCMODE != libc::O_RDONLY { - reply.error(EACCES); + Err(Errno::EACCES) } else if ino != Self::FILE_INO { eprintln!("Got open for nonexistent inode {}", ino); - reply.error(ENOENT); + Err(Errno::ENOENT) } else { - reply.opened(ino, consts::FOPEN_KEEP_CACHE); + Ok(Open { + fh: ino, // Using ino as fh, as it's unique for the file + flags: consts::FOPEN_KEEP_CACHE, + }) } } - fn read( + fn read<'a>( &mut self, - _req: &Request, + _req: RequestMeta, ino: u64, - _fh: u64, + _fh: u64, // fh is ino in this implementation as set in open() offset: i64, size: u32, _flags: i32, _lock_owner: Option, - reply: ReplyData, - ) { + ) -> Result, Errno> { assert!(ino == Self::FILE_INO); if offset < 0 { - reply.error(EINVAL); - return; + return Err(Errno::EINVAL); } - let file = self.file_contents.lock().unwrap(); - let filedata = file.as_bytes(); - let dlen = filedata.len().try_into().unwrap(); - let Ok(start) = offset.min(dlen).try_into() else { - reply.error(EINVAL); - return; - }; - let Ok(end) = (offset + size as i64).min(dlen).try_into() else { - reply.error(EINVAL); - return; + let file_guard = self.file_contents.lock().unwrap(); + let filedata = file_guard.as_bytes(); + let dlen: i64 = filedata.len().try_into().map_err(|_| Errno::EIO)?; // EIO if size doesn't fit i64 + + let start_index: usize = offset.try_into().map_err(|_| Errno::EINVAL)?; + + let data_to_return = if start_index > filedata.len() { + Vec::new() // Read past EOF + } else { + let end_index: usize = (offset + i64::from(size)) + .min(dlen) // cap at file length + .try_into() + .map_err(|_| Errno::EINVAL)?; // Should not fail if dlen fits usize + let stop_index = std::cmp::min(end_index, filedata.len()); + filedata[start_index..stop_index].to_vec() }; - eprintln!("read returning {} bytes at offset {}", end - start, offset); - reply.data(&filedata[start..end]); + + eprintln!( + "read returning {} bytes at offset {}", + data_to_return.len(), + offset + ); + Ok(Bytes::from(data_to_return)) } } diff --git a/examples/null.rs b/examples/null.rs index 6b4feecd..35dc67fe 100644 --- a/examples/null.rs +++ b/examples/null.rs @@ -10,3 +10,54 @@ fn main() { let mountpoint = env::args_os().nth(1).unwrap(); fuser::mount2(NullFS, mountpoint, &[MountOption::AutoUnmount]).unwrap(); } + +#[cfg(test)] +mod test { + use fuser::{Filesystem, RequestMeta, Errno}; + use std::path::PathBuf; + + fn dummy_meta() -> RequestMeta { + RequestMeta { unique: 0, uid: 1000, gid: 1000, pid: 2000 } + } + + #[test] + fn test_unsupported() { + let mut nullfs = super::NullFS {}; + let req = dummy_meta(); + + // Test lookup + let lookup_result = nullfs.lookup(req, 1, &PathBuf::from("nonexistent")); + assert!(lookup_result.is_err(), "Lookup should fail for NullFS"); + if let Err(e) = lookup_result { + assert_eq!(e, Errno::ENOSYS, "Lookup should return ENOSYS"); + } + + // Test getattr + let getattr_result = nullfs.getattr(req, 1, None); + assert!(getattr_result.is_err(), "Getattr should fail for NullFS"); + if let Err(e) = getattr_result { + assert_eq!(e, Errno::ENOSYS, "Getattr should return ENOSYS"); + } + + // Test readdir + let readdir_result = nullfs.readdir(req, 1, 0, 0, 4096); + assert!(readdir_result.is_err(), "Readdir should fail for NullFS"); + if let Err(e) = readdir_result { + assert_eq!(e, Errno::ENOSYS, "Readdir should return ENOSYS"); + } + + // Test open + let open_result = nullfs.open(req, 1, 0); + assert!(open_result.is_err(), "Open should fail for NullFS"); + if let Err(e) = open_result { + assert_eq!(e, Errno::ENOSYS, "Open should return ENOSYS"); + } + + // Test create + let create_result = nullfs.create(req, 1, &PathBuf::from("testfile"), 0o644, 0, 0); + assert!(create_result.is_err(), "Create should fail for NullFS"); + if let Err(e) = create_result { + assert_eq!(e, Errno::ENOSYS, "Create should return ENOSYS"); + } + } +} diff --git a/examples/passthrough.rs b/examples/passthrough.rs index abee030c..0bc8ab01 100644 --- a/examples/passthrough.rs +++ b/examples/passthrough.rs @@ -4,12 +4,11 @@ use clap::{crate_version, Arg, ArgAction, Command}; use fuser::{ - consts, BackingId, FileAttr, FileType, Filesystem, KernelConfig, MountOption, ReplyAttr, - ReplyDirectory, ReplyEmpty, ReplyEntry, ReplyOpen, Request, + consts, BackingId, Bytes, Dirent, DirentList, Entry, Errno, + FileAttr, FileType, Filesystem, KernelConfig, MountOption, Open, RequestMeta, }; -use libc::ENOENT; use std::collections::HashMap; -use std::ffi::{c_int, OsStr}; +use std::path::Path; use std::fs::File; use std::rc::{Rc, Weak}; use std::time::{Duration, UNIX_EPOCH}; @@ -87,6 +86,12 @@ struct PassthroughFs { backing_cache: BackingCache, } +const ROOT_DIR_ENTRIES: [Dirent; 3] = [ + Dirent { ino: 1, offset: 1, kind: FileType::Directory, name: Bytes::Ref(b".") }, + Dirent { ino: 1, offset: 2, kind: FileType::Directory, name: Bytes::Ref(b"..") }, + Dirent { ino: 2, offset: 3, kind: FileType::RegularFile, name: Bytes::Ref(b"passthrough") }, +]; + impl PassthroughFs { fn new() -> Self { let uid = unsafe { libc::getuid() }; @@ -139,88 +144,99 @@ impl PassthroughFs { impl Filesystem for PassthroughFs { fn init( &mut self, - _req: &Request, - config: &mut KernelConfig, - ) -> std::result::Result<(), c_int> { - config.add_capabilities(consts::FUSE_PASSTHROUGH).unwrap(); + _req: RequestMeta, + config: KernelConfig, + ) -> Result { + let mut config = config; + config.add_capabilities(consts::FUSE_PASSTHROUGH) + .expect("FUSE Kernel did not advertise support for passthrough. Refused capability"); config.set_max_stack_depth(2).unwrap(); - Ok(()) + Ok(config) } - fn lookup(&mut self, _req: &Request, parent: u64, name: &OsStr, reply: ReplyEntry) { + fn lookup(&mut self, _req: RequestMeta, parent: u64, name: &Path) -> Result { if parent == 1 && name.to_str() == Some("passthrough") { - reply.entry(&TTL, &self.passthrough_file_attr, 0); + Ok(Entry { + ino: self.passthrough_file_attr.ino, + generation: None, + file_ttl: TTL, + attr: self.passthrough_file_attr, + attr_ttl: TTL, + }) } else { - reply.error(ENOENT); + Err(Errno::ENOENT) } } - fn getattr(&mut self, _req: &Request, ino: u64, _fh: Option, reply: ReplyAttr) { + fn getattr(&mut self, + _req: RequestMeta, + ino: u64, + _fh: Option, + ) -> Result<(FileAttr, Duration), Errno> { match ino { - 1 => reply.attr(&TTL, &self.root_attr), - 2 => reply.attr(&TTL, &self.passthrough_file_attr), - _ => reply.error(ENOENT), + 1 => Ok((self.root_attr, TTL)), + 2 => Ok((self.passthrough_file_attr, TTL)), + _ =>Err(Errno::ENOENT), } } - fn open(&mut self, _req: &Request, ino: u64, _flags: i32, reply: ReplyOpen) { + fn open(&mut self, _req: RequestMeta, ino: u64, _flags: i32) -> Result { if ino != 2 { - reply.error(ENOENT); - return; + return Err(Errno::ENOENT); } let (fh, id) = self .backing_cache .get_or(ino, || { - let file = File::open("/etc/os-release")?; - reply.open_backing(file) + let _file = File::open("/etc/os-release")?; + // TODO: Implement opening the backing file and returning appropriate + // information, possibly including a BackingId within the Open struct, + // or handle it through other means if fd-passthrough is intended here. + Err(std::io::Error::new(std::io::ErrorKind::Other, "TODO: passthrough open not fully implemented")) }) .unwrap(); eprintln!(" -> opened_passthrough({fh:?}, 0, {id:?});\n"); - reply.opened_passthrough(fh, 0, &id); + // TODO: Ensure fd-passthrough is correctly set up if intended. + // The Open struct would carry necessary info. + // TODO: implement flags for Open struct + Ok(Open{fh, flags: 0 }) } fn release( &mut self, - _req: &Request<'_>, + _req: RequestMeta, _ino: u64, fh: u64, _flags: i32, _lock_owner: Option, _flush: bool, - reply: ReplyEmpty, - ) { + ) -> Result<(), Errno> { self.backing_cache.put(fh); - reply.ok(); + Ok(()) } - fn readdir( + fn readdir<'dir, 'name>( &mut self, - _req: &Request, + _req: RequestMeta, ino: u64, _fh: u64, offset: i64, - mut reply: ReplyDirectory, - ) { + _max_bytes: u32 + ) -> Result, Errno> { if ino != 1 { - reply.error(ENOENT); - return; + return Err(Errno::ENOENT); } - - let entries = vec![ - (1, FileType::Directory, "."), - (1, FileType::Directory, ".."), - (2, FileType::RegularFile, "passthrough"), - ]; - - for (i, entry) in entries.into_iter().enumerate().skip(offset as usize) { - // i + 1 means the index of the next entry - if reply.add(entry.0, (i + 1) as i64, entry.1, entry.2) { - break; - } + // In this example, return up to three entries depending on the offset. + if offset > 2 || offset < 0 { + // Case 1: offset out of range: + // No need to allocate anything; just use the Empty enum case. + Ok(DirentList::Empty) + } else { + // Case 2: offset in range: + // Return a borrowed ('static) slice of entries. + Ok((&ROOT_DIR_ENTRIES[offset as usize..]).into()) } - reply.ok(); } } diff --git a/examples/poll.rs b/examples/poll.rs index a86e0879..f6d4e8e3 100644 --- a/examples/poll.rs +++ b/examples/poll.rs @@ -7,10 +7,13 @@ // Due to the above provenance, unlike the rest of fuser this file is // licensed under the terms of the GNU GPLv2. +// Requires feature = "abi-7-11" + use std::{ convert::TryInto, - ffi::OsStr, - os::unix::ffi::OsStrExt, + ffi::OsString, + path::Path, + os::unix::ffi::{OsStrExt, OsStringExt}, // for converting to and from sync::{ atomic::{AtomicU64, Ordering::SeqCst}, Arc, Mutex, @@ -19,11 +22,11 @@ use std::{ time::{Duration, UNIX_EPOCH}, }; -use libc::{EACCES, EBADF, EBUSY, EINVAL, ENOENT, ENOTDIR}; - use fuser::{ consts::{FOPEN_DIRECT_IO, FOPEN_NONSEEKABLE, FUSE_POLL_SCHEDULE_NOTIFY}, - FileAttr, FileType, MountOption, PollHandle, Request, FUSE_ROOT_ID, + Bytes, Dirent, DirentList, Entry, Errno, + FileAttr, Filesystem, FileType, MountOption, Open, RequestMeta, + FUSE_ROOT_ID, }; const NUMFILES: u8 = 16; @@ -80,28 +83,42 @@ impl FSelFS { } } -impl fuser::Filesystem for FSelFS { - fn lookup(&mut self, _req: &Request, parent: u64, name: &OsStr, reply: fuser::ReplyEntry) { - if parent != FUSE_ROOT_ID || name.len() != 1 { - reply.error(ENOENT); - return; +impl Filesystem for FSelFS { + fn lookup( + &mut self, + _req: RequestMeta, + parent: u64, + name: &Path + ) -> Result { + if parent != FUSE_ROOT_ID || name.as_os_str().len() != 1 { + return Err(Errno::ENOENT); } - let name = name.as_bytes(); + let name_bytes = name.as_os_str().as_bytes(); - let idx = match name[0] { - b'0'..=b'9' => name[0] - b'0', - b'A'..=b'F' => name[0] - b'A' + 10, + let idx = match name_bytes[0] { + b'0'..=b'9' => name_bytes[0] - b'0', + b'A'..=b'F' => name_bytes[0] - b'A' + 10, _ => { - reply.error(ENOENT); - return; + return Err(Errno::ENOENT); } }; - - reply.entry(&Duration::ZERO, &self.get_data().filestat(idx), 0); + Ok(Entry { + ino: FSelData::idx_to_ino(idx), + generation: Some(0), + file_ttl: Duration::ZERO, + attr: self.get_data().filestat(idx), + attr_ttl: Duration::ZERO, + }) } - fn getattr(&mut self, _req: &Request, ino: u64, _fh: Option, reply: fuser::ReplyAttr) { + fn getattr( + &mut self, + _req: + RequestMeta, + ino: u64, + _fh: Option + ) -> Result<(FileAttr, Duration), Errno> { if ino == FUSE_ROOT_ID { let a = FileAttr { ino: FUSE_ROOT_ID, @@ -120,150 +137,146 @@ impl fuser::Filesystem for FSelFS { flags: 0, blksize: 0, }; - reply.attr(&Duration::ZERO, &a); - return; + return Ok((a, Duration::ZERO)); } let idx = FSelData::ino_to_idx(ino); if idx < NUMFILES { - reply.attr(&Duration::ZERO, &self.get_data().filestat(idx)); + Ok((self.get_data().filestat(idx), Duration::ZERO)) } else { - reply.error(ENOENT); + Err(Errno::ENOENT) } } - fn readdir( + fn readdir<'dir, 'name>( &mut self, - _req: &Request, + _req: RequestMeta, ino: u64, _fh: u64, offset: i64, - mut reply: fuser::ReplyDirectory, - ) { + _max_bytes: u32, + ) -> Result, Errno> { if ino != FUSE_ROOT_ID { - reply.error(ENOTDIR); - return; + return Err(Errno::ENOTDIR); } - let Ok(offset): Result = offset.try_into() else { - reply.error(EINVAL); - return; + let Ok(start_offset): Result = offset.try_into() else { + return Err(Errno::EINVAL); }; - for idx in offset..NUMFILES { - let ascii = match idx { - 0..=9 => [b'0' + idx], - 10..=16 => [b'A' + idx - 10], - _ => panic!(), + let mut entries: Vec = Vec::new(); + for idx in start_offset..NUMFILES { + let ascii_char_val = match idx { + 0..=9 => b'0' + idx, + 10..=15 => b'A' + idx - 10, // Corrected range to 15 for NUMFILES = 16 + _ => panic!("idx out of range for NUMFILES"), }; - let name = OsStr::from_bytes(&ascii); - if reply.add( - FSelData::idx_to_ino(idx), - (idx + 1).into(), - FileType::RegularFile, - name, - ) { - break; - } + // Create OsString from the single byte character + let name_os_string = OsString::from_vec(vec![ascii_char_val]); + let entry_data = Dirent { + ino: FSelData::idx_to_ino(idx), + offset: (idx + 1).into(), + kind: FileType::RegularFile, + // Convert the OsString back into an owned vector, + // and then into an appropriate Bytes variant, in one step, + // using the From trait. + name: Bytes::from(name_os_string), + }; + entries.push(entry_data); + // Fuser library will ensure that max_bytes is respected. } - - reply.ok(); + // convert the vector of entries into an appropriate DirentList variant, + // using the Into trait + Ok(entries.into()) } - fn open(&mut self, _req: &Request, ino: u64, flags: i32, reply: fuser::ReplyOpen) { + fn open(&mut self, _req: RequestMeta, ino: u64, flags: i32) -> Result { let idx = FSelData::ino_to_idx(ino); if idx >= NUMFILES { - reply.error(ENOENT); - return; + return Err(Errno::ENOENT); } if (flags & libc::O_ACCMODE) != libc::O_RDONLY { - reply.error(EACCES); - return; + return Err(Errno::EACCES); } { let mut d = self.get_data(); if d.open_mask & (1 << idx) != 0 { - reply.error(EBUSY); - return; + return Err(Errno::EBUSY); } - d.open_mask |= 1 << idx; } - reply.opened(idx.into(), FOPEN_DIRECT_IO | FOPEN_NONSEEKABLE); + Ok(Open { + fh: idx.into(), // Using idx as file handle + flags: FOPEN_DIRECT_IO | FOPEN_NONSEEKABLE, + }) } fn release( &mut self, - _req: &Request, + _req: RequestMeta, _ino: u64, fh: u64, _flags: i32, _lock_owner: Option, _flush: bool, - reply: fuser::ReplyEmpty, - ) { - let idx = fh; + ) -> Result<(), Errno> { + let idx = fh; // fh is the idx from open() if idx >= NUMFILES.into() { - reply.error(EBADF); - return; + return Err(Errno::EBADF); } self.get_data().open_mask &= !(1 << idx); - reply.ok(); + Ok(()) } - fn read( + fn read<'a>( &mut self, - _req: &Request, + _req: RequestMeta, _ino: u64, fh: u64, - _offset: i64, - size: u32, + _offset: i64, // offset is ignored due to FOPEN_NONSEEKABLE + max_size: u32, _flags: i32, _lock_owner: Option, - reply: fuser::ReplyData, - ) { + ) -> Result, Errno> { let Ok(idx): Result = fh.try_into() else { - reply.error(EINVAL); - return; + return Err(Errno::EINVAL); }; if idx >= NUMFILES { - reply.error(EBADF); - return; + return Err(Errno::EBADF); } let cnt = &mut self.get_data().bytecnt[idx as usize]; - let size = (*cnt).min(size.into()); + let size = (*cnt).min(max_size.into()); println!("READ {:X} transferred={} cnt={}", idx, size, *cnt); *cnt -= size; let elt = match idx { 0..=9 => b'0' + idx, - 10..=16 => b'A' + idx - 10, - _ => panic!(), + 10..=15 => b'A' + idx - 10, // Corrected range + _ => panic!("idx out of range for NUMFILES"), }; let data = vec![elt; size.try_into().unwrap()]; - reply.data(data.as_slice()); + // example of converting to an explicit Bytes Box variant + Ok(Bytes::Box(data.into_boxed_slice())) } + #[cfg(feature = "abi-7-11")] fn poll( &mut self, - _req: &Request, + _req: RequestMeta, _ino: u64, fh: u64, - ph: PollHandle, + ph: u64, _events: u32, flags: u32, - reply: fuser::ReplyPoll, - ) { + ) -> Result { static POLLED_ZERO: AtomicU64 = AtomicU64::new(0); let Ok(idx): Result = fh.try_into() else { - reply.error(EINVAL); - return; + return Err(Errno::EINVAL); }; if idx >= NUMFILES { - reply.error(EBADF); - return; + return Err(Errno::EBADF); } let revents = { @@ -288,8 +301,7 @@ impl fuser::Filesystem for FSelFS { 0 } }; - - reply.poll(revents); + Ok(revents) } } @@ -342,3 +354,72 @@ fn main() { producer(&data, &bg.notifier()); } + +#[cfg(test)] +mod test { + use super::*; + use fuser::{Filesystem, RequestMeta, Errno}; + use std::sync::{Arc, Mutex}; + + fn setup_test_fs() -> FSelFS { + let data = Arc::new(Mutex::new(FSelData { + bytecnt: [0; NUMFILES as usize], + open_mask: 0, + notify_mask: 0, + poll_handles: [0; NUMFILES as usize], + })); + FSelFS { data } + } + + #[test] + fn test_poll_data_available() { + let mut fs = setup_test_fs(); + let req = RequestMeta { unique: 0, uid: 0, gid: 0, pid: 0 }; + let idx = 0; + let fh = idx as u64; + let ph = 1; + { + let mut data = fs.get_data(); + data.bytecnt[idx as usize] = 5; // Simulate data available + } + let result = fs.poll(req, FSelData::idx_to_ino(idx), fh, ph, libc::POLLIN as u32, FUSE_POLL_SCHEDULE_NOTIFY); + assert!(result.is_ok(), "Poll should succeed when data is available"); + if let Ok(revents) = result { + assert_eq!(revents, libc::POLLIN as u32, "Should return POLLIN when data is available"); + } + let data = fs.get_data(); + assert_eq!(data.notify_mask & (1 << idx), 1 << idx, "Notify mask should be set for this index"); + assert_eq!(data.poll_handles[idx as usize], 1, "Poll handle should be stored"); + } + + #[test] + fn test_poll_no_data() { + let mut fs = setup_test_fs(); + let req = RequestMeta { unique: 0, uid: 0, gid: 0, pid: 0 }; + let idx = 0; + let fh = idx as u64; + let ph = 1; + { + let mut data = fs.get_data(); + data.bytecnt[idx as usize] = 0; // No data available + } + let result = fs.poll(req, FSelData::idx_to_ino(idx), fh, ph, libc::POLLIN as u32, FUSE_POLL_SCHEDULE_NOTIFY); + assert!(result.is_ok(), "Poll should succeed even when no data is available"); + if let Ok(revents) = result { + assert_eq!(revents, 0, "Should return 0 when no data is available"); + } + } + + #[test] + fn test_poll_invalid_handle() { + let mut fs = setup_test_fs(); + let req = RequestMeta { unique: 0, uid: 0, gid: 0, pid: 0 }; + let invalid_idx = NUMFILES as u64; + let ph = 1; + let result = fs.poll(req, FSelData::idx_to_ino(0), invalid_idx, ph, libc::POLLIN as u32, FUSE_POLL_SCHEDULE_NOTIFY); + assert!(result.is_err(), "Poll should fail for invalid file handle"); + if let Err(e) = result { + assert_eq!(e, Errno::EBADF, "Should return EBADF for invalid handle"); + } + } +} diff --git a/examples/simple.rs b/examples/simple.rs index 6f7c0f3a..0e3adc85 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -5,30 +5,31 @@ use clap::{crate_version, Arg, ArgAction, Command}; use fuser::consts::FOPEN_DIRECT_IO; #[cfg(feature = "abi-7-26")] use fuser::consts::FUSE_HANDLE_KILLPRIV; -// #[cfg(feature = "abi-7-31")] -// use fuser::consts::FUSE_WRITE_KILL_PRIV; +/* +// Note: see fn write(). +#[cfg(feature = "abi-7-31")] +use fuser::consts::FUSE_WRITE_KILL_PRIV; +*/ use fuser::TimeOrNow::Now; use fuser::{ - Filesystem, KernelConfig, MountOption, ReplyAttr, ReplyCreate, ReplyData, ReplyDirectory, - ReplyEmpty, ReplyEntry, ReplyOpen, ReplyStatfs, ReplyWrite, ReplyXattr, Request, TimeOrNow, + Bytes, Dirent, DirentList, Entry, Errno, FileAttr, Filesystem, + Forget, KernelConfig, MountOption, Open, RequestMeta, Statfs, TimeOrNow, Xattr, FUSE_ROOT_ID, }; -#[cfg(feature = "abi-7-26")] -use log::info; -use log::{debug, warn}; -use log::{error, LevelFilter}; +#[allow(unused_imports)] +use log::{debug, info, warn, error, LevelFilter}; use serde::{Deserialize, Serialize}; use std::cmp::min; use std::collections::BTreeMap; -use std::ffi::OsStr; use std::fs::{File, OpenOptions}; use std::io::{BufRead, BufReader, ErrorKind, Read, Seek, SeekFrom, Write}; use std::os::raw::c_int; +use std::ffi::OsStr; use std::os::unix::ffi::OsStrExt; +use std::path::{Path, PathBuf}; use std::os::unix::fs::FileExt; #[cfg(target_os = "linux")] use std::os::unix::io::IntoRawFd; -use std::path::{Path, PathBuf}; use std::sync::atomic::{AtomicU64, Ordering}; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use std::{env, fs, io}; @@ -130,16 +131,16 @@ fn xattr_access_check( key: &[u8], access_mask: i32, inode_attrs: &InodeAttributes, - request: &Request<'_>, + request: RequestMeta, ) -> Result<(), c_int> { match parse_xattr_namespace(key)? { XattrNamespace::Security => { - if access_mask != libc::R_OK && request.uid() != 0 { + if access_mask != libc::R_OK && request.uid != 0 { return Err(libc::EPERM); } } XattrNamespace::Trusted => { - if request.uid() != 0 { + if request.uid != 0 { return Err(libc::EPERM); } } @@ -149,13 +150,15 @@ fn xattr_access_check( inode_attrs.uid, inode_attrs.gid, inode_attrs.mode, - request.uid(), - request.gid(), + request.uid, + request.gid, access_mask, ) { return Err(libc::EPERM); } - } else if request.uid() != 0 { + } else if key.eq(b"system.posix_acl_default") | key.eq(b"system.nfs4_acl") { + return Err(libc::EOPNOTSUPP); + } else if request.uid != 0 { return Err(libc::EPERM); } } @@ -164,8 +167,8 @@ fn xattr_access_check( inode_attrs.uid, inode_attrs.gid, inode_attrs.mode, - request.uid(), - request.gid(), + request.uid, + request.gid, access_mask, ) { return Err(libc::EPERM); @@ -216,12 +219,12 @@ struct InodeAttributes { pub xattrs: BTreeMap, Vec>, } -impl From for fuser::FileAttr { +impl From for FileAttr { fn from(attrs: InodeAttributes) -> Self { - fuser::FileAttr { + FileAttr { ino: attrs.inode, size: attrs.size, - blocks: (attrs.size + BLOCK_SIZE - 1) / BLOCK_SIZE, + blocks: attrs.size.div_ceil(BLOCK_SIZE), atime: system_time_from_time(attrs.last_accessed.0, attrs.last_accessed.1), mtime: system_time_from_time(attrs.last_modified.0, attrs.last_modified.1), ctime: system_time_from_time( @@ -248,6 +251,7 @@ struct SimpleFS { next_file_handle: AtomicU64, direct_io: bool, suid_support: bool, + usermode: bool } impl SimpleFS { @@ -255,6 +259,7 @@ impl SimpleFS { data_dir: String, direct_io: bool, #[allow(unused_variables)] suid_support: bool, + usermode: bool ) -> SimpleFS { #[cfg(feature = "abi-7-26")] { @@ -263,6 +268,7 @@ impl SimpleFS { next_file_handle: AtomicU64::new(1), direct_io, suid_support, + usermode, } } #[cfg(not(feature = "abi-7-26"))] @@ -272,6 +278,7 @@ impl SimpleFS { next_file_handle: AtomicU64::new(1), direct_io, suid_support: false, + usermode, } } } @@ -442,7 +449,7 @@ impl SimpleFS { fn insert_link( &self, - req: &Request, + req: RequestMeta, parent: u64, name: &OsStr, inode: u64, @@ -458,8 +465,8 @@ impl SimpleFS { parent_attrs.uid, parent_attrs.gid, parent_attrs.mode, - req.uid(), - req.gid(), + req.uid, + req.gid, libc::W_OK, ) { return Err(libc::EACCES); @@ -479,16 +486,29 @@ impl SimpleFS { impl Filesystem for SimpleFS { fn init( &mut self, - _req: &Request, - #[allow(unused_variables)] config: &mut KernelConfig, - ) -> Result<(), c_int> { + _req: RequestMeta, + config: KernelConfig, + ) -> Result { #[cfg(feature = "abi-7-26")] - config.add_capabilities(FUSE_HANDLE_KILLPRIV).unwrap(); - + let config = { + let mut config = config; + config.add_capabilities(FUSE_HANDLE_KILLPRIV).unwrap(); + config + }; fs::create_dir_all(Path::new(&self.data_dir).join("inodes")).unwrap(); fs::create_dir_all(Path::new(&self.data_dir).join("contents")).unwrap(); if self.get_inode(FUSE_ROOT_ID).is_err() { // Initialize with empty filesystem + let (init_uid, init_gid, init_mode) = if self.usermode { + // root dir: owned by current user, private + use libc::{getuid, getgid}; + let current_uid = unsafe { getuid() }; + let current_gid = unsafe { getgid() }; + (current_uid, current_gid, 0o700) + } else { + // root dir: owned by root user, world writable + (0, 0, 0o777) + }; let root = InodeAttributes { inode: FUSE_ROOT_ID, open_file_handles: 0, @@ -497,10 +517,10 @@ impl Filesystem for SimpleFS { last_modified: time_now(), last_metadata_changed: time_now(), kind: FileKind::Directory, - mode: 0o777, + mode: init_mode, hardlinks: 2, - uid: 0, - gid: 0, + uid: init_uid, + gid: init_gid, xattrs: Default::default(), }; self.write_inode(&root); @@ -508,77 +528,90 @@ impl Filesystem for SimpleFS { entries.insert(b".".to_vec(), (FUSE_ROOT_ID, FileKind::Directory)); self.write_directory_content(FUSE_ROOT_ID, entries); } - Ok(()) + Ok(config) } - fn lookup(&mut self, req: &Request, parent: u64, name: &OsStr, reply: ReplyEntry) { - if name.len() > MAX_NAME_LENGTH as usize { - reply.error(libc::ENAMETOOLONG); - return; + fn destroy(&mut self) {} + + fn lookup(&mut self, req: RequestMeta, parent: u64, name: &Path) -> Result { + if name.as_os_str().len() > MAX_NAME_LENGTH as usize { + return Err(Errno::ENAMETOOLONG); } - let parent_attrs = self.get_inode(parent).unwrap(); + let parent_attrs = match self.get_inode(parent) { + Ok(attrs) => attrs, + Err(e) => { + return Err(Errno::from_i32(e)); + } + }; if !check_access( parent_attrs.uid, parent_attrs.gid, parent_attrs.mode, - req.uid(), - req.gid(), + req.uid, + req.gid, libc::X_OK, ) { - reply.error(libc::EACCES); - return; + return Err(Errno::EACCES); } - match self.lookup_name(parent, name) { - Ok(attrs) => reply.entry(&Duration::new(0, 0), &attrs.into(), 0), - Err(error_code) => reply.error(error_code), + match self.lookup_name(parent, name.as_os_str()) { + Ok(attrs) => Ok(Entry { + ino: attrs.inode, + generation: None, + file_ttl: Duration::new(0, 0), + attr: attrs.into(), + attr_ttl: Duration::new(0, 0), + }), + Err(error_code) => Err(Errno::from_i32(error_code)), } } - fn forget(&mut self, _req: &Request, _ino: u64, _nlookup: u64) {} + fn forget(&mut self, _req: RequestMeta, _target: Forget) {} - fn getattr(&mut self, _req: &Request, inode: u64, _fh: Option, reply: ReplyAttr) { - match self.get_inode(inode) { - Ok(attrs) => reply.attr(&Duration::new(0, 0), &attrs.into()), - Err(error_code) => reply.error(error_code), + fn getattr( + &mut self, + _req: RequestMeta, + ino: u64, + _fh: Option, + ) -> Result<(FileAttr, Duration), Errno> { + match self.get_inode(ino) { + Ok(inode) => Ok((inode.into(), Duration::new(0, 0))), + Err(e) => Err(Errno::from_i32(e)), } } fn setattr( &mut self, - req: &Request, + req: RequestMeta, inode: u64, - mode: Option, - uid: Option, - gid: Option, - size: Option, - atime: Option, - mtime: Option, + mode_option: Option, + uid_option: Option, + gid_option: Option, + size_option: Option, + atime_option: Option, + mtime_option: Option, _ctime: Option, fh: Option, _crtime: Option, _chgtime: Option, _bkuptime: Option, _flags: Option, - reply: ReplyAttr, - ) { + ) -> Result<(FileAttr, Duration), Errno> { let mut attrs = match self.get_inode(inode) { Ok(attrs) => attrs, Err(error_code) => { - reply.error(error_code); - return; + return Err(Errno::from_i32(error_code)); } }; - if let Some(mode) = mode { + if let Some(mode) = mode_option { debug!("chmod() called with {:?}, {:o}", inode, mode); - if req.uid() != 0 && req.uid() != attrs.uid { - reply.error(libc::EPERM); - return; + if req.uid != 0 && req.uid != attrs.uid { + return Err(Errno::EPERM); } - if req.uid() != 0 - && req.gid() != attrs.gid - && !get_groups(req.pid()).contains(&attrs.gid) + if req.uid != 0 + && req.gid != attrs.gid + && !get_groups(req.pid).contains(&attrs.gid) { // If SGID is set and the file belongs to a group that the caller is not part of // then the SGID bit is suppose to be cleared during chmod @@ -588,32 +621,28 @@ impl Filesystem for SimpleFS { } attrs.last_metadata_changed = time_now(); self.write_inode(&attrs); - reply.attr(&Duration::new(0, 0), &attrs.into()); - return; + return Ok((attrs.into(), Duration::new(0, 0))); } - if uid.is_some() || gid.is_some() { - debug!("chown() called with {:?} {:?} {:?}", inode, uid, gid); - if let Some(gid) = gid { + if uid_option.is_some() || gid_option.is_some() { + debug!("chown() called with {:?} {:?} {:?}", inode, uid_option, gid_option); + if let Some(gid) = gid_option { // Non-root users can only change gid to a group they're in - if req.uid() != 0 && !get_groups(req.pid()).contains(&gid) { - reply.error(libc::EPERM); - return; + if req.uid != 0 && !get_groups(req.pid).contains(&gid) { + return Err(Errno::EPERM); } } - if let Some(uid) = uid { - if req.uid() != 0 + if let Some(uid) = uid_option { + if req.uid != 0 // but no-op changes by the owner are not an error - && !(uid == attrs.uid && req.uid() == attrs.uid) + && !(uid == attrs.uid && req.uid == attrs.uid) { - reply.error(libc::EPERM); - return; + return Err(Errno::EPERM); } } // Only owner may change the group - if gid.is_some() && req.uid() != 0 && req.uid() != attrs.uid { - reply.error(libc::EPERM); - return; + if gid_option.is_some() && req.uid != 0 && req.uid != attrs.uid { + return Err(Errno::EPERM); } if attrs.mode & (libc::S_IXUSR | libc::S_IXGRP | libc::S_IXOTH) as u16 != 0 { @@ -621,67 +650,61 @@ impl Filesystem for SimpleFS { clear_suid_sgid(&mut attrs); } - if let Some(uid) = uid { + if let Some(uid) = uid_option { attrs.uid = uid; // Clear SETUID on owner change attrs.mode &= !libc::S_ISUID as u16; } - if let Some(gid) = gid { + if let Some(gid) = gid_option { attrs.gid = gid; // Clear SETGID unless user is root - if req.uid() != 0 { + if req.uid != 0 { attrs.mode &= !libc::S_ISGID as u16; } } attrs.last_metadata_changed = time_now(); self.write_inode(&attrs); - reply.attr(&Duration::new(0, 0), &attrs.into()); - return; + return Ok((attrs.into(), Duration::new(0, 0))); } - if let Some(size) = size { + if let Some(size) = size_option { debug!("truncate() called with {:?} {:?}", inode, size); - if let Some(handle) = fh { - // If the file handle is available, check access locally. - // This is important as it preserves the semantic that a file handle opened - // with W_OK will never fail to truncate, even if the file has been subsequently - // chmod'ed + let truncated_attrs_result = if let Some(handle) = fh { if self.check_file_handle_write(handle) { - if let Err(error_code) = self.truncate(inode, size, 0, 0) { - reply.error(error_code); - return; - } + self.truncate(inode, size, 0, 0) } else { - reply.error(libc::EACCES); - return; + return Err(Errno::EACCES); } - } else if let Err(error_code) = self.truncate(inode, size, req.uid(), req.gid()) { - reply.error(error_code); - return; - } - } + } else { + self.truncate(inode, size, req.uid, req.gid) + }; + return match truncated_attrs_result { + Ok(current_attrs) => Ok((current_attrs.into(), Duration::new(0, 0))), + Err(error_code) => Err(Errno::from_i32(error_code)), + }; + } + // Note: If any of the above attributes were changed, the remaining part is not reached. let now = time_now(); - if let Some(atime) = atime { + let mut modified_time_attr = false; + if let Some(atime) = atime_option { debug!("utimens() called with {:?}, atime={:?}", inode, atime); - if attrs.uid != req.uid() && req.uid() != 0 && atime != Now { - reply.error(libc::EPERM); - return; + if attrs.uid != req.uid && req.uid != 0 && atime != Now { + return Err(Errno::EPERM); } - if attrs.uid != req.uid() + if attrs.uid != req.uid && !check_access( attrs.uid, attrs.gid, attrs.mode, - req.uid(), - req.gid(), + req.uid, + req.gid, libc::W_OK, ) { - reply.error(libc::EACCES); - return; + return Err(Errno::EACCES); } attrs.last_accessed = match atime { @@ -689,28 +712,26 @@ impl Filesystem for SimpleFS { Now => now, }; attrs.last_metadata_changed = now; - self.write_inode(&attrs); + modified_time_attr = true; } - if let Some(mtime) = mtime { + if let Some(mtime) = mtime_option { debug!("utimens() called with {:?}, mtime={:?}", inode, mtime); - if attrs.uid != req.uid() && req.uid() != 0 && mtime != Now { - reply.error(libc::EPERM); - return; + if attrs.uid != req.uid && req.uid != 0 && mtime != Now { + return Err(Errno::EPERM); } - if attrs.uid != req.uid() + if attrs.uid != req.uid && !check_access( attrs.uid, attrs.gid, attrs.mode, - req.uid(), - req.gid(), + req.uid, + req.gid, libc::W_OK, ) { - reply.error(libc::EACCES); - return; + return Err(Errno::EACCES); } attrs.last_modified = match mtime { @@ -718,37 +739,50 @@ impl Filesystem for SimpleFS { Now => now, }; attrs.last_metadata_changed = now; + modified_time_attr = true; + } + + if modified_time_attr { self.write_inode(&attrs); } - let attrs = self.get_inode(inode).unwrap(); - reply.attr(&Duration::new(0, 0), &attrs.into()); - return; + // If atime/mtime were set, or if no attributes were set, + // we fetch the latest attributes and return them. + let final_attrs = self.get_inode(inode).map_err(Errno::from_i32)?; + Ok((final_attrs.into(), Duration::new(0, 0))) } - fn readlink(&mut self, _req: &Request, inode: u64, reply: ReplyData) { + fn readlink<'a>(&mut self, _req: RequestMeta, inode: u64) -> Result, Errno> { debug!("readlink() called on {:?}", inode); let path = self.content_path(inode); - if let Ok(mut file) = File::open(path) { - let file_size = file.metadata().unwrap().len(); - let mut buffer = vec![0; file_size as usize]; - file.read_exact(&mut buffer).unwrap(); - reply.data(&buffer); - } else { - reply.error(libc::ENOENT); + match File::open(path) { + Ok(mut file) => { + let file_size = match file.metadata() { + Ok(md) => md.len(), + Err(_) => return Err(Errno::EIO), // Or some other appropriate error + }; + let mut buffer = vec![0; file_size as usize]; + match file.read_exact(&mut buffer) { + Ok(_) => { + // OsString into OsBox::Owned + Ok(Bytes::from(buffer)) + } + Err(_) => Err(Errno::EIO), // Or some other appropriate error + } + } + Err(_) => Err(Errno::ENOENT), } } fn mknod( &mut self, - req: &Request, + req: RequestMeta, parent: u64, - name: &OsStr, - mut mode: u32, + name: &Path, + mode: u32, _umask: u32, _rdev: u32, - reply: ReplyEntry, - ) { + ) -> Result { let file_type = mode & libc::S_IFMT as u32; if file_type != libc::S_IFREG as u32 @@ -757,20 +791,17 @@ impl Filesystem for SimpleFS { { // TODO warn!("mknod() implementation is incomplete. Only supports regular files, symlinks, and directories. Got {:o}", mode); - reply.error(libc::EPERM); - return; + return Err(Errno::EPERM); } - if self.lookup_name(parent, name).is_ok() { - reply.error(libc::EEXIST); - return; + if self.lookup_name(parent, name.as_os_str()).is_ok() { + return Err(Errno::EEXIST); } let mut parent_attrs = match self.get_inode(parent) { Ok(attrs) => attrs, Err(error_code) => { - reply.error(error_code); - return; + return Err(Errno::from_i32(error_code)); } }; @@ -778,20 +809,22 @@ impl Filesystem for SimpleFS { parent_attrs.uid, parent_attrs.gid, parent_attrs.mode, - req.uid(), - req.gid(), + req.uid, + req.gid, libc::W_OK, ) { - reply.error(libc::EACCES); - return; + return Err(Errno::EACCES); } parent_attrs.last_modified = time_now(); parent_attrs.last_metadata_changed = time_now(); self.write_inode(&parent_attrs); - if req.uid() != 0 { - mode &= !(libc::S_ISUID | libc::S_ISGID) as u32; - } + let new_mode = if req.uid != 0 { + // regular users may not set uid or set gid + mode & !(libc::S_ISUID | libc::S_ISGID) as u32 + } else { + mode + }; let inode = self.allocate_next_inode(); let attrs = InodeAttributes { @@ -801,51 +834,52 @@ impl Filesystem for SimpleFS { last_accessed: time_now(), last_modified: time_now(), last_metadata_changed: time_now(), - kind: as_file_kind(mode), - mode: self.creation_mode(mode), + kind: as_file_kind(new_mode), + mode: self.creation_mode(new_mode), hardlinks: 1, - uid: req.uid(), - gid: creation_gid(&parent_attrs, req.gid()), + uid: req.uid, + gid: creation_gid(&parent_attrs, req.gid), xattrs: Default::default(), }; self.write_inode(&attrs); - File::create(self.content_path(inode)).unwrap(); + File::create(self.content_path(inode)).map_err(|_| Errno::EIO)?; - if as_file_kind(mode) == FileKind::Directory { + if as_file_kind(new_mode) == FileKind::Directory { let mut entries = BTreeMap::new(); entries.insert(b".".to_vec(), (inode, FileKind::Directory)); entries.insert(b"..".to_vec(), (parent, FileKind::Directory)); self.write_directory_content(inode, entries); } - let mut entries = self.get_directory_content(parent).unwrap(); - entries.insert(name.as_bytes().to_vec(), (inode, attrs.kind)); + let mut entries = self.get_directory_content(parent).map_err(Errno::from_i32)?; + entries.insert(name.as_os_str().as_bytes().to_vec(), (inode, attrs.kind)); self.write_directory_content(parent, entries); - - // TODO: implement flags - reply.entry(&Duration::new(0, 0), &attrs.into(), 0); + Ok(Entry { + ino: attrs.inode, + generation: None, + file_ttl: Duration::new(0, 0), + attr: attrs.into(), + attr_ttl: Duration::new(0, 0), + }) } fn mkdir( &mut self, - req: &Request, + req: RequestMeta, parent: u64, - name: &OsStr, - mut mode: u32, - _umask: u32, - reply: ReplyEntry, - ) { + name: &Path, + mode: u32, + #[allow(unused_variables)] _umask: u32, + ) -> Result { debug!("mkdir() called with {:?} {:?} {:o}", parent, name, mode); - if self.lookup_name(parent, name).is_ok() { - reply.error(libc::EEXIST); - return; + if self.lookup_name(parent, name.as_os_str()).is_ok() { + return Err(Errno::EEXIST); } let mut parent_attrs = match self.get_inode(parent) { Ok(attrs) => attrs, Err(error_code) => { - reply.error(error_code); - return; + return Err(Errno::from_i32(error_code)); } }; @@ -853,23 +887,25 @@ impl Filesystem for SimpleFS { parent_attrs.uid, parent_attrs.gid, parent_attrs.mode, - req.uid(), - req.gid(), + req.uid, + req.gid, libc::W_OK, ) { - reply.error(libc::EACCES); - return; + return Err(Errno::EACCES); } parent_attrs.last_modified = time_now(); parent_attrs.last_metadata_changed = time_now(); self.write_inode(&parent_attrs); - - if req.uid() != 0 { - mode &= !(libc::S_ISUID | libc::S_ISGID) as u32; - } - if parent_attrs.mode & libc::S_ISGID as u16 != 0 { - mode |= libc::S_ISGID as u32; - } + + let new_mode = if req.uid != 0 { + // regular users may not set uid or set gid + mode & !(libc::S_ISUID | libc::S_ISGID) as u32 + } else if parent_attrs.mode & libc::S_ISGID as u16 != 0 { + // root user must set gid if the parent diretory does + mode | libc::S_ISGID as u32 + } else { + mode + }; let inode = self.allocate_next_inode(); let attrs = InodeAttributes { @@ -880,10 +916,10 @@ impl Filesystem for SimpleFS { last_modified: time_now(), last_metadata_changed: time_now(), kind: FileKind::Directory, - mode: self.creation_mode(mode), + mode: self.creation_mode(new_mode), hardlinks: 2, // Directories start with link count of 2, since they have a self link - uid: req.uid(), - gid: creation_gid(&parent_attrs, req.gid()), + uid: req.uid, + gid: creation_gid(&parent_attrs, req.gid), xattrs: Default::default(), }; self.write_inode(&attrs); @@ -893,28 +929,31 @@ impl Filesystem for SimpleFS { entries.insert(b"..".to_vec(), (parent, FileKind::Directory)); self.write_directory_content(inode, entries); - let mut entries = self.get_directory_content(parent).unwrap(); - entries.insert(name.as_bytes().to_vec(), (inode, FileKind::Directory)); + let mut entries = self.get_directory_content(parent).map_err(Errno::from_i32)?; + entries.insert(name.as_os_str().as_bytes().to_vec(), (inode, FileKind::Directory)); self.write_directory_content(parent, entries); - - reply.entry(&Duration::new(0, 0), &attrs.into(), 0); + Ok(Entry { + ino: attrs.inode, + generation: None, + file_ttl: Duration::new(0, 0), + attr: attrs.into(), + attr_ttl: Duration::new(0, 0), + }) } - fn unlink(&mut self, req: &Request, parent: u64, name: &OsStr, reply: ReplyEmpty) { + fn unlink(&mut self, req: RequestMeta, parent: u64, name: &Path) -> Result<(), Errno> { debug!("unlink() called with {:?} {:?}", parent, name); - let mut attrs = match self.lookup_name(parent, name) { + let mut attrs = match self.lookup_name(parent, name.as_os_str()) { Ok(attrs) => attrs, Err(error_code) => { - reply.error(error_code); - return; + return Err(Errno::from_i32(error_code)); } }; let mut parent_attrs = match self.get_inode(parent) { Ok(attrs) => attrs, Err(error_code) => { - reply.error(error_code); - return; + return Err(Errno::from_i32(error_code)); } }; @@ -922,23 +961,21 @@ impl Filesystem for SimpleFS { parent_attrs.uid, parent_attrs.gid, parent_attrs.mode, - req.uid(), - req.gid(), + req.uid, + req.gid, libc::W_OK, ) { - reply.error(libc::EACCES); - return; + return Err(Errno::EACCES); } - let uid = req.uid(); + let uid = req.uid; // "Sticky bit" handling if parent_attrs.mode & libc::S_ISVTX as u16 != 0 && uid != 0 && uid != parent_attrs.uid && uid != attrs.uid { - reply.error(libc::EACCES); - return; + return Err(Errno::EACCES); } parent_attrs.last_metadata_changed = time_now(); @@ -951,55 +988,55 @@ impl Filesystem for SimpleFS { self.gc_inode(&attrs); let mut entries = self.get_directory_content(parent).unwrap(); - entries.remove(name.as_bytes()); + entries.remove(name.as_os_str().as_bytes()); self.write_directory_content(parent, entries); - reply.ok(); + Ok(()) } - fn rmdir(&mut self, req: &Request, parent: u64, name: &OsStr, reply: ReplyEmpty) { + fn rmdir(&mut self, req: RequestMeta, parent: u64, name: &Path) -> Result<(), Errno> { debug!("rmdir() called with {:?} {:?}", parent, name); - let mut attrs = match self.lookup_name(parent, name) { + let mut attrs = match self.lookup_name(parent, name.as_os_str()) { Ok(attrs) => attrs, Err(error_code) => { - reply.error(error_code); - return; + return Err(Errno::from_i32(error_code)); } }; let mut parent_attrs = match self.get_inode(parent) { Ok(attrs) => attrs, Err(error_code) => { - reply.error(error_code); - return; + return Err(Errno::from_i32(error_code)); } }; // Directories always have a self and parent link - if self.get_directory_content(attrs.inode).unwrap().len() > 2 { - reply.error(libc::ENOTEMPTY); - return; + match self.get_directory_content(attrs.inode) { + Ok(dir_entries) => { + if dir_entries.len() > 2 { + return Err(Errno::ENOTEMPTY); + } + } + Err(e) => return Err(Errno::from_i32(e)), } if !check_access( parent_attrs.uid, parent_attrs.gid, parent_attrs.mode, - req.uid(), - req.gid(), + req.uid, + req.gid, libc::W_OK, ) { - reply.error(libc::EACCES); - return; + return Err(Errno::EACCES); } // "Sticky bit" handling if parent_attrs.mode & libc::S_ISVTX as u16 != 0 - && req.uid() != 0 - && req.uid() != parent_attrs.uid - && req.uid() != attrs.uid + && req.uid != 0 + && req.uid != parent_attrs.uid + && req.uid != attrs.uid { - reply.error(libc::EACCES); - return; + return Err(Errno::EACCES); } parent_attrs.last_metadata_changed = time_now(); @@ -1011,21 +1048,20 @@ impl Filesystem for SimpleFS { self.write_inode(&attrs); self.gc_inode(&attrs); - let mut entries = self.get_directory_content(parent).unwrap(); - entries.remove(name.as_bytes()); + let mut entries = self.get_directory_content(parent).map_err(Errno::from_i32)?; + entries.remove(name.as_os_str().as_bytes()); self.write_directory_content(parent, entries); - reply.ok(); + Ok(()) } fn symlink( &mut self, - req: &Request, + req: RequestMeta, parent: u64, - link_name: &OsStr, + link_name: &Path, target: &Path, - reply: ReplyEntry, - ) { + ) -> Result { debug!( "symlink() called with {:?} {:?} {:?}", parent, link_name, target @@ -1033,8 +1069,7 @@ impl Filesystem for SimpleFS { let mut parent_attrs = match self.get_inode(parent) { Ok(attrs) => attrs, Err(error_code) => { - reply.error(error_code); - return; + return Err(Errno::from_i32(error_code)); } }; @@ -1042,12 +1077,11 @@ impl Filesystem for SimpleFS { parent_attrs.uid, parent_attrs.gid, parent_attrs.mode, - req.uid(), - req.gid(), + req.uid, + req.gid, libc::W_OK, ) { - reply.error(libc::EACCES); - return; + return Err(Errno::EACCES); } parent_attrs.last_modified = time_now(); parent_attrs.last_metadata_changed = time_now(); @@ -1064,15 +1098,14 @@ impl Filesystem for SimpleFS { kind: FileKind::Symlink, mode: 0o777, hardlinks: 1, - uid: req.uid(), - gid: creation_gid(&parent_attrs, req.gid()), + uid: req.uid, + gid: creation_gid(&parent_attrs, req.gid), xattrs: Default::default(), }; - if let Err(error_code) = self.insert_link(req, parent, link_name, inode, FileKind::Symlink) + if let Err(error_code) = self.insert_link(req, parent, link_name.as_os_str(), inode, FileKind::Symlink) { - reply.error(error_code); - return; + return Err(Errno::from_i32(error_code)); } self.write_inode(&attrs); @@ -1082,39 +1115,42 @@ impl Filesystem for SimpleFS { .create(true) .truncate(true) .open(path) - .unwrap(); - file.write_all(target.as_os_str().as_bytes()).unwrap(); - - reply.entry(&Duration::new(0, 0), &attrs.into(), 0); + .map_err(|_| Errno::EIO)?; + file.write_all(target.as_os_str().as_bytes()) + .map_err(|_| Errno::EIO)?; + Ok(Entry { + ino: attrs.inode, + generation: None, + file_ttl: Duration::new(0, 0), + attr: attrs.into(), + attr_ttl: Duration::new(0, 0), + }) } fn rename( &mut self, - req: &Request, + req: RequestMeta, parent: u64, - name: &OsStr, + name: &Path, new_parent: u64, - new_name: &OsStr, + new_name: &Path, flags: u32, - reply: ReplyEmpty, - ) { + ) -> Result<(), Errno> { debug!( "rename() called with: source {parent:?} {name:?}, \ destination {new_parent:?} {new_name:?}, flags {flags:#b}", ); - let mut inode_attrs = match self.lookup_name(parent, name) { + let mut inode_attrs = match self.lookup_name(parent, name.as_os_str()) { Ok(attrs) => attrs, Err(error_code) => { - reply.error(error_code); - return; + return Err(Errno::from_i32(error_code)); } }; let mut parent_attrs = match self.get_inode(parent) { Ok(attrs) => attrs, Err(error_code) => { - reply.error(error_code); - return; + return Err(Errno::from_i32(error_code)); } }; @@ -1122,29 +1158,26 @@ impl Filesystem for SimpleFS { parent_attrs.uid, parent_attrs.gid, parent_attrs.mode, - req.uid(), - req.gid(), + req.uid, + req.gid, libc::W_OK, ) { - reply.error(libc::EACCES); - return; + return Err(Errno::EACCES); } // "Sticky bit" handling if parent_attrs.mode & libc::S_ISVTX as u16 != 0 - && req.uid() != 0 - && req.uid() != parent_attrs.uid - && req.uid() != inode_attrs.uid + && req.uid != 0 + && req.uid != parent_attrs.uid + && req.uid != inode_attrs.uid { - reply.error(libc::EACCES); - return; + return Err(Errno::EACCES); } let mut new_parent_attrs = match self.get_inode(new_parent) { Ok(attrs) => attrs, Err(error_code) => { - reply.error(error_code); - return; + return Err(Errno::from_i32(error_code)); } }; @@ -1152,47 +1185,44 @@ impl Filesystem for SimpleFS { new_parent_attrs.uid, new_parent_attrs.gid, new_parent_attrs.mode, - req.uid(), - req.gid(), + req.uid, + req.gid, libc::W_OK, ) { - reply.error(libc::EACCES); - return; + return Err(Errno::EACCES); } // "Sticky bit" handling in new_parent if new_parent_attrs.mode & libc::S_ISVTX as u16 != 0 { - if let Ok(existing_attrs) = self.lookup_name(new_parent, new_name) { - if req.uid() != 0 - && req.uid() != new_parent_attrs.uid - && req.uid() != existing_attrs.uid + if let Ok(existing_attrs) = self.lookup_name(new_parent, new_name.as_os_str()) { + if req.uid != 0 + && req.uid != new_parent_attrs.uid + && req.uid != existing_attrs.uid { - reply.error(libc::EACCES); - return; + return Err(Errno::EACCES); } } } #[cfg(target_os = "linux")] if flags & libc::RENAME_EXCHANGE as u32 != 0 { - let mut new_inode_attrs = match self.lookup_name(new_parent, new_name) { + let mut new_inode_attrs = match self.lookup_name(new_parent, new_name.as_os_str()) { Ok(attrs) => attrs, Err(error_code) => { - reply.error(error_code); - return; + return Err(Errno::from_i32(error_code)); } }; - let mut entries = self.get_directory_content(new_parent).unwrap(); + let mut entries = self.get_directory_content(new_parent).map_err(Errno::from_i32)?; entries.insert( - new_name.as_bytes().to_vec(), + new_name.as_os_str().as_bytes().to_vec(), (inode_attrs.inode, inode_attrs.kind), ); self.write_directory_content(new_parent, entries); - let mut entries = self.get_directory_content(parent).unwrap(); + let mut entries = self.get_directory_content(parent).map_err(Errno::from_i32)?; entries.insert( - name.as_bytes().to_vec(), + name.as_os_str().as_bytes().to_vec(), (new_inode_attrs.inode, new_inode_attrs.kind), ); self.write_directory_content(parent, entries); @@ -1209,31 +1239,26 @@ impl Filesystem for SimpleFS { self.write_inode(&new_inode_attrs); if inode_attrs.kind == FileKind::Directory { - let mut entries = self.get_directory_content(inode_attrs.inode).unwrap(); + let mut entries = self.get_directory_content(inode_attrs.inode).map_err(Errno::from_i32)?; entries.insert(b"..".to_vec(), (new_parent, FileKind::Directory)); self.write_directory_content(inode_attrs.inode, entries); } if new_inode_attrs.kind == FileKind::Directory { - let mut entries = self.get_directory_content(new_inode_attrs.inode).unwrap(); + let mut entries = self.get_directory_content(new_inode_attrs.inode).map_err(Errno::from_i32)?; entries.insert(b"..".to_vec(), (parent, FileKind::Directory)); self.write_directory_content(new_inode_attrs.inode, entries); } - reply.ok(); - return; + return Ok(()); } // Only overwrite an existing directory if it's empty - if let Ok(new_name_attrs) = self.lookup_name(new_parent, new_name) { - if new_name_attrs.kind == FileKind::Directory - && self - .get_directory_content(new_name_attrs.inode) - .unwrap() - .len() - > 2 - { - reply.error(libc::ENOTEMPTY); - return; + if let Ok(new_name_attrs) = self.lookup_name(new_parent, new_name.as_os_str()) { + if new_name_attrs.kind == FileKind::Directory { + let dir_entries = self.get_directory_content(new_name_attrs.inode).map_err(Errno::from_i32)?; + if dir_entries.len() > 2 { + return Err(Errno::ENOTEMPTY); + } } } @@ -1245,19 +1270,18 @@ impl Filesystem for SimpleFS { inode_attrs.uid, inode_attrs.gid, inode_attrs.mode, - req.uid(), - req.gid(), + req.uid, + req.gid, libc::W_OK, ) { - reply.error(libc::EACCES); - return; + return Err(Errno::EACCES); } // If target already exists decrement its hardlink count - if let Ok(mut existing_inode_attrs) = self.lookup_name(new_parent, new_name) { - let mut entries = self.get_directory_content(new_parent).unwrap(); - entries.remove(new_name.as_bytes()); + if let Ok(mut existing_inode_attrs) = self.lookup_name(new_parent, new_name.as_os_str()) { + let mut entries = self.get_directory_content(new_parent).map_err(Errno::from_i32)?; + entries.remove(new_name.as_os_str().as_bytes()); self.write_directory_content(new_parent, entries); if existing_inode_attrs.kind == FileKind::Directory { @@ -1270,13 +1294,13 @@ impl Filesystem for SimpleFS { self.gc_inode(&existing_inode_attrs); } - let mut entries = self.get_directory_content(parent).unwrap(); - entries.remove(name.as_bytes()); + let mut entries = self.get_directory_content(parent).map_err(Errno::from_i32)?; + entries.remove(name.as_os_str().as_bytes()); self.write_directory_content(parent, entries); - let mut entries = self.get_directory_content(new_parent).unwrap(); + let mut entries = self.get_directory_content(new_parent).map_err(Errno::from_i32)?; entries.insert( - new_name.as_bytes().to_vec(), + new_name.as_os_str().as_bytes().to_vec(), (inode_attrs.inode, inode_attrs.kind), ); self.write_directory_content(new_parent, entries); @@ -1291,22 +1315,21 @@ impl Filesystem for SimpleFS { self.write_inode(&inode_attrs); if inode_attrs.kind == FileKind::Directory { - let mut entries = self.get_directory_content(inode_attrs.inode).unwrap(); + let mut entries = self.get_directory_content(inode_attrs.inode).map_err(Errno::from_i32)?; entries.insert(b"..".to_vec(), (new_parent, FileKind::Directory)); self.write_directory_content(inode_attrs.inode, entries); } - reply.ok(); + Ok(()) } fn link( &mut self, - req: &Request, + req: RequestMeta, inode: u64, new_parent: u64, - new_name: &OsStr, - reply: ReplyEntry, - ) { + new_name: &Path, + ) -> Result { debug!( "link() called for {}, {}, {:?}", inode, new_parent, new_name @@ -1314,28 +1337,32 @@ impl Filesystem for SimpleFS { let mut attrs = match self.get_inode(inode) { Ok(attrs) => attrs, Err(error_code) => { - reply.error(error_code); - return; + return Err(Errno::from_i32(error_code)); } }; - if let Err(error_code) = self.insert_link(req, new_parent, new_name, inode, attrs.kind) { - reply.error(error_code); + if let Err(error_code) = self.insert_link(req, new_parent, new_name.as_os_str(), inode, attrs.kind) { + return Err(Errno::from_i32(error_code)); } else { attrs.hardlinks += 1; attrs.last_metadata_changed = time_now(); self.write_inode(&attrs); - reply.entry(&Duration::new(0, 0), &attrs.into(), 0); + return Ok(Entry { + ino: attrs.inode, + generation: None, + file_ttl: Duration::new(0, 0), + attr: attrs.into(), + attr_ttl: Duration::new(0, 0), + }); } } - fn open(&mut self, req: &Request, inode: u64, flags: i32, reply: ReplyOpen) { + fn open(&mut self, req: RequestMeta, inode: u64, flags: i32) -> Result { debug!("open() called for {:?}", inode); let (access_mask, read, write) = match flags & libc::O_ACCMODE { libc::O_RDONLY => { // Behavior is undefined, but most filesystems return EACCES if flags & libc::O_TRUNC != 0 { - reply.error(libc::EACCES); - return; + return Err(Errno::EACCES); } if flags & FMODE_EXEC != 0 { // Open is from internal exec syscall @@ -1348,8 +1375,7 @@ impl Filesystem for SimpleFS { libc::O_RDWR => (libc::R_OK | libc::W_OK, true, true), // Exactly one access mode flag must be specified _ => { - reply.error(libc::EINVAL); - return; + return Err(Errno::EINVAL); } }; @@ -1359,42 +1385,42 @@ impl Filesystem for SimpleFS { attr.uid, attr.gid, attr.mode, - req.uid(), - req.gid(), + req.uid, + req.gid, access_mask, ) { attr.open_file_handles += 1; self.write_inode(&attr); let open_flags = if self.direct_io { FOPEN_DIRECT_IO } else { 0 }; - reply.opened(self.allocate_next_file_handle(read, write), open_flags); + return Ok(Open { + fh: self.allocate_next_file_handle(read, write), + flags: open_flags, + }); } else { - reply.error(libc::EACCES); + return Err(Errno::EACCES); } - return; } - Err(error_code) => reply.error(error_code), + Err(error_code) => return Err(Errno::from_i32(error_code)), } } - fn read( + fn read<'a>( &mut self, - _req: &Request, + _req: RequestMeta, inode: u64, fh: u64, offset: i64, size: u32, _flags: i32, _lock_owner: Option, - reply: ReplyData, - ) { + ) -> Result, Errno> { debug!( "read() called on {:?} offset={:?} size={:?}", inode, offset, size ); assert!(offset >= 0); if !self.check_file_handle_read(fh) { - reply.error(libc::EACCES); - return; + return Err(Errno::EACCES); } let path = self.content_path(inode); @@ -1405,15 +1431,15 @@ impl Filesystem for SimpleFS { let mut buffer = vec![0; read_size as usize]; file.read_exact_at(&mut buffer, offset as u64).unwrap(); - reply.data(&buffer); + Ok(Bytes::from(buffer)) } else { - reply.error(libc::ENOENT); + Err(Errno::ENOENT) } } fn write( &mut self, - _req: &Request, + _req: RequestMeta, inode: u64, fh: u64, offset: i64, @@ -1421,19 +1447,17 @@ impl Filesystem for SimpleFS { _write_flags: u32, #[allow(unused_variables)] flags: i32, _lock_owner: Option, - reply: ReplyWrite, - ) { + ) -> Result { debug!("write() called with {:?} size={:?}", inode, data.len()); assert!(offset >= 0); if !self.check_file_handle_write(fh) { - reply.error(libc::EACCES); - return; + return Err(Errno::EACCES); } let path = self.content_path(inode); if let Ok(mut file) = OpenOptions::new().write(true).open(path) { file.seek(SeekFrom::Start(offset as u64)).unwrap(); - file.write_all(data).unwrap(); + file.write_all(&data).unwrap(); let mut attrs = self.get_inode(inode).unwrap(); attrs.last_metadata_changed = time_now(); @@ -1450,36 +1474,34 @@ impl Filesystem for SimpleFS { clear_suid_sgid(&mut attrs); self.write_inode(&attrs); - reply.written(data.len() as u32); + Ok(data.len() as u32) } else { - reply.error(libc::EBADF); + Err(Errno::EBADF) } } fn release( &mut self, - _req: &Request<'_>, + _req: RequestMeta, inode: u64, _fh: u64, _flags: i32, _lock_owner: Option, _flush: bool, - reply: ReplyEmpty, - ) { + ) -> Result<(), Errno> { if let Ok(mut attrs) = self.get_inode(inode) { attrs.open_file_handles -= 1; } - reply.ok(); + Ok(()) } - fn opendir(&mut self, req: &Request, inode: u64, flags: i32, reply: ReplyOpen) { + fn opendir(&mut self, req: RequestMeta, inode: u64, flags: i32) -> Result { debug!("opendir() called on {:?}", inode); let (access_mask, read, write) = match flags & libc::O_ACCMODE { libc::O_RDONLY => { // Behavior is undefined, but most filesystems return EACCES if flags & libc::O_TRUNC != 0 { - reply.error(libc::EACCES); - return; + return Err(Errno::EACCES); } (libc::R_OK, true, false) } @@ -1487,8 +1509,7 @@ impl Filesystem for SimpleFS { libc::O_RDWR => (libc::R_OK | libc::W_OK, true, true), // Exactly one access mode flag must be specified _ => { - reply.error(libc::EINVAL); - return; + return Err(Errno::EINVAL); } }; @@ -1498,216 +1519,220 @@ impl Filesystem for SimpleFS { attr.uid, attr.gid, attr.mode, - req.uid(), - req.gid(), + req.uid, + req.gid, access_mask, ) { attr.open_file_handles += 1; self.write_inode(&attr); let open_flags = if self.direct_io { FOPEN_DIRECT_IO } else { 0 }; - reply.opened(self.allocate_next_file_handle(read, write), open_flags); + return Ok(Open { + fh: self.allocate_next_file_handle(read, write), + flags: open_flags, + }); } else { - reply.error(libc::EACCES); + return Err(Errno::EACCES); } - return; } - Err(error_code) => reply.error(error_code), + Err(error_code) => return Err(Errno::from_i32(error_code)), } } - fn readdir( + fn readdir<'dir, 'name>( &mut self, - _req: &Request, + _req: RequestMeta, inode: u64, _fh: u64, offset: i64, - mut reply: ReplyDirectory, - ) { + _max_bytes: u32, + ) -> Result, Errno> { debug!("readdir() called with {:?}", inode); assert!(offset >= 0); - let entries = match self.get_directory_content(inode) { - Ok(entries) => entries, + // get_directory_content() returns an owned tree structure, or an error. + let tree = match self.get_directory_content(inode) { + Ok(tree) => tree, Err(error_code) => { - reply.error(error_code); - return; + return Err(Errno::from_i32(error_code)); } }; - - for (index, entry) in entries.iter().skip(offset as usize).enumerate() { - let (name, (inode, file_type)) = entry; - - let buffer_full: bool = reply.add( - *inode, - offset + index as i64 + 1, - (*file_type).into(), - OsStr::from_bytes(name), - ); - - if buffer_full { - break; - } + // Implicit anonymous lifetime '_ because this function won't be returning any borrowed data. + let mut entries = Vec::new(); + // into_iter() moves ownership of the elements into the loop variables, to avoid a copy + // This tree structure does not include an index; enumerate() adds an index + for (index, (name_bytes, (ino, file_kind))) in + tree.into_iter().enumerate().skip(offset as usize) + { + let dir_entry_data = Dirent { + ino, + offset: index as i64 + 1, // directory offsets are 1-indexed + kind: file_kind.into(), + name: Bytes::from(name_bytes), + }; + entries.push(dir_entry_data); + // TODO: Optionally, stop if bytes > _max_bytes + // if not here, then fuser library will still ensure that max_bytes is respected. } - - reply.ok(); + // In this example, into() moves ownership of the Vec> into the matching enum variant. + Ok(entries.into()) } fn releasedir( &mut self, - _req: &Request<'_>, + _req: RequestMeta, inode: u64, _fh: u64, _flags: i32, - reply: ReplyEmpty, - ) { + ) -> Result<(), Errno> { if let Ok(mut attrs) = self.get_inode(inode) { attrs.open_file_handles -= 1; } - reply.ok(); + Ok(()) } - fn statfs(&mut self, _req: &Request, _ino: u64, reply: ReplyStatfs) { + fn statfs(&mut self, _req: RequestMeta, _ino: u64) -> Result { warn!("statfs() implementation is a stub"); // TODO: real implementation of this - reply.statfs( - 10_000, - 10_000, - 10_000, - 1, - 10_000, - BLOCK_SIZE as u32, - MAX_NAME_LENGTH, - BLOCK_SIZE as u32, - ); + Ok(Statfs { + blocks: 10_000, + bfree: 10_000, + bavail: 10_000, + files: 1, + ffree: 10_000, + bsize: BLOCK_SIZE as u32, + namelen: MAX_NAME_LENGTH, + frsize: BLOCK_SIZE as u32, + }) } fn setxattr( &mut self, - request: &Request<'_>, + request: RequestMeta, inode: u64, key: &OsStr, value: &[u8], _flags: i32, _position: u32, - reply: ReplyEmpty, - ) { + ) -> Result<(), Errno> { if let Ok(mut attrs) = self.get_inode(inode) { if let Err(error) = xattr_access_check(key.as_bytes(), libc::W_OK, &attrs, request) { - reply.error(error); - return; + return Err(Errno::from_i32(error)); } attrs.xattrs.insert(key.as_bytes().to_vec(), value.to_vec()); attrs.last_metadata_changed = time_now(); self.write_inode(&attrs); - reply.ok(); + Ok(()) } else { - reply.error(libc::EBADF); + Err(Errno::EBADF) } } - fn getxattr( + fn getxattr<'a>( &mut self, - request: &Request<'_>, + request: RequestMeta, inode: u64, key: &OsStr, size: u32, - reply: ReplyXattr, - ) { - if let Ok(attrs) = self.get_inode(inode) { + ) -> Result, Errno> { + // attrs is declared mutable so that it can be disassembled for parts later. + if let Ok(mut attrs) = self.get_inode(inode) { if let Err(error) = xattr_access_check(key.as_bytes(), libc::R_OK, &attrs, request) { - reply.error(error); - return; + return Err(Errno::from_i32(error)); } - - if let Some(data) = attrs.xattrs.get(key.as_bytes()) { + // bmap.remove() takes ownership of the value, avoiding a copy. + if let Some(data) = attrs.xattrs.remove(key.as_bytes()) { if size == 0 { - reply.size(data.len() as u32); + return Ok(Xattr::Size(data.len() as u32)); } else if data.len() <= size as usize { - reply.data(data); + // vec.into_boxed_slice() takes ownership of the value, avoiding a copy. + return Ok(Xattr::Data(Bytes::from(data))); } else { - reply.error(libc::ERANGE); + return Err(Errno::ERANGE); } } else { #[cfg(target_os = "linux")] - reply.error(libc::ENODATA); + return Err(Errno::ENODATA); #[cfg(not(target_os = "linux"))] - reply.error(libc::ENOATTR); + return Err(Errno::ENOATTR); } } else { - reply.error(libc::EBADF); + Err(Errno::EBADF) } } - fn listxattr(&mut self, _req: &Request<'_>, inode: u64, size: u32, reply: ReplyXattr) { + fn listxattr<'a>( + &mut self, + _req: RequestMeta, + inode: u64, + size: u32 + ) -> Result, Errno> { if let Ok(attrs) = self.get_inode(inode) { let mut bytes = vec![]; - // Convert to concatenated null-terminated strings - for key in attrs.xattrs.keys() { - bytes.extend(key); - bytes.push(0); + // into_keys() takes ownership, potentially avoiding an accidental copy + // but probably a copy is unavoidable during the following reorganization. + for key in attrs.xattrs.into_keys() + { + bytes.extend(key); // concatenate keys + bytes.push(0); // as null-terminated } if size == 0 { - reply.size(bytes.len() as u32); + return Ok(Xattr::Size(bytes.len() as u32)); } else if bytes.len() <= size as usize { - reply.data(&bytes); + return Ok(Xattr::Data(Bytes::from(bytes))); } else { - reply.error(libc::ERANGE); + return Err(Errno::ERANGE); } } else { - reply.error(libc::EBADF); + Err(Errno::EBADF) } } - fn removexattr(&mut self, request: &Request<'_>, inode: u64, key: &OsStr, reply: ReplyEmpty) { + fn removexattr(&mut self, request: RequestMeta, inode: u64, key: &OsStr) -> Result<(), Errno> { if let Ok(mut attrs) = self.get_inode(inode) { if let Err(error) = xattr_access_check(key.as_bytes(), libc::W_OK, &attrs, request) { - reply.error(error); - return; + return Err(Errno::from_i32(error)); } if attrs.xattrs.remove(key.as_bytes()).is_none() { #[cfg(target_os = "linux")] - reply.error(libc::ENODATA); + return Err(Errno::ENODATA); #[cfg(not(target_os = "linux"))] - reply.error(libc::ENOATTR); - return; + return Err(Errno::ENOATTR); } attrs.last_metadata_changed = time_now(); self.write_inode(&attrs); - reply.ok(); + Ok(()) } else { - reply.error(libc::EBADF); + Err(Errno::EBADF) } } - fn access(&mut self, req: &Request, inode: u64, mask: i32, reply: ReplyEmpty) { + fn access(&mut self, req: RequestMeta, inode: u64, mask: i32) -> Result<(), Errno> { debug!("access() called with {:?} {:?}", inode, mask); match self.get_inode(inode) { Ok(attr) => { - if check_access(attr.uid, attr.gid, attr.mode, req.uid(), req.gid(), mask) { - reply.ok(); + if check_access(attr.uid, attr.gid, attr.mode, req.uid, req.gid, mask) { + return Ok(()); } else { - reply.error(libc::EACCES); + Err(Errno::EACCES) } } - Err(error_code) => reply.error(error_code), + Err(error_code) => Err(Errno::from_i32(error_code)), } } fn create( &mut self, - req: &Request, + req: RequestMeta, parent: u64, - name: &OsStr, + name: &Path, mut mode: u32, _umask: u32, flags: i32, - reply: ReplyCreate, - ) { + ) -> Result<(Entry, Open), Errno> { debug!("create() called with {:?} {:?}", parent, name); - if self.lookup_name(parent, name).is_ok() { - reply.error(libc::EEXIST); - return; + if self.lookup_name(parent, name.as_os_str()).is_ok() { + return Err(Errno::EEXIST); } let (read, write) = match flags & libc::O_ACCMODE { @@ -1716,16 +1741,14 @@ impl Filesystem for SimpleFS { libc::O_RDWR => (true, true), // Exactly one access mode flag must be specified _ => { - reply.error(libc::EINVAL); - return; + return Err(Errno::EINVAL); } }; let mut parent_attrs = match self.get_inode(parent) { Ok(attrs) => attrs, Err(error_code) => { - reply.error(error_code); - return; + return Err(Errno::from_i32(error_code)); } }; @@ -1733,18 +1756,17 @@ impl Filesystem for SimpleFS { parent_attrs.uid, parent_attrs.gid, parent_attrs.mode, - req.uid(), - req.gid(), + req.uid, + req.gid, libc::W_OK, ) { - reply.error(libc::EACCES); - return; + return Err(Errno::EACCES); } parent_attrs.last_modified = time_now(); parent_attrs.last_metadata_changed = time_now(); self.write_inode(&parent_attrs); - if req.uid() != 0 { + if req.uid != 0 { mode &= !(libc::S_ISUID | libc::S_ISGID) as u32; } @@ -1759,8 +1781,8 @@ impl Filesystem for SimpleFS { kind: as_file_kind(mode), mode: self.creation_mode(mode), hardlinks: 1, - uid: req.uid(), - gid: creation_gid(&parent_attrs, req.gid()), + uid: req.uid, + gid: creation_gid(&parent_attrs, req.gid), xattrs: Default::default(), }; self.write_inode(&attrs); @@ -1774,30 +1796,35 @@ impl Filesystem for SimpleFS { } let mut entries = self.get_directory_content(parent).unwrap(); - entries.insert(name.as_bytes().to_vec(), (inode, attrs.kind)); + entries.insert(name.as_os_str().as_bytes().to_vec(), (inode, attrs.kind)); self.write_directory_content(parent, entries); // TODO: implement flags - reply.created( - &Duration::new(0, 0), - &attrs.into(), - 0, - self.allocate_next_file_handle(read, write), - 0, - ); + return Ok(( + Entry { + ino: attrs.inode, + generation: None, + file_ttl: Duration::new(0, 0), + attr: attrs.into(), + attr_ttl: Duration::new(0, 0), + }, + Open { + fh: self.allocate_next_file_handle(read, write), + flags: 0, + }, + )); } #[cfg(target_os = "linux")] fn fallocate( &mut self, - _req: &Request<'_>, + _req: RequestMeta, inode: u64, _fh: u64, offset: i64, length: i64, mode: i32, - reply: ReplyEmpty, - ) { + ) -> Result<(), Errno> { let path = self.content_path(inode); if let Ok(file) = OpenOptions::new().write(true).open(path) { unsafe { @@ -1812,15 +1839,15 @@ impl Filesystem for SimpleFS { } self.write_inode(&attrs); } - reply.ok(); + Ok(()) } else { - reply.error(libc::ENOENT); + Err(Errno::ENOENT) } } fn copy_file_range( &mut self, - _req: &Request<'_>, + _req: RequestMeta, src_inode: u64, src_fh: u64, src_offset: i64, @@ -1829,19 +1856,16 @@ impl Filesystem for SimpleFS { dest_offset: i64, size: u64, _flags: u32, - reply: ReplyWrite, - ) { + ) -> Result { debug!( "copy_file_range() called with src ({}, {}, {}) dest ({}, {}, {}) size={}", src_fh, src_inode, src_offset, dest_fh, dest_inode, dest_offset, size ); if !self.check_file_handle_read(src_fh) { - reply.error(libc::EACCES); - return; + return Err(Errno::EACCES); } if !self.check_file_handle_write(dest_fh) { - reply.error(libc::EACCES); - return; + return Err(Errno::EACCES); } let src_path = self.content_path(src_inode); @@ -1866,12 +1890,12 @@ impl Filesystem for SimpleFS { } self.write_inode(&attrs); - reply.written(data.len() as u32); + return Ok(data.len() as u32); } else { - reply.error(libc::EBADF); + return Err(Errno::EBADF); } } else { - reply.error(libc::ENOENT); + return Err(Errno::ENOENT); } } } @@ -1991,6 +2015,12 @@ fn main() { .action(ArgAction::SetTrue) .help("Enable setuid support when run as root"), ) + .arg( + Arg::new("user") + .long("user") + .action(ArgAction::SetTrue) + .help("disable root-priviledge features"), + ) .arg( Arg::new("v") .short('v') @@ -2014,25 +2044,27 @@ fn main() { let mut options = vec![MountOption::FSName("fuser".to_string())]; - #[cfg(feature = "abi-7-26")] - { - if matches.get_flag("suid") { - info!("setuid bit support enabled"); - options.push(MountOption::Suid); - } else { + if !matches.get_flag("user"){ + #[cfg(feature = "abi-7-26")] + { + if matches.get_flag("suid") { + info!("setuid bit support enabled"); + options.push(MountOption::Suid); + } else { + options.push(MountOption::AutoUnmount); + } + } + #[cfg(not(feature = "abi-7-26"))] + { options.push(MountOption::AutoUnmount); } - } - #[cfg(not(feature = "abi-7-26"))] - { - options.push(MountOption::AutoUnmount); - } - if let Ok(enabled) = fuse_allow_other_enabled() { - if enabled { - options.push(MountOption::AllowOther); + if let Ok(enabled) = fuse_allow_other_enabled() { + if enabled { + options.push(MountOption::AllowOther); + } + } else { + eprintln!("Unable to read /etc/fuse.conf"); } - } else { - eprintln!("Unable to read /etc/fuse.conf"); } let data_dir = matches.get_one::("data-dir").unwrap().to_string(); @@ -2047,6 +2079,7 @@ fn main() { data_dir, matches.get_flag("direct-io"), matches.get_flag("suid"), + matches.get_flag("user") ), mountpoint, &options, @@ -2055,8 +2088,119 @@ fn main() { // Return a special error code for permission denied, which usually indicates that // "user_allow_other" is missing from /etc/fuse.conf if e.kind() == ErrorKind::PermissionDenied { - error!("{}", e.to_string()); + error!("{}", e); std::process::exit(2); } } } + +#[cfg(test)] +mod test { + use super::*; + + fn dummy_meta() -> RequestMeta { + RequestMeta { unique: 0, uid: 1000, gid: 1000, pid: 2000 } + } + + // Helper function to initialize a SimpleFS without a session mount + fn setup_test_fs(name: &str) -> SimpleFS { + let dir_path = format!("/tmp/tests/{}", name); + let fs = SimpleFS::new(dir_path.clone(), false, false, true); + // Ensure the directory structure is created + std::fs::create_dir_all(format!("{}/inodes", dir_path)).unwrap(); + std::fs::create_dir_all(format!("{}/contents", dir_path)).unwrap(); + // Initialize root inode + let root_attrs = InodeAttributes { + inode: FUSE_ROOT_ID, + open_file_handles: 0, + size: 0, + last_accessed: time_now(), + last_modified: time_now(), + last_metadata_changed: time_now(), + kind: FileKind::Directory, + mode: 0o755, + hardlinks: 2, + uid: 1000, + gid: 1000, + xattrs: Default::default(), + }; + fs.write_inode(&root_attrs); + let mut root_entries = BTreeMap::new(); + root_entries.insert(b".".to_vec(), (FUSE_ROOT_ID, FileKind::Directory)); + root_entries.insert(b"..".to_vec(), (FUSE_ROOT_ID, FileKind::Directory)); + fs.write_directory_content(FUSE_ROOT_ID, root_entries); + // Create a test file inode + let file_attrs = InodeAttributes { + inode: 2, + open_file_handles: 0, + size: 0, + last_accessed: time_now(), + last_modified: time_now(), + last_metadata_changed: time_now(), + kind: FileKind::File, + mode: 0o644, + hardlinks: 1, + uid: 1000, + gid: 1000, + xattrs: Default::default(), + }; + fs.write_inode(&file_attrs); + File::create(fs.content_path(2)).unwrap(); + let mut root_entries = fs.get_directory_content(FUSE_ROOT_ID).unwrap(); + root_entries.insert(b"testfile.txt".to_vec(), (2, FileKind::File)); + fs.write_directory_content(FUSE_ROOT_ID, root_entries); + fs + } + + #[test] + fn test_setattr_mode_change_success() { + let mut fs = setup_test_fs("setattr_success"); + let req = dummy_meta(); + let new_mode = 0o664; + let result = fs.setattr(req, 2, Some(new_mode), None, None, None, None, None, None, None, None, None, None, None); + assert!(result.is_ok(), "Setattr should succeed for mode change as owner"); + if let Ok((attr, _ttl)) = result { + assert_eq!(attr.perm, new_mode as u16, "Mode should be updated to 0o664"); + } + } + + #[test] + fn test_setattr_mode_change_fail_permission() { + let mut fs = setup_test_fs("setattr_fail"); + let mut req = dummy_meta(); + req.uid = 2000; // Different UID to simulate non-owner + let new_mode = 0o664; + let result = fs.setattr(req, 2, Some(new_mode), None, None, None, None, None, None, None, None, None, None, None); + assert!(result.is_err(), "Setattr should fail for mode change as non-owner"); + if let Err(e) = result { + assert_eq!(e, Errno::EPERM, "Should return EPERM for permission denied"); + } + } + + #[test] + fn test_symlink_readlink_success() { + let mut fs = setup_test_fs("symlink_success"); + let req = dummy_meta(); + let target = PathBuf::from("test_target.txt"); + let symlink_result = fs.symlink(req, FUSE_ROOT_ID, &PathBuf::from("testlink"), &target); + assert!(symlink_result.is_ok(), "Symlink creation should succeed"); + if let Ok(entry) = symlink_result { + let readlink_result = fs.readlink(req, entry.attr.ino); + assert!(readlink_result.is_ok(), "Readlink should succeed"); + if let Ok(target_data) = readlink_result { + assert_eq!(target_data.as_ref(), target.as_os_str().as_bytes(), "Readlink should return the correct target"); + } + } + } + + #[test] + fn test_readlink_fail_invalid_inode() { + let mut fs = setup_test_fs("readlink_fail"); + let req = dummy_meta(); + let result = fs.readlink(req, 9999); + assert!(result.is_err(), "Readlink should fail for invalid inode"); + if let Err(e) = result { + assert_eq!(e, Errno::ENOENT, "Should return ENOENT for non-existent inode"); + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 37970405..fcf53b9a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -918,7 +918,7 @@ pub trait Filesystem { _req: RequestMeta, ino: u64, fh: u64, - ph: PollHandle, + ph: u64, events: u32, flags: u32, ) -> Result { @@ -1029,10 +1029,13 @@ pub trait Filesystem { } /// Mount the given filesystem to the given mountpoint. This function will -/// not return until the filesystem is unmounted. +/// block until the filesystem is unmounted. /// -/// Note that you need to lead each option with a separate `"-o"` string. -#[deprecated(note = "use mount2() instead")] +/// `filesystem`: The filesystem implementation. +/// `mountpoint`: The path to the mountpoint. +/// `options`: A slice of mount options. Each option needs to be a separate string, +/// typically starting with `"-o"`. For example: `&[OsStr::new("-o"), OsStr::new("auto_unmount")]`. +#[deprecated(note = "Use `mount2` instead, which takes a slice of `MountOption` enums for better type safety and clarity.")] pub fn mount>( filesystem: FS, mountpoint: P, @@ -1043,9 +1046,13 @@ pub fn mount>( } /// Mount the given filesystem to the given mountpoint. This function will -/// not return until the filesystem is unmounted. +/// block until the filesystem is unmounted. /// -/// NOTE: This will eventually replace mount(), once the API is stable +/// `filesystem`: The filesystem implementation. +/// `mountpoint`: The path to the mountpoint. +/// `options`: A slice of `MountOption` enums specifying mount options. +/// +/// This is the recommended way to mount a FUSE filesystem. pub fn mount2>( filesystem: FS, mountpoint: P, @@ -1055,12 +1062,17 @@ pub fn mount2>( Session::new(filesystem, mountpoint.as_ref(), options).and_then(|mut se| se.run()) } -/// Mount the given filesystem to the given mountpoint. This function spawns -/// a background thread to handle filesystem operations while being mounted -/// and therefore returns immediately. The returned handle should be stored -/// to reference the mounted filesystem. If it's dropped, the filesystem will +/// Mount the given filesystem to the given mountpoint in a background thread. +/// This function spawns a new thread to handle filesystem operations and returns +/// immediately. The returned `BackgroundSession` handle should be stored to +/// keep the filesystem mounted. When the handle is dropped, the filesystem will /// be unmounted. -#[deprecated(note = "use spawn_mount2() instead")] +/// +/// `filesystem`: The filesystem implementation. Must be `Send + 'static`. +/// `mountpoint`: The path to the mountpoint. +/// `options`: A slice of mount options. Each option needs to be a separate string, +/// typically starting with `"-o"`. For example: `&[OsStr::new("-o"), OsStr::new("auto_unmount")]`. +#[deprecated(note = "Use `spawn_mount2` instead, which takes a slice of `MountOption` enums for better type safety and clarity.")] pub fn spawn_mount<'a, FS: Filesystem + Send + 'static + 'a, P: AsRef>( filesystem: FS, mountpoint: P, @@ -1074,13 +1086,17 @@ pub fn spawn_mount<'a, FS: Filesystem + Send + 'static + 'a, P: AsRef>( Session::new(filesystem, mountpoint.as_ref(), options.as_ref()).and_then(|se| se.spawn()) } -/// Mount the given filesystem to the given mountpoint. This function spawns -/// a background thread to handle filesystem operations while being mounted -/// and therefore returns immediately. The returned handle should be stored -/// to reference the mounted filesystem. If it's dropped, the filesystem will +/// Mount the given filesystem to the given mountpoint in a background thread. +/// This function spawns a new thread to handle filesystem operations and returns +/// immediately. The returned `BackgroundSession` handle should be stored to +/// keep the filesystem mounted. When the handle is dropped, the filesystem will /// be unmounted. /// -/// NOTE: This is the corresponding function to mount2. +/// `filesystem`: The filesystem implementation. Must be `Send + 'static`. +/// `mountpoint`: The path to the mountpoint. +/// `options`: A slice of `MountOption` enums specifying mount options. +/// +/// This is the recommended way to mount a FUSE filesystem in the background. pub fn spawn_mount2<'a, FS: Filesystem + Send + 'static + 'a, P: AsRef>( filesystem: FS, mountpoint: P, diff --git a/src/request.rs b/src/request.rs index 2b07644e..5c61c2a1 100644 --- a/src/request.rs +++ b/src/request.rs @@ -771,7 +771,7 @@ impl<'a> Request<'a> { self.meta, self.request.nodeid().into(), x.file_handle().into(), - ph, + ph.into(), x.events(), x.flags() ); From 28d788799db632a0d08fa5f8b7d9156ffc57e6a5 Mon Sep 17 00:00:00 2001 From: Richard Lawrence Date: Wed, 16 Jul 2025 16:55:50 -0500 Subject: [PATCH 08/19] Style changes suggested by Clippy. Created `no_reply()` method to prevent an error on `drop()`. --- build.rs | 4 +- examples/hello.rs | 14 +- examples/ioctl.rs | 12 +- examples/notify_inval_inode.rs | 8 +- examples/null.rs | 2 +- examples/passthrough.rs | 17 +- examples/poll.rs | 18 +- examples/poll_client.rs | 6 +- examples/simple.rs | 205 +++++++++++------------ src/channel.rs | 2 +- src/container/core.rs | 9 +- src/container/specialized.rs | 11 +- src/container/tests.rs | 20 +-- src/container/utils.rs | 9 +- src/lib.rs | 234 +++++++++++--------------- src/ll/argument.rs | 12 +- src/ll/fuse_abi.rs | 2 +- src/ll/mod.rs | 2 +- src/ll/reply.rs | 53 +++--- src/ll/request.rs | 226 ++++++++++++------------- src/mnt/fuse2_sys.rs | 2 + src/mnt/mod.rs | 11 +- src/mnt/mount_options.rs | 15 +- src/notify.rs | 2 +- src/reply.rs | 177 ++++++++++++-------- src/request.rs | 291 ++++++++++++++++----------------- src/session.rs | 13 +- 27 files changed, 696 insertions(+), 681 deletions(-) diff --git a/build.rs b/build.rs index 92ffe7fb..36e5bfdf 100644 --- a/build.rs +++ b/build.rs @@ -16,7 +16,7 @@ fn main() { if pkg_config::Config::new() .atleast_version("2.6.0") .probe("fuse") // for macFUSE 4.x - .map_err(|e| eprintln!("{}", e)) + .map_err(|e| eprintln!("{e}")) .is_ok() { println!("cargo:rustc-cfg=fuser_mount_impl=\"libfuse2\""); @@ -25,7 +25,7 @@ fn main() { pkg_config::Config::new() .atleast_version("2.6.0") .probe("osxfuse") // for osxfuse 3.x - .map_err(|e| eprintln!("{}", e)) + .map_err(|e| eprintln!("{e}")) .unwrap(); println!("cargo:rustc-cfg=fuser_mount_impl=\"libfuse2\""); } diff --git a/examples/hello.rs b/examples/hello.rs index 96d0a7a6..a7e47b5b 100644 --- a/examples/hello.rs +++ b/examples/hello.rs @@ -6,7 +6,7 @@ use fuser::{ use std::ffi::{OsStr, OsString}; use std::path::Path; use std::time::{Duration, UNIX_EPOCH}; -use std::sync::Arc; +use std::rc::Rc; const TTL: Duration = Duration::from_secs(1); // 1 second @@ -60,7 +60,7 @@ const DOT_ENTRY: Dirent<'static> = Dirent { /// Example Filesystem data struct HelloFS<'a> { - hello_entry: Arc>, + hello_entry: Rc>, } impl HelloFS<'_> { @@ -69,7 +69,7 @@ impl HelloFS<'_> { // An example of reusable Shared data. // Entry #3 is allocated here once. // It is persistent until replaced. - hello_entry: Arc::new( + hello_entry: Rc::new( Dirent { ino: 2, offset: 3, @@ -111,6 +111,7 @@ impl Filesystem for HelloFS<'static> { } } + #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] fn read<'a>( &mut self, _req: RequestMeta, @@ -137,6 +138,7 @@ impl Filesystem for HelloFS<'static> { } } + #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] fn readdir<'dir, 'name>( &mut self, _req: RequestMeta, @@ -273,21 +275,21 @@ mod test { assert_eq!(entries_slice.len(), 3, "Root directory should contain exactly 3 entries"); // Check entry 0: "." - let entry0_data = &entries_slice[0]; + let entry0_data = entries_slice[0]; assert_eq!(entry0_data.name.as_ref(), OsStr::new(".").as_bytes(), "First entry should be '.'"); assert_eq!(entry0_data.ino, 1, "Inode for '.' should be 1"); assert_eq!(entry0_data.offset, 1, "Offset for '.' should be 1"); assert_eq!(entry0_data.kind, FileType::Directory, "'.' should be a directory"); // Check entry 1: ".." - let entry1_data = &entries_slice[1]; + let entry1_data = entries_slice[1]; assert_eq!(entry1_data.name.as_ref(), OsStr::new("..").as_bytes(), "Second entry should be '..'"); assert_eq!(entry1_data.ino, 1, "Inode for '..' should be 1"); assert_eq!(entry1_data.offset, 2, "Offset for '..' should be 2"); assert_eq!(entry1_data.kind, FileType::Directory, "'..' should be a directory"); // Check entry 2: "hello.txt" - let entry2_data = &entries_slice[2]; + let entry2_data = entries_slice[2]; assert_eq!(entry2_data.name.as_ref(), OsStr::new("hello.txt").as_bytes(), "Third entry should be 'hello.txt'"); assert_eq!(entry2_data.ino, 2, "Inode for 'hello.txt' should be 2"); assert_eq!(entry2_data.offset, 3, "Offset for 'hello.txt' should be 3"); diff --git a/examples/ioctl.rs b/examples/ioctl.rs index cac725e3..e302bacf 100644 --- a/examples/ioctl.rs +++ b/examples/ioctl.rs @@ -2,6 +2,8 @@ // // cargo run --example ioctl --features abi-7-11 /tmp/foobar +#![allow(clippy::cast_possible_truncation)] // many conversions with unhandled errors + use clap::{crate_version, Arg, ArgAction, Command}; use fuser::{ Bytes, Dirent, DirentList, Entry, Errno, FileAttr, @@ -14,6 +16,9 @@ use std::time::{Duration, UNIX_EPOCH}; const TTL: Duration = Duration::from_secs(1); // 1 second +const FIOC_GET_SIZE: u64 = nix::request_code_read!('E', 0, std::mem::size_of::()); +const FIOC_SET_SIZE: u64 = nix::request_code_write!('E', 1, std::mem::size_of::()); + struct FiocFS { content: Vec, root_attr: FileAttr, @@ -99,6 +104,7 @@ impl Filesystem for FiocFS{ } } + #[allow(clippy::cast_sign_loss)] fn read<'a>( &mut self, _req: RequestMeta, @@ -128,6 +134,7 @@ impl Filesystem for FiocFS{ } } + #[allow(clippy::cast_possible_truncation)] fn readdir<'dir, 'name>( &mut self, _req: RequestMeta, @@ -159,9 +166,6 @@ impl Filesystem for FiocFS{ return Err(Errno::EINVAL); } - const FIOC_GET_SIZE: u64 = nix::request_code_read!('E', 0, std::mem::size_of::()); - const FIOC_SET_SIZE: u64 = nix::request_code_write!('E', 1, std::mem::size_of::()); - match cmd.into() { FIOC_GET_SIZE => { let size_bytes = self.content.len().to_ne_bytes(); @@ -179,7 +183,7 @@ impl Filesystem for FiocFS{ }) } _ => { - debug!("unknown ioctl: {}", cmd); + debug!("unknown ioctl: {cmd}"); Err(Errno::EINVAL) } } diff --git a/examples/notify_inval_inode.rs b/examples/notify_inval_inode.rs index 1af987ee..cb694aad 100644 --- a/examples/notify_inval_inode.rs +++ b/examples/notify_inval_inode.rs @@ -139,7 +139,7 @@ impl Filesystem for ClockFS<'_> { } else if flags & libc::O_ACCMODE != libc::O_RDONLY { Err(Errno::EACCES) } else if ino != Self::FILE_INO { - eprintln!("Got open for nonexistent inode {}", ino); + eprintln!("Got open for nonexistent inode {ino}"); Err(Errno::ENOENT) } else { Ok(Open { @@ -209,7 +209,7 @@ struct Options { #[clap(short, long)] no_notify: bool, - /// Use notify_store() instead of notify_inval_inode() + /// Use `notify_store()` instead of `notify_inval_inode()` #[clap(short = 's', long)] notify_store: bool, } @@ -237,12 +237,12 @@ fn main() { if let Err(e) = notifier.store(ClockFS::FILE_INO, 0, fdata.lock().unwrap().as_bytes()) { - eprintln!("Warning: failed to update kernel cache: {}", e); + eprintln!("Warning: failed to update kernel cache: {e}"); } } else if let Err(e) = notifier.inval_inode(ClockFS::FILE_INO, 0, olddata.len().try_into().unwrap()) { - eprintln!("Warning: failed to invalidate inode: {}", e); + eprintln!("Warning: failed to invalidate inode: {e}"); } } thread::sleep(Duration::from_secs_f32(opts.update_interval)); diff --git a/examples/null.rs b/examples/null.rs index 35dc67fe..67370284 100644 --- a/examples/null.rs +++ b/examples/null.rs @@ -13,7 +13,7 @@ fn main() { #[cfg(test)] mod test { - use fuser::{Filesystem, RequestMeta, Errno}; + use fuser::{Errno, Filesystem, RequestMeta}; use std::path::PathBuf; fn dummy_meta() -> RequestMeta { diff --git a/examples/passthrough.rs b/examples/passthrough.rs index 0bc8ab01..611d21d4 100644 --- a/examples/passthrough.rs +++ b/examples/passthrough.rs @@ -149,12 +149,14 @@ impl Filesystem for PassthroughFs { ) -> Result { let mut config = config; config.add_capabilities(consts::FUSE_PASSTHROUGH) - .expect("FUSE Kernel did not advertise support for passthrough. Refused capability"); + .expect("FUSE Kernel did not advertise support for passthrough (required for this example)."); config.set_max_stack_depth(2).unwrap(); Ok(config) } + #[allow(clippy::cast_sign_loss)] fn lookup(&mut self, _req: RequestMeta, parent: u64, name: &Path) -> Result { + log::info!("lookup(name={name:?})"); if parent == 1 && name.to_str() == Some("passthrough") { Ok(Entry { ino: self.passthrough_file_attr.ino, @@ -216,6 +218,7 @@ impl Filesystem for PassthroughFs { Ok(()) } + #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] fn readdir<'dir, 'name>( &mut self, _req: RequestMeta, @@ -228,14 +231,14 @@ impl Filesystem for PassthroughFs { return Err(Errno::ENOENT); } // In this example, return up to three entries depending on the offset. - if offset > 2 || offset < 0 { - // Case 1: offset out of range: - // No need to allocate anything; just use the Empty enum case. - Ok(DirentList::Empty) - } else { - // Case 2: offset in range: + if (0..=2).contains(&offset) { + // Case: offset in range: // Return a borrowed ('static) slice of entries. Ok((&ROOT_DIR_ENTRIES[offset as usize..]).into()) + } else { + // Case: offset out of range: + // No need to allocate anything; just use the Empty enum case. + Ok(DirentList::Empty) } } } diff --git a/examples/poll.rs b/examples/poll.rs index f6d4e8e3..684e050c 100644 --- a/examples/poll.rs +++ b/examples/poll.rs @@ -163,7 +163,7 @@ impl Filesystem for FSelFS { return Err(Errno::EINVAL); }; - let mut entries: Vec = Vec::new(); + let mut entries = Vec::new(); for idx in start_offset..NUMFILES { let ascii_char_val = match idx { 0..=9 => b'0' + idx, @@ -199,14 +199,10 @@ impl Filesystem for FSelFS { return Err(Errno::EACCES); } - { - let mut d = self.get_data(); - - if d.open_mask & (1 << idx) != 0 { - return Err(Errno::EBUSY); - } - d.open_mask |= 1 << idx; + if self.data.open_mask & (1 << idx) != 0 { + return Err(Errno::EBUSY); } + self.data.open_mask |= 1 << idx; Ok(Open { fh: idx.into(), // Using idx as file handle @@ -284,7 +280,7 @@ impl Filesystem for FSelFS { if flags & FUSE_POLL_SCHEDULE_NOTIFY != 0 { d.notify_mask |= 1 << idx; - d.poll_handles[idx as usize] = ph.into(); + d.poll_handles[idx as usize] = ph; } let nbytes = d.bytecnt[idx as usize]; @@ -318,9 +314,9 @@ fn producer(data: &Mutex, notifier: &fuser::Notifier) { if d.bytecnt[tidx] != MAXBYTES { d.bytecnt[tidx] += 1; if d.notify_mask & (1 << t) != 0 { - println!("NOTIFY {:X}", t); + println!("NOTIFY {t:X}"); if let Err(e) = notifier.poll(d.poll_handles[tidx]) { - eprintln!("poll notification failed: {}", e); + eprintln!("poll notification failed: {e}"); } d.notify_mask &= !(1 << t); } diff --git a/examples/poll_client.rs b/examples/poll_client.rs index 273d19b2..2a5184e8 100644 --- a/examples/poll_client.rs +++ b/examples/poll_client.rs @@ -25,7 +25,7 @@ fn make_nonblock(fd: RawFd) { fn main() -> std::io::Result<()> { let mut files = Vec::with_capacity(NUMFILES); for c in "0123456789ABCDEF".chars() { - let name = format!("{}", c); + let name = format!("{c}"); let f = std::fs::File::open(name)?; make_nonblock(f.as_raw_fd()); files.push(f); @@ -46,10 +46,10 @@ fn main() -> std::io::Result<()> { print!("_: "); continue; } - print!("{:X}:", i); + print!("{i:X}:"); let fd = pfd.as_fd().as_raw_fd(); let nbytes = nix::unistd::read(fd, readbuf.as_mut_slice())?; - print!("{:02} ", nbytes); + print!("{nbytes:02} "); } println!(); } diff --git a/examples/simple.rs b/examples/simple.rs index 0e3adc85..98236999 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -1,5 +1,7 @@ #![allow(clippy::needless_return)] #![allow(clippy::unnecessary_cast)] // libc::S_* are u16 or u32 depending on the platform +#![allow(clippy::cast_possible_truncation)] // u32 -> u16 without error handling +#![allow(clippy::cast_sign_loss)] // i64 -> u32 without error handling use clap::{crate_version, Arg, ArgAction, Command}; use fuser::consts::FOPEN_DIRECT_IO; @@ -34,7 +36,7 @@ use std::sync::atomic::{AtomicU64, Ordering}; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use std::{env, fs, io}; -const BLOCK_SIZE: u64 = 512; +const BLOCK_SIZE: u32 = 512; const MAX_NAME_LENGTH: u32 = 255; const MAX_FILE_SIZE: u64 = 1024 * 1024 * 1024 * 1024; @@ -224,7 +226,7 @@ impl From for FileAttr { FileAttr { ino: attrs.inode, size: attrs.size, - blocks: attrs.size.div_ceil(BLOCK_SIZE), + blocks: attrs.size.div_ceil(u64::from(BLOCK_SIZE)), atime: system_time_from_time(attrs.last_accessed.0, attrs.last_accessed.1), mtime: system_time_from_time(attrs.last_modified.0, attrs.last_modified.1), ctime: system_time_from_time( @@ -238,7 +240,7 @@ impl From for FileAttr { uid: attrs.uid, gid: attrs.gid, rdev: 0, - blksize: BLOCK_SIZE as u32, + blksize: BLOCK_SIZE, flags: 0, } } @@ -284,10 +286,10 @@ impl SimpleFS { } fn creation_mode(&self, mode: u32) -> u16 { - if !self.suid_support { - (mode & !(libc::S_ISUID | libc::S_ISGID) as u32) as u16 - } else { + if self.suid_support { mode as u16 + } else { + (mode & !(libc::S_ISUID | libc::S_ISGID) as u32) as u16 } } @@ -349,7 +351,7 @@ impl SimpleFS { } } - fn write_directory_content(&self, inode: Inode, entries: DirectoryDescriptor) { + fn write_directory_content(&self, inode: Inode, entries: &DirectoryDescriptor) { let path = Path::new(&self.data_dir) .join("contents") .join(inode.to_string()); @@ -442,9 +444,8 @@ impl SimpleFS { let entries = self.get_directory_content(parent)?; if let Some((inode, _)) = entries.get(name.as_bytes()) { return self.get_inode(*inode); - } else { - return Err(libc::ENOENT); } + return Err(libc::ENOENT); } fn insert_link( @@ -477,7 +478,7 @@ impl SimpleFS { let mut entries = self.get_directory_content(parent).unwrap(); entries.insert(name.as_bytes().to_vec(), (inode, kind)); - self.write_directory_content(parent, entries); + self.write_directory_content(parent, &entries); Ok(()) } @@ -521,12 +522,12 @@ impl Filesystem for SimpleFS { hardlinks: 2, uid: init_uid, gid: init_gid, - xattrs: Default::default(), + xattrs: BTreeMap::default(), }; self.write_inode(&root); let mut entries = BTreeMap::new(); entries.insert(b".".to_vec(), (FUSE_ROOT_ID, FileKind::Directory)); - self.write_directory_content(FUSE_ROOT_ID, entries); + self.write_directory_content(FUSE_ROOT_ID, &entries); } Ok(config) } @@ -575,7 +576,7 @@ impl Filesystem for SimpleFS { _fh: Option, ) -> Result<(FileAttr, Duration), Errno> { match self.get_inode(ino) { - Ok(inode) => Ok((inode.into(), Duration::new(0, 0))), + Ok(attrs) => Ok((attrs.into(), Duration::new(0, 0))), Err(e) => Err(Errno::from_i32(e)), } } @@ -605,7 +606,7 @@ impl Filesystem for SimpleFS { }; if let Some(mode) = mode_option { - debug!("chmod() called with {:?}, {:o}", inode, mode); + debug!("chmod() called with {inode:?}, {mode:o}"); if req.uid != 0 && req.uid != attrs.uid { return Err(Errno::EPERM); } @@ -625,7 +626,7 @@ impl Filesystem for SimpleFS { } if uid_option.is_some() || gid_option.is_some() { - debug!("chown() called with {:?} {:?} {:?}", inode, uid_option, gid_option); + debug!("chown() called with {inode:?} {uid_option:?} {gid_option:?}"); if let Some(gid) = gid_option { // Non-root users can only change gid to a group they're in if req.uid != 0 && !get_groups(req.pid).contains(&gid) { @@ -668,7 +669,7 @@ impl Filesystem for SimpleFS { } if let Some(size) = size_option { - debug!("truncate() called with {:?} {:?}", inode, size); + debug!("truncate() called with {inode:?} {size:?}"); let truncated_attrs_result = if let Some(handle) = fh { if self.check_file_handle_write(handle) { self.truncate(inode, size, 0, 0) @@ -688,7 +689,7 @@ impl Filesystem for SimpleFS { let now = time_now(); let mut modified_time_attr = false; if let Some(atime) = atime_option { - debug!("utimens() called with {:?}, atime={:?}", inode, atime); + debug!("utimens() called with {inode:?}, atime={atime:?}"); if attrs.uid != req.uid && req.uid != 0 && atime != Now { return Err(Errno::EPERM); @@ -715,7 +716,7 @@ impl Filesystem for SimpleFS { modified_time_attr = true; } if let Some(mtime) = mtime_option { - debug!("utimens() called with {:?}, mtime={:?}", inode, mtime); + debug!("utimens() called with {inode:?}, mtime={mtime:?}"); if attrs.uid != req.uid && req.uid != 0 && mtime != Now { return Err(Errno::EPERM); @@ -753,7 +754,7 @@ impl Filesystem for SimpleFS { } fn readlink<'a>(&mut self, _req: RequestMeta, inode: u64) -> Result, Errno> { - debug!("readlink() called on {:?}", inode); + debug!("readlink() called on {inode:?}"); let path = self.content_path(inode); match File::open(path) { Ok(mut file) => { @@ -763,7 +764,7 @@ impl Filesystem for SimpleFS { }; let mut buffer = vec![0; file_size as usize]; match file.read_exact(&mut buffer) { - Ok(_) => { + Ok(()) => { // OsString into OsBox::Owned Ok(Bytes::from(buffer)) } @@ -790,7 +791,7 @@ impl Filesystem for SimpleFS { && file_type != libc::S_IFDIR as u32 { // TODO - warn!("mknod() implementation is incomplete. Only supports regular files, symlinks, and directories. Got {:o}", mode); + warn!("mknod() implementation is incomplete. Only supports regular files, symlinks, and directories. Got {mode:o}"); return Err(Errno::EPERM); } @@ -839,7 +840,7 @@ impl Filesystem for SimpleFS { hardlinks: 1, uid: req.uid, gid: creation_gid(&parent_attrs, req.gid), - xattrs: Default::default(), + xattrs: BTreeMap::default(), }; self.write_inode(&attrs); File::create(self.content_path(inode)).map_err(|_| Errno::EIO)?; @@ -848,12 +849,12 @@ impl Filesystem for SimpleFS { let mut entries = BTreeMap::new(); entries.insert(b".".to_vec(), (inode, FileKind::Directory)); entries.insert(b"..".to_vec(), (parent, FileKind::Directory)); - self.write_directory_content(inode, entries); + self.write_directory_content(inode, &entries); } let mut entries = self.get_directory_content(parent).map_err(Errno::from_i32)?; entries.insert(name.as_os_str().as_bytes().to_vec(), (inode, attrs.kind)); - self.write_directory_content(parent, entries); + self.write_directory_content(parent, &entries); Ok(Entry { ino: attrs.inode, generation: None, @@ -871,7 +872,7 @@ impl Filesystem for SimpleFS { mode: u32, #[allow(unused_variables)] _umask: u32, ) -> Result { - debug!("mkdir() called with {:?} {:?} {:o}", parent, name, mode); + debug!("mkdir() called with {parent:?} {name:?} {mode:o}"); if self.lookup_name(parent, name.as_os_str()).is_ok() { return Err(Errno::EEXIST); } @@ -911,7 +912,7 @@ impl Filesystem for SimpleFS { let attrs = InodeAttributes { inode, open_file_handles: 0, - size: BLOCK_SIZE, + size: u64::from(BLOCK_SIZE), last_accessed: time_now(), last_modified: time_now(), last_metadata_changed: time_now(), @@ -920,18 +921,18 @@ impl Filesystem for SimpleFS { hardlinks: 2, // Directories start with link count of 2, since they have a self link uid: req.uid, gid: creation_gid(&parent_attrs, req.gid), - xattrs: Default::default(), + xattrs: BTreeMap::default(), }; self.write_inode(&attrs); let mut entries = BTreeMap::new(); entries.insert(b".".to_vec(), (inode, FileKind::Directory)); entries.insert(b"..".to_vec(), (parent, FileKind::Directory)); - self.write_directory_content(inode, entries); + self.write_directory_content(inode, &entries); let mut entries = self.get_directory_content(parent).map_err(Errno::from_i32)?; entries.insert(name.as_os_str().as_bytes().to_vec(), (inode, FileKind::Directory)); - self.write_directory_content(parent, entries); + self.write_directory_content(parent, &entries); Ok(Entry { ino: attrs.inode, generation: None, @@ -942,7 +943,7 @@ impl Filesystem for SimpleFS { } fn unlink(&mut self, req: RequestMeta, parent: u64, name: &Path) -> Result<(), Errno> { - debug!("unlink() called with {:?} {:?}", parent, name); + debug!("unlink() called with {parent:?} {name:?}"); let mut attrs = match self.lookup_name(parent, name.as_os_str()) { Ok(attrs) => attrs, Err(error_code) => { @@ -989,13 +990,13 @@ impl Filesystem for SimpleFS { let mut entries = self.get_directory_content(parent).unwrap(); entries.remove(name.as_os_str().as_bytes()); - self.write_directory_content(parent, entries); + self.write_directory_content(parent, &entries); Ok(()) } fn rmdir(&mut self, req: RequestMeta, parent: u64, name: &Path) -> Result<(), Errno> { - debug!("rmdir() called with {:?} {:?}", parent, name); + debug!("rmdir() called with {parent:?} {name:?}"); let mut attrs = match self.lookup_name(parent, name.as_os_str()) { Ok(attrs) => attrs, Err(error_code) => { @@ -1050,7 +1051,7 @@ impl Filesystem for SimpleFS { let mut entries = self.get_directory_content(parent).map_err(Errno::from_i32)?; entries.remove(name.as_os_str().as_bytes()); - self.write_directory_content(parent, entries); + self.write_directory_content(parent, &entries); Ok(()) } @@ -1063,8 +1064,7 @@ impl Filesystem for SimpleFS { target: &Path, ) -> Result { debug!( - "symlink() called with {:?} {:?} {:?}", - parent, link_name, target + "symlink() called with {parent:?} {link_name:?} {target:?}" ); let mut parent_attrs = match self.get_inode(parent) { Ok(attrs) => attrs, @@ -1100,7 +1100,7 @@ impl Filesystem for SimpleFS { hardlinks: 1, uid: req.uid, gid: creation_gid(&parent_attrs, req.gid), - xattrs: Default::default(), + xattrs: BTreeMap::default(), }; if let Err(error_code) = self.insert_link(req, parent, link_name.as_os_str(), inode, FileKind::Symlink) @@ -1218,14 +1218,14 @@ impl Filesystem for SimpleFS { new_name.as_os_str().as_bytes().to_vec(), (inode_attrs.inode, inode_attrs.kind), ); - self.write_directory_content(new_parent, entries); + self.write_directory_content(new_parent, &entries); let mut entries = self.get_directory_content(parent).map_err(Errno::from_i32)?; entries.insert( name.as_os_str().as_bytes().to_vec(), (new_inode_attrs.inode, new_inode_attrs.kind), ); - self.write_directory_content(parent, entries); + self.write_directory_content(parent, &entries); parent_attrs.last_metadata_changed = time_now(); parent_attrs.last_modified = time_now(); @@ -1241,12 +1241,12 @@ impl Filesystem for SimpleFS { if inode_attrs.kind == FileKind::Directory { let mut entries = self.get_directory_content(inode_attrs.inode).map_err(Errno::from_i32)?; entries.insert(b"..".to_vec(), (new_parent, FileKind::Directory)); - self.write_directory_content(inode_attrs.inode, entries); + self.write_directory_content(inode_attrs.inode, &entries); } if new_inode_attrs.kind == FileKind::Directory { let mut entries = self.get_directory_content(new_inode_attrs.inode).map_err(Errno::from_i32)?; entries.insert(b"..".to_vec(), (parent, FileKind::Directory)); - self.write_directory_content(new_inode_attrs.inode, entries); + self.write_directory_content(new_inode_attrs.inode, &entries); } return Ok(()); @@ -1282,7 +1282,7 @@ impl Filesystem for SimpleFS { if let Ok(mut existing_inode_attrs) = self.lookup_name(new_parent, new_name.as_os_str()) { let mut entries = self.get_directory_content(new_parent).map_err(Errno::from_i32)?; entries.remove(new_name.as_os_str().as_bytes()); - self.write_directory_content(new_parent, entries); + self.write_directory_content(new_parent, &entries); if existing_inode_attrs.kind == FileKind::Directory { existing_inode_attrs.hardlinks = 0; @@ -1296,14 +1296,14 @@ impl Filesystem for SimpleFS { let mut entries = self.get_directory_content(parent).map_err(Errno::from_i32)?; entries.remove(name.as_os_str().as_bytes()); - self.write_directory_content(parent, entries); + self.write_directory_content(parent, &entries); let mut entries = self.get_directory_content(new_parent).map_err(Errno::from_i32)?; entries.insert( new_name.as_os_str().as_bytes().to_vec(), (inode_attrs.inode, inode_attrs.kind), ); - self.write_directory_content(new_parent, entries); + self.write_directory_content(new_parent, &entries); parent_attrs.last_metadata_changed = time_now(); parent_attrs.last_modified = time_now(); @@ -1317,7 +1317,7 @@ impl Filesystem for SimpleFS { if inode_attrs.kind == FileKind::Directory { let mut entries = self.get_directory_content(inode_attrs.inode).map_err(Errno::from_i32)?; entries.insert(b"..".to_vec(), (new_parent, FileKind::Directory)); - self.write_directory_content(inode_attrs.inode, entries); + self.write_directory_content(inode_attrs.inode, &entries); } Ok(()) @@ -1331,8 +1331,7 @@ impl Filesystem for SimpleFS { new_name: &Path, ) -> Result { debug!( - "link() called for {}, {}, {:?}", - inode, new_parent, new_name + "link() called for {inode}, {new_parent}, {new_name:?}" ); let mut attrs = match self.get_inode(inode) { Ok(attrs) => attrs, @@ -1342,22 +1341,21 @@ impl Filesystem for SimpleFS { }; if let Err(error_code) = self.insert_link(req, new_parent, new_name.as_os_str(), inode, attrs.kind) { return Err(Errno::from_i32(error_code)); - } else { - attrs.hardlinks += 1; - attrs.last_metadata_changed = time_now(); - self.write_inode(&attrs); - return Ok(Entry { - ino: attrs.inode, - generation: None, - file_ttl: Duration::new(0, 0), - attr: attrs.into(), - attr_ttl: Duration::new(0, 0), - }); } + attrs.hardlinks += 1; + attrs.last_metadata_changed = time_now(); + self.write_inode(&attrs); + return Ok(Entry { + ino: attrs.inode, + generation: None, + file_ttl: Duration::new(0, 0), + attr: attrs.into(), + attr_ttl: Duration::new(0, 0), + }); } fn open(&mut self, req: RequestMeta, inode: u64, flags: i32) -> Result { - debug!("open() called for {:?}", inode); + debug!("open() called for {inode:?}"); let (access_mask, read, write) = match flags & libc::O_ACCMODE { libc::O_RDONLY => { // Behavior is undefined, but most filesystems return EACCES @@ -1396,9 +1394,8 @@ impl Filesystem for SimpleFS { fh: self.allocate_next_file_handle(read, write), flags: open_flags, }); - } else { - return Err(Errno::EACCES); } + return Err(Errno::EACCES); } Err(error_code) => return Err(Errno::from_i32(error_code)), } @@ -1414,10 +1411,7 @@ impl Filesystem for SimpleFS { _flags: i32, _lock_owner: Option, ) -> Result, Errno> { - debug!( - "read() called on {:?} offset={:?} size={:?}", - inode, offset, size - ); + debug!("read() called on {inode:?} offset={offset:?} size={size:?}"); assert!(offset >= 0); if !self.check_file_handle_read(fh) { return Err(Errno::EACCES); @@ -1457,7 +1451,7 @@ impl Filesystem for SimpleFS { let path = self.content_path(inode); if let Ok(mut file) = OpenOptions::new().write(true).open(path) { file.seek(SeekFrom::Start(offset as u64)).unwrap(); - file.write_all(&data).unwrap(); + file.write_all(data).unwrap(); let mut attrs = self.get_inode(inode).unwrap(); attrs.last_metadata_changed = time_now(); @@ -1496,7 +1490,7 @@ impl Filesystem for SimpleFS { } fn opendir(&mut self, req: RequestMeta, inode: u64, flags: i32) -> Result { - debug!("opendir() called on {:?}", inode); + debug!("opendir() called on {inode:?}"); let (access_mask, read, write) = match flags & libc::O_ACCMODE { libc::O_RDONLY => { // Behavior is undefined, but most filesystems return EACCES @@ -1530,9 +1524,8 @@ impl Filesystem for SimpleFS { fh: self.allocate_next_file_handle(read, write), flags: open_flags, }); - } else { - return Err(Errno::EACCES); } + return Err(Errno::EACCES); } Err(error_code) => return Err(Errno::from_i32(error_code)), } @@ -1546,7 +1539,7 @@ impl Filesystem for SimpleFS { offset: i64, _max_bytes: u32, ) -> Result, Errno> { - debug!("readdir() called with {:?}", inode); + debug!("readdir() called with {inode:?}"); assert!(offset >= 0); // get_directory_content() returns an owned tree structure, or an error. let tree = match self.get_directory_content(inode) { @@ -1598,9 +1591,9 @@ impl Filesystem for SimpleFS { bavail: 10_000, files: 1, ffree: 10_000, - bsize: BLOCK_SIZE as u32, + bsize: BLOCK_SIZE, namelen: MAX_NAME_LENGTH, - frsize: BLOCK_SIZE as u32, + frsize: BLOCK_SIZE, }) } @@ -1646,18 +1639,12 @@ impl Filesystem for SimpleFS { } else if data.len() <= size as usize { // vec.into_boxed_slice() takes ownership of the value, avoiding a copy. return Ok(Xattr::Data(Bytes::from(data))); - } else { - return Err(Errno::ERANGE); } - } else { - #[cfg(target_os = "linux")] - return Err(Errno::ENODATA); - #[cfg(not(target_os = "linux"))] - return Err(Errno::ENOATTR); + return Err(Errno::ERANGE); } - } else { - Err(Errno::EBADF) + return Err(Errno::NO_XATTR); } + Err(Errno::EBADF) } fn listxattr<'a>( @@ -1679,15 +1666,18 @@ impl Filesystem for SimpleFS { return Ok(Xattr::Size(bytes.len() as u32)); } else if bytes.len() <= size as usize { return Ok(Xattr::Data(Bytes::from(bytes))); - } else { - return Err(Errno::ERANGE); } - } else { - Err(Errno::EBADF) + return Err(Errno::ERANGE); } + Err(Errno::EBADF) } - fn removexattr(&mut self, request: RequestMeta, inode: u64, key: &OsStr) -> Result<(), Errno> { + fn removexattr( + &mut self, + request: RequestMeta, + inode: u64, + key: &OsStr, + ) -> Result<(), Errno> { if let Ok(mut attrs) = self.get_inode(inode) { if let Err(error) = xattr_access_check(key.as_bytes(), libc::W_OK, &attrs, request) { return Err(Errno::from_i32(error)); @@ -1707,15 +1697,19 @@ impl Filesystem for SimpleFS { } } - fn access(&mut self, req: RequestMeta, inode: u64, mask: i32) -> Result<(), Errno> { - debug!("access() called with {:?} {:?}", inode, mask); + fn access( + &mut self, + req: RequestMeta, + inode: u64, + mask: i32 + ) -> Result<(), Errno> { + debug!("access() called with {inode:?} {mask:?}"); match self.get_inode(inode) { Ok(attr) => { if check_access(attr.uid, attr.gid, attr.mode, req.uid, req.gid, mask) { return Ok(()); - } else { - Err(Errno::EACCES) } + Err(Errno::EACCES) } Err(error_code) => Err(Errno::from_i32(error_code)), } @@ -1730,7 +1724,7 @@ impl Filesystem for SimpleFS { _umask: u32, flags: i32, ) -> Result<(Entry, Open), Errno> { - debug!("create() called with {:?} {:?}", parent, name); + debug!("create() called with {parent:?} {name:?}"); if self.lookup_name(parent, name.as_os_str()).is_ok() { return Err(Errno::EEXIST); } @@ -1783,7 +1777,7 @@ impl Filesystem for SimpleFS { hardlinks: 1, uid: req.uid, gid: creation_gid(&parent_attrs, req.gid), - xattrs: Default::default(), + xattrs: BTreeMap::default(), }; self.write_inode(&attrs); File::create(self.content_path(inode)).unwrap(); @@ -1792,12 +1786,12 @@ impl Filesystem for SimpleFS { let mut entries = BTreeMap::new(); entries.insert(b".".to_vec(), (inode, FileKind::Directory)); entries.insert(b"..".to_vec(), (parent, FileKind::Directory)); - self.write_directory_content(inode, entries); + self.write_directory_content(inode, &entries); } let mut entries = self.get_directory_content(parent).unwrap(); entries.insert(name.as_os_str().as_bytes().to_vec(), (inode, attrs.kind)); - self.write_directory_content(parent, entries); + self.write_directory_content(parent, &entries); // TODO: implement flags return Ok(( @@ -1857,10 +1851,7 @@ impl Filesystem for SimpleFS { size: u64, _flags: u32, ) -> Result { - debug!( - "copy_file_range() called with src ({}, {}, {}) dest ({}, {}, {}) size={}", - src_fh, src_inode, src_offset, dest_fh, dest_inode, dest_offset, size - ); + debug!("copy_file_range() called with src=({src_fh}, {src_inode}, {src_offset}) dest=({dest_fh}, {dest_inode}, {dest_offset}) size={size}"); if !self.check_file_handle_read(src_fh) { return Err(Errno::EACCES); } @@ -1891,15 +1882,14 @@ impl Filesystem for SimpleFS { self.write_inode(&attrs); return Ok(data.len() as u32); - } else { - return Err(Errno::EBADF); } - } else { - return Err(Errno::ENOENT); + return Err(Errno::EBADF); } + return Err(Errno::ENOENT); } } +#[must_use] pub fn check_access( file_uid: u32, file_gid: u32, @@ -1944,9 +1934,8 @@ fn as_file_kind(mut mode: u32) -> FileKind { return FileKind::Symlink; } else if mode == libc::S_IFDIR as u32 { return FileKind::Directory; - } else { - unimplemented!("{}", mode); } + unimplemented!("{}", mode); } fn get_groups(pid: u32) -> Vec { @@ -2088,7 +2077,7 @@ fn main() { // Return a special error code for permission denied, which usually indicates that // "user_allow_other" is missing from /etc/fuse.conf if e.kind() == ErrorKind::PermissionDenied { - error!("{}", e); + error!("{e}"); std::process::exit(2); } } @@ -2122,13 +2111,13 @@ mod test { hardlinks: 2, uid: 1000, gid: 1000, - xattrs: Default::default(), + xattrs: BTreeMap::default(), }; fs.write_inode(&root_attrs); let mut root_entries = BTreeMap::new(); root_entries.insert(b".".to_vec(), (FUSE_ROOT_ID, FileKind::Directory)); root_entries.insert(b"..".to_vec(), (FUSE_ROOT_ID, FileKind::Directory)); - fs.write_directory_content(FUSE_ROOT_ID, root_entries); + fs.write_directory_content(FUSE_ROOT_ID, &root_entries); // Create a test file inode let file_attrs = InodeAttributes { inode: 2, @@ -2142,13 +2131,13 @@ mod test { hardlinks: 1, uid: 1000, gid: 1000, - xattrs: Default::default(), + xattrs: BTreeMap::default(), }; fs.write_inode(&file_attrs); File::create(fs.content_path(2)).unwrap(); let mut root_entries = fs.get_directory_content(FUSE_ROOT_ID).unwrap(); root_entries.insert(b"testfile.txt".to_vec(), (2, FileKind::File)); - fs.write_directory_content(FUSE_ROOT_ID, root_entries); + fs.write_directory_content(FUSE_ROOT_ID, &root_entries); fs } diff --git a/src/channel.rs b/src/channel.rs index 11614224..9c7c52c6 100644 --- a/src/channel.rs +++ b/src/channel.rs @@ -66,7 +66,7 @@ impl ReplySender for ChannelSender { let rc = unsafe { libc::writev( self.0.as_raw_fd(), - bufs.as_ptr() as *const libc::iovec, + bufs.as_ptr().cast::(), bufs.len() as c_int, ) }; diff --git a/src/container/core.rs b/src/container/core.rs index a5eb9390..1c338019 100644 --- a/src/container/core.rs +++ b/src/container/core.rs @@ -117,6 +117,7 @@ impl From> for BorrowError { impl Container<'_, T> { /// Borrows a slice-like immutable reference from the container. /// Will attempt to gain access to a locking variant. + /// # Errors /// Returns an error if the source data is unavailable. pub fn try_borrow(&self) -> Result, BorrowError> { match self { @@ -149,14 +150,17 @@ impl Container<'_, T> { /// Borrows a slice-like immutable reference from the container. /// Will attempt to gain access to a locking variant. + /// # Panics /// Panics if the source data is unavailable. + #[must_use] pub fn borrow(&self) -> Borrow<'_, T> { self.try_borrow().unwrap() } /// Returns a borrowed slice &[] from the container if it is an immutable variant. + /// # Errors /// Returns an error if the container is a locking variant. - /// Hint: use try_borrow() to handle locking variants. + /// Hint: use `try_borrow()` to handle locking variants. pub fn try_as_ref(&self) -> Result<&[T], &str> { match self { // ----- Simple Variants ----- @@ -184,8 +188,9 @@ impl Container<'_, T> { impl AsRef<[T]> for Container<'_, T> { /// Returns a borrowed slice &[] from the container. + /// # Panics /// Will panic if the container is a locking variant. - /// Hint: use borrow() to handle locking variants. + /// Hint: use `borrow()` to handle locking variants. fn as_ref(&self) -> &[T] { self.try_as_ref().unwrap() } diff --git a/src/container/specialized.rs b/src/container/specialized.rs index 88e9b5c2..a3184e3c 100644 --- a/src/container/specialized.rs +++ b/src/container/specialized.rs @@ -66,7 +66,7 @@ impl<'a> From<&'a OsStr> for Container<'a, u8> { impl Container<'_, u8> { /// Converts the bytes to an owned UTF-8 String. /// Most likely, this is a copy. - /// + /// # Errors /// Returns an error if the byte slice is not valid UTF-8. /// Returns an error if the source data is not available. pub fn try_to_string(&self) -> Result { @@ -74,9 +74,9 @@ impl Container<'_, u8> { Ok(std::str::from_utf8(&borrowed)?.to_string()) } - /// Converts the container's content to an owned OsString. + /// Converts the container's content to an owned `OsString`. /// Most likely, this is a copy. - /// + /// # Errors /// Returns an error if the source data is not available. pub fn try_to_os_string(&self) -> Result { let borrowed = self.try_borrow()?; @@ -88,13 +88,14 @@ impl Container<'_, u8> { impl Borrow<'_, u8> { /// Converts the borrowed bytes to an borrowed UTF-8 String. - /// + /// # Errors /// Returns an error if the byte slice is not valid UTF-8. pub fn try_to_str(&self) -> Result<&str, ToStringError> { Ok(std::str::from_utf8(self)?) } - /// Converts the borrowed bytes to a borrowed OsStr. + /// Converts the borrowed bytes to a borrowed `OsStr`. + #[must_use] pub fn to_os_str(&self) -> &OsStr { OsStr::from_bytes(self) } diff --git a/src/container/tests.rs b/src/container/tests.rs index 4f36db76..1d093b20 100644 --- a/src/container/tests.rs +++ b/src/container/tests.rs @@ -1,5 +1,6 @@ // This file contains unit tests for the Container enum // and its trait implementations. +#![allow(clippy::match_wild_err_arm)] // any error fails the test use crate::container::Container; use std::sync::Arc; @@ -24,9 +25,8 @@ mod u8 { let borrowed_slice: &[u8] = &[4,5,6]; let c_borrowed1 : Container<'_, u8> = Container::Ref(borrowed_slice); let c_borrowed2 = c_borrowed1.clone(); - assert_eq!(&*c_borrowed1.borrow(), &*c_borrowed2.borrow()); - assert_eq!((&*c_borrowed1.borrow()).as_ptr(), (&*c_borrowed2.borrow()).as_ptr()); - + assert_eq!(*c_borrowed1.borrow(), *c_borrowed2.borrow()); + assert_eq!((*c_borrowed1.borrow()).as_ptr(), (*c_borrowed2.borrow()).as_ptr()); let shared_slice_arc: Arc<[u8]> = Arc::new([7,8,9]); let c_shared1 : Container<'_, u8> = Container::Arc(shared_slice_arc.clone()); @@ -135,7 +135,7 @@ mod u8 { match container_rc_ref_cell_box.try_borrow() { Ok(guard) => assert_eq!(&*guard, data_box_orig.as_ref()), Err(_) => panic!("Expected Ok for RcRefCellBox get_slice"), - }; + } // Container::RcRefCellVec let rc_ref_cell_vec = std::rc::Rc::new(std::cell::RefCell::new(data_vec_orig.clone())); @@ -143,7 +143,7 @@ mod u8 { match container_rc_ref_cell_vec.try_borrow() { Ok(guard) => assert_eq!(&*guard, data_vec_orig.as_slice()), Err(_) => panic!("Expected Ok for RcRefCellVec get_slice"), - }; + } // Container::ArcMutexBox let arc_mutex_box = Arc::new(std::sync::Mutex::new(data_box_orig.clone())); @@ -151,7 +151,7 @@ mod u8 { match container_arc_mutex_box.try_borrow() { Ok(guard) => assert_eq!(&*guard, data_box_orig.as_ref()), Err(_) => panic!("Expected Ok for ArcMutexBox get_slice"), - }; + } // Container::ArcMutexVec let arc_mutex_vec = Arc::new(std::sync::Mutex::new(data_vec_orig.clone())); @@ -159,7 +159,7 @@ mod u8 { match container_arc_mutex_vec.try_borrow() { Ok(guard) => assert_eq!(&*guard, data_vec_orig.as_slice()), Err(_) => panic!("Expected Ok for ArcMutexVec get_slice"), - }; + } // Container::ArcRwLockBox let arc_rw_lock_box = Arc::new(std::sync::RwLock::new(data_box_orig.clone())); @@ -167,7 +167,7 @@ mod u8 { match container_arc_rw_lock_box.try_borrow() { Ok(guard) => assert_eq!(&*guard, data_box_orig.as_ref()), Err(_) => panic!("Expected Ok for ArcRwLockBox get_slice"), - }; + } // Container::ArcRwLockVec let arc_rw_lock_vec = Arc::new(std::sync::RwLock::new(data_vec_orig.clone())); @@ -277,7 +277,7 @@ mod string { } // --- Helper to create containers for testing --- - fn create_non_locking_containers<'a>(bytes: &'a [u8]) -> Vec> { + fn create_non_locking_containers(bytes: &[u8]) -> Vec> { vec![ Container::Ref(bytes), Container::Vec(bytes.to_vec()), @@ -289,7 +289,7 @@ mod string { ] } - fn create_locking_containers<'a>(bytes: &'a [u8]) -> Vec> { + fn create_locking_containers(bytes: &[u8]) -> Vec> { vec![ Container::ArcMutexVec(Arc::new(Mutex::new(bytes.to_vec()))), Container::ArcRwLockVec(Arc::new(RwLock::new(bytes.to_vec()))), diff --git a/src/container/utils.rs b/src/container/utils.rs index 6e680d96..2cd2424b 100644 --- a/src/container/utils.rs +++ b/src/container/utils.rs @@ -7,7 +7,7 @@ use std::cell::RefCell; // --- From Raw --- // ----- Simple Variants ----- -impl From<(/* Empty */)> for Container<'_, T> {fn from(_: ()) -> Self {Container::Empty}} +impl From<(/* Empty */)> for Container<'_, T> {fn from((): ()) -> Self {Container::Empty}} impl From> for Container<'_, T> {fn from(value: Box<[T]>) -> Self {Container::Box(value)}} impl From> for Container<'_, T> {fn from(value: Vec) -> Self {Container::Vec(value)}} impl<'a, T: Clone> From<&'a [T]> for Container<'a, T> {fn from(value: &'a [T]) -> Self {Container::Ref(value)}} @@ -73,6 +73,7 @@ impl Clone for Container<'_, T> { impl Container<'_, T> { /// Returns the length of the container. /// Returns zero if the source data is unavailable. + #[must_use] pub fn len(&self) -> usize { match self.try_borrow(){ Ok(value) => value.len(), @@ -82,6 +83,7 @@ impl Container<'_, T> { /// Returns true if the container is empty. /// Also returns true if the source data is unavailable. + #[must_use] pub fn is_empty(&self) -> bool { self.len() == 0 } @@ -89,6 +91,7 @@ impl Container<'_, T> { /// Converts the container to an owned Vec. /// This will most likely be a copy. /// Returns an empty vector if the source data is unavailable. + #[must_use] pub fn to_vec(&self) -> Vec { match self.try_borrow(){ Ok(value) => value.to_vec(), @@ -100,7 +103,7 @@ impl Container<'_, T> { // ----- Serialize ----- #[cfg(feature = "serializable")] mod serialize { - use super::*; + use super::Container; use super::super::core::Borrow; use serde::{Deserialize, Deserializer, Serialize, Serializer, ser::SerializeSeq}; /// Serialize a Borrow. @@ -136,4 +139,4 @@ mod serialize { Ok(Container::Vec(v)) } } -} +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index fcf53b9a..8a9ee506 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,7 +6,8 @@ #![warn(missing_docs, missing_debug_implementations, rust_2018_idioms)] -use log::warn; +#[allow(unused_imports)] +use log::{debug, info, warn, error}; use mnt::mount_options::parse_options_from_args; /* #[cfg(feature = "serializable")] @@ -19,10 +20,11 @@ use std::io; use std::path::Path; use std::time::{Duration, SystemTime}; use std::{convert::AsRef, io::ErrorKind}; - +#[allow(clippy::wildcard_imports)] // avoid duplicating feature gates use crate::ll::fuse_abi::consts::*; pub use crate::ll::fuse_abi::FUSE_ROOT_ID; pub use crate::ll::{fuse_abi::consts, TimeOrNow}; +pub use ll::Errno; use crate::mnt::mount_options::check_option_conflicts; use crate::session::MAX_WRITE_SIZE; pub use mnt::mount_options::MountOption; @@ -35,7 +37,6 @@ pub use passthrough::BackingId; #[cfg(target_os = "macos")] pub use reply::XTimes; pub use reply::{Entry, FileAttr, FileType, Dirent, DirentList, DirentPlusList, Open, Statfs, Xattr, Lock}; -pub use ll::Errno; pub use request::RequestMeta; pub use session::{BackgroundSession, Session, SessionACL, SessionUnmounter}; pub use container::{Container, Borrow}; @@ -145,12 +146,12 @@ impl KernelConfig { /// This has to be at least 1 to support passthrough to backing files. Setting this to 0 (the /// default) effectively disables support for passthrough. /// - /// With max_stack_depth > 1, the backing files can be on a stacked fs (e.g. overlayfs) - /// themselves and with max_stack_depth == 1, this FUSE filesystem can be stacked as the + /// With `max_stack_depth` > 1, the backing files can be on a stacked fs (e.g. overlayfs) + /// themselves and with `max_stack_depth` == 1, this FUSE filesystem can be stacked as the /// underlying fs of a stacked fs (e.g. overlayfs). /// /// The kernel currently has a hard maximum value of 2. Anything higher won't work. - /// + /// # Errors /// On success, returns the previous value. On error, returns the nearest value which will succeed. #[cfg(feature = "abi-7-40")] pub fn set_max_stack_depth(&mut self, value: u32) -> Result { @@ -169,8 +170,8 @@ impl KernelConfig { /// Set the timestamp granularity /// /// Must be a power of 10 nanoseconds. i.e. 1s, 0.1s, 0.01s, 1ms, 0.1ms...etc - /// - /// On success returns the previous value. On error returns the nearest value which will succeed + /// # Errors + /// On success returns the previous value. On error returns the nearest value which will succeed. #[cfg(feature = "abi-7-23")] pub fn set_time_granularity(&mut self, value: Duration) -> Result { if value.as_nanos() == 0 { @@ -193,8 +194,8 @@ impl KernelConfig { } /// Set the maximum write size for a single request - /// - /// On success returns the previous value. On error returns the nearest value which will succeed + /// # Errors + /// On success returns the previous value. On error returns the nearest value which will succeed. pub fn set_max_write(&mut self, value: u32) -> Result { if value == 0 { return Err(1); @@ -208,8 +209,8 @@ impl KernelConfig { } /// Set the maximum readahead size - /// - /// On success returns the previous value. On error returns the nearest value which will succeed + /// # Errors + /// On success returns the previous value. On error returns the nearest value which will succeed. pub fn set_max_readahead(&mut self, value: u32) -> Result { if value == 0 { return Err(1); @@ -223,8 +224,8 @@ impl KernelConfig { } /// Add a set of capabilities. - /// - /// On success returns Ok, else return bits of capabilities not supported when capabilities you provided are not all supported by kernel. + /// # Errors + /// On success returns Ok. On error, returns the bits of capabilities not supported by kernel. pub fn add_capabilities(&mut self, capabilities_to_add: u64) -> Result<(), u64> { if capabilities_to_add & self.capabilities != capabilities_to_add { return Err(capabilities_to_add - (capabilities_to_add & self.capabilities)); @@ -234,7 +235,7 @@ impl KernelConfig { } /// Set the maximum number of pending background requests. Such as readahead requests. - /// + /// # Errors /// On success returns the previous value. On error returns the nearest value which will succeed #[cfg(feature = "abi-7-13")] pub fn set_max_background(&mut self, value: u16) -> Result { @@ -248,8 +249,8 @@ impl KernelConfig { /// Set the threshold of background requests at which the kernel will consider the filesystem /// request queue congested. (it may then switch to sleeping instead of spin-waiting, for example) - /// - /// On success returns the previous value. On error returns the nearest value which will succeed + /// # Errors + /// On success returns the previous value. On error returns the nearest value which will succeed. #[cfg(feature = "abi-7-13")] pub fn set_congestion_threshold(&mut self, value: u16) -> Result { if value == 0 { @@ -264,12 +265,13 @@ impl KernelConfig { fn congestion_threshold(&self) -> u16 { match self.congestion_threshold { // Default to a threshold of 3/4 of the max background threads - None => (self.max_background as u32 * 3 / 4) as u16, + None => (u32::from(self.max_background) * 3 / 4) as u16, Some(value) => min(value, self.max_background), } } #[cfg(feature = "abi-7-28")] + #[allow(clippy::cast_possible_truncation)] // truncation is a feature of this computation fn max_pages(&self) -> u16 { ((max(self.max_write, self.max_readahead) - 1) / page_size::get() as u32) as u16 + 1 } @@ -278,15 +280,16 @@ impl KernelConfig { /// Filesystem trait. /// /// This trait must be implemented to provide a userspace filesystem via FUSE. -/// These methods correspond to fuse_lowlevel_ops in libfuse. Reasonable default +/// These methods correspond to `fuse_lowlevel_ops` in libfuse. Reasonable default /// implementations are provided here to get a mountable filesystem that does /// nothing. #[allow(clippy::too_many_arguments)] #[allow(unused_variables)] // This is the main API, so variables are named without the underscore even though the defaults may not use them. +#[allow(clippy::missing_errors_doc)] // These default implementations do not define the conditions that cause errors pub trait Filesystem { /// Initialize filesystem. /// Called before any other filesystem method. - /// The kernel module connection can be configured using the KernelConfig object. + /// The kernel module connection can be configured using the `KernelConfig` object. /// The method should return `Ok(KernelConfig)` to accept the connection, or `Err(Errno)` to reject it. fn init(&mut self, req: RequestMeta, config: KernelConfig) -> Result { Ok(config) @@ -299,10 +302,7 @@ pub trait Filesystem { /// Look up a directory entry by name and get its attributes. /// The method should return `Ok(Entry)` if the entry is found, or `Err(Errno)` otherwise. fn lookup(&mut self, req: RequestMeta, parent: u64, name: &Path) -> Result { - warn!( - "[Not Implemented] lookup(parent: {:#x?}, name {:?})", - parent, name - ); + warn!("[Not Implemented] lookup(parent: {parent:#x?}, name {name:?})"); Err(Errno::ENOSYS) } @@ -327,10 +327,7 @@ pub trait Filesystem { /// Get file attributes. /// The method should return `Ok(Attr)` with the file attributes, or `Err(Errno)` otherwise. fn getattr(&mut self, req: RequestMeta, ino: u64, fh: Option) -> Result<(FileAttr, Duration), Errno> { - warn!( - "[Not Implemented] getattr(ino: {:#x?}, fh: {:#x?})", - ino, fh - ); + warn!("[Not Implemented] getattr(ino: {ino:#x?}, fh: {fh:#x?})"); Err(Errno::ENOSYS) } @@ -354,9 +351,8 @@ pub trait Filesystem { flags: Option ) -> Result<(FileAttr, std::time::Duration), Errno> { warn!( - "[Not Implemented] setattr(ino: {:#x?}, mode: {:?}, uid: {:?}, \ - gid: {:?}, size: {:?}, fh: {:?}, flags: {:?})", - ino, mode, uid, gid, size, fh, flags + "[Not Implemented] setattr(ino: {ino:#x?}, mode: {mode:?}, uid: {uid:?}, \ + gid: {gid:?}, size: {size:?}, fh: {fh:?}, flags: {flags:?})" ); Err(Errno::ENOSYS) } @@ -366,7 +362,7 @@ pub trait Filesystem { /// or `Err(Errno)` otherwise. /// `Bytes` allows for returning data under various ownership models potentially avoiding a copy. fn readlink<'a>(&mut self, req: RequestMeta, ino: u64) -> Result, Errno> { - warn!("[Not Implemented] readlink(ino: {:#x?})", ino); + warn!("[Not Implemented] readlink(ino: {ino:#x?})"); Err(Errno::ENOSYS) } @@ -383,9 +379,8 @@ pub trait Filesystem { rdev: u32, ) -> Result { warn!( - "[Not Implemented] mknod(parent: {:#x?}, name: {:?}, mode: {}, \ - umask: {:#x?}, rdev: {})", - parent, name, mode, umask, rdev + "[Not Implemented] mknod(parent: {parent:#x?}, name: {name:?}, mode: {mode}, \ + umask: {umask:#x?}, rdev: {rdev})" ); Err(Errno::ENOSYS) } @@ -400,30 +395,21 @@ pub trait Filesystem { mode: u32, umask: u32, ) -> Result { - warn!( - "[Not Implemented] mkdir(parent: {:#x?}, name: {:?}, mode: {}, umask: {:#x?})", - parent, name, mode, umask - ); + warn!("[Not Implemented] mkdir(parent: {parent:#x?}, name: {name:?}, mode: {mode}, umask: {umask:#x?})"); Err(Errno::ENOSYS) } /// Remove a file. /// The method should return `Ok(())` on success, or `Err(Errno)` otherwise. fn unlink(&mut self, req: RequestMeta, parent: u64, name: &Path) -> Result<(), Errno> { - warn!( - "[Not Implemented] unlink(parent: {:#x?}, name: {:?})", - parent, name, - ); + warn!("[Not Implemented] unlink(parent: {parent:#x?}, name: {name:?})"); Err(Errno::ENOSYS) } /// Remove a directory. /// The method should return `Ok(())` on success, or `Err(Errno)` otherwise. fn rmdir(&mut self, req: RequestMeta, parent: u64, name: &Path) -> Result<(), Errno> { - warn!( - "[Not Implemented] rmdir(parent: {:#x?}, name: {:?})", - parent, name, - ); + warn!("[Not Implemented] rmdir(parent: {parent:#x?}, name: {name:?})"); Err(Errno::ENOSYS) } @@ -436,10 +422,7 @@ pub trait Filesystem { link_name: &Path, target: &Path, ) -> Result { - warn!( - "[Not Implemented] symlink(parent: {:#x?}, link_name: {:?}, target: {:?})", - parent, link_name, target, - ); + warn!("[Not Implemented] symlink(parent: {parent:#x?}, link_name: {link_name:?}, target: {target:?})"); Err(Errno::EPERM) // why isn't this ENOSYS? } @@ -456,9 +439,8 @@ pub trait Filesystem { flags: u32, ) -> Result<(), Errno> { warn!( - "[Not Implemented] rename(parent: {:#x?}, name: {:?}, newparent: {:#x?}, \ - newname: {:?}, flags: {})", - parent, name, newparent, newname, flags, + "[Not Implemented] rename(parent: {parent:#x?}, name: {name:?}, newparent: {newparent:#x?}, \ + newname: {newname:?}, flags: {flags})", ); Err(Errno::ENOSYS) } @@ -472,38 +454,35 @@ pub trait Filesystem { newparent: u64, newname: &Path, ) -> Result { - warn!( - "[Not Implemented] link(ino: {:#x?}, newparent: {:#x?}, newname: {:?})", - ino, newparent, newname - ); + warn!("[Not Implemented] link(ino: {ino:#x?}, newparent: {newparent:#x?}, newname: {newname:?})"); Err(Errno::EPERM) // why isn't this ENOSYS? } /// Open a file. - /// Open flags (with the exception of O_CREAT, O_EXCL, O_NOCTTY and O_TRUNC) are + /// 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 `Open.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 `Open.fh`. There are also some flags (direct_io, keep_cache) which the - /// filesystem may set in `Open.flags`, to change the way the file is opened. See fuse_file_info + /// anything in `Open.fh`. There are also some flags (`direct_io`, `keep_cache`) which the + /// filesystem may set in `Open.flags`, to change the way the file is opened. See `fuse_file_info` /// structure in `` for more details. /// The method should return `Ok(Open)` on success, or `Err(Errno)` otherwise. fn open(&mut self, req: RequestMeta, ino: u64, flags: i32) -> Result { - warn!("[Not Implemented] open(ino: {:#x?}, flags: {})", ino, flags); + warn!("[Not Implemented] open(ino: {ino:#x?}, flags: {flags})"); Err(Errno::ENOSYS) } /// Read data. /// Read should return 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 + /// 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. `fh` will contain the value set by the open method, or will be undefined /// if the open method didn't set any value. /// The method should return `Ok(Bytes<'a>)` with the read data, or `Err(Errno)` otherwise. /// `Bytes` allows for returning borrowed or owned data, potentially avoiding data copies. /// - /// `flags`: these are the file flags, such as O_SYNC. Only supported with ABI >= 7.9 + /// `flags`: these are the file flags, such as `O_SYNC`. Only supported with ABI >= 7.9 /// `lock_owner`: only supported with ABI >= 7.9 fn read<'a>( &mut self, @@ -516,16 +495,15 @@ pub trait Filesystem { lock_owner: Option, ) -> Result, Errno> { warn!( - "[Not Implemented] read(ino: {:#x?}, fh: {}, offset: {}, size: {}, \ - flags: {:#x?}, lock_owner: {:?})", - ino, fh, offset, size, flags, lock_owner + "[Not Implemented] read(ino: {ino:#x?}, fh: {fh}, offset: {offset}, size: {size}, \ + flags: {flags:#x?}, lock_owner: {lock_owner:?})" ); Err(Errno::ENOSYS) } /// 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 + /// 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. `fh` will contain the value set by the open method, or /// will be undefined if the open method didn't set any value. @@ -534,7 +512,7 @@ pub trait Filesystem { /// `write_flags`: 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. - /// `flags`: these are the file flags, such as O_SYNC. Only supported with ABI >= 7.9. + /// `flags`: these are the file flags, such as `O_SYNC`. Only supported with ABI >= 7.9. /// `lock_owner`: only supported with ABI >= 7.9. fn write( &mut self, @@ -573,10 +551,7 @@ pub trait Filesystem { /// operations (setlk, getlk) it should remove all locks belonging to `lock_owner`. /// The method should return `Ok(())` on success, or `Err(Errno)` otherwise. fn flush(&mut self, req: RequestMeta, ino: u64, fh: u64, lock_owner: u64) -> Result<(), Errno> { - warn!( - "[Not Implemented] flush(ino: {:#x?}, fh: {}, lock_owner: {:?})", - ino, fh, lock_owner - ); + warn!("[Not Implemented] flush(ino: {ino:#x?}, fh: {fh}, lock_owner: {lock_owner:?})"); Err(Errno::ENOSYS) } @@ -606,10 +581,7 @@ pub trait Filesystem { /// not the meta data. /// The method should return `Ok(())` on success, or `Err(Errno)` otherwise. fn fsync(&mut self, req: RequestMeta, ino: u64, fh: u64, datasync: bool) -> Result<(), Errno> { - warn!( - "[Not Implemented] fsync(ino: {:#x?}, fh: {}, datasync: {})", - ino, fh, datasync - ); + warn!("[Not Implemented] fsync(ino: {ino:#x?}, fh: {fh}, datasync: {datasync})"); Err(Errno::ENOSYS) } @@ -622,9 +594,9 @@ pub trait Filesystem { /// between opendir and releasedir. /// The method should return `Ok(Open)` on success, or `Err(Errno)` otherwise. fn opendir(&mut self, req: RequestMeta, ino: u64, flags: i32) -> Result { - warn!("[Not Implemented] open(ino: {:#x?}, flags: {})", ino, flags); + warn!("[Not Implemented] open(ino: {ino:#x?}, flags: {flags})"); Err(Errno::ENOSYS) - // TODO: Open{0,0} + // TODO: maybe Open{0, 0} instead? } /// Read directory. @@ -641,10 +613,7 @@ pub trait Filesystem { offset: i64, max_bytes: u32 ) -> Result, Errno> { - warn!( - "[Not Implemented] readdir(ino: {:#x?}, fh: {}, offset: {}, max_bytes: {})", - ino, fh, offset, max_bytes - ); + warn!("[Not Implemented] readdir(ino: {ino:#x?}, fh: {fh}, offset: {offset}, max_bytes: {max_bytes})"); Err(Errno::ENOSYS) } @@ -665,8 +634,8 @@ pub trait Filesystem { max_bytes: u32, ) -> Result, Errno> { warn!( - "[Not Implemented] readdirplus(ino: {:#x?}, fh: {}, offset: {}, max_bytes: {})", - ino, fh, offset, max_bytes + "[Not Implemented] readdirplus(ino: {ino:#x?}, fh: {fh}, \ + offset: {offset}, max_bytes: {max_bytes})" ); Err(Errno::ENOSYS) } @@ -698,17 +667,14 @@ pub trait Filesystem { fh: u64, datasync: bool, ) -> Result<(), Errno> { - warn!( - "[Not Implemented] fsyncdir(ino: {:#x?}, fh: {}, datasync: {})", - ino, fh, datasync - ); + warn!("[Not Implemented] fsyncdir(ino: {ino:#x?}, fh: {fh}, datasync: {datasync})"); Err(Errno::ENOSYS) } /// Get file system statistics. /// The method should return `Ok(Statfs)` with the filesystem statistics, or `Err(Errno)` otherwise. fn statfs(&mut self, req: RequestMeta, ino: u64) -> Result { - warn!("[Not Implemented] statfs(ino: {:#x?})", ino); + warn!("[Not Implemented] statfs(ino: {ino:#x?})"); Err(Errno::ENOSYS) // TODO: Statfs{0, 0, 0, 0, 0, 512, 255, 0} } @@ -724,10 +690,7 @@ pub trait Filesystem { flags: i32, position: u32, ) -> Result<(), Errno> { - warn!( - "[Not Implemented] setxattr(ino: {:#x?}, name: {:?}, flags: {:#x?}, position: {})", - ino, name, flags, position - ); + warn!("[Not Implemented] setxattr(ino: {ino:#x?}, name: {name:?}, flags: {flags:#x?}, position: {position})"); Err(Errno::ENOSYS) } @@ -744,10 +707,7 @@ pub trait Filesystem { name: &OsStr, size: u32, ) -> Result, Errno> { - warn!( - "[Not Implemented] getxattr(ino: {:#x?}, name: {:?}, size: {})", - ino, name, size - ); + warn!("[Not Implemented] getxattr(ino: {ino:#x?}, name: {name:?}, size: {size})"); Err(Errno::ENOSYS) } @@ -763,10 +723,7 @@ pub trait Filesystem { ino: u64, size: u32, ) -> Result, Errno> { - warn!( - "[Not Implemented] listxattr(ino: {:#x?}, size: {})", - ino, size - ); + warn!("[Not Implemented] listxattr(ino: {ino:#x?}, size: {size})"); Err(Errno::ENOSYS) } @@ -778,20 +735,17 @@ pub trait Filesystem { ino: u64, name: &OsStr, ) -> Result<(), Errno> { - warn!( - "[Not Implemented] removexattr(ino: {:#x?}, name: {:?})", - ino, name - ); + warn!("[Not Implemented] removexattr(ino: {ino:#x?}, name: {name:?})"); Err(Errno::ENOSYS) } /// Check file access permissions. - /// This will be called for the `access()` system call. If the 'default_permissions' + /// This will be called for the `access()` system call. If the '`default_permissions`' /// mount option is given, this method is not called. This method is not called /// under Linux kernel versions 2.4.x /// The method should return `Ok(())` if access is allowed, or `Err(Errno)` otherwise. fn access(&mut self, req: RequestMeta, ino: u64, mask: i32) -> Result<(), Errno> { - warn!("[Not Implemented] access(ino: {:#x?}, mask: {})", ino, mask); + warn!("[Not Implemented] access(ino: {ino:#x?}, mask: {mask})"); Err(Errno::ENOSYS) } @@ -816,9 +770,8 @@ pub trait Filesystem { flags: i32, ) -> Result<(Entry,Open), Errno> { warn!( - "[Not Implemented] create(parent: {:#x?}, name: {:?}, mode: {}, umask: {:#x?}, \ - flags: {:#x?})", - parent, name, mode, umask, flags + "[Not Implemented] create(parent: {parent:#x?}, name: {name:?}, \ + mode: {mode}, umask: {umask:#x?}, flags: {flags:#x?})" ); Err(Errno::ENOSYS) } @@ -837,9 +790,8 @@ pub trait Filesystem { pid: u32, ) -> Result { warn!( - "[Not Implemented] getlk(ino: {:#x?}, fh: {}, lock_owner: {}, start: {}, \ - end: {}, typ: {}, pid: {})", - ino, fh, lock_owner, start, end, typ, pid + "[Not Implemented] getlk(ino: {ino:#x?}, fh: {fh}, lock_owner: {lock_owner}, \ + start: {start}, end: {end}, typ: {typ}, pid: {pid})" ); Err(Errno::ENOSYS) } @@ -865,9 +817,8 @@ pub trait Filesystem { sleep: bool, ) -> Result<(), Errno> { warn!( - "[Not Implemented] setlk(ino: {:#x?}, fh: {}, lock_owner: {}, start: {}, \ - end: {}, typ: {}, pid: {}, sleep: {})", - ino, fh, lock_owner, start, end, typ, pid, sleep + "[Not Implemented] setlk(ino: {ino:#x?}, fh: {fh}, lock_owner: {lock_owner}, \ + start: {start}, end: {end}, typ: {typ}, pid: {pid}, sleep: {sleep})" ); Err(Errno::ENOSYS) } @@ -877,10 +828,7 @@ pub trait Filesystem { /// with the 'blkdev' option. /// The method should return `Ok(u64)` with the device block index, or `Err(Errno)` otherwise. fn bmap(&mut self, req: RequestMeta, ino: u64, blocksize: u32, idx: u64) -> Result { - warn!( - "[Not Implemented] bmap(ino: {:#x?}, blocksize: {}, idx: {})", - ino, blocksize, idx, - ); + warn!("[Not Implemented] bmap(ino: {ino:#x?}, blocksize: {blocksize}, idx: {idx})"); Err(Errno::ENOSYS) } @@ -915,7 +863,7 @@ pub trait Filesystem { #[cfg(feature = "abi-7-11")] fn poll( &mut self, - _req: RequestMeta, + req: RequestMeta, ino: u64, fh: u64, ph: u64, @@ -923,8 +871,8 @@ pub trait Filesystem { flags: u32, ) -> Result { warn!( - "[Not Implemented] poll(ino: {:#x?}, fh: {}, ph: {:?}, events: {}, flags: {})", - ino, fh, ph, events, flags + "[Not Implemented] poll(ino: {ino:#x?}, fh: {fh}, \ + ph: {ph:?}, events: {events}, flags: {flags})" ); Err(Errno::ENOSYS) } @@ -941,9 +889,8 @@ pub trait Filesystem { mode: i32, ) -> Result<(), Errno> { warn!( - "[Not Implemented] fallocate(ino: {:#x?}, fh: {}, offset: {}, \ - length: {}, mode: {})", - ino, fh, offset, length, mode + "[Not Implemented] fallocate(ino: {ino:#x?}, fh: {fh}, offset: {offset}, \ + length: {length}, mode: {mode})" ); Err(Errno::ENOSYS) } @@ -959,10 +906,7 @@ pub trait Filesystem { offset: i64, whence: i32, ) -> Result { - warn!( - "[Not Implemented] lseek(ino: {:#x?}, fh: {}, offset: {}, whence: {})", - ino, fh, offset, whence - ); + warn!("[Not Implemented] lseek(ino: {ino:#x?}, fh: {fh}, offset: {offset}, whence: {whence})"); Err(Errno::ENOSYS) } @@ -981,10 +925,9 @@ pub trait Filesystem { flags: u32, ) -> Result { warn!( - "[Not Implemented] copy_file_range(ino_in: {:#x?}, fh_in: {}, \ - offset_in: {}, ino_out: {:#x?}, fh_out: {}, offset_out: {}, \ - len: {}, flags: {})", - ino_in, fh_in, offset_in, ino_out, fh_out, offset_out, len, flags + "[Not Implemented] copy_file_range(ino_in: {ino_in:#x?}, fh_in: {fh_in}, \ + offset_in: {offset_in}, ino_out: {ino_out:#x?}, fh_out: {fh_out}, \ + offset_out: {offset_out}, len: {len}, flags: {flags})" ); Err(Errno::ENOSYS) } @@ -994,7 +937,7 @@ pub trait Filesystem { /// The method should return `Ok(())` on success, or `Err(Errno)` otherwise. #[cfg(target_os = "macos")] fn setvolname(&mut self, req: RequestMeta, name: OsStr) -> Result<(), Errno> { - warn!("[Not Implemented] setvolname(name: {:?})", name); + warn!("[Not Implemented] setvolname(name: {name:?})"); Err(Errno::ENOSYS) } @@ -1011,9 +954,8 @@ pub trait Filesystem { options: u64 ) -> Result<(), Errno> { warn!( - "[Not Implemented] exchange(parent: {:#x?}, name: {:?}, newparent: {:#x?}, \ - newname: {:?}, options: {})", - parent, name, newparent, newname, options + "[Not Implemented] exchange(parent: {parent:#x?}, name: {name:?}, \ + newparent: {newparent:#x?}, newname: {newname:?}, options: {options:?})" ); Err(Errno::ENOSYS) } @@ -1023,7 +965,7 @@ pub trait Filesystem { /// The method should return `Ok(XTimes)` with the extended times, or `Err(Errno)` otherwise. #[cfg(target_os = "macos")] fn getxtimes(&mut self, req: RequestMeta, ino: u64) -> Result { - warn!("[Not Implemented] getxtimes(ino: {:#x?})", ino); + warn!("[Not Implemented] getxtimes(ino: {ino:#x?})"); Err(Errno::ENOSYS) } } @@ -1035,6 +977,8 @@ pub trait Filesystem { /// `mountpoint`: The path to the mountpoint. /// `options`: A slice of mount options. Each option needs to be a separate string, /// typically starting with `"-o"`. For example: `&[OsStr::new("-o"), OsStr::new("auto_unmount")]`. +/// # Errors +/// Error if the mount does not succeed. #[deprecated(note = "Use `mount2` instead, which takes a slice of `MountOption` enums for better type safety and clarity.")] pub fn mount>( filesystem: FS, @@ -1053,6 +997,8 @@ pub fn mount>( /// `options`: A slice of `MountOption` enums specifying mount options. /// /// This is the recommended way to mount a FUSE filesystem. +/// # Errors +/// Error if the mount does not succeed. pub fn mount2>( filesystem: FS, mountpoint: P, @@ -1072,6 +1018,8 @@ pub fn mount2>( /// `mountpoint`: The path to the mountpoint. /// `options`: A slice of mount options. Each option needs to be a separate string, /// typically starting with `"-o"`. For example: `&[OsStr::new("-o"), OsStr::new("auto_unmount")]`. +/// # Errors +/// Error if the session is not started. #[deprecated(note = "Use `spawn_mount2` instead, which takes a slice of `MountOption` enums for better type safety and clarity.")] pub fn spawn_mount<'a, FS: Filesystem + Send + 'static + 'a, P: AsRef>( filesystem: FS, @@ -1083,7 +1031,7 @@ pub fn spawn_mount<'a, FS: Filesystem + Send + 'static + 'a, P: AsRef>( .map(|x| Some(MountOption::from_str(x.to_str()?))) .collect(); let options = options.ok_or(ErrorKind::InvalidData)?; - Session::new(filesystem, mountpoint.as_ref(), options.as_ref()).and_then(|se| se.spawn()) + Session::new(filesystem, mountpoint.as_ref(), options.as_ref()).and_then(session::Session::spawn) } /// Mount the given filesystem to the given mountpoint in a background thread. @@ -1097,11 +1045,13 @@ pub fn spawn_mount<'a, FS: Filesystem + Send + 'static + 'a, P: AsRef>( /// `options`: A slice of `MountOption` enums specifying mount options. /// /// This is the recommended way to mount a FUSE filesystem in the background. +/// # Errors +/// Error if the session is not started. pub fn spawn_mount2<'a, FS: Filesystem + Send + 'static + 'a, P: AsRef>( filesystem: FS, mountpoint: P, options: &[MountOption], ) -> io::Result { check_option_conflicts(options)?; - Session::new(filesystem, mountpoint.as_ref(), options).and_then(|se| se.spawn()) + Session::new(filesystem, mountpoint.as_ref(), options).and_then(session::Session::spawn) } diff --git a/src/ll/argument.rs b/src/ll/argument.rs index aba183ed..0ead9989 100644 --- a/src/ll/argument.rs +++ b/src/ll/argument.rs @@ -87,7 +87,7 @@ impl<'a> ArgumentIterator<'a> { #[cfg(test)] pub mod tests { - use std::ops::Deref; + use super::super::test::AlignedData; use super::*; @@ -106,7 +106,7 @@ pub mod tests { #[test] fn all_data() { - let mut it = ArgumentIterator::new(TEST_DATA.deref()); + let mut it = ArgumentIterator::new(&*TEST_DATA); it.fetch_str().unwrap(); let arg = it.fetch_all(); assert_eq!(arg, [0x62, 0x61, 0x72, 0x00, 0x62, 0x61]); @@ -114,7 +114,7 @@ pub mod tests { #[test] fn generic_argument() { - let mut it = ArgumentIterator::new(TEST_DATA.deref()); + let mut it = ArgumentIterator::new(&*TEST_DATA); let arg: &TestArgument = it.fetch().unwrap(); assert_eq!(arg.p1, 0x66); assert_eq!(arg.p2, 0x6f); @@ -128,7 +128,7 @@ pub mod tests { #[test] fn string_argument() { - let mut it = ArgumentIterator::new(TEST_DATA.deref()); + let mut it = ArgumentIterator::new(&*TEST_DATA); let arg = it.fetch_str().unwrap(); assert_eq!(arg, "foo"); let arg = it.fetch_str().unwrap(); @@ -138,7 +138,7 @@ pub mod tests { #[test] fn mixed_arguments() { - let mut it = ArgumentIterator::new(TEST_DATA.deref()); + let mut it = ArgumentIterator::new(&*TEST_DATA); let arg: &TestArgument = it.fetch().unwrap(); assert_eq!(arg.p1, 0x66); assert_eq!(arg.p2, 0x6f); @@ -151,7 +151,7 @@ pub mod tests { #[test] fn out_of_data() { - let mut it = ArgumentIterator::new(TEST_DATA.deref()); + let mut it = ArgumentIterator::new(&*TEST_DATA); it.fetch::().unwrap(); let arg: Option<&TestArgument> = it.fetch(); assert!(arg.is_none()); diff --git a/src/ll/fuse_abi.rs b/src/ll/fuse_abi.rs index d5e9c01c..541bf64a 100644 --- a/src/ll/fuse_abi.rs +++ b/src/ll/fuse_abi.rs @@ -23,7 +23,6 @@ // TODO: fix all these non camel case types #![allow(non_camel_case_types)] - #[cfg(feature = "abi-7-9")] use crate::consts::{FATTR_ATIME_NOW, FATTR_MTIME_NOW}; use std::convert::TryFrom; @@ -739,6 +738,7 @@ pub struct fuse_open_out { pub open_flags: u32, #[cfg(not(feature = "abi-7-40"))] pub padding: u32, + /// The `backing_id` field is used to pass a backing file descriptor to the kernel. #[cfg(feature = "abi-7-40")] pub backing_id: u32, } diff --git a/src/ll/mod.rs b/src/ll/mod.rs index 834c2bab..65da5268 100644 --- a/src/ll/mod.rs +++ b/src/ll/mod.rs @@ -227,7 +227,7 @@ impl Errno { /// Use this to try to convert an integer error code into a fuser Errno pub fn from_i32(err: i32) -> Errno { - err.try_into().ok().map(Errno).unwrap_or(Errno::EIO) + err.try_into().ok().map_or(Errno::EIO, Errno) } } impl From for Errno { diff --git a/src/ll/reply.rs b/src/ll/reply.rs index 8dee26c3..aea5c9eb 100644 --- a/src/ll/reply.rs +++ b/src/ll/reply.rs @@ -88,7 +88,7 @@ impl<'a> Response<'a> { attr_valid: attr_ttl.as_secs(), entry_valid_nsec: file_ttl.subsec_nanos(), attr_valid_nsec: attr_ttl.subsec_nanos(), - attr: fuse_attr_from_attr(&attr), + attr: fuse_attr_from_attr(attr), }; Self::from_struct(d.as_bytes()) } @@ -228,7 +228,7 @@ impl<'a> Response<'a> { // these fields are only needed for unrestricted ioctls flags: 0, in_iovs: 1, - out_iovs: if !data.is_empty() { 1 } else { 0 }, + out_iovs: if data.is_empty() { 0 } else { 1 }, }; // TODO: Don't copy this data let mut v: ResponseBuf = ResponseBuf::from_slice(r.as_bytes()); @@ -291,9 +291,9 @@ pub(crate) fn mode_from_kind_and_perm(kind: FileType, perm: u16) -> u32 { FileType::Symlink => libc::S_IFLNK, FileType::Socket => libc::S_IFSOCK, }) as u32 - | perm as u32 + | u32::from(perm) } -/// Returns a fuse_attr from FileAttr +/// Returns a `fuse_attr` from `FileAttr` pub(crate) fn fuse_attr_from_attr(attr: &crate::FileAttr) -> abi::fuse_attr { let (atime_secs, atime_nanos) = time_from_system_time(&attr.atime); let (mtime_secs, mtime_nanos) = time_from_system_time(&attr.mtime); @@ -350,6 +350,7 @@ impl From for Attr { } #[derive(Debug)] +/// A generic data buffer struct EntListBuf { max_size: usize, buf: ResponseBuf, @@ -391,7 +392,7 @@ impl From for i64 { } */ -/// Used to respond to [ReaddirPlus] requests. +/// Data buffer used to respond to [`Readdir`] requests. #[derive(Debug)] pub struct DirentBuf(EntListBuf); impl From for Response<'_> { @@ -408,21 +409,25 @@ impl DirentBuf { /// Add an entry to the directory reply buffer. Returns true if the buffer is full. /// A transparent offset value can be provided for each entry. The kernel uses these /// value to request the next entries in further readdir calls - #[must_use] - pub fn push(&mut self, ent: &crate::Dirent<'_>) -> bool { - let name = ent.name.try_borrow() - .expect("Borrow name from dirent failed"); + pub fn push(&mut self, ent: &crate::Dirent<'_>) -> Result { + let name = match ent.name.try_borrow() { + Ok(slice) => slice, + Err(e) => { + log::error!("Dirent.name Borrow error {e:?}"); + return Err(Errno::EIO); + } + }; let header = abi::fuse_dirent { - ino: ent.ino.into(), + ino: ent.ino, off: ent.offset, namelen: name.len().try_into().expect("Name too long"), typ: mode_from_kind_and_perm(ent.kind, 0) >> 12, }; - self.0.push([header.as_bytes(), &name]) + Ok(self.0.push([header.as_bytes(), &name])) } } -/// Used to respond to [Readdir] requests. +/// Data buffer used to respond to [`ReaddirPlus`] requests. #[cfg(feature = "abi-7-21")] #[derive(Debug)] pub struct DirentPlusBuf(EntListBuf); @@ -443,14 +448,18 @@ impl DirentPlusBuf { /// Add an entry to the directory reply buffer. Returns true if the buffer is full. /// A transparent offset value can be provided for each entry. The kernel uses these /// value to request the next entries in further readdir calls - #[must_use] - pub fn push(&mut self, x: &crate::Dirent<'_>, y: &crate::Entry) -> bool { - let name = x.name.try_borrow() - .expect("Borrow name from dirent failed"); + pub fn push(&mut self, x: &crate::Dirent<'_>, y: &crate::Entry) -> Result { + let name = match x.name.try_borrow() { + Ok(slice) => slice, + Err(e) => { + log::error!("Dirent.name Borrow error {e:?}"); + return Err(Errno::EIO); + } + }; let header = abi::fuse_direntplus { entry_out: abi::fuse_entry_out { nodeid: y.ino, - generation: y.generation.unwrap_or(1).into(), + generation: y.generation.unwrap_or(1), entry_valid: y.file_ttl.as_secs(), attr_valid: y.attr_ttl.as_secs(), entry_valid_nsec: y.file_ttl.subsec_nanos(), @@ -464,15 +473,17 @@ impl DirentPlusBuf { typ: mode_from_kind_and_perm(x.kind, 0) >> 12, }, }; - self.0.push([header.as_bytes(), &name]) + Ok(self.0.push([header.as_bytes(), &name])) } } #[cfg(test)] +#[allow(clippy::cast_possible_truncation)] // predetermined literals will not be truncated mod test { use std::num::NonZeroI32; use super::super::test::ioslice_to_vec; + #[allow(clippy::wildcard_imports)] use super::*; #[test] @@ -566,7 +577,7 @@ mod test { flags: 0x99, blksize: 0xbb, }; - let r = Response::new_entry(INodeNo(0x11), Generation(0xaa), ttl, &attr.into(), ttl); + let r = Response::new_entry(INodeNo(0x11), Generation(0xaa), ttl, &attr, ttl); assert_eq!( r.with_iovec(RequestId(0xdeadbeef), ioslice_to_vec), expected @@ -841,13 +852,13 @@ mod test { offset: 1, kind: FileType::Directory, name: "hello".into() - })); + }).unwrap()); assert!(!buf.push(&crate::Dirent { ino: 0xccdd, offset: 2, kind: FileType::RegularFile, name: "world.rs".into() - })); + }).unwrap()); let r: Response<'_> = buf.into(); assert_eq!( r.with_iovec(RequestId(0xdeadbeef), ioslice_to_vec), diff --git a/src/ll/request.rs b/src/ll/request.rs index 819d6c07..28305220 100644 --- a/src/ll/request.rs +++ b/src/ll/request.rs @@ -32,10 +32,10 @@ pub enum RequestError { /// 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 +/// 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]. +/// [`SetLkW`]. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] #[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))] pub struct RequestId(pub u64); @@ -48,16 +48,16 @@ impl From for u64 { /// 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 +/// 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]. +/// 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 +/// [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 @@ -65,9 +65,9 @@ impl From for u64 { /// may be recycled and your filesystem implementation may clean up its /// internal data-structures relating to that inode. /// -/// We implement conversion from [INodeNo] to [u64] but not vice-versa because -/// not all [u64]s are valid [INodeNo]s, but the reverse is true. So to produce -/// a [INodeNo] from a [u64] we must be explicit. +/// We implement conversion from [`INodeNo`] to [u64] but not vice-versa because +/// not all [u64]s are valid [`INodeNo`]s, but the reverse is true. So to produce +/// a [`INodeNo`] from a [u64] we must be explicit. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] #[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))] pub struct INodeNo(pub u64); @@ -80,38 +80,38 @@ impl From for u64 { /// 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. +/// 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 +/// 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. +/// 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]). +/// [`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 +/// 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 +/// 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 +/// 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. /// -/// We implement conversion from FileHandle to u64 but not vice-versa because -/// not all u64s are valid FileHandles, but the reverse is true. So to produce -/// a FileHandle from a u64 we must be explicit. +/// We implement conversion from `FileHandle` to u64 but not vice-versa because +/// not all u64s are valid `FileHandles`, but the reverse is true. So to produce +/// a `FileHandle` from a u64 we must be explicit. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] #[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))] pub struct FileHandle(pub u64); @@ -126,11 +126,11 @@ impl From for u64 { /// /// TODO: Document lock lifecycle and how and when to implement file locking. /// -/// See [Read], [Write], [Release], [Flush], [GetLk], [SetLk], [SetLkW]. +/// See [`Read`], [`Write`], [`Release`], [`Flush`], [`GetLk`], [`SetLk`], [`SetLkW`]. /// -/// We implement conversion from [LockOwner] to [u64] but not vice-versa -/// because all LockOwners are valid [u64]s, but not vice-versa. So to produce -/// a [LockOwner] from a [u64] we must be explicit. +/// We implement conversion from [`LockOwner`] to [`u64`] but not vice-versa +/// because all `LockOwners` are valid [`u64`]s, but not vice-versa. So to produce +/// a [`LockOwner`] from a [`u64`] we must be explicit. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] #[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))] pub struct LockOwner(pub u64); @@ -264,8 +264,10 @@ mod op { super::{argument::ArgumentIterator, TimeOrNow}, FilenameInDir, Request, }; + #[allow(clippy::wildcard_imports)] use super::{ - abi::consts::*, abi::*, FileHandle, INodeNo, Lock, LockOwner, Operation, RequestId, + abi::consts::*, abi::*, + FileHandle, INodeNo, Lock, LockOwner, Operation, RequestId, }; use std::{ convert::TryInto, @@ -278,9 +280,9 @@ mod op { /// 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]. + /// 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 struct Lookup<'a> { header: &'a fuse_in_header, @@ -294,13 +296,13 @@ mod op { } /// Forget about an inode. /// - /// The nlookup parameter indicates the number of lookups previously performed on + /// 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]. + /// 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. @@ -405,10 +407,10 @@ mod op { #[cfg(not(feature = "abi-7-23"))] None } - /// The value set by the [Open] method. See [FileHandle]. + /// 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]. + /// attributes - i.e. they used [`libc::fchmod`] rather than [`libc::chmod`]. pub fn file_handle(&self) -> Option { match self.arg.valid & FATTR_FH { 0 => None, @@ -640,9 +642,9 @@ mod op { /// 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 for more details. + /// 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 struct Open<'a> { header: &'a fuse_in_header, @@ -659,7 +661,7 @@ mod op { /// /// 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 + /// 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)] @@ -702,7 +704,7 @@ mod op { /// 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 + /// 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)] @@ -723,15 +725,15 @@ mod op { pub fn data(&self) -> &'a [u8] { self.data } - /// Will contain FUSE_WRITE_CACHE, if this write is from the page cache. If set, + /// 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 /// - /// TODO: WriteFlags type or remove this + /// TODO: `WriteFlags` type or remove this pub fn write_flags(&self) -> u32 { self.arg.write_flags } - /// lock_owner: only supported with ABI >= 7.9 + /// `lock_owner`: only supported with ABI >= 7.9 pub fn lock_owner(&self) -> Option { #[cfg(feature = "abi-7-9")] if self.arg.write_flags & FUSE_WRITE_LOCKOWNER != 0 { @@ -742,7 +744,7 @@ mod op { #[cfg(not(feature = "abi-7-9"))] None } - /// flags: these are the file flags, such as O_SYNC. Only supported with ABI >= 7.9 + /// flags: these are the file flags, such as `O_SYNC`. Only supported with ABI >= 7.9 /// TODO: Make a Flags type specifying valid values pub fn flags(&self) -> i32 { #[cfg(feature = "abi-7-9")] @@ -762,7 +764,7 @@ mod op { /// 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] + /// 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. @@ -776,7 +778,7 @@ mod op { pub fn flush(&self) -> bool { self.arg.release_flags & FUSE_RELEASE_FLUSH != 0 } - /// The value set by the [Open] method. + /// The value set by the [`Open`] method. pub fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) } @@ -805,7 +807,7 @@ mod op { } impl_request!(FSync<'a>); impl FSync<'_> { - /// The value set by the [Open] method. + /// The value set by the [`Open`] method. pub fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) } @@ -835,7 +837,7 @@ mod op { pub fn flags(&self) -> i32 { self.arg.flags } - /// This will always be 0 except on MacOS. It's recommended that + /// This will always be 0 except on `MacOS`. It's recommended that /// implementations return EINVAL if this is not 0. pub fn position(&self) -> u32 { #[cfg(target_os = "macos")] @@ -847,7 +849,7 @@ mod op { /// Get an extended attribute. /// - /// If the requested XAttr doesn't exist return [Err(Errno::NO_XATTR)] which will + /// 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 struct GetXAttr<'a> { @@ -857,32 +859,32 @@ mod op { } impl_request!(GetXAttr<'a>); - /// Type for [GetXAttrSizeEnum::GetSize]. + /// Type for [`GetXAttrSizeEnum::GetSize`]. /// - /// Represents a request from the user to get the size of the data stored in the XAttr. + /// Represents a request from the user to get the size of the data stored in the `XAttr`. #[derive(Debug)] pub struct GetXAttrSize(); #[derive(Debug)] - /// Return type for [GetXAttr::size]. + /// Return type for [`GetXAttr::size`]. pub enum GetXAttrSizeEnum { - /// User is requesting the size of the data stored in the XAttr + /// 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)]. + /// 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 + /// Name of the `XAttr` pub fn name(&self) -> &'a OsStr { self.name } - /// See [GetXAttrSizeEnum]. + /// 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 + /// 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 fn size(&self) -> GetXAttrSizeEnum { let s: Result = self.arg.size.try_into(); @@ -891,7 +893,7 @@ mod op { Err(_) => GetXAttrSizeEnum::GetSize(GetXAttrSize()), } } - /// The size of the buffer the user has allocated to store the XAttr value. + /// The size of the buffer the user has allocated to store the `XAttr` value. pub(crate) fn size_u32(&self) -> u32 { self.arg.size } @@ -906,7 +908,7 @@ mod op { impl_request!(ListXAttr<'a>); 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 + /// `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. @@ -917,8 +919,8 @@ mod op { /// 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 + /// 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 struct RemoveXAttr<'a> { header: &'a fuse_in_header, @@ -926,7 +928,7 @@ mod op { } impl_request!(RemoveXAttr<'a>); impl<'a> RemoveXAttr<'a> { - /// Name of the XAttr to remove + /// Name of the `XAttr` to remove pub fn name(&self) -> &'a OsStr { self.name } @@ -934,7 +936,7 @@ mod op { /// Flush method. /// - /// This is called on each close() of the opened file. Since file descriptors can + /// 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. @@ -942,7 +944,7 @@ mod op { /// 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'. + /// operations (setlk, getlk) it should remove all locks belonging to `lock_owner`. #[derive(Debug)] pub struct Flush<'a> { header: &'a fuse_in_header, @@ -966,12 +968,13 @@ mod op { } impl_request!(Init<'a>); impl Init<'_> { + #[allow(clippy::cast_possible_truncation)] // truncation is a feature of this computation pub fn capabilities(&self) -> u64 { #[cfg(feature = "abi-7-36")] if self.arg.flags & (FUSE_INIT_EXT as u32) != 0 { - return (self.arg.flags as u64) | ((self.arg.flags2 as u64) << 32); + return u64::from(self.arg.flags) | (u64::from(self.arg.flags2) << 32); } - self.arg.flags as u64 + u64::from(self.arg.flags) } pub fn max_readahead(&self) -> u32 { self.arg.max_readahead @@ -984,11 +987,11 @@ mod op { /// 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 + /// 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]. + /// between [`OpenDir`] and [`ReleaseDir`]. /// /// TODO: Document how to implement "standard conforming directory stream operations" #[derive(Debug)] @@ -1012,7 +1015,7 @@ mod op { } impl_request!(ReadDir<'a>); impl ReadDir<'_> { - /// The value set by the [OpenDir] method. + /// The value set by the [`OpenDir`] method. pub fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) } @@ -1026,7 +1029,7 @@ mod op { /// Release an open directory. /// - /// For every [OpenDir] call there will be exactly one [ReleaseDir] call. + /// For every [`OpenDir`] call there will be exactly one [`ReleaseDir`] call. #[derive(Debug)] pub struct ReleaseDir<'a> { header: &'a fuse_in_header, @@ -1034,7 +1037,7 @@ mod op { } impl_request!(ReleaseDir<'a>); impl ReleaseDir<'_> { - /// The value set by the [OpenDir] method. + /// The value set by the [`OpenDir`] method. pub fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) } @@ -1065,7 +1068,7 @@ mod op { } impl_request!(FSyncDir<'a>); impl FSyncDir<'_> { - /// The value set by the [OpenDir] method. See [FileHandle]. + /// The value set by the [`OpenDir`] method. See [`FileHandle`]. pub fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) } @@ -1083,7 +1086,7 @@ mod op { } impl_request!(GetLk<'a>); impl GetLk<'_> { - /// The value set by the [Open] method. See [FileHandle]. + /// The value set by the [Open] method. See [`FileHandle`]. pub fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) } @@ -1099,8 +1102,8 @@ mod op { /// /// 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 + /// '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)] @@ -1110,7 +1113,7 @@ mod op { } impl_request!(SetLk<'a>); impl SetLk<'_> { - /// The value set by the [Open] method. See [FileHandle]. + /// The value set by the [`Open`] method. See [`FileHandle`]. pub fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) } @@ -1128,7 +1131,7 @@ mod op { } impl_request!(SetLkW<'a>); impl SetLkW<'_> { - /// The value set by the [Open] method. See [FileHandle]. + /// The value set by the [`Open`] method. See [`FileHandle`]. pub fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) } @@ -1142,7 +1145,7 @@ mod op { /// Check file access permissions. /// - /// This will be called for the `access()` system call. If the 'default_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 struct Access<'a> { @@ -1161,12 +1164,12 @@ mod op { /// 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 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. + /// 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 struct Create<'a> { header: &'a fuse_in_header, @@ -1181,7 +1184,7 @@ mod op { pub fn mode(&self) -> u32 { self.arg.mode } - /// Flags as passed to the creat() call + /// Flags as passed to the `create()` call pub fn flags(&self) -> i32 { self.arg.flags } @@ -1213,7 +1216,7 @@ mod op { /// /// 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]. + /// 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 @@ -1227,7 +1230,7 @@ mod op { /// /// 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. + /// 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)] @@ -1244,7 +1247,7 @@ mod op { /// 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 + /// with the `blkdev` option #[derive(Debug)] pub struct BMap<'a> { header: &'a fuse_in_header, @@ -1284,7 +1287,7 @@ mod op { pub fn unrestricted(&self) -> bool { self.arg.flags & consts::FUSE_IOCTL_UNRESTRICTED != 0 } - /// The value set by the [Open] method. See [FileHandle]. + /// The value set by the [`Open`] method. See [`FileHandle`]. pub fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) } @@ -1312,7 +1315,7 @@ mod op { impl_request!(Poll<'a>); #[cfg(feature = "abi-7-11")] impl Poll<'_> { - /// The value set by the [Open] method. See [FileHandle]. + /// The value set by the [`Open`] method. See [`FileHandle`]. pub fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) } @@ -1336,7 +1339,7 @@ mod op { } } - /// NotifyReply. TODO: currently unsupported by fuser + /// `NotifyReply`. TODO: currently unsupported by fuser #[cfg(feature = "abi-7-15")] #[derive(Debug)] pub struct NotifyReply<'a> { @@ -1347,7 +1350,7 @@ mod op { #[cfg(feature = "abi-7-15")] impl_request!(NotifyReply<'a>); - /// BatchForget: TODO: merge with Forget + /// `BatchForget`: TODO: merge with Forget #[cfg(feature = "abi-7-16")] #[derive(Debug)] pub struct BatchForget<'a> { @@ -1360,7 +1363,7 @@ mod op { impl_request!(BatchForget<'a>); #[cfg(feature = "abi-7-16")] impl<'a> BatchForget<'a> { - /// TODO: Don't return fuse_forget_one, this should be private + /// TODO: Don't return `fuse_forget_one`, this should be private pub fn nodes(&self) -> &'a [fuse_forget_one] { self.nodes } @@ -1379,7 +1382,7 @@ mod op { ino: node.nodeid, nlookup: node.nlookup } - }) + }); } buf } @@ -1398,7 +1401,7 @@ mod op { impl_request!(FAllocate<'a>); #[cfg(feature = "abi-7-19")] impl FAllocate<'_> { - /// The value set by the [Open] method. See [FileHandle]. + /// The value set by the [`Open`] method. See [`FileHandle`]. pub fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) } @@ -1416,7 +1419,7 @@ mod op { /// Read directory. /// - /// TODO: Document when this is called rather than ReadDirectory + /// TODO: Document when this is called rather than `ReadDir` #[cfg(feature = "abi-7-21")] #[derive(Debug)] pub struct ReadDirPlus<'a> { @@ -1427,7 +1430,7 @@ mod op { impl_request!(ReadDirPlus<'a>); #[cfg(feature = "abi-7-21")] impl ReadDirPlus<'_> { - /// The value set by the [Open] method. See [FileHandle]. + /// The value set by the [`Open`] method. See [`FileHandle`]. pub fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) } @@ -1441,7 +1444,7 @@ mod op { /// Rename a file. /// - /// TODO: Document the differences to [Rename] and [Exchange] + /// TODO: Document the differences to [`Rename`] and [`Exchange`] #[cfg(feature = "abi-7-23")] #[derive(Debug)] pub struct Rename2<'a> { @@ -1468,8 +1471,8 @@ mod op { } } /// 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 + /// [`libc::RENAME_EXCHANGE`], [`libc::RENAME_NOREPLACE`] and + /// [`libc::RENAME_WHITEOUT`]. If you don't handle a particular flag /// reply with an EINVAL error. /// /// TODO: Replace with enum/flags type @@ -1491,7 +1494,7 @@ mod op { impl_request!(Lseek<'a>); #[cfg(feature = "abi-7-24")] impl Lseek<'_> { - /// The value set by the [Open] method. See [FileHandle]. + /// The value set by the [`Open`] method. See [`FileHandle`]. pub fn file_handle(&self) -> FileHandle { FileHandle(self.arg.fh) } @@ -1509,7 +1512,7 @@ mod op { #[derive(Debug, Clone, Copy)] pub struct CopyFileRangeFile { pub inode: INodeNo, - /// The value set by the [Open] method. See [FileHandle]. + /// The value set by the [`Open`] method. See [`FileHandle`]. pub file_handle: FileHandle, pub offset: i64, } @@ -1566,7 +1569,7 @@ mod op { } } - /// macOS only: Query extended times (bkuptime and crtime). Set fuse_init_out.flags + /// 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)] @@ -1863,6 +1866,7 @@ mod op { }) } } +#[allow(clippy::wildcard_imports)] use op::*; /// Filesystem operation (and arguments) the kernel driver wants us to perform. The fields of each diff --git a/src/mnt/fuse2_sys.rs b/src/mnt/fuse2_sys.rs index 406bcbd9..4eea4bce 100644 --- a/src/mnt/fuse2_sys.rs +++ b/src/mnt/fuse2_sys.rs @@ -5,6 +5,8 @@ #![warn(missing_debug_implementations)] #![allow(missing_docs)] +// TODO: fix all these non camel case types +#![allow(non_camel_case_types)] use libc::{c_char, c_int}; diff --git a/src/mnt/mod.rs b/src/mnt/mod.rs index ee8001cd..0b01fa6b 100644 --- a/src/mnt/mod.rs +++ b/src/mnt/mod.rs @@ -25,7 +25,7 @@ use std::io; #[cfg(any(feature = "libfuse", test))] use mount_options::MountOption; -/// Helper function to provide options as a fuse_args struct +/// Helper function to provide options as a `fuse_args` struct /// (which contains an argc count and an argv pointer) #[cfg(any(feature = "libfuse", test))] fn with_fuse_args T>(options: &[MountOption], f: F) -> T { @@ -104,12 +104,11 @@ fn is_mounted(fuse_device: &File) -> bool { let err = io::Error::last_os_error(); if err.kind() == io::ErrorKind::Interrupted { continue; - } else { - // This should never happen. The fd is guaranteed good as `File` owns it. - // According to man poll ENOMEM is the only error code unhandled, so we panic - // consistent with rust's usual ENOMEM behaviour. - panic!("Poll failed with error {}", err) } + // This should never happen. The fd is guaranteed good as `File` owns it. + // According to man poll ENOMEM is the only error code unhandled, so we panic + // consistent with rust's usual ENOMEM behaviour. + panic!("Poll failed with error {err}") } _ => unreachable!(), }; diff --git a/src/mnt/mount_options.rs b/src/mnt/mount_options.rs index 1778d28e..2c1e5974 100644 --- a/src/mnt/mount_options.rs +++ b/src/mnt/mount_options.rs @@ -91,13 +91,13 @@ pub fn check_option_conflicts(options: &[MountOption]) -> Result<(), io::Error> options_set.extend(options.iter().cloned()); let conflicting: HashSet = options.iter().flat_map(conflicts_with).collect(); let intersection: Vec = conflicting.intersection(&options_set).cloned().collect(); - if !intersection.is_empty() { + if intersection.is_empty() { + Ok(()) + } else { Err(io::Error::new( ErrorKind::InvalidInput, format!("Conflicting mount options found: {intersection:?}"), )) - } else { - Ok(()) } } @@ -157,7 +157,7 @@ pub fn option_to_string(option: &MountOption) -> String { /// Parses mount command args. /// /// Input: ["-o", "suid", "-o", "ro,nodev,noexec", "-osync"] -/// Output Ok([Suid, RO, NoDev, NoExec, Sync]) +/// Output Ok([`Suid`, `RO`, `NoDev`, `NoExec`, `Sync`]) pub(crate) fn parse_options_from_args(args: &[&OsStr]) -> io::Result> { let err = |x| io::Error::new(ErrorKind::InvalidInput, x); let args: Option> = args.iter().map(|x| x.to_str()).collect(); @@ -174,7 +174,7 @@ pub(crate) fn parse_options_from_args(args: &[&OsStr]) -> io::Result return Err(err(format!("Error parsing args: expected -o, got {x}"))), }; for x in opt.split(',') { - out.push(MountOption::from_str(x)) + out.push(MountOption::from_str(x)); } } Ok(out) @@ -194,7 +194,7 @@ mod test { #[test] fn option_round_trip() { use super::MountOption::*; - for x in [ + for x in &[ FSName("Blah".to_owned()), Subtype("Bloo".to_owned()), CUSTOM("bongos".to_owned()), @@ -215,9 +215,8 @@ mod test { Sync, Async, ] - .iter() { - assert_eq!(*x, MountOption::from_str(option_to_string(x).as_ref())) + assert_eq!(*x, MountOption::from_str(option_to_string(x).as_ref())); } } diff --git a/src/notify.rs b/src/notify.rs index 4442252a..f5461516 100644 --- a/src/notify.rs +++ b/src/notify.rs @@ -116,6 +116,6 @@ impl Notifier { /// would exceed the capacity that its length descriptor field is /// capable of encoding. fn too_big_err(tfie: std::num::TryFromIntError) -> io::Error { - io::Error::new(io::ErrorKind::Other, format!("Data too large: {}", tfie)) + io::Error::new(io::ErrorKind::Other, format!("Data too large: {tfie}")) } } diff --git a/src/reply.rs b/src/reply.rs index 0e8c0594..80ca5b34 100644 --- a/src/reply.rs +++ b/src/reply.rs @@ -36,7 +36,7 @@ impl fmt::Debug for Box { } } -/// ReplyHander is a struct which holds the unique identifiers needed to reply +/// `ReplyHander` is a struct which holds the unique identifiers needed to reply /// to a specific request. Traits are implemented on the struct so that ownership /// of the struct determines whether the identifiers have ever been used. /// This guarantees that a reply is send at most once per request. @@ -59,26 +59,26 @@ impl ReplyHandler { } /// Reply to a request with a formatted reponse. Can be called - /// more than once (the `&mut self`` argument does not consume `self`) + /// more than once (the `&mut self` argument does not consume `self`) /// Avoid using this variant unless you know what you are doing! fn send_ll_mut(&mut self, response: &ll::Response<'_>) { assert!(self.sender.is_some()); let sender = self.sender.take().unwrap(); let res = response.with_iovec(self.unique, |iov| sender.send(iov)); if let Err(err) = res { - error!("Failed to send FUSE reply: {}", err); + error!("Failed to send FUSE reply: {err}"); } } /// Reply to a request with a formatted reponse. May be called - /// only once (the `mut self`` argument consumes `self`). + /// only once (the `mut self` argument consumes `self`). /// Use this variant for general replies. fn send_ll(mut self, response: &ll::Response<'_>) { - self.send_ll_mut(response) + self.send_ll_mut(response); } } -/// Drop is implemented on ReplyHandler so that if the program logic fails +/// Drop is implemented on `ReplyHandler` so that if the program logic fails /// (for example, due to an interrupt or a panic), /// a reply will be sent when the Reply Handler falls out of scope. impl Drop for ReplyHandler { @@ -97,19 +97,19 @@ impl Drop for ReplyHandler { #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))] pub enum FileType { - /// Named pipe (S_IFIFO) + /// Named pipe (`S_IFIFO`) NamedPipe, - /// Character device (S_IFCHR) + /// Character device (`S_IFCHR`) CharDevice, - /// Block device (S_IFBLK) + /// Block device (`S_IFBLK`) BlockDevice, - /// Directory (S_IFDIR) + /// Directory (`S_IFDIR`) Directory, - /// Regular file (S_IFREG) + /// Regular file (`S_IFREG`) RegularFile, - /// Symbolic link (S_IFLNK) + /// Symbolic link (`S_IFLNK`) Symlink, - /// Unix domain socket (S_IFSOCK) + /// Unix domain socket (`S_IFSOCK`) Socket, } @@ -165,20 +165,25 @@ pub struct Entry { pub attr_ttl: Duration, } -#[derive(Debug, Clone)] //TODO #[derive(Copy)] +#[derive(Debug, Clone, Copy)] #[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))] /// Open file handle response data pub struct Open { /// File handle for the opened file pub fh: u64, /// Flags for the opened file - pub flags: u32 + pub flags: u32, + /// Optional backing id for passthrough + pub backing_id: Option } +/// A container for bytes, implementing flexible ownership. +pub type Bytes<'a> = Container<'a, u8>; + #[derive(Debug, Clone)] #[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))] -/// A sinegle directory entry. -/// The `'name` lifetime parameter is associated with the `name` field if it is from borrowed Bytes. +/// A single directory entry. +/// The `'name` lifetime parameter is associated with the `name` field if it is a borrowed value. pub struct Dirent<'name> { /// file inode number pub ino: u64, @@ -196,7 +201,6 @@ pub type DirentList<'dir, 'name> = Container<'dir, Dirent<'name>>; /// A list of directory entries, plus additional file data for the kernel cache. pub type DirentPlusList<'dir, 'name> = Container<'dir, (Dirent<'name>, Entry)>; - #[cfg(target_os = "macos")] #[derive(Debug)] /// Xtimes response data @@ -283,8 +287,16 @@ impl ReplyHandler { } /// Reply to a general request with data - pub fn data<'a>(self, data: Bytes<'a>) { - self.send_ll(&ll::Response::new_slice(&data.borrow())); + pub fn data(self, data: Bytes<'_>) { + match data.try_borrow(){ + Ok(slice) => { + self.send_ll(&ll::Response::new_slice(&slice)); + }, + Err(e) => { + log::error!("ReplyHandler::data: Borrow Error: {e:?}"); + self.error(Errno::EIO); + } + } } // Reply to an init request with available features @@ -334,9 +346,8 @@ impl ReplyHandler { ll::INodeNo(entry.ino), ll::Generation(entry.generation.unwrap_or(1)), entry.file_ttl, - &entry.attr.into(), + &entry.attr, entry.attr_ttl, - )); } @@ -355,22 +366,19 @@ impl ReplyHandler { pub fn opened(self, open: Open) { #[cfg(feature = "abi-7-40")] assert_eq!(open.flags & FOPEN_PASSTHROUGH, 0); - self.send_ll(&ll::Response::new_open(ll::FileHandle(open.fh), open.flags, 0)) + self.send_ll(&ll::Response::new_open(ll::FileHandle(open.fh), open.flags, 0)); } /// Reply to a request with the number of bytes written pub fn written(self, size: u32) { - self.send_ll(&ll::Response::new_write(size)) + self.send_ll(&ll::Response::new_write(size)); } - /// Reply to a statfs request - pub fn statfs( - self, - statfs: Statfs - ) { + /// Reply to a statfs request + pub fn statfs(self, statfs: Statfs) { self.send_ll(&ll::Response::new_statfs( statfs.blocks, statfs.bfree, statfs.bavail, statfs.files, statfs.ffree, statfs.bsize, statfs.namelen, statfs.frsize, - )) + )); } /// Reply to a request with a newle created file entry and its newly open file handle @@ -384,7 +392,7 @@ impl ReplyHandler { ll::FileHandle(open.fh), open.flags, 0, - )) + )); } /// Reply to a request with a file lock @@ -393,24 +401,32 @@ impl ReplyHandler { range: (lock.start, lock.end), typ: lock.typ, pid: lock.pid, - })) + })); } /// Reply to a request with a bmap pub fn bmap(self, block: u64) { - self.send_ll(&ll::Response::new_bmap(block)) + self.send_ll(&ll::Response::new_bmap(block)); } #[cfg(feature = "abi-7-11")] /// Reply to a request with an ioctl pub fn ioctl(self, ioctl: Ioctl<'_>) { - self.send_ll(&ll::Response::new_ioctl(ioctl.result, &ioctl.data.borrow())); + match ioctl.data.try_borrow(){ + Ok(slice) => { + self.send_ll(&ll::Response::new_ioctl(ioctl.result, &slice)); + }, + Err(e) => { + log::error!("ReplyHandler::ioctl: Borrow Error: {e:?}"); + self.error(Errno::EIO); + } + } } #[cfg(feature = "abi-7-11")] /// Reply to a request with a poll result pub fn poll(self, revents: u32) { - self.send_ll(&ll::Response::new_poll(revents)) + self.send_ll(&ll::Response::new_poll(revents)); } /// Reply to a request with a filled directory buffer @@ -421,10 +437,12 @@ impl ReplyHandler { min_offset: i64, ) { let mut buf = DirentBuf::new(size); + // Alternatively, consider a panic if the borrow fails. let entries = match entries_list.try_borrow(){ Ok(entries) => entries, Err(e) => { - log::error!("ReplyHandler::dir: Borrow Error: {:?}", e); + log::error!("ReplyHandler::dir: Borrow Error: {e:?}"); + self.error(Errno::EIO); return; } }; @@ -432,13 +450,19 @@ impl ReplyHandler { if item.offset < min_offset { log::debug!("ReplyHandler::dir: skipping item with offset #{}", item.offset); continue; - } else { - log::debug!("ReplyHandler::dir: processing item with offset #{}", item.offset); } - let full= buf.push(item); - if full { - log::debug!("ReplyHandler::dir: buffer full!"); - break; + log::debug!("ReplyHandler::dir: processing item with offset #{}", item.offset); + match buf.push(item) { + Ok(true) => { + log::debug!("ReplyHandler::dir: buffer full!"); + break; + } + Ok(false) => {} + Err(e) => { + log::error!("ReplyHandler::dir: abort!"); + self.error(e); + return; + } } } self.send_ll(&buf.into()); @@ -453,10 +477,12 @@ impl ReplyHandler { min_offset: i64, ) { let mut buf = DirentPlusBuf::new(size); + // Alternatively, consider a panic if the borrow fails. let entries = match entries_plus_list.try_borrow(){ Ok(entries) => entries, Err(e) => { - log::error!("ReplyHandler::dirplus: Borrow Error: {:?}", e); + log::error!("ReplyHandler::dirplus: Borrow Error: {e:?}"); + self.error(Errno::EIO); return; } }; @@ -464,13 +490,19 @@ impl ReplyHandler { if dirent.offset < min_offset { log::debug!("ReplyHandler::dirplus: skipping item with offset #{}", dirent.offset); continue; - } else { - log::debug!("ReplyHandler::dirplus: processing item with offset #{}", dirent.offset); } - let full = buf.push(&dirent, &entry); - if full { - log::debug!("ReplyHandler::dirplus: buffer full!"); - break; + log::debug!("ReplyHandler::dirplus: processing item with offset #{}", dirent.offset); + match buf.push(dirent, entry) { + Ok(true) => { + log::debug!("ReplyHandler::dirplus: buffer full!"); + break; + } + Ok(false) => {} + Err(e) => { + log::error!("ReplyHandler::dirplus: abort!"); + self.error(e); + return; + } } } self.send_ll(&buf.into()); @@ -486,24 +518,39 @@ impl ReplyHandler { /// Reply to a request with the size of an xattr result. pub fn xattr_size(self, size: u32) { - self.send_ll(&ll::Response::new_xattr_size(size)) + self.send_ll(&ll::Response::new_xattr_size(size)); } /// Reply to a request with the data in an xattr result. pub fn xattr_data(self, data: Bytes<'_>) { - self.send_ll(&ll::Response::new_slice(&data.borrow())) + match data.try_borrow(){ + Ok(slice) => { + self.send_ll(&ll::Response::new_slice(&slice)); + }, + Err(e) => { + log::error!("ReplyHandler::xattr_data: Borrow Error: {e:?}"); + self.error(Errno::EIO); + } + } } #[cfg(feature = "abi-7-24")] /// Reply to a request with a seeked offset pub fn offset(self, offset: i64) { - self.send_ll(&ll::Response::new_lseek(offset)) + self.send_ll(&ll::Response::new_lseek(offset)); } + /// Disable this replyhandler. No reply will be sent. + pub fn no_reply(mut self) { + self.sender = None; + } } #[cfg(test)] +#[allow(clippy::unreadable_literal)] // ugly hardcoded literals for testing +#[allow(clippy::cast_possible_truncation)] // predetermined literals will not be truncated mod test { + #[allow(clippy::wildcard_imports)] use super::*; use crate::{FileAttr, FileType}; use std::io::IoSlice; @@ -550,7 +597,7 @@ mod test { fn send(&self, data: &[IoSlice<'_>]) -> std::io::Result<()> { let mut v = vec![]; for x in data { - v.extend_from_slice(x) + v.extend_from_slice(x); } assert_eq!(self.expected, v); Ok(()) @@ -653,7 +700,7 @@ mod test { 0x78, 0x56, 0x00, 0x00, ]); #[cfg(target_os = "macos")] - expected.extend_from_slice([ + expected.extend_from_slice(&[ // crtime (ns) 0x78, 0x56, 0x00, 0x00, ]); @@ -701,11 +748,11 @@ mod test { }; // send the test reply replyhandler.entry( - Entry{ + Entry { ino: attr.ino, generation: Some(0xaa), file_ttl: ttl, - attr: attr, + attr, attr_ttl: ttl, } ); @@ -911,7 +958,7 @@ mod test { ino: attr.ino, generation: Some(0xaa), file_ttl: ttl, - attr: attr, + attr, attr_ttl: ttl, }, Open { @@ -931,14 +978,12 @@ mod test { ], }; let replyhandler: ReplyHandler = ReplyHandler::new(0xdeadbeef, sender); - replyhandler.locked( - Lock { - start: 0x11, - end: 0x22, - typ: 0x33, - pid: 0x44 - } - ); + replyhandler.locked(Lock { + start: 0x11, + end: 0x22, + typ: 0x33, + pid: 0x44, + }); } #[test] @@ -1010,7 +1055,7 @@ mod test { 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, ]); #[cfg(target_os = "macos")] - attr_bytes.extend_from_slice([ + attr_bytes.extend_from_slice(&[ 0x78, 0x56, 0x00, 0x00, ]); attr_bytes.extend_from_slice(&[ diff --git a/src/request.rs b/src/request.rs index 5c61c2a1..309ac8e8 100644 --- a/src/request.rs +++ b/src/request.rs @@ -6,28 +6,27 @@ //! TODO: This module is meant to go away soon in favor of `ll::Request`. use crate::ll::{fuse_abi as abi, Errno}; -use log::{debug, error, warn}; -use std::convert::TryFrom; +#[allow(unused_imports)] +use log::{debug, info, warn, error}; +use std::convert::{TryFrom, Into}; #[cfg(feature = "abi-7-28")] use std::convert::TryInto; use crate::channel::ChannelSender; use crate::ll::Request as _; -use crate::reply::{ReplyHandler}; +use crate::reply::ReplyHandler; use crate::session::{Session, SessionACL}; use crate::Filesystem; #[cfg(feature = "abi-7-11")] use crate::PollHandle; -use crate::{ll, KernelConfig, Forget}; +use crate::{ll, Forget, KernelConfig}; /// Request data structure #[derive(Debug)] pub struct Request<'a> { - /// Channel sender for sending the reply - ch: ChannelSender, /// Request raw data #[allow(unused)] - data: &'a [u8], + data: &'a [u8], // TODO: maybe Vec? /// Parsed request request: ll::AnyRequest<'a>, /// Request metadata @@ -55,7 +54,7 @@ impl<'a> Request<'a> { let request = match ll::AnyRequest::try_from(data) { Ok(request) => request, Err(err) => { - error!("{}", err); + error!("{err}"); return None; } }; @@ -67,7 +66,7 @@ impl<'a> Request<'a> { pid: request.pid() }; let replyhandler = ReplyHandler::new(request.unique().into(), ch.clone()); - Some(Self { ch, data, request, meta, replyhandler }) + Some(Self { data, request, meta, replyhandler }) } /// Dispatch request to the given filesystem. @@ -122,7 +121,7 @@ impl<'a> Request<'a> { // We don't support ABI versions before 7.6 let v = x.version(); if v < ll::Version(7, 6) { - error!("Unsupported FUSE ABI version {}", v); + error!("Unsupported FUSE ABI version {v}"); self.replyhandler.error(Errno::EPROTO); return; } @@ -153,7 +152,7 @@ impl<'a> Request<'a> { // Filesystem refused the config. self.replyhandler.error(errno); } - }; + } } // Any operation is invalid before initialization _ if !se.initialized => { @@ -185,27 +184,27 @@ impl<'a> Request<'a> { ); match response { Ok(entry) => { - self.replyhandler.entry(entry) + self.replyhandler.entry(entry); }, - Err(err)=>{ - self.replyhandler.error(err) + Err(err)=> { + self.replyhandler.error(err); } } } ll::Operation::Forget(x) => { let target = Forget { ino: self.request.nodeid().into(), - nlookup: x.nlookup() + nlookup: x.nlookup(), }; - se.filesystem - .forget(self.meta, target); // no reply + se.filesystem.forget(self.meta, target); // no response + self.replyhandler.no_reply(); // no reply } ll::Operation::GetAttr(_attr) => { #[cfg(feature = "abi-7-9")] let response = se.filesystem.getattr( self.meta, self.request.nodeid().into(), - _attr.file_handle().map(|fh| fh.into()) + _attr.file_handle().map(Into::into) ); // Pre-abi-7-9 does not support providing a file handle. #[cfg(not(feature = "abi-7-9"))] @@ -216,10 +215,10 @@ impl<'a> Request<'a> { ); match response { Ok((attr,ttl))=> { - self.replyhandler.attr(attr, ttl) + self.replyhandler.attr(attr, ttl); } - Err(err)=>{ - self.replyhandler.error(err) + Err(err)=> { + self.replyhandler.error(err); } } } @@ -234,7 +233,7 @@ impl<'a> Request<'a> { x.atime(), x.mtime(), x.ctime(), - x.file_handle().map(|fh| fh.into()), + x.file_handle().map(Into::into), x.crtime(), x.chgtime(), x.bkuptime(), @@ -242,10 +241,10 @@ impl<'a> Request<'a> { ); match response { Ok((attr, ttl))=> { - self.replyhandler.attr(attr, ttl) + self.replyhandler.attr(attr, ttl); } - Err(err)=>{ - self.replyhandler.error(err) + Err(err)=> { + self.replyhandler.error(err); } } } @@ -256,10 +255,10 @@ impl<'a> Request<'a> { ); match response { Ok(data)=> { - self.replyhandler.data(data) + self.replyhandler.data(data); } - Err(err)=>{ - self.replyhandler.error(err) + Err(err)=> { + self.replyhandler.error(err); } } } @@ -274,10 +273,10 @@ impl<'a> Request<'a> { ); match response { Ok(entry)=> { - self.replyhandler.entry(entry) + self.replyhandler.entry(entry); } - Err(err)=>{ - self.replyhandler.error(err) + Err(err)=> { + self.replyhandler.error(err); } } } @@ -291,10 +290,10 @@ impl<'a> Request<'a> { ); match response { Ok(entry)=> { - self.replyhandler.entry(entry) + self.replyhandler.entry(entry); } - Err(err)=>{ - self.replyhandler.error(err) + Err(err)=> { + self.replyhandler.error(err); } } } @@ -306,10 +305,10 @@ impl<'a> Request<'a> { ); match response { Ok(())=> { - self.replyhandler.ok() + self.replyhandler.ok(); } - Err(err)=>{ - self.replyhandler.error(err) + Err(err)=> { + self.replyhandler.error(err); } } } @@ -321,10 +320,10 @@ impl<'a> Request<'a> { ); match response { Ok(())=> { - self.replyhandler.ok() + self.replyhandler.ok(); } - Err(err)=>{ - self.replyhandler.error(err) + Err(err)=> { + self.replyhandler.error(err); } } } @@ -337,10 +336,10 @@ impl<'a> Request<'a> { ); match response { Ok(entry)=> { - self.replyhandler.entry(entry) + self.replyhandler.entry(entry); } - Err(err)=>{ - self.replyhandler.error(err) + Err(err)=> { + self.replyhandler.error(err); } } } @@ -355,10 +354,10 @@ impl<'a> Request<'a> { ); match response { Ok(())=> { - self.replyhandler.ok() + self.replyhandler.ok(); } - Err(err)=>{ - self.replyhandler.error(err) + Err(err)=> { + self.replyhandler.error(err); } } } @@ -371,10 +370,10 @@ impl<'a> Request<'a> { ); match response { Ok(entry)=> { - self.replyhandler.entry(entry) + self.replyhandler.entry(entry); } - Err(err)=>{ - self.replyhandler.error(err) + Err(err)=> { + self.replyhandler.error(err); } } } @@ -386,10 +385,10 @@ impl<'a> Request<'a> { ); match response { Ok(open)=> { - self.replyhandler.opened(open) + self.replyhandler.opened(open); } - Err(err)=>{ - self.replyhandler.error(err) + Err(err)=> { + self.replyhandler.error(err); } } } @@ -401,14 +400,14 @@ impl<'a> Request<'a> { x.offset(), x.size(), x.flags(), - x.lock_owner().map(|l| l.into()) + x.lock_owner().map(Into::into) ); match response { Ok(data)=> { - self.replyhandler.data(data) + self.replyhandler.data(data); } - Err(err)=>{ - self.replyhandler.error(err) + Err(err)=> { + self.replyhandler.error(err); } } } @@ -421,14 +420,14 @@ impl<'a> Request<'a> { x.data(), x.write_flags(), x.flags(), - x.lock_owner().map(|l| l.into()) + x.lock_owner().map(Into::into) ); match response { Ok(size)=> { - self.replyhandler.written(size) + self.replyhandler.written(size); } - Err(err)=>{ - self.replyhandler.error(err) + Err(err)=> { + self.replyhandler.error(err); } } } @@ -441,10 +440,10 @@ impl<'a> Request<'a> { ); match response { Ok(())=> { - self.replyhandler.ok() + self.replyhandler.ok(); } - Err(err)=>{ - self.replyhandler.error(err) + Err(err)=> { + self.replyhandler.error(err); } } } @@ -454,15 +453,15 @@ impl<'a> Request<'a> { self.request.nodeid().into(), x.file_handle().into(), x.flags(), - x.lock_owner().map(|x| x.into()), + x.lock_owner().map(Into::into), x.flush() ); match response { Ok(())=> { - self.replyhandler.ok() + self.replyhandler.ok(); } - Err(err)=>{ - self.replyhandler.error(err) + Err(err)=> { + self.replyhandler.error(err); } } } @@ -475,10 +474,10 @@ impl<'a> Request<'a> { ); match response { Ok(())=> { - self.replyhandler.ok() + self.replyhandler.ok(); } - Err(err)=>{ - self.replyhandler.error(err) + Err(err)=> { + self.replyhandler.error(err); } } } @@ -490,10 +489,10 @@ impl<'a> Request<'a> { ); match response { Ok(open)=> { - self.replyhandler.opened(open) + self.replyhandler.opened(open); } - Err(err)=>{ - self.replyhandler.error(err) + Err(err)=> { + self.replyhandler.error(err); } } } @@ -506,15 +505,15 @@ impl<'a> Request<'a> { x.size() ); match response { - Ok(entries_list_result)=> { + Ok(dirent_list)=> { self.replyhandler.dir( - &entries_list_result, + &dirent_list, x.size() as usize, x.offset(), - ) + ); } - Err(err)=>{ - self.replyhandler.error(err) + Err(err)=> { + self.replyhandler.error(err); } } } @@ -527,10 +526,10 @@ impl<'a> Request<'a> { ); match response { Ok(())=> { - self.replyhandler.ok() + self.replyhandler.ok(); } - Err(err)=>{ - self.replyhandler.error(err) + Err(err)=> { + self.replyhandler.error(err); } } } @@ -543,10 +542,10 @@ impl<'a> Request<'a> { ); match response { Ok(())=> { - self.replyhandler.ok() + self.replyhandler.ok(); } - Err(err)=>{ - self.replyhandler.error(err) + Err(err)=> { + self.replyhandler.error(err); } } } @@ -557,10 +556,10 @@ impl<'a> Request<'a> { ); match response { Ok(statfs)=> { - self.replyhandler.statfs(statfs) + self.replyhandler.statfs(statfs); } - Err(err)=>{ - self.replyhandler.error(err) + Err(err)=> { + self.replyhandler.error(err); } } } @@ -575,10 +574,10 @@ impl<'a> Request<'a> { ); match response { Ok(())=> { - self.replyhandler.ok() + self.replyhandler.ok(); } - Err(err)=>{ - self.replyhandler.error(err) + Err(err)=> { + self.replyhandler.error(err); } } } @@ -591,10 +590,10 @@ impl<'a> Request<'a> { ); match response { Ok(xattr)=> { - self.replyhandler.xattr(xattr) + self.replyhandler.xattr(xattr); } - Err(err)=>{ - self.replyhandler.error(err) + Err(err)=> { + self.replyhandler.error(err); } } } @@ -606,10 +605,10 @@ impl<'a> Request<'a> { ); match response { Ok(xattr)=> { - self.replyhandler.xattr(xattr) + self.replyhandler.xattr(xattr); } - Err(err)=>{ - self.replyhandler.error(err) + Err(err)=> { + self.replyhandler.error(err); } } } @@ -621,10 +620,10 @@ impl<'a> Request<'a> { ); match response { Ok(())=> { - self.replyhandler.ok() + self.replyhandler.ok(); } - Err(err)=>{ - self.replyhandler.error(err) + Err(err)=> { + self.replyhandler.error(err); } } } @@ -636,10 +635,10 @@ impl<'a> Request<'a> { ); match response { Ok(())=> { - self.replyhandler.ok() + self.replyhandler.ok(); } - Err(err)=>{ - self.replyhandler.error(err) + Err(err)=> { + self.replyhandler.error(err); } } } @@ -654,10 +653,10 @@ impl<'a> Request<'a> { ); match response { Ok((entry, open))=> { - self.replyhandler.created(entry, open) + self.replyhandler.created(entry, open); } - Err(err)=>{ - self.replyhandler.error(err) + Err(err)=> { + self.replyhandler.error(err); } } } @@ -674,10 +673,10 @@ impl<'a> Request<'a> { ); match response { Ok(lock)=> { - self.replyhandler.locked(lock) + self.replyhandler.locked(lock); } - Err(err)=>{ - self.replyhandler.error(err) + Err(err)=> { + self.replyhandler.error(err); } } } @@ -695,10 +694,10 @@ impl<'a> Request<'a> { ); match response { Ok(())=> { - self.replyhandler.ok() + self.replyhandler.ok(); } - Err(err)=>{ - self.replyhandler.error(err) + Err(err)=> { + self.replyhandler.error(err); } } } @@ -716,10 +715,10 @@ impl<'a> Request<'a> { ); match response { Ok(())=> { - self.replyhandler.ok() + self.replyhandler.ok(); } - Err(err)=>{ - self.replyhandler.error(err) + Err(err)=> { + self.replyhandler.error(err); } } } @@ -732,10 +731,10 @@ impl<'a> Request<'a> { ); match response { Ok(block)=> { - self.replyhandler.bmap(block) + self.replyhandler.bmap(block); } - Err(err)=>{ - self.replyhandler.error(err) + Err(err)=> { + self.replyhandler.error(err); } } } @@ -756,10 +755,10 @@ impl<'a> Request<'a> { ); match response { Ok(ioctl)=> { - self.replyhandler.ioctl(ioctl) + self.replyhandler.ioctl(ioctl); } - Err(err)=>{ - self.replyhandler.error(err) + Err(err)=> { + self.replyhandler.error(err); } } } @@ -776,11 +775,11 @@ impl<'a> Request<'a> { x.flags() ); match response { - Ok(revents)=> { - self.replyhandler.poll(revents) + Ok(ready_events)=> { + self.replyhandler.poll(ready_events); } - Err(err)=>{ - self.replyhandler.error(err) + Err(err)=> { + self.replyhandler.error(err); } } // TODO: register the poll handler @@ -794,7 +793,8 @@ impl<'a> Request<'a> { } #[cfg(feature = "abi-7-16")] ll::Operation::BatchForget(x) => { - se.filesystem.batch_forget(self.meta, x.into()); // no reply + se.filesystem.batch_forget(self.meta, x.into()); // no response + self.replyhandler.no_reply(); // no reply } #[cfg(feature = "abi-7-19")] ll::Operation::FAllocate(x) => { @@ -808,10 +808,10 @@ impl<'a> Request<'a> { ); match response { Ok(())=> { - self.replyhandler.ok() + self.replyhandler.ok(); } - Err(err)=>{ - self.replyhandler.error(err) + Err(err)=> { + self.replyhandler.error(err); } } } @@ -825,15 +825,15 @@ impl<'a> Request<'a> { x.size() ); match response { - Ok(plus_entries_list_result)=> { + Ok(dirent_plus_list)=> { self.replyhandler.dirplus( - &plus_entries_list_result, + &dirent_plus_list, x.size() as usize, x.offset() - ) + ); } - Err(err)=>{ - self.replyhandler.error(err) + Err(err)=> { + self.replyhandler.error(err); } } } @@ -849,10 +849,10 @@ impl<'a> Request<'a> { ); match response { Ok(())=> { - self.replyhandler.ok() + self.replyhandler.ok(); } - Err(err)=>{ - self.replyhandler.error(err) + Err(err)=> { + self.replyhandler.error(err); } } } @@ -867,10 +867,10 @@ impl<'a> Request<'a> { ); match response { Ok(offset)=> { - self.replyhandler.offset(offset) + self.replyhandler.offset(offset); } - Err(err)=>{ - self.replyhandler.error(err) + Err(err)=> { + self.replyhandler.error(err); } } } @@ -890,10 +890,10 @@ impl<'a> Request<'a> { ); match response { Ok(written)=> { - self.replyhandler.written(written) + self.replyhandler.written(written); } - Err(err)=>{ - self.replyhandler.error(err) + Err(err)=> { + self.replyhandler.error(err); } } } @@ -907,7 +907,7 @@ impl<'a> Request<'a> { Ok(())=> { self.replyhandler.ok() } - Err(err)=>{ + Err(err)=> { self.replyhandler.error(err) } } @@ -922,10 +922,9 @@ impl<'a> Request<'a> { Ok(xtimes)=> { self.replyhandler.xtimes(xtimes) } - Err(err)=>{ + Err(err)=> { self.replyhandler.error(err) } - } } #[cfg(target_os = "macos")] @@ -942,7 +941,7 @@ impl<'a> Request<'a> { Ok(())=> { self.replyhandler.ok() } - Err(err)=>{ + Err(err)=> { self.replyhandler.error(err) } } diff --git a/src/session.rs b/src/session.rs index 956befdc..1e2686a2 100644 --- a/src/session.rs +++ b/src/session.rs @@ -6,14 +6,15 @@ //! for filesystem operations under its mount point. use libc::{EAGAIN, EINTR, ENODEV, ENOENT}; -use log::{info, warn}; +#[allow(unused_imports)] +use log::{debug, info, warn, error}; use nix::unistd::geteuid; use std::fmt; use std::os::fd::{AsFd, BorrowedFd, OwnedFd}; use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex}; use std::thread::{self, JoinHandle}; -use std::{io, ops::DerefMut}; +use std::io; use crate::ll::fuse_abi as abi; use crate::request::Request; @@ -29,7 +30,7 @@ use crate::{channel::ChannelSender, notify::Notifier}; pub const MAX_WRITE_SIZE: usize = 16 * 1024 * 1024; /// Size of the buffer for reading a request from the kernel. Since the kernel may send -/// up to MAX_WRITE_SIZE bytes in a write request, we use that value plus some extra space. +/// up to `MAX_WRITE_SIZE` bytes in a write request, we use that value plus some extra space. const BUFFER_SIZE: usize = MAX_WRITE_SIZE + 4096; #[derive(Default, Debug, Eq, PartialEq)] @@ -54,7 +55,7 @@ pub struct Session { /// Handle to the mount. Dropping this unmounts. mount: Arc>>, /// Whether to restrict access to owner, root + owner, or unrestricted - /// Used to implement allow_root and auto_unmount + /// Used to implement `allow_root` and `auto_unmount` pub(crate) allowed: SessionACL, /// User that launched the fuser process pub(crate) session_owner: u32, @@ -146,7 +147,7 @@ impl Session { // it is reused immediately after dispatching to conserve memory and allocations. let mut buffer = vec![0; BUFFER_SIZE]; let buf = aligned_sub_buf( - buffer.deref_mut(), + &mut buffer, std::mem::align_of::(), ); loop { @@ -218,6 +219,8 @@ fn aligned_sub_buf(buf: &mut [u8], alignment: usize) -> &mut [u8] { } } +/// A session can be run synchronously in the current thread using `run()` or spawned into a +/// background thread using `spawn()`. impl Session { /// Run the session loop in a background thread pub fn spawn(self) -> io::Result { From b82724d2725596e13eed743e7e290a86de04640e Mon Sep 17 00:00:00 2001 From: Richard Lawrence Date: Mon, 21 Jul 2025 09:32:23 -0500 Subject: [PATCH 09/19] Corrections to MacOS features. --- src/lib.rs | 2 +- src/ll/mod.rs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 8a9ee506..dbae042a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -936,7 +936,7 @@ pub trait Filesystem { /// `FUSE_VOL_RENAME` to enable. /// The method should return `Ok(())` on success, or `Err(Errno)` otherwise. #[cfg(target_os = "macos")] - fn setvolname(&mut self, req: RequestMeta, name: OsStr) -> Result<(), Errno> { + fn setvolname(&mut self, req: RequestMeta, name: &OsStr) -> Result<(), Errno> { warn!("[Not Implemented] setvolname(name: {name:?})"); Err(Errno::ENOSYS) } diff --git a/src/ll/mod.rs b/src/ll/mod.rs index 65da5268..932ac7ec 100644 --- a/src/ll/mod.rs +++ b/src/ll/mod.rs @@ -219,9 +219,11 @@ impl Errno { pub const ENOATTR: Errno = errno!(libc::ENOATTR); /// Use this as an error return from getxattr/removexattr to indicate that the xattr doesn't - /// exist. This resolves to the appropriate platform specific error code. + /// exist. This resolves to the appropriate platform-specific error code. #[cfg(target_os = "linux")] pub const NO_XATTR: Errno = Self::ENODATA; + /// Use this as an error return from getxattr/removexattr to indicate that the xattr doesn't + /// exist. This resolves to the appropriate platform-specific error code. #[cfg(not(target_os = "linux"))] pub const NO_XATTR: Errno = Self::ENOATTR; From 3fb1e6e83ea5aeda0daf5f6f8252f4b788cf17f8 Mon Sep 17 00:00:00 2001 From: Richard Lawrence Date: Mon, 21 Jul 2025 09:36:14 -0500 Subject: [PATCH 10/19] Reorganized definitions and imports slightly. --- src/lib.rs | 5 +---- src/reply.rs | 7 +++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index dbae042a..51ffc01a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,14 +36,11 @@ pub use reply::Ioctl; pub use passthrough::BackingId; #[cfg(target_os = "macos")] pub use reply::XTimes; -pub use reply::{Entry, FileAttr, FileType, Dirent, DirentList, DirentPlusList, Open, Statfs, Xattr, Lock}; +pub use reply::{Bytes, Entry, FileAttr, FileType, Dirent, DirentList, DirentPlusList, Open, Statfs, Xattr, Lock}; pub use request::RequestMeta; pub use session::{BackgroundSession, Session, SessionACL, SessionUnmounter}; pub use container::{Container, Borrow}; -/// A container for bytes, implementing flexible ownership. -pub type Bytes<'a> = Container<'a, u8>; - #[cfg(feature = "abi-7-28")] use std::cmp::max; #[cfg(feature = "abi-7-13")] diff --git a/src/reply.rs b/src/reply.rs index 80ca5b34..e07d97c6 100644 --- a/src/reply.rs +++ b/src/reply.rs @@ -4,7 +4,7 @@ //! Either the request logic will call the one of the reply handler's self-destructive methods, //! or, if the reply handler goes out of scope before that happens, the drop trait will send an error response. -use crate::{Container, Bytes, KernelConfig}; +use crate::{Container, Errno, KernelConfig}; use crate::ll::{self, reply::DirentBuf}; #[cfg(feature = "abi-7-21")] use crate::ll::reply::{DirentPlusBuf}; @@ -88,7 +88,7 @@ impl Drop for ReplyHandler { "Reply not sent for operation {}, replying with I/O error", self.unique.0 ); - self.send_ll_mut(&ll::Response::new_error(ll::Errno::EIO)); + self.send_ll_mut(&ll::Response::new_error(Errno::EIO)); } } } @@ -282,7 +282,7 @@ impl ReplyHandler { } /// Reply to a general request with an error code - pub fn error(self, err: ll::Errno) { + pub fn error(self, err: Errno) { self.send_ll(&ll::Response::new_error(err)); } @@ -635,7 +635,6 @@ mod test { ], }; let replyhandler: ReplyHandler = ReplyHandler::new(0xdeadbeef, sender); - use crate::ll::Errno; replyhandler.error(Errno::from_i32(66)); } From b2d3977aaccf2c34a40002f731c69b1be98d8d98 Mon Sep 17 00:00:00 2001 From: Richard Lawrence Date: Mon, 21 Jul 2025 10:03:37 -0500 Subject: [PATCH 11/19] New feature! Crossbeam_channel-based Notifications handled safely by a synchronous session loop. Updated notify and poll examples. Corrected a bugs related to polls flags. Refactored poll example by moving poll handle logic into a seperate module. --- Cargo.toml | 1 + examples/notify_inval_entry.rs | 171 +++++++++---- examples/notify_inval_inode.rs | 144 ++++++++--- examples/poll.rs | 332 ++++++++++++++---------- examples/poll_data/mod.rs | 447 +++++++++++++++++++++++++++++++++ src/channel.rs | 42 ++++ src/lib.rs | 41 ++- src/ll/request.rs | 6 +- src/notify.rs | 217 ++++++++++++---- src/request.rs | 8 +- src/session.rs | 250 ++++++++++++++++-- 11 files changed, 1372 insertions(+), 287 deletions(-) create mode 100644 examples/poll_data/mod.rs diff --git a/Cargo.toml b/Cargo.toml index c49de827..5885e7f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ serde = { version = "1.0.102", features = ["std", "derive"], optional = true } smallvec = "1.6.1" zerocopy = { version = "0.8", features = ["derive"] } nix = { version = "0.29.0", features = ["fs", "user"] } +crossbeam-channel = "0.5" [dev-dependencies] env_logger = "0.11.7" diff --git a/examples/notify_inval_entry.rs b/examples/notify_inval_entry.rs index 3c483cb9..ae18e644 100644 --- a/examples/notify_inval_entry.rs +++ b/examples/notify_inval_entry.rs @@ -6,36 +6,40 @@ // // Due to the above provenance, unlike the rest of fuser this file is // licensed under the terms of the GNU GPLv2. +// +// Converted to the synchronous execution model by Richard Lawrence use std::{ - ffi::{OsStr, OsString}, + ffi::OsString, path::Path, - sync::{ - atomic::{AtomicU64, Ordering::SeqCst}, - Arc, Mutex, - }, - thread, time::{Duration, SystemTime, UNIX_EPOCH}, }; +#[allow(unused_imports)] +use log::{error, warn, info, debug}; use clap::Parser; - +use crossbeam_channel::{Receiver, Sender}; use fuser::{ - Dirent, DirentList, Entry, Errno, FileAttr, FileType, - Filesystem, Forget, MountOption, RequestMeta, FUSE_ROOT_ID, + Dirent, DirentList, Entry, Errno, FileAttr, FileType, Filesystem, Forget, + FsStatus, InvalEntry, MountOption, Notification, RequestMeta, FUSE_ROOT_ID, }; -struct ClockFS<'a> { - file_name: Arc>, - lookup_cnt: &'a AtomicU64, +struct ClockFS { + file_name: OsString, + lookup_cnt: u64, + last_update: SystemTime, + opts: Options, timeout: Duration, + update_interval: Duration, + notification_sender: Option>, + // the reply is just for some extra logging + notification_reply: Option>>, } -impl ClockFS<'_> { +impl ClockFS { const FILE_INO: u64 = 2; - fn get_filename(&self) -> String { - let n = self.file_name.lock().unwrap(); - n.clone() + fn get_filename(&self) -> OsString { + self.file_name.clone() } fn stat(ino: u64) -> Option { @@ -65,13 +69,77 @@ impl ClockFS<'_> { } } -impl Filesystem for ClockFS<'_> { +impl Filesystem for ClockFS { + #[cfg(feature = "abi-7-11")] + fn init_notification_sender( + &mut self, + sender: Sender, + ) -> bool { + self.notification_sender = Some(sender); + true + } + + fn heartbeat(&mut self) -> Result { + // log the reply, if there is one. + if let Some(r) = &self.notification_reply { + if let Ok(result) = r.try_recv() { + match result { + Ok(()) => debug!("Received OK reply"), + Err(e) => warn!("Received error reply: {e}"), + } + // Only read a reply once. + self.notification_reply=None; + } + } + let now = SystemTime::now(); + if now.duration_since(self.last_update).unwrap_or_default() >= self.update_interval { + // Update filename + let old_filename = self.get_filename(); + self.file_name = now_filename(); + self.last_update = now; + // Notifications, as appropriate + if !self.opts.no_notify && self.lookup_cnt != 0 { + if let Some(sender) = &self.notification_sender { + if self.opts.only_expire { + // TODO: implement expiration method + } else { + // invalidate old_filename + let notification = Notification::from(InvalEntry { + parent: FUSE_ROOT_ID, + name: old_filename, + }); + if let Err(e) = sender.send(notification) { + warn!("Warning: failed to send InvalEntry notification: {e}"); + } else { + info!("Sent InvalEntry notification (old filename)."); + } + // invalidate new filename + let (s, r) = crossbeam_channel::bounded(1); + let notification = Notification::InvalEntry(( + InvalEntry { + parent: FUSE_ROOT_ID, + name: self.get_filename(), + }, + Some(s), + )); + if let Err(e) = sender.send(notification) { + warn!("Warning: failed to send InvalEntry notification: {e}"); + } else { + info!("Sent InvalEntry notification (new filename)."); + self.notification_reply = Some(r); + } + } + } + } + } + Ok(FsStatus::Ready) + } + fn lookup(&mut self, _req: RequestMeta, parent: u64, name: &Path) -> Result { - if parent != FUSE_ROOT_ID || name != OsStr::new(&self.get_filename()) { + if parent != FUSE_ROOT_ID || name != self.file_name { return Err(Errno::ENOENT); } - - self.lookup_cnt.fetch_add(1, SeqCst); + self.lookup_cnt += 1; match ClockFS::stat(ClockFS::FILE_INO) { Some(attr) => Ok(Entry { ino: attr.ino, @@ -86,8 +154,8 @@ impl Filesystem for ClockFS<'_> { fn forget(&mut self, _req: RequestMeta, target: Forget) { if target.ino == ClockFS::FILE_INO { - let prev = self.lookup_cnt.fetch_sub(target.nlookup, SeqCst); - assert!(prev >= target.nlookup); + assert!(self.lookup_cnt >= target.nlookup); + self.lookup_cnt -= target.nlookup; } else { assert!(target.ino == FUSE_ROOT_ID); } @@ -115,14 +183,11 @@ impl Filesystem for ClockFS<'_> { // containing owned bytes. let mut entries= Vec::new(); if offset == 0 { - let filename_string = self.get_filename(); // Returns String - let filename_os_string = OsString::from(filename_string); - let entry = Dirent { ino: ClockFS::FILE_INO, - offset: 1, // This entry's cookie + offset: 1, kind: FileType::RegularFile, - name: filename_os_string.into(), + name: self.get_filename().into(), }; entries.push(entry); } @@ -132,14 +197,14 @@ impl Filesystem for ClockFS<'_> { } } -fn now_filename() -> String { +fn now_filename() -> OsString { let Ok(d) = SystemTime::now().duration_since(UNIX_EPOCH) else { panic!("Pre-epoch SystemTime"); }; - format!("Time_is_{}", d.as_secs()) + OsString::from(format!("Time_is_{}", d.as_secs())) } -#[derive(Parser)] +#[derive(Parser, Debug)] struct Options { /// Mount demo filesystem at given path mount_point: String, @@ -162,31 +227,31 @@ struct Options { } fn main() { + env_logger::init(); let opts = Options::parse(); - let options = vec![MountOption::RO, MountOption::FSName("clock".to_string())]; - let fname = Arc::new(Mutex::new(now_filename())); - let lookup_cnt = Box::leak(Box::new(AtomicU64::new(0))); + eprintln!("Mounting ClockFS (entry invalidation) at {}", &opts.mount_point); + eprintln!("Press Ctrl-C to unmount and exit."); + + let mount_point = OsString::from(&opts.mount_point); + let timeout = Duration::from_secs_f32(opts.timeout); + let update_interval = Duration::from_secs_f32(opts.update_interval); let fs = ClockFS { - file_name: fname.clone(), - lookup_cnt, - timeout: Duration::from_secs_f32(opts.timeout), + file_name: now_filename(), + lookup_cnt: 0, + last_update: SystemTime::now(), + opts, + timeout, + update_interval, + notification_sender: None, + notification_reply: None, }; + let mount_options = vec![MountOption::RO, MountOption::FSName("clock_entry".to_string())]; + let mut session = fuser::Session::new(fs, &mount_point, &mount_options) + .unwrap_or_else(|e| panic!("Failed to create FUSE session: {e}")); - let session = fuser::Session::new(fs, opts.mount_point, &options).unwrap(); - let notifier = session.notifier(); - let _bg = session.spawn().unwrap(); - - loop { - let mut fname = fname.lock().unwrap(); - let oldname = std::mem::replace(&mut *fname, now_filename()); - drop(fname); - if !opts.no_notify && lookup_cnt.load(SeqCst) != 0 { - if opts.only_expire { - // fuser::notify_expire_entry(_SOME_HANDLE_, FUSE_ROOT_ID, &oldname); - } else if let Err(e) = notifier.inval_entry(FUSE_ROOT_ID, oldname.as_ref()) { - eprintln!("Warning: failed to invalidate entry '{}': {}", oldname, e); - } - } - thread::sleep(Duration::from_secs_f32(opts.update_interval)); - } + session + .run_with_notifications() + .expect("Session ended with an error."); //TODO: log the error + + eprintln!("ClockFS (entry invalidation) unmounted and exited."); } diff --git a/examples/notify_inval_inode.rs b/examples/notify_inval_inode.rs index cb694aad..8c406fc0 100644 --- a/examples/notify_inval_inode.rs +++ b/examples/notify_inval_inode.rs @@ -14,20 +14,25 @@ use std::{ atomic::{AtomicU64, Ordering::SeqCst}, Arc, Mutex, }, - thread, time::{Duration, SystemTime, UNIX_EPOCH}, }; use clap::Parser; - +use crossbeam_channel::{Receiver, Sender}; use fuser::{ - consts, Bytes, Dirent, DirentList, Entry, Errno, - FileAttr, FileType, Filesystem, Forget, MountOption, Open, RequestMeta, FUSE_ROOT_ID, + consts, Bytes, Dirent, DirentList, Entry, Errno, FileAttr, FileType, Filesystem, + Forget, FsStatus, InvalInode, MountOption, Notification, Open, Store, RequestMeta, + FUSE_ROOT_ID, }; +use log::{warn,info}; struct ClockFS<'a> { file_contents: Arc>, lookup_cnt: &'a AtomicU64, + notification_sender: Option>, + notification_reply: Option>>, + opts: Arc, + last_update: Mutex, } impl ClockFS<'_> { @@ -66,6 +71,74 @@ impl ClockFS<'_> { } impl Filesystem for ClockFS<'_> { + #[cfg(feature = "abi-7-11")] + fn init_notification_sender( + &mut self, + sender: Sender, + ) -> bool { + self.notification_sender = Some(sender); + true + } + + fn heartbeat(&mut self) -> Result { + if let Some(r) = &self.notification_reply { + if let Ok(result) = r.try_recv() { + match result { + Ok(()) => info!("Received OK reply"), + Err(e) => warn!("Received error reply: {e}"), + } + // Only read a reply once. + self.notification_reply=None; + } + } + let mut last_update_guard = self.last_update.lock().unwrap(); + let now = SystemTime::now(); + if now.duration_since(*last_update_guard).unwrap_or_default() >= Duration::from_secs_f32(self.opts.update_interval) { + let mut s = self.file_contents.lock().unwrap(); + let olddata = std::mem::replace(&mut *s, now_string()); + drop(s); // Release lock on file_contents + + if !self.opts.no_notify && self.lookup_cnt.load(SeqCst) != 0 { + if let Some(sender) = &self.notification_sender { + let (s, r) = crossbeam_channel::bounded(1); + if self.opts.notify_store { + let notification = Notification::Store(( + Store { + ino: Self::FILE_INO, + offset: 0, + data: self.file_contents.lock().unwrap().as_bytes().to_vec(), + }, + Some(s), + )); + if let Err(e) = sender.send(notification) { + warn!("Warning: failed to send Store notification: {e}"); + } else { + info!("Sent Store notification, preparing for reply."); + self.notification_reply = Some(r); + } + } else { + let notification = Notification::InvalInode(( + InvalInode { + ino: Self::FILE_INO, + offset: 0, + len: olddata.len().try_into().unwrap_or(-1), + }, + Some(s), + )); + if let Err(e) = sender.send(notification) { + warn!("Warning: failed to send InvalInode notification: {e}"); + } else { + info!("Sent InvalInode notification, preparing for reply."); + self.notification_reply = Some(r); + } + } + } + } + *last_update_guard = now; + } + Ok(FsStatus::Ready) + } + fn lookup(&mut self, _req: RequestMeta, parent: u64, name: &Path) -> Result { if parent != FUSE_ROOT_ID || name != OsStr::new(Self::FILE_NAME) { return Err(Errno::ENOENT); @@ -196,7 +269,7 @@ fn now_string() -> String { format!("The current time is {}\n", d.as_secs()) } -#[derive(Parser)] +#[derive(Parser, Debug, Clone)] struct Options { /// Mount demo filesystem at given path mount_point: String, @@ -215,36 +288,45 @@ struct Options { } fn main() { - let opts = Options::parse(); - let options = vec![MountOption::RO, MountOption::FSName("clock".to_string())]; + let opts = Arc::new(Options::parse()); + let mount_options = vec![MountOption::RO, MountOption::FSName("clock_inode".to_string())]; let fdata = Arc::new(Mutex::new(now_string())); - let lookup_cnt = Box::leak(Box::new(AtomicU64::new(0))); + let lookup_cnt = Box::leak(Box::new(AtomicU64::new(0))); // Keep as is for simplicity, though not ideal + + env_logger::init(); + let fs = ClockFS { file_contents: fdata.clone(), lookup_cnt, + notification_sender: None, // Will be initialized by the session + notification_reply: None, + opts: opts.clone(), + last_update: Mutex::new(SystemTime::now()), }; - let session = fuser::Session::new(fs, opts.mount_point, &options).unwrap(); - let notifier = session.notifier(); - let _bg = session.spawn().unwrap(); - - loop { - let mut s = fdata.lock().unwrap(); - let olddata = std::mem::replace(&mut *s, now_string()); - drop(s); - if !opts.no_notify && lookup_cnt.load(SeqCst) != 0 { - if opts.notify_store { - if let Err(e) = - notifier.store(ClockFS::FILE_INO, 0, fdata.lock().unwrap().as_bytes()) - { - eprintln!("Warning: failed to update kernel cache: {e}"); - } - } else if let Err(e) = - notifier.inval_inode(ClockFS::FILE_INO, 0, olddata.len().try_into().unwrap()) - { - eprintln!("Warning: failed to invalidate inode: {e}"); - } - } - thread::sleep(Duration::from_secs_f32(opts.update_interval)); - } + // The main thread will run the FUSE session loop. + // No separate thread for notification logic is needed anymore. + // No direct use of session.notifier() from main. + // No thread::sleep in main's loop. + // Ctrl-C will be handled by the FUSE session ending, or manually if needed. + // For now, relying on unmount or external Ctrl-C to stop. + + eprintln!("Mounting ClockFS (inode invalidation) at {}", opts.mount_point); + eprintln!("Press Ctrl-C to unmount and exit."); + + // Setup Ctrl-C handler to gracefully unmount (optional but good practice) + // This part is a bit tricky as Session takes ownership or runs in its own thread. + // For a direct run like `run_with_notifications`, we might need to handle Ctrl-C + // to signal the FS to stop or rely on the unmount to terminate. + // The simplest for now is to let the user unmount the FS to stop. + // Or, if the session itself handles Ctrl-C, that's also fine. + + let mut session = fuser::Session::new(fs, &opts.mount_point, &mount_options) + .expect("Failed to create FUSE session."); + + session + .run_with_notifications() + .expect("Session ended with an error."); //TODO: log the error + + eprintln!("ClockFS (inode invalidation) unmounted and exited."); } diff --git a/examples/poll.rs b/examples/poll.rs index 684e050c..0a664a82 100644 --- a/examples/poll.rs +++ b/examples/poll.rs @@ -14,33 +14,57 @@ use std::{ ffi::OsString, path::Path, os::unix::ffi::{OsStrExt, OsStringExt}, // for converting to and from - sync::{ - atomic::{AtomicU64, Ordering::SeqCst}, - Arc, Mutex, - }, - thread, time::{Duration, UNIX_EPOCH}, }; +#[cfg(feature = "abi-7-11")] +use crossbeam_channel::Sender; + +mod poll_data; +use poll_data::PollData; + use fuser::{ consts::{FOPEN_DIRECT_IO, FOPEN_NONSEEKABLE, FUSE_POLL_SCHEDULE_NOTIFY}, - Bytes, Dirent, DirentList, Entry, Errno, - FileAttr, Filesystem, FileType, MountOption, Open, RequestMeta, + Bytes, Dirent, DirentList, Entry, Errno, FileAttr, Filesystem, + FileType, MountOption, Notification, Open, RequestMeta, FUSE_ROOT_ID, }; const NUMFILES: u8 = 16; const MAXBYTES: u64 = 10; +const PRODUCER_INTERVAL: Duration = Duration::from_millis(250); + +struct ProducerData { + next_time: std::time::SystemTime, + next_idx: u8, + next_nr: u8 +} + +impl ProducerData { + fn advance(&mut self) { + self.next_idx = (self.next_idx + 1) % NUMFILES; + if self.next_idx == 0 { + self.next_nr = (self.next_nr % (NUMFILES / 2)) + 1; + } + self.next_time += PRODUCER_INTERVAL; + } + fn is_ready(&self) -> bool { + std::time::SystemTime::now() >= self.next_time + } +} struct FSelData { bytecnt: [u64; NUMFILES as usize], open_mask: u16, - notify_mask: u16, - poll_handles: [u64; NUMFILES as usize], } struct FSelFS { - data: Arc>, + // Byte counts for each file + data: FSelData, + // Helper functions for handling polls + poll_handler: PollData, + // Helper functions for updating Byte counts + producer: ProducerData } impl FSelData { @@ -78,12 +102,33 @@ impl FSelData { } impl FSelFS { - fn get_data(&self) -> std::sync::MutexGuard<'_, FSelData> { - self.data.lock().unwrap() + fn produce_data(&mut self) { + let mut t = self.producer.next_idx; + for _ in 0..self.producer.next_nr { + let tidx = t as usize; + if self.data.bytecnt[tidx] < MAXBYTES { + self.data.bytecnt[tidx] += 1; + log::info!("PRODUCER: Increased bytecnt for file {:X} to {}", t, self.data.bytecnt[tidx]); + self.poll_handler.mark_inode_ready( + FSelData::idx_to_ino(t), + libc::POLLIN as u32 + ); + } + t = (t + NUMFILES / self.producer.next_nr) % NUMFILES; + } } } impl Filesystem for FSelFS { + fn heartbeat(&mut self) -> Result { + self.poll_handler.check_replies(); + if self.producer.is_ready() { + self.produce_data(); + self.producer.advance(); + } + Ok(fuser::FsStatus::Ready) + } + fn lookup( &mut self, _req: RequestMeta, @@ -107,7 +152,7 @@ impl Filesystem for FSelFS { ino: FSelData::idx_to_ino(idx), generation: Some(0), file_ttl: Duration::ZERO, - attr: self.get_data().filestat(idx), + attr: self.data.filestat(idx), attr_ttl: Duration::ZERO, }) } @@ -141,7 +186,7 @@ impl Filesystem for FSelFS { } let idx = FSelData::ino_to_idx(ino); if idx < NUMFILES { - Ok((self.get_data().filestat(idx), Duration::ZERO)) + Ok((self.data.filestat(idx), Duration::ZERO)) } else { Err(Errno::ENOENT) } @@ -223,7 +268,7 @@ impl Filesystem for FSelFS { if idx >= NUMFILES.into() { return Err(Errno::EBADF); } - self.get_data().open_mask &= !(1 << idx); + self.data.open_mask &= !(1 << idx); Ok(()) } @@ -243,10 +288,15 @@ impl Filesystem for FSelFS { if idx >= NUMFILES { return Err(Errno::EBADF); } - let cnt = &mut self.get_data().bytecnt[idx as usize]; + let cnt = &mut self.data.bytecnt[idx as usize]; let size = (*cnt).min(max_size.into()); println!("READ {:X} transferred={} cnt={}", idx, size, *cnt); *cnt -= size; + // if cnt is now equal to zero, mark the node as not ready. + if *cnt == 0 { + // Mark the inode as no longer ready for POLLIN events specifically + self.poll_handler.mark_inode_not_ready(FSelData::idx_to_ino(idx), libc::POLLIN as u32); + } let elt = match idx { 0..=9 => b'0' + idx, 10..=15 => b'A' + idx - 10, // Corrected range @@ -264,158 +314,180 @@ impl Filesystem for FSelFS { _ino: u64, fh: u64, ph: u64, - _events: u32, + events: u32, flags: u32, ) -> Result { - static POLLED_ZERO: AtomicU64 = AtomicU64::new(0); + log::info!("poll() called: fh={fh}, ph={ph}, events={events}, flags={flags}"); + if flags & FUSE_POLL_SCHEDULE_NOTIFY == 0 { + // TODO: handle this unexpected case. + } let Ok(idx): Result = fh.try_into() else { return Err(Errno::EINVAL); }; if idx >= NUMFILES { return Err(Errno::EBADF); } - - let revents = { - let mut d = self.get_data(); - - if flags & FUSE_POLL_SCHEDULE_NOTIFY != 0 { - d.notify_mask |= 1 << idx; - d.poll_handles[idx as usize] = ph; - } - - let nbytes = d.bytecnt[idx as usize]; - if nbytes != 0 { - println!( - "POLL {:X} cnt={} polled_zero={}", - idx, - nbytes, - POLLED_ZERO.swap(0, SeqCst) - ); - libc::POLLIN.try_into().unwrap() - } else { - POLLED_ZERO.fetch_add(1, SeqCst); - 0 - } - }; - Ok(revents) + let ino = FSelData::idx_to_ino(idx); + if let Some(initial_events) = self.poll_handler.register_poll_handle(ph, ino, events) { + log::debug!("poll(): Registered poll handle {ph} for ino {ino}, initial_events={initial_events}"); + Ok(initial_events) + } else { + log::debug!("poll(): Registered poll handle {ph} for ino {ino}, no initial events"); + Ok(0) + } } -} -fn producer(data: &Mutex, notifier: &fuser::Notifier) { - let mut idx: u8 = 0; - let mut nr = 1; - loop { - { - let mut d = data.lock().unwrap(); - let mut t = idx; - - for _ in 0..nr { - let tidx = t as usize; - if d.bytecnt[tidx] != MAXBYTES { - d.bytecnt[tidx] += 1; - if d.notify_mask & (1 << t) != 0 { - println!("NOTIFY {t:X}"); - if let Err(e) = notifier.poll(d.poll_handles[tidx]) { - eprintln!("poll notification failed: {e}"); - } - d.notify_mask &= !(1 << t); - } - } - - t = (t + NUMFILES / nr) % NUMFILES; - } - - idx = (idx + 1) % NUMFILES; - if idx == 0 { - nr = (nr * 2) % 7; - } - } - thread::sleep(Duration::from_millis(250)); + #[cfg(feature = "abi-7-11")] + fn init_notification_sender(&mut self, sender: Sender) -> bool { + log::info!("init_poll_sender() called"); + self.poll_handler.set_sender(sender); + true } } fn main() { let options = vec![MountOption::RO, MountOption::FSName("fsel".to_string())]; - let data = Arc::new(Mutex::new(FSelData { + env_logger::init(); + log::info!("Starting fsel example with poll support."); + + let data = FSelData { bytecnt: [0; NUMFILES as usize], open_mask: 0, - notify_mask: 0, - poll_handles: [0; NUMFILES as usize], - })); - let fs = FSelFS { data: data.clone() }; - - let mntpt = std::env::args().nth(1).unwrap(); - let session = fuser::Session::new(fs, mntpt, &options).unwrap(); - let bg = session.spawn().unwrap(); - - producer(&data, &bg.notifier()); + }; + let poll_handler = PollData::new(None); + let producer = ProducerData { + next_time: std::time::SystemTime::now()+Duration::from_millis(1000), + next_idx: 0, + next_nr: 1 + }; + let fs = FSelFS { + data, + poll_handler, + producer + }; + let mntpt = std::env::args().nth(1).expect("Expected mountpoint argument"); + let mut session = fuser::Session::new( + fs, + &mntpt, + &options + ).expect("Failed to create FUSE session."); + println!("FUSE filesystem 'fsel_chan' mounted on {mntpt}. Press Ctrl-C to unmount."); + session.run_with_notifications() + .expect("Failed to spawn FUSE session"); } #[cfg(test)] mod test { use super::*; - use fuser::{Filesystem, RequestMeta, Errno}; - use std::sync::{Arc, Mutex}; + use crossbeam_channel::{unbounded, Receiver}; - fn setup_test_fs() -> FSelFS { - let data = Arc::new(Mutex::new(FSelData { + // Helper to create FSelFS and a channel pair for its PollData for tests + fn setup_test_fs_with_channel() -> (FSelFS, Sender, Receiver) { + log::debug!("Setting up test FS with poll channel"); + let (tx, rx) = unbounded(); + let data = FSelData { bytecnt: [0; NUMFILES as usize], open_mask: 0, - notify_mask: 0, - poll_handles: [0; NUMFILES as usize], - })); - FSelFS { data } + }; + // PollData with None sender. + let poll_handler = PollData::new(None); + let fs = FSelFS { + data, + poll_handler, + producer: ProducerData { next_time: UNIX_EPOCH, next_idx: 0, next_nr: 1 } + }; + (fs, tx, rx) } #[test] - fn test_poll_data_available() { - let mut fs = setup_test_fs(); + fn test_fs_poll_registers_handle_no_initial_event() { + log::info!("test_fs_poll_registers_handle_no_initial_event: starting"); + let (mut fs, tx_to_fs, rx_from_fs) = setup_test_fs_with_channel(); + assert!(fs.init_notification_sender(tx_to_fs)); // Link FS's PollData to our test sender + let req = RequestMeta { unique: 0, uid: 0, gid: 0, pid: 0 }; - let idx = 0; + let idx: u8 = 0; let fh = idx as u64; - let ph = 1; - { - let mut data = fs.get_data(); - data.bytecnt[idx as usize] = 5; // Simulate data available - } - let result = fs.poll(req, FSelData::idx_to_ino(idx), fh, ph, libc::POLLIN as u32, FUSE_POLL_SCHEDULE_NOTIFY); - assert!(result.is_ok(), "Poll should succeed when data is available"); - if let Ok(revents) = result { - assert_eq!(revents, libc::POLLIN as u32, "Should return POLLIN when data is available"); - } - let data = fs.get_data(); - assert_eq!(data.notify_mask & (1 << idx), 1 << idx, "Notify mask should be set for this index"); - assert_eq!(data.poll_handles[idx as usize], 1, "Poll handle should be stored"); + let ino = FSelData::idx_to_ino(idx); + let ph: u64 = 12345; + let events = libc::POLLIN as u32; + + fs.data.bytecnt[idx as usize] = 0; + fs.poll_handler.mark_inode_not_ready(ino, libc::POLLIN as u32); // Ensure PollData also knows it's not ready + + let result = fs.poll(req, ino, fh, ph, events, FUSE_POLL_SCHEDULE_NOTIFY); + log::debug!("test_fs_poll_registers_handle_no_initial_event: poll result = {:?}", result); + assert!(result.is_ok(), "FS poll method should succeed"); + assert_eq!(result.unwrap(), 0, "Should return 0 as no initial event is expected"); + + assert!(fs.poll_handler.registered_poll_handles.contains_key(&ph)); + assert_eq!(fs.poll_handler.registered_poll_handles.get(&ph), Some(&(ino, events))); + assert!(fs.poll_handler.inode_poll_handles.get(&ino).unwrap().contains(&ph)); + + assert!(rx_from_fs.try_recv().is_err()); } #[test] - fn test_poll_no_data() { - let mut fs = setup_test_fs(); + fn test_fs_poll_registers_handle_with_initial_event() { + log::info!("test_fs_poll_registers_handle_with_initial_event: starting"); + let (mut fs, tx_to_fs, rx_from_fs) = setup_test_fs_with_channel(); + assert!(fs.init_notification_sender(tx_to_fs)); + let req = RequestMeta { unique: 0, uid: 0, gid: 0, pid: 0 }; - let idx = 0; + let idx: u8 = 1; let fh = idx as u64; - let ph = 1; - { - let mut data = fs.get_data(); - data.bytecnt[idx as usize] = 0; // No data available - } - let result = fs.poll(req, FSelData::idx_to_ino(idx), fh, ph, libc::POLLIN as u32, FUSE_POLL_SCHEDULE_NOTIFY); - assert!(result.is_ok(), "Poll should succeed even when no data is available"); - if let Ok(revents) = result { - assert_eq!(revents, 0, "Should return 0 when no data is available"); + let ino = FSelData::idx_to_ino(idx); + let ph: u64 = 54321; + let events = libc::POLLIN as u32; + + fs.poll_handler.mark_inode_ready(ino, libc::POLLIN as u32); + // Clear the channel from the mark_inode_ready call if any (no handle registered yet, so it shouldn't send) + while rx_from_fs.try_recv().is_ok() {} + + let result = fs.poll(req, ino, fh, ph, events, FUSE_POLL_SCHEDULE_NOTIFY); + log::debug!("test_fs_poll_registers_handle_with_initial_event: poll result = {:?}", result); + assert!(result.is_ok(), "FS poll method should succeed"); + assert_eq!(result.unwrap(), libc::POLLIN as u32, "Should return POLLIN as an initial event"); + + assert!(!fs.poll_handler.registered_poll_handles.contains_key(&ph)); + + match rx_from_fs.try_recv() { + Ok(Notification::Poll((poll, _))) => { + assert_eq!(poll.ph, ph); + assert_eq!(poll.events, libc::POLLIN as u32); + } + _ => panic!("Expected an initial event on the channel"), } } #[test] - fn test_poll_invalid_handle() { - let mut fs = setup_test_fs(); - let req = RequestMeta { unique: 0, uid: 0, gid: 0, pid: 0 }; - let invalid_idx = NUMFILES as u64; - let ph = 1; - let result = fs.poll(req, FSelData::idx_to_ino(0), invalid_idx, ph, libc::POLLIN as u32, FUSE_POLL_SCHEDULE_NOTIFY); - assert!(result.is_err(), "Poll should fail for invalid file handle"); - if let Err(e) = result { - assert_eq!(e, Errno::EBADF, "Should return EBADF for invalid handle"); + fn test_producer_marks_inode_ready_triggers_event() { + log::info!("test_producer_marks_inode_ready_triggers_event: starting"); + // For this test, we need an Arc> because producer runs in a separate thread. + let (mut fs_instance, tx_to_fs, rx_from_fs) = setup_test_fs_with_channel(); + assert!(fs_instance.init_notification_sender(tx_to_fs)); + + let idx_to_test: u8 = 2; + let ino_to_test = FSelData::idx_to_ino(idx_to_test); + let ph_to_test: u64 = 67890; + let events_to_test = libc::POLLIN as u32; + + // Simulate a poll request being registered by directly accessing PollData via the Arc + fs_instance.poll_handler.register_poll_handle(ph_to_test, ino_to_test, events_to_test); + while rx_from_fs.try_recv().is_ok() {} // Clear channel + + // Manually simulate one iteration of the producer logic for a specific file + fs_instance.data.bytecnt[idx_to_test as usize] = 1; + fs_instance.poll_handler.mark_inode_ready(ino_to_test, libc::POLLIN as u32); + log::debug!("test_producer_marks_inode_ready_triggers_event: marked inode ready"); + + match rx_from_fs.try_recv() { + Ok(Notification::Poll((poll, _))) => { + assert_eq!(poll.ph, ph_to_test); + assert_eq!(poll.events, libc::POLLIN as u32); + } + _ => panic!("Producer marking inode ready should have triggered an event on the channel"), } + assert!(!fs_instance.poll_handler.registered_poll_handles.contains_key(&ph_to_test)); } -} +} \ No newline at end of file diff --git a/examples/poll_data/mod.rs b/examples/poll_data/mod.rs new file mode 100644 index 00000000..5ac4b0d5 --- /dev/null +++ b/examples/poll_data/mod.rs @@ -0,0 +1,447 @@ +use crossbeam_channel::{Receiver, Sender}; +use std::collections::{HashMap, HashSet}; +use fuser::{Notification, Poll}; +use std::io; + +/// `PollData` holds the state required for managing asynchronous poll notifications. +/// It is typically owned by a `Filesystem` implementation. The `Sender` end of its +/// MPMC channel (`ready_events_sender`) is provided by the `Session` to the +/// `Filesystem` (e.g., via a method like `init_poll_sender`). +#[derive(Debug)] +pub struct PollData { + /// Sender part of the MPMC channel for (`poll_handle`, `events_bitmask`). + /// This is used by the filesystem logic to send readiness events. + /// Typically set via `PollData::new` or `PollData::set_sender`. + pub ready_events_sender: Option>, + /// Stores registered poll handles. + /// Maps a kernel poll handle (`u64`) to a tuple of (inode, `requested_events`). + /// This allows us to know which inode and which events a poll handle is interested in. + pub registered_poll_handles: HashMap, + /// Stores active poll handles for a given inode. + /// Maps an inode (`u64`) to a set of kernel poll handles (`u64`). + /// This is useful to quickly find all poll handles interested in a particular inode + /// when that inode's state changes. + pub inode_poll_handles: HashMap>, + /// Tracks inodes that are currently ready for I/O (e.g., POLLIN). + /// This set is updated by filesystem operations. + /// Stores the actual current readiness mask for each inode. + pub ready_inodes: HashMap, + /// Receivers for pending replies to notifications + pending_replies: HashMap>>, + /// Last reply received for a notification + pub last_reply: HashMap>, +} + +impl PollData { + /// Creates a new `PollData` instance, optionally with an initial sender. + pub fn new(sender: Option>) -> Self { + PollData { + ready_events_sender: sender, + registered_poll_handles: HashMap::new(), + inode_poll_handles: HashMap::new(), + ready_inodes: HashMap::new(), + pending_replies: HashMap::new(), + last_reply: HashMap::new(), + } + } + + /// Sets or updates the sender for ready events. + /// This is typically called by the `Filesystem` implementation when the `Session` provides the sender. + pub fn set_sender(&mut self, new_sender: Sender) { + self.ready_events_sender = Some(new_sender); + } + + /// Registers a new poll request. + /// + /// Stores the kernel poll handle (`ph`) associated with an inode and the events + /// it's interested in. If the inode is already ready for the requested events, + /// an immediate notification is sent. + /// + /// # Arguments + /// + /// * `ph`: The kernel poll handle. + /// * `ino`: The inode number being polled. + /// * `events_requested`: The event bitmask the poll handle is interested in. + /// + /// # Returns + /// + /// * `Option`: An initial event mask if the file is already ready, otherwise `None`. + pub fn register_poll_handle( + &mut self, + ph: u64, + ino: u64, + events_requested: u32, + ) -> Option { + self.registered_poll_handles + .insert(ph, (ino, events_requested)); + self.inode_poll_handles.entry(ino).or_default().insert(ph); + + // Check if the file is already ready for any of the requested events. + if let Some(current_readiness_mask) = self.ready_inodes.get(&ino) { + let initial_events_to_send = events_requested & *current_readiness_mask; + if initial_events_to_send != 0 { + if let Some(sender) = &self.ready_events_sender { + log::debug!( + "PollData::register_poll_handle() sending initial event: ph={ph}, initial_events_to_send={initial_events_to_send:#x}" + ); + let (tx, rx) = crossbeam_channel::bounded(1); + let notification = Notification::Poll(( + Poll { + ph, + events: initial_events_to_send, + }, + Some(tx), + )); + if sender.send(notification).is_err() { + log::warn!("PollData: Failed to send initial poll readiness event for ph {ph}. Channel might be disconnected."); + } else { + self.pending_replies.insert(ph, rx); + } + } + // Unregister the poll handle after sending the notification + self.unregister_poll_handle(ph); + // Return the subset of requested events that are currently ready. + return Some(initial_events_to_send); + } + } + None + } + + // NOTE: the example does not currently process poll cancellations. + // If it did, it would use this convenience function. + #[allow(unused)] + /// Unregisters a poll handle. + /// + /// This is typically called when the poll request is cancelled or the associated + /// file descriptor is closed. + /// + /// # Arguments + /// + /// * `ph`: The kernel poll handle to unregister. + pub fn unregister_poll_handle(&mut self, ph: u64) { + if let Some((ino, _)) = self.registered_poll_handles.remove(&ph) { + if let Some(handles) = self.inode_poll_handles.get_mut(&ino) { + handles.remove(&ph); + if handles.is_empty() { + self.inode_poll_handles.remove(&ino); + } + } + } + } + + /// Marks an inode as ready for I/O and notifies registered poll handles. + /// + /// # Arguments + /// + /// * `ino`: The inode number that has become ready. + /// * `ready_events`: The bitmask of events that are now active for the inode (e.g., `libc::POLLIN`). + pub fn mark_inode_ready(&mut self, ino: u64, ready_events_mask: u32) { + log::info!( + "PollData::mark_inode_ready() called: ino={ino}, ready_events_mask={ready_events_mask:#x}" + ); + // Update the readiness state for the inode or insert it if new. + // If an inode becomes ready for POLLIN, then later for POLLOUT, + // its readiness mask should reflect both (POLLIN | POLLOUT). + let current_mask = self.ready_inodes.entry(ino).or_insert(0); + *current_mask |= ready_events_mask; + + let mut handles_to_unregister = Vec::new(); + if let Some(sender) = &self.ready_events_sender { + if let Some(poll_handles) = self.inode_poll_handles.get(&ino) { + for &ph in poll_handles { + if let Some((_ino_of_ph, requested_events_for_ph)) = self.registered_poll_handles.get(&ph) { + // Notify if any of the newly ready events are requested by this handle. + let events_to_send = *requested_events_for_ph & ready_events_mask; + if events_to_send != 0 { + log::debug!( + "PollData::mark_inode_ready() sending event: ino={ino}, ph={ph}, events_to_send={events_to_send:#x}" + ); + let (tx, rx) = crossbeam_channel::bounded(1); + let notification = Notification::Poll(( + Poll { + ph, + events: events_to_send, + }, + Some(tx), + )); + if sender.send(notification).is_err() { + log::warn!("PollData: Failed to send poll readiness event for ino {ino}, ph {ph}. Channel might be disconnected."); + } else { + self.pending_replies.insert(ph, rx); + handles_to_unregister.push(ph); + } + } + } + } + } + } + for ph in handles_to_unregister { + self.unregister_poll_handle(ph); + } + } + + /// Marks an inode as no longer ready for specific I/O events. + /// + /// This function clears the specified event bits from the inode's readiness mask. + /// If the resulting readiness mask is zero, the inode is removed from the set of + /// ready inodes. + /// + /// # Arguments + /// + /// * `ino`: The inode number. + /// * `no_longer_ready_events_mask`: A bitmask of events that are no longer ready for the inode. + pub fn mark_inode_not_ready(&mut self, ino: u64, no_longer_ready_events_mask: u32) { + if let Some(current_mask) = self.ready_inodes.get_mut(&ino) { + *current_mask &= !no_longer_ready_events_mask; // Clear the bits + if *current_mask == 0 { + self.ready_inodes.remove(&ino); + } + } + // Note: FUSE usually doesn't have explicit "not ready anymore" notifications for poll, + // other than timeout. Applications will re-poll if needed. + // However, managing this state internally is important for subsequent poll registrations + // and for correctly reporting initial readiness. + } + + /// Checks for and processes any pending replies from notifications. + pub fn check_replies(&mut self) { + self.pending_replies.retain(|ph, rx| { + match rx.try_recv() { + Ok(reply) => { + log::debug!("Received reply for ph {ph}: {reply:?}"); + self.last_reply.insert(*ph, reply); + false // Remove from pending_replies + } + Err(crossbeam_channel::TryRecvError::Empty) => { + true // Keep in pending_replies + } + Err(crossbeam_channel::TryRecvError::Disconnected) => { + log::debug!("Channel disconnected for ph {ph}"); + // The sender (Session) might have dropped. + false // Remove from pending_replies + } + } + }); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crossbeam_channel::unbounded; + use std::sync::{Arc, Mutex}; + + #[test] + fn test_poll_data_new() { + let (_tx, rx) = unbounded(); + let poll_data = PollData::new(Some(_tx)); + assert!(poll_data.ready_events_sender.is_some()); + assert!(poll_data.registered_poll_handles.is_empty()); + assert!(poll_data.inode_poll_handles.is_empty()); + assert!(poll_data.ready_inodes.is_empty()); + assert!(poll_data.pending_replies.is_empty()); + assert!(poll_data.last_reply.is_empty()); + drop(rx); // ensure channel is dropped + } + + #[test] + fn test_register_and_unregister_poll_handle() { + let poll_data_arc = Arc::new(Mutex::new(PollData::new(None))); + let ph1: u64 = 1001; + let ino1: u64 = 1; + let events1: u32 = libc::POLLIN as u32; + + { + let mut poll_data = poll_data_arc.lock().unwrap(); + poll_data.register_poll_handle(ph1, ino1, events1); + + assert_eq!(poll_data.registered_poll_handles.get(&ph1), Some(&(ino1, events1))); + assert!(poll_data.inode_poll_handles.get(&ino1).unwrap().contains(&ph1)); + } + + { + let mut poll_data = poll_data_arc.lock().unwrap(); + poll_data.unregister_poll_handle(ph1); + assert!(!poll_data.registered_poll_handles.contains_key(&ph1)); + assert!(!poll_data.inode_poll_handles.contains_key(&ino1)); + } + } + + #[test] + fn test_mark_inode_ready_sends_event() { + let (tx, rx) = unbounded(); + let poll_data_arc = Arc::new(Mutex::new(PollData::new(Some(tx)))); + + let ph1: u64 = 1002; + let ino1: u64 = 2; + let events1: u32 = libc::POLLIN as u32; + + { + let mut poll_data = poll_data_arc.lock().unwrap(); + poll_data.register_poll_handle(ph1, ino1, events1); + } + + { + let mut poll_data = poll_data_arc.lock().unwrap(); + poll_data.mark_inode_ready(ino1, libc::POLLIN as u32); + } + + match rx.try_recv() { + Ok(Notification::Poll((poll, Some(reply_tx)))) => { + assert_eq!(poll.ph, ph1); + assert_eq!(poll.events, libc::POLLIN as u32); + reply_tx.send(Ok(())).unwrap(); + } + Ok(_) => panic!("Unexpected notification type"), + Err(e) => panic!("Expected to receive a poll event, but got error: {}", e), + } + + let mut poll_data = poll_data_arc.lock().unwrap(); + poll_data.check_replies(); + assert!(poll_data.last_reply.get(&ph1).is_some()); + assert!(poll_data.last_reply.get(&ph1).unwrap().is_ok()); + } + + #[test] + fn test_mark_inode_ready_sends_event_only_for_requested_events() { + let (tx, rx) = unbounded(); + let poll_data_arc = Arc::new(Mutex::new(PollData::new(Some(tx)))); + + let ph1: u64 = 1003; + let ino1: u64 = 3; + let requested_events_in: u32 = libc::POLLIN as u32; + let _requested_events_out: u32 = libc::POLLOUT as u32; // Prefixed with underscore + + { + let mut poll_data = poll_data_arc.lock().unwrap(); + // ph1 is interested in POLLIN + poll_data.register_poll_handle(ph1, ino1, requested_events_in); + } + + { + let mut poll_data = poll_data_arc.lock().unwrap(); + // Mark inode ready for POLLOUT. ph1 should not be notified. + poll_data.mark_inode_ready(ino1, libc::POLLOUT as u32); + } + assert!(rx.try_recv().is_err(), "Should not receive event if not requested"); + + { + let mut poll_data = poll_data_arc.lock().unwrap(); + // Mark inode ready for POLLIN. ph1 should be notified. + poll_data.mark_inode_ready(ino1, libc::POLLIN as u32); + } + match rx.try_recv() { + Ok(Notification::Poll((poll, Some(reply_tx)))) => { + assert_eq!(poll.ph, ph1); + assert_eq!(poll.events, libc::POLLIN as u32); + reply_tx.send(Ok(())).unwrap(); + } + Ok(_) => panic!("Unexpected notification type"), + Err(e) => panic!("Expected to receive a POLLIN event, but got error: {}", e), + } + } + + #[test] + fn test_initial_notification_if_already_ready() { + let (tx, rx) = unbounded(); + let poll_data_arc = Arc::new(Mutex::new(PollData::new(Some(tx)))); + + let ph1: u64 = 1004; + let ino1: u64 = 4; + let events1: u32 = libc::POLLIN as u32; + + { + let mut poll_data = poll_data_arc.lock().unwrap(); + // Mark ino1 as ready *before* registering the poll handle + poll_data.mark_inode_ready(ino1, libc::POLLIN as u32); + } + + // Clear any messages from the mark_inode_ready call (which should be none as no handle was registered yet) + while rx.try_recv().is_ok() {} + + let initial_event_mask: Option; + { + let mut poll_data = poll_data_arc.lock().unwrap(); + // Now register the poll handle + initial_event_mask = poll_data.register_poll_handle(ph1, ino1, events1); + } + + assert_eq!(initial_event_mask, Some(libc::POLLIN as u32), "Initial event mask should be POLLIN"); + + match rx.try_recv() { + Ok(Notification::Poll((poll, Some(reply_tx)))) => { + assert_eq!(poll.ph, ph1); + assert_eq!(poll.events, libc::POLLIN as u32); + reply_tx.send(Ok(())).unwrap(); + } + Ok(_) => panic!("Unexpected notification type"), + Err(e) => panic!("Expected to receive an initial poll event, but got error: {}", e), + } + } + + #[test] + fn test_mark_inode_not_ready() { + let poll_data_arc = Arc::new(Mutex::new(PollData::new(None))); + let ino1: u64 = 5; + let poll_in_event = libc::POLLIN as u32; + let poll_out_event = libc::POLLOUT as u32; + + { + let mut poll_data = poll_data_arc.lock().unwrap(); + // Mark ready for POLLIN and POLLOUT + poll_data.mark_inode_ready(ino1, poll_in_event | poll_out_event); + assert_eq!(poll_data.ready_inodes.get(&ino1), Some(&(poll_in_event | poll_out_event))); + } + + { + let mut poll_data = poll_data_arc.lock().unwrap(); + // Mark no longer ready for POLLIN + poll_data.mark_inode_not_ready(ino1, poll_in_event); + // Should still be ready for POLLOUT + assert_eq!(poll_data.ready_inodes.get(&ino1), Some(&poll_out_event)); + } + + { + let mut poll_data = poll_data_arc.lock().unwrap(); + // Mark no longer ready for POLLOUT + poll_data.mark_inode_not_ready(ino1, poll_out_event); + // Should not be ready for anything, so removed from map + assert!(!poll_data.ready_inodes.contains_key(&ino1)); + } + } + + #[test] + fn test_set_sender() { // Renamed test function + let poll_data_arc = Arc::new(Mutex::new(PollData::new(None))); + assert!(poll_data_arc.lock().unwrap().ready_events_sender.is_none()); + + let (tx, _rx) = unbounded(); + poll_data_arc.lock().unwrap().set_sender(tx); // Use set_sender + assert!(poll_data_arc.lock().unwrap().ready_events_sender.is_some()); + } + + #[test] + fn test_check_replies() { + let (tx, rx) = unbounded(); + let mut poll_data = PollData::new(Some(tx)); + let ph1: u64 = 2001; + let ino1: u64 = 6; + let events1: u32 = libc::POLLIN as u32; + + poll_data.register_poll_handle(ph1, ino1, events1); + poll_data.mark_inode_ready(ino1, libc::POLLIN as u32); + + // Simulate receiving a notification and sending a reply + if let Ok(Notification::Poll((_, Some(reply_tx)))) = rx.try_recv() { + reply_tx.send(Ok(())).unwrap(); + } else { + panic!("Failed to receive notification"); + } + + poll_data.check_replies(); + + assert!(poll_data.last_reply.contains_key(&ph1)); + assert!(poll_data.last_reply.get(&ph1).unwrap().is_ok()); + assert!(!poll_data.pending_replies.contains_key(&ph1)); + } +} diff --git a/src/channel.rs b/src/channel.rs index 9c7c52c6..4d0d4269 100644 --- a/src/channel.rs +++ b/src/channel.rs @@ -48,6 +48,48 @@ impl Channel { } } + /// Polls the kernel to determine if a request is ready for reading (does not block). + /// This method is used in the synchronous notifications execution model. + pub(crate) fn ready(&self) -> io::Result { + let mut buf = [libc::pollfd { + fd: self.0.as_raw_fd(), + events: libc::POLLIN, + revents: 0, + }]; + let rc = unsafe { + libc::poll( + buf.as_mut_ptr(), + 1, + 0, // ms; Non-blocking poll + ) + }; + match rc { + -1 => { + Err(io::Error::last_os_error()) + } + 0 => { + // Timeout with no events on FUSE FD. + Ok(false) + } + _ => { + // ret > 0, events are available + if (buf[0].revents & libc::POLLIN) != 0 { + // FUSE FD is ready to read. + Ok(true) + } else { + // Handling unexpected events + if (buf[0].revents & (libc::POLLERR | libc::POLLHUP | libc::POLLNVAL)) != 0 { + // Probably very bad + Err(io::Error::other(format!("Poll error, revents: {:#x}.", buf[0].revents))) + } else { + // Probably fine + Ok(false) + } + } + } + } + } + /// Returns a sender object for this channel. The sender object can be /// used to send to the channel. Multiple sender objects can be used /// and they can safely be sent to other threads. diff --git a/src/lib.rs b/src/lib.rs index 51ffc01a..7cda0c2d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,7 +29,13 @@ use crate::mnt::mount_options::check_option_conflicts; use crate::session::MAX_WRITE_SIZE; pub use mnt::mount_options::MountOption; #[cfg(feature = "abi-7-11")] -pub use notify::{Notifier, PollHandle}; +pub use notify::{Notification, Poll}; +#[cfg(feature = "abi-7-12")] +pub use notify::{InvalEntry, InvalInode}; +#[cfg(feature = "abi-7-15")] +pub use notify::Store; +#[cfg(feature = "abi-7-18")] +pub use notify::Delete; #[cfg(feature = "abi-7-11")] pub use reply::Ioctl; #[cfg(feature = "abi-7-40")] @@ -274,6 +280,19 @@ impl KernelConfig { } } +#[derive(Debug)] +/// This enum is an optional way for the Filesystem to report its status to a Session thread. +pub enum FsStatus { + /// Default may be used when the Filesystem does not implement a status + Default, + /// Ready indicates the Filesystem has no actions in progress + Ready, + /// Busy indicates the Filesytem has one or more actions in progress + Busy, + /// Stopped indicates that the Filesystem will not accept new requests + Stopped +} + /// Filesystem trait. /// /// This trait must be implemented to provide a userspace filesystem via FUSE. @@ -857,6 +876,10 @@ pub trait Filesystem { /// Poll for events. /// The method should return `Ok(u32)` with the poll events, or `Err(Errno)` otherwise. + /// Ok(nonzero) indicates that the file is ready now. + /// Ok(0) indicates that the file is not ready. In that case, + /// the filesystem should save the poll handle (`ph`) in its internal structure. + /// Later events should be sent via the notification channel. #[cfg(feature = "abi-7-11")] fn poll( &mut self, @@ -874,6 +897,22 @@ pub trait Filesystem { Err(Errno::ENOSYS) } + /// Initializes the notification event sender for the filesystem. + /// The boolean indicates whether the filesystem supports it. + #[cfg(feature = "abi-7-11")] + fn init_notification_sender( + &mut self, + _sender: crossbeam_channel::Sender, + ) -> bool { + false // Default: not supported + } + + /// In a syncronous execution model where a sleep may happen, + /// the heartbeat may be used to alert the Filesystem that time has passed. + fn heartbeat(&mut self) -> Result { + Ok(FsStatus::Default) + } + /// Preallocate or deallocate space to a file. /// The method should return `Ok(())` on success, or `Err(Errno)` otherwise. fn fallocate( diff --git a/src/ll/request.rs b/src/ll/request.rs index 28305220..d7360a8f 100644 --- a/src/ll/request.rs +++ b/src/ll/request.rs @@ -1330,7 +1330,11 @@ mod op { #[cfg(feature = "abi-7-21")] return self.arg.events; #[cfg(not(feature = "abi-7-21"))] - return 0; + // For older ABIs where fuse_poll_in.events is not used or is zero, + // the kernel implies interest in basic readiness. + // Commonly, this means POLLIN, POLLOUT, and POLLPRI. + // POLLRDNORM and POLLWRNORM are often included with POLLIN/POLLOUT. + return (libc::POLLIN | libc::POLLRDNORM | libc::POLLOUT | libc::POLLWRNORM | libc::POLLPRI) as u32; } /// The poll request's flags diff --git a/src/notify.rs b/src/notify.rs index f5461516..95c1da6e 100644 --- a/src/notify.rs +++ b/src/notify.rs @@ -1,87 +1,220 @@ use std::io; #[allow(unused)] -use std::{convert::TryInto, ffi::OsStr}; +use std::{convert::TryInto, ffi::OsStr, ffi::OsString}; +use crossbeam_channel::{SendError, Sender}; use crate::{ channel::ChannelSender, - ll::{fuse_abi::fuse_notify_code as notify_code, notify::Notification}, - + // renaming ll::notify::Notificatition to distinguish from crate::Notification + ll::{fuse_abi::fuse_notify_code as notify_code, notify::Notification as NotificationBuf}, // What we're sending here aren't really replies, but they // move in the same direction (userspace->kernel), so we can // reuse ReplySender for it. reply::ReplySender, }; -/// A handle to a pending poll() request. Can be saved and used to notify the -/// kernel when a poll is ready. -#[derive(Clone)] -pub struct PollHandle { - handle: u64, - notifier: Notifier, +/// Poll event data to be sent to the kernel +#[cfg(feature = "abi-7-11")] +#[derive(Debug, Copy, Clone)] +pub struct Poll { + /// Poll handle: the unique idenifier from a previous poll request + pub ph: u64, + /// Events flag: binary encoded information about resource availability + pub events: u32 } -impl PollHandle { - pub(crate) fn new(cs: ChannelSender, kh: u64) -> Self { - Self { - handle: kh, - notifier: Notifier::new(cs), - } - } +/// Invalid entry notification to be sent to the kernel +#[cfg(feature = "abi-7-12")] +#[derive(Debug, Clone)] +pub struct InvalEntry { + /// Parent: the inode of the parent of the invalid entry + pub parent: u64, + /// Name: the file name of the invalid entry + pub name: OsString +} - /// Notify the kernel that the associated file handle is ready to be polled. - pub fn notify(self) -> io::Result<()> { - self.notifier.poll(self.handle) - } +/// Invalid inode notification to be sent to the kernel +#[cfg(feature = "abi-7-12")] +#[derive(Debug, Copy, Clone)] +pub struct InvalInode { + /// Inode with invalid metadata + pub ino: u64, + /// Start of invalid metadata + pub offset: i64, + /// Length of invalid metadata + pub len: i64 } -impl From for u64 { - fn from(value: PollHandle) -> Self { - value.handle - } +/// Store inode notification to be sent to the kernel +#[cfg(feature = "abi-7-15")] +#[derive(Debug, Clone)] +pub struct Store { + /// ino: the inode to be updated + pub ino: u64, + /// The start location of the metadata to be updated + pub offset: u64, + /// The new metadata + pub data: Vec } -impl std::fmt::Debug for PollHandle { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("PollHandle").field(&self.handle).finish() - } +/// Deleted file notification to be sent to the kernel +#[cfg(feature = "abi-7-18")] +#[derive(Debug, Clone)] +pub struct Delete { + /// Parent: the inode of the parent directory that contained the deleted entry + pub parent: u64, + /// ino: the inode of the deleted file + pub ino: u64, + /// Name: the file name of the deleted entry + pub name: OsString +} + +/// The list of supported notification types +#[derive(Debug)] +pub enum Notification { + /// A poll event notification + #[cfg(feature = "abi-7-11")] + Poll((Poll, Option>>)), + /// An invalid entry notification + #[cfg(feature = "abi-7-12")] + InvalEntry((InvalEntry, Option>>)), + /// An invalid inode notification + #[cfg(feature = "abi-7-12")] + InvalInode((InvalInode, Option>>)), + /// An inode metadata update notification + #[cfg(feature = "abi-7-15")] + Store((Store, Option>>)), + /// An inode deletion notification + #[cfg(feature = "abi-7-18")] + Delete((Delete, Option>>)), + /// (Internal) Disable notifications for this session + Stop } +#[cfg(feature = "abi-7-11")] +impl From for Notification {fn from(notification: Poll) -> Self{Notification::Poll((notification, None))}} +#[cfg(feature = "abi-7-12")] +impl From for Notification {fn from(notification: InvalEntry) -> Self{Notification::InvalEntry((notification, None))}} +#[cfg(feature = "abi-7-12")] +impl From for Notification {fn from(notification: InvalInode) -> Self{Notification::InvalInode((notification, None))}} +#[cfg(feature = "abi-7-15")] +impl From for Notification {fn from(notification: Store) -> Self{Notification::Store((notification, None))}} +#[cfg(feature = "abi-7-18")] +impl From for Notification {fn from(notification: Delete) -> Self{Notification::Delete((notification, None))}} + /// A handle by which the application can send notifications to the server #[derive(Debug, Clone)] -pub struct Notifier(ChannelSender); +pub(crate) struct Notifier(ChannelSender); impl Notifier { pub(crate) fn new(cs: ChannelSender) -> Self { Self(cs) } + pub(crate) fn notify(&self, notification: Notification) -> io::Result<()> { + // These branches follow a pattern: + // 1: Attempt to deliver the notification to the Kernel. + // 2: Attempt to deliver the result of 1 to the Filesystem. + // 3: Return something to the Session. + // - If the result of 1 was delivered to the Filesystem, + // then `Ok` returns to Session loop. + // the Filesystem is expected to handle the error, if any. + // - If the result of 1 could not be delivered to the filesystem, + // then the result of 1 (but just the error part) is returned to Session. + // The ok value, if any, is discarded using the trivial closure `|_|{}` + match notification { + #[cfg(feature = "abi-7-11")] + Notification::Poll((data, sender)) => { + let res = self.poll(data); + if let Some(sender) = sender { + if let Err(SendError(res)) = sender.send(res) { + log::warn!("Poll notification reply {res:?} could not be delivered."); + return res; + } + return Ok(()); + } + res + }, + #[cfg(feature = "abi-7-12")] + Notification::InvalEntry((data, sender)) => { + let res = self.inval_entry(data); + if let Some(sender) = sender { + if let Err(SendError(res)) = sender.send(res) { + log::warn!("InvalEntry notification reply {res:?} could not be delivered."); + return res; + } + return Ok(()); + } + res + }, + #[cfg(feature = "abi-7-12")] + Notification::InvalInode((data, sender)) => { + let res = self.inval_inode(data); + if let Some(sender) = sender { + if let Err(SendError(res)) = sender.send(res) { + log::warn!("InvalInode notification reply {res:?} could not be delivered."); + return res; + } + return Ok(()); + } + res + }, + #[cfg(feature = "abi-7-15")] + Notification::Store((data, sender)) => { + let res = self.store(data); + if let Some(sender) = sender { + if let Err(SendError(res)) = sender.send(res) { + log::warn!("Store notification reply {res:?} could not be delivered."); + return res; + } + return Ok(()); + } + res + }, + #[cfg(feature = "abi-7-18")] + Notification::Delete((data, sender)) => { + let res = self.delete(data); + if let Some(sender) = sender { + if let Err(SendError(res)) = sender.send(res) { + log::warn!("Delete notification reply {res:?} could not be delivered."); + return res; + } + return Ok(()); + } + res + }, + // For completeness + Notification::Stop => Ok(()) + } + } + /// Notify poll clients of I/O readiness #[cfg(feature = "abi-7-11")] - pub fn poll(&self, kh: u64) -> io::Result<()> { - let notif = Notification::new_poll(kh); + pub fn poll(&self, notification: Poll) -> io::Result<()> { + let notif = NotificationBuf::new_poll(notification.ph); self.send(notify_code::FUSE_POLL, ¬if) } /// Invalidate the kernel cache for a given directory entry #[cfg(feature = "abi-7-12")] - pub fn inval_entry(&self, parent: u64, name: &OsStr) -> io::Result<()> { - let notif = Notification::new_inval_entry(parent, name).map_err(Self::too_big_err)?; + pub fn inval_entry(&self, notification: InvalEntry) -> io::Result<()> { + let notif = NotificationBuf::new_inval_entry(notification.parent, notification.name.as_ref()).map_err(Self::too_big_err)?; self.send_inval(notify_code::FUSE_NOTIFY_INVAL_ENTRY, ¬if) } /// Invalidate the kernel cache for a given inode (metadata and /// data in the given range) #[cfg(feature = "abi-7-12")] - pub fn inval_inode(&self, ino: u64, offset: i64, len: i64) -> io::Result<()> { - let notif = Notification::new_inval_inode(ino, offset, len); + pub fn inval_inode(&self, notification: InvalInode ) -> io::Result<()> { + let notif = NotificationBuf::new_inval_inode(notification.ino, notification.offset, notification.len); self.send_inval(notify_code::FUSE_NOTIFY_INVAL_INODE, ¬if) } /// Update the kernel's cached copy of a given inode's data #[cfg(feature = "abi-7-15")] - pub fn store(&self, ino: u64, offset: u64, data: &[u8]) -> io::Result<()> { - let notif = Notification::new_store(ino, offset, data).map_err(Self::too_big_err)?; + pub fn store(&self, notification: Store) -> io::Result<()> { + let notif = NotificationBuf::new_store(notification.ino, notification.offset, ¬ification.data).map_err(Self::too_big_err)?; // Not strictly an invalidate, but the inode we're operating // on may have been evicted anyway, so treat is as such self.send_inval(notify_code::FUSE_NOTIFY_STORE, ¬if) @@ -90,13 +223,13 @@ impl Notifier { /// Invalidate the kernel cache for a given directory entry and inform /// inotify watchers of a file deletion. #[cfg(feature = "abi-7-18")] - pub fn delete(&self, parent: u64, child: u64, name: &OsStr) -> io::Result<()> { - let notif = Notification::new_delete(parent, child, name).map_err(Self::too_big_err)?; + pub fn delete(&self, notification: Delete) -> io::Result<()> { + let notif = NotificationBuf::new_delete(notification.parent, notification.ino, ¬ification.name).map_err(Self::too_big_err)?; self.send_inval(notify_code::FUSE_NOTIFY_DELETE, ¬if) } - #[allow(unused)] - fn send_inval(&self, code: notify_code, notification: &Notification<'_>) -> io::Result<()> { + #[cfg(feature = "abi-7-12")] + fn send_inval(&self, code: notify_code, notification: &NotificationBuf<'_>) -> io::Result<()> { match self.send(code, notification) { // ENOENT is harmless for an invalidation (the // kernel may have already dropped the cached @@ -106,7 +239,7 @@ impl Notifier { } } - fn send(&self, code: notify_code, notification: &Notification<'_>) -> io::Result<()> { + fn send(&self, code: notify_code, notification: &NotificationBuf<'_>) -> io::Result<()> { notification .with_iovec(code, |iov| self.0.send(iov)) .map_err(Self::too_big_err)? diff --git a/src/request.rs b/src/request.rs index 309ac8e8..d2376047 100644 --- a/src/request.rs +++ b/src/request.rs @@ -17,8 +17,6 @@ use crate::ll::Request as _; use crate::reply::ReplyHandler; use crate::session::{Session, SessionACL}; use crate::Filesystem; -#[cfg(feature = "abi-7-11")] -use crate::PollHandle; use crate::{ll, Forget, KernelConfig}; /// Request data structure @@ -765,12 +763,11 @@ impl<'a> Request<'a> { } #[cfg(feature = "abi-7-11")] ll::Operation::Poll(x) => { - let ph = PollHandle::new(se.ch.sender(), x.kernel_handle()); let response = se.filesystem.poll( self.meta, self.request.nodeid().into(), x.file_handle().into(), - ph.into(), + x.kernel_handle(), x.events(), x.flags() ); @@ -782,9 +779,6 @@ impl<'a> Request<'a> { self.replyhandler.error(err); } } - // TODO: register the poll handler - // TODO: receive poll data from the application - // TODO: use the poll handler to send the data } #[cfg(feature = "abi-7-15")] ll::Operation::NotifyReply(_) => { diff --git a/src/session.rs b/src/session.rs index 1e2686a2..54328dec 100644 --- a/src/session.rs +++ b/src/session.rs @@ -18,11 +18,14 @@ use std::io; use crate::ll::fuse_abi as abi; use crate::request::Request; -use crate::Filesystem; +use crate::{Filesystem, FsStatus}; use crate::MountOption; use crate::{channel::Channel, mnt::Mount}; #[cfg(feature = "abi-7-11")] -use crate::{channel::ChannelSender, notify::Notifier}; +use crate::notify::{Notification, Notifier}; +#[cfg(feature = "abi-7-11")] +use crossbeam_channel::{Sender, Receiver}; + /// The max size of write requests from the kernel. The absolute minimum is 4k, /// FUSE recommends at least 128k, max 16M. The FUSE default is 16M on macOS @@ -33,6 +36,9 @@ pub const MAX_WRITE_SIZE: usize = 16 * 1024 * 1024; /// up to `MAX_WRITE_SIZE` bytes in a write request, we use that value plus some extra space. const BUFFER_SIZE: usize = MAX_WRITE_SIZE + 4096; +/// This value is used to prevent a busy loop in the synchronous run with notification +const SYNC_SLEEP_INTERVAL: std::time::Duration = std::time::Duration::from_millis(5); + #[derive(Default, Debug, Eq, PartialEq)] /// How requests should be filtered based on the calling UID. pub enum SessionACL { @@ -67,6 +73,15 @@ pub struct Session { pub(crate) initialized: bool, /// True if the filesystem was destroyed (destroy operation done) pub(crate) destroyed: bool, + #[cfg(feature = "abi-7-11")] + /// Whether this session currently has notification support + pub(crate) notify: bool, + #[cfg(feature = "abi-7-11")] + /// Sender for poll events to the filesystem. It will be cloned and passed to Filesystem. + pub(crate) ns: Sender, + #[cfg(feature = "abi-7-11")] + /// Receiver for poll events from the filesystem. + pub(crate) nr: Receiver, } impl AsFd for Session { @@ -98,7 +113,7 @@ impl Session { } else { Mount::new(mountpoint, options)? }; - + // Create the channel for fuse messages let ch = Channel::new(file); let allowed = if options.contains(&MountOption::AllowRoot) { SessionACL::RootAndOwner @@ -107,8 +122,15 @@ impl Session { } else { SessionACL::Owner }; - - Ok(Session { + #[cfg(feature = "abi-7-11")] + let mut filesystem = filesystem; + #[cfg(feature = "abi-7-11")] + // Create the channel for poll events. + let (ns, nr) = crossbeam_channel::unbounded(); + #[cfg(feature = "abi-7-11")] + // Pass the sender to the filesystem. + let notify = filesystem.init_notification_sender(ns.clone()); + let new_session = Session { filesystem, ch, mount: Arc::new(Mutex::new(Some((mountpoint.to_owned(), mount)))), @@ -118,13 +140,29 @@ impl Session { proto_minor: 0, initialized: false, destroyed: false, - }) + #[cfg(feature = "abi-7-11")] + notify, + #[cfg(feature = "abi-7-11")] + ns, + #[cfg(feature = "abi-7-11")] + nr, + }; + Ok(new_session) } /// Wrap an existing /dev/fuse file descriptor. This doesn't mount the /// filesystem anywhere; that must be done separately. pub fn from_fd(filesystem: FS, fd: OwnedFd, acl: SessionACL) -> Self { + // Create the channel for fuse messages let ch = Channel::new(Arc::new(fd.into())); + #[cfg(feature = "abi-7-11")] + let mut filesystem = filesystem; + #[cfg(feature = "abi-7-11")] + // Create the channel for poll events. + let (ns, nr) = crossbeam_channel::unbounded(); + #[cfg(feature = "abi-7-11")] + // Pass the sender to the filesystem. + let notify = filesystem.init_notification_sender(ns.clone()); Session { filesystem, ch, @@ -135,6 +173,12 @@ impl Session { proto_minor: 0, initialized: false, destroyed: false, + #[cfg(feature = "abi-7-11")] + notify, + #[cfg(feature = "abi-7-11")] + ns, + #[cfg(feature = "abi-7-11")] + nr, } } @@ -173,6 +217,7 @@ impl Session { _ => return Err(err), }, } + // TODO: maybe add a heartbeat? } Ok(()) } @@ -191,9 +236,153 @@ impl Session { /// Returns an object that can be used to send notifications to the kernel #[cfg(feature = "abi-7-11")] - pub fn notifier(&self) -> Notifier { + fn notifier(&self) -> Notifier { Notifier::new(self.ch.sender()) } + + /// Returns an object that can be used to send poll event notifications + #[cfg(feature = "abi-7-11")] + pub fn get_notification_sender(&self) -> Sender { + self.ns.clone() + } + + /// Run the session loop in a single thread, same as `run()`, but additionally + /// processing both FUSE requests and poll events without blocking. + pub fn run_with_notifications(&mut self) -> io::Result<()> { + // Buffer for receiving requests from the kernel + let mut buffer = vec![0; BUFFER_SIZE]; + let buf = aligned_sub_buf( + &mut buffer, + std::mem::align_of::(), + ); + + info!("Running FUSE session in single-threaded mode"); + + loop { + let mut work_done = false; + // Check for outgoing Notifications (non-blocking) + #[cfg(feature = "abi-7-11")] + if self.handle_notifications()? { + work_done = true; + } + + if work_done { + // skip checking for incoming FUSE requests, + // to prioritize checking for additional outgoing messages + continue; + } + // Check for incoming FUSE requests (non-blocking) + match self.ch.ready() { + Err(err) => { + if err.raw_os_error() == Some(EINTR) { + debug!("FUSE fd connection interrupted, will retry."); + } else { + warn!("FUSE fd connection: {err}"); + // Assume very bad. Stop the run. TODO: maybe some handling. + return Err(err); + } + } + Ok(ready) => { + if ready { + // Read a FUSE request (blocks until read succeeds) + match self.ch.receive(buf) { + Ok(size) => { + if size == 0 { + // Read of 0 bytes on FUSE FD typically means it was closed (unmounted) + info!("FUSE channel read 0 bytes, session ending."); + break; + } + if let Some(req) = Request::new(self.ch.sender(), &buf[..size]) { + req.dispatch(self); + } else { + // Illegal request, quit loop + warn!("Failed to parse FUSE request, session ending."); + break; + } + work_done = true; + } + Err(err) => match err.raw_os_error() { + Some(ENOENT) => { + debug!("FUSE channel receive ENOENT, retrying."); + continue; + } + Some(EINTR) => { + debug!("FUSE channel receive EINTR, retrying."); + continue; + } + Some(EAGAIN) => { + debug!("FUSE channel receive EAGAIN, retrying."); + continue; + } + Some(ENODEV) => { + info!("FUSE device not available (ENODEV), session ending."); + break; // Filesystem was unmounted + } + _ => { + error!("Error receiving FUSE request: {err}"); + return Err(err); // Unhandled error + } + }, + } + } + // if not ready, do nothing. + } + } + if !work_done { + // No actions taken this loop iteration. + // Sleep briefly to yield CPU. + std::thread::sleep(SYNC_SLEEP_INTERVAL); + // Do a heartbeat to let the Filesystem know that some time has passed. + match FS::heartbeat(&mut self.filesystem) { + Ok(status) => { + if let FsStatus::Stopped = status { + break; + } + // TODO: handle other cases + } + Err(e) => { + warn!("Heartbeat error: {e:?}"); + } + } + } + } + Ok(()) + } + + #[cfg(feature = "abi-7-11")] + fn handle_notifications(&mut self) -> io::Result { + if self.notify { + match self.nr.try_recv() { + Ok(notification) => { + debug!("Notification: {:?}", ¬ification); + if let Notification::Stop = notification { + // Filesystem says no more notifications. + info!("Filesystem sent Stop notification; disabling notifications."); + self.notify = false; + } + if let Err(_e) = self.notifier().notify(notification) { + error!("Failed to send notification."); + // TODO. Decide if error is fatal. ENODEV might mean unmounted. + } + Ok(true) + } + Err(crossbeam_channel::TryRecvError::Empty) => { + // No poll events pending, proceed to check FUSE FD + Ok(false) + } + Err(crossbeam_channel::TryRecvError::Disconnected) => { + // Filesystem's Notification Sender disconnected. + // This is not necessarily a fatal error for the session itself, + // as FUSE requests can still be processed. + warn!("Notification channel disconnected."); + self.notify = false; + Ok(false) + } + } + } else { + Ok(false) + } + } } #[derive(Debug)] @@ -243,11 +432,11 @@ impl Drop for Session { /// The background session data structure pub struct BackgroundSession { - /// Thread guard of the background session - pub guard: JoinHandle>, + /// Thread guard of the main session loop + pub main_loop_guard: JoinHandle>, /// Object for creating Notifiers for client use #[cfg(feature = "abi-7-11")] - sender: ChannelSender, + sender: Sender, /// Ensures the filesystem is unmounted when the session ends _mount: Option, } @@ -256,17 +445,24 @@ impl BackgroundSession { /// Create a new background session for the given session by running its /// session loop in a background thread. If the returned handle is dropped, /// the filesystem is unmounted and the given session ends. - pub fn new(se: Session) -> io::Result { + pub fn new(mut se: Session) -> io::Result { #[cfg(feature = "abi-7-11")] - let sender = se.ch.sender(); - // Take the fuse_session, so that we can unmount it + let sender = se.ns.clone(); + let mount = std::mem::take(&mut *se.mount.lock().unwrap()).map(|(_, mount)| mount); - let guard = thread::spawn(move || { - let mut se = se; + + #[cfg(not(feature = "abi-7-11"))] + // The main session (se) is moved into this thread. + let main_loop_guard = thread::spawn(move || { se.run() }); + #[cfg(feature = "abi-7-11")] + let main_loop_guard = thread::spawn(move || { + se.run_with_notifications() + }); + Ok(BackgroundSession { - guard, + main_loop_guard, #[cfg(feature = "abi-7-11")] sender, _mount: mount, @@ -275,19 +471,22 @@ impl BackgroundSession { /// Unmount the filesystem and join the background thread. pub fn join(self) { let Self { - guard, + main_loop_guard, #[cfg(feature = "abi-7-11")] - sender: _, + sender: _, _mount, } = self; + // Unmount the filesystem drop(_mount); - guard.join().unwrap().unwrap(); + // Stop the background thread + main_loop_guard.join().unwrap().unwrap(); } /// Returns an object that can be used to send notifications to the kernel #[cfg(feature = "abi-7-11")] - pub fn notifier(&self) -> Notifier { - Notifier::new(self.sender.clone()) + #[must_use] + pub fn get_notification_sender(&self) -> Sender { + self.sender.clone() } } @@ -295,6 +494,13 @@ impl BackgroundSession { // thread_scoped::JoinGuard impl fmt::Debug for BackgroundSession { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - write!(f, "BackgroundSession {{ guard: JoinGuard<()> }}",) + let mut builder = f.debug_struct("BackgroundSession"); + builder.field("main_loop_guard", &self.main_loop_guard); + #[cfg(feature = "abi-7-11")] + { + builder.field("sender", &self.sender); + } + builder.field("_mount", &self._mount); + builder.finish() } } From 08fba79d9760b5ae7927494d96621426e6cdc5da Mon Sep 17 00:00:00 2001 From: Richard Lawrence Date: Mon, 21 Jul 2025 10:23:39 -0500 Subject: [PATCH 12/19] Completed the passthrough feature. Backing ids are acquired through the Notification API. Backing id handling has been relocated to the example passthrough filesystem implementation. Backing id is now an (optional) field of the `Open` struct that returns from `open()`, etc. Related kernel communication has been relocated to the (new) `ll::ioctl` module. Updated the passthrough integration test to be compatible with MacOS. --- examples/notify_inval_inode.rs | 1 + examples/passthrough.rs | 417 ++++++++++++++++++++++++++------- examples/poll.rs | 3 +- examples/simple.rs | 3 + src/channel.rs | 19 +- src/lib.rs | 4 - src/ll/ioctl.rs | 61 +++++ src/ll/mod.rs | 2 + src/notify.rs | 44 ++++ src/passthrough.rs | 74 ------ src/reply.rs | 59 +++-- tests/test_passthrough.sh | 2 +- 12 files changed, 485 insertions(+), 204 deletions(-) create mode 100644 src/ll/ioctl.rs delete mode 100644 src/passthrough.rs diff --git a/examples/notify_inval_inode.rs b/examples/notify_inval_inode.rs index 8c406fc0..b9f6ea12 100644 --- a/examples/notify_inval_inode.rs +++ b/examples/notify_inval_inode.rs @@ -218,6 +218,7 @@ impl Filesystem for ClockFS<'_> { Ok(Open { fh: ino, // Using ino as fh, as it's unique for the file flags: consts::FOPEN_KEEP_CACHE, + backing_id: None, }) } } diff --git a/examples/passthrough.rs b/examples/passthrough.rs index 611d21d4..7345b9ce 100644 --- a/examples/passthrough.rs +++ b/examples/passthrough.rs @@ -1,89 +1,86 @@ -// This example requires fuse 7.40 or later. Run with: +// This example requires fuse 7.40 or later. // -// cargo run --example passthrough --features abi-7-40 /tmp/foobar +// To run this example, do the following: +// +// sudo RUST_LOG=info ./target/debug/examples/passthrough /tmp/mnt & +// sudo cat /tmp/mnt/passthrough +// sudo pkill passthrough +// sudo umount /tmp/mnt use clap::{crate_version, Arg, ArgAction, Command}; +use crossbeam_channel::{Sender, Receiver}; use fuser::{ - consts, BackingId, Bytes, Dirent, DirentList, Entry, Errno, - FileAttr, FileType, Filesystem, KernelConfig, MountOption, Open, RequestMeta, + consts, Bytes, Dirent, DirentList, Entry, Errno, FileAttr, FileType, + Filesystem, KernelConfig, MountOption, Open, Notification, RequestMeta, }; use std::collections::HashMap; +use std::io; use std::path::Path; use std::fs::File; -use std::rc::{Rc, Weak}; -use std::time::{Duration, UNIX_EPOCH}; +use std::sync::Arc; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; const TTL: Duration = Duration::from_secs(1); // 1 second +const BACKING_TIMEOUT: Duration = Duration::from_secs(2); // 2 seconds -/// BackingCache is an example of how a filesystem might manage BackingId objects for fd -/// passthrough. The idea is to avoid creating more than one BackingId object per file at a time. -/// -/// We do this by keeping a weak "by inode" hash table mapping inode numbers to BackingId. If a -/// BackingId already exists, we use it. Otherwise, we create it. This is not enough to keep the -/// BackingId alive, though. For each Filesystem::open() request we allocate a fresh 'fh' -/// (monotonically increasing u64, next_fh, never recycled) and use that to keep a *strong* -/// reference on the BackingId for that open. We drop it from the table on Filesystem::release(), -/// which means the BackingId will be dropped in the kernel when the last user of it closes. -/// -/// In this way, if a request to open a file comes in and the file is already open, we'll reuse the -/// BackingId, but as soon as all references are closed, the BackingId will be dropped. -/// -/// It's left as an exercise to the reader to implement an active cleanup of the by_inode table, if -/// desired, but our little example filesystem only contains one file. :) -#[derive(Debug, Default)] -struct BackingCache { - by_handle: HashMap>, - by_inode: HashMap>, - next_fh: u64, -} +// ----- BackingID ----- -impl BackingCache { - fn next_fh(&mut self) -> u64 { - self.next_fh += 1; - self.next_fh - } +// A BackingId can be in three states: pending, ready, and closed. +// The closed variant is not strictly necessary; it simply provides some additional logging. +#[derive(Debug)] +enum BackingStatus { + Pending(PendingBackingId), + Ready(ReadyBackingId), + // Closed variant is just for the extra logging + Closed(ClosedBackingId), +} - /// Gets the existing BackingId for `ino` if it exists, or calls `callback` to create it. - /// - /// Returns a unique file handle and the BackingID (possibly shared, possibly new). The - /// returned file handle should be `put()` when you're done with it. - fn get_or( - &mut self, - ino: u64, - callback: impl Fn() -> std::io::Result, - ) -> std::io::Result<(u64, Rc)> { - let fh = self.next_fh(); +#[derive(Debug)] +struct PendingBackingId { + // reply will receive a backing_id from the kernel + reply: Receiver>, + #[allow(dead_code)] + // The file needs to stay open until the kernel finishes processing the open backing request. + // It's behind an Arc so that if the Filesystem plans to interact with the file again later, + // the Filesystem can clone the Arc to avoid a redundant File::open() operation. + _file: Arc, +} - let id = if let Some(id) = self.by_inode.get(&ino).and_then(Weak::upgrade) { - eprintln!("HIT! reusing {id:?}"); - id - } else { - let id = Rc::new(callback()?); - self.by_inode.insert(ino, Rc::downgrade(&id)); - eprintln!("MISS! new {id:?}"); - id - }; +#[derive(Debug)] +struct ReadyBackingId { + // The notifier is used to safely close the backing id after any miscellaneous unexpected failures. + notifier: Sender, + // This is the literal backing_id the kernel assigns to the filesystem. + backing_id: u32, + // there is a limit to how many you backing id the filesystem can hold open. + // timestamp is one example strategy for retiring old backing ids. + timestamp: SystemTime, + // The reply_sender is just for extra logging. + reply_sender: Option>>, +} - self.by_handle.insert(fh, Rc::clone(&id)); - Ok((fh, id)) +impl Drop for ReadyBackingId { + fn drop(&mut self) { + // It is important to notify the kernel when backing ids are no longer in use. + let notification = Notification::CloseBacking((self.backing_id, self.reply_sender.take())); + let _ = self.notifier.send(notification); + // TODO: handle the case where the notifier is broken. } +} - /// Releases a file handle previously obtained from `get_or()`. If this was a last user of a - /// particular BackingId then it will be dropped. - fn put(&mut self, fh: u64) { - eprintln!("Put fh {fh}"); - match self.by_handle.remove(&fh) { - None => eprintln!("ERROR: Put fh {fh} but it wasn't found in cache!!\n"), - Some(id) => eprintln!("Put fh {fh}, was {id:?}\n"), - } - } +#[derive(Debug)] +struct ClosedBackingId { + // the reply is just for extra logging + reply: Receiver>, } #[derive(Debug)] struct PassthroughFs { root_attr: FileAttr, passthrough_file_attr: FileAttr, - backing_cache: BackingCache, + backing_cache: HashMap, + next_fh: u64, + notification_sender: Option>, } const ROOT_DIR_ENTRIES: [Dirent; 3] = [ @@ -136,7 +133,111 @@ impl PassthroughFs { Self { root_attr, passthrough_file_attr, - backing_cache: Default::default(), + backing_cache: HashMap::new(), + next_fh: 0, + notification_sender: None, + } + } + + fn next_fh(&mut self) -> u64 { + self.next_fh += 1; + self.next_fh + } + + // Get the backing status for a given inode, after all available updates are applied. + // This will advance the status as appropriate and remove it from the cache if it is stale. + // The returning an immutable reference to the updated status (or None) + fn get_update_backing_status(&mut self, ino: u64) -> Option<&BackingStatus> { + let mut remove = false; + // using the "update, save a boolean, remove" pattern because we can't remove it while holding it as a mutable borrow. + if let Some(backing_status) = self.backing_cache.get_mut(&ino) { + if let Some(notifier) = self.notification_sender.clone() { + if !Self::update_backing_status(backing_status, ¬ifier, true) { + remove = true; + } + } + } + if remove { + self.backing_cache.remove(&ino); + } + self.backing_cache.get(&ino) + } + + // update_backing_status mutates a BackingStatus, advancing it to the next status as appropriate. + // It returns a boolean indicating whether the item is still valid and should be retained in the cache. + // The boolean return is so that it works with `HashMap::retain` for efficiently dropping stale cache entries. + fn update_backing_status( + backing_status: &mut BackingStatus, + notifier: &Sender, + extend: bool, + ) -> bool { + match backing_status { + BackingStatus::Pending(p) => { + log::debug!("processing pending {p:?}"); + match p.reply.try_recv() { + Ok(Ok(backing_id)) => { + let now = SystemTime::now(); + *backing_status = BackingStatus::Ready(ReadyBackingId { + notifier: notifier.clone(), + backing_id, + timestamp: now, + reply_sender: None, + }); + log::info!("Backing Id {backing_id} Ready"); + true + } + Ok(Err(e)) => { + log::error!("error {e}"); + false + } + Err(crossbeam_channel::TryRecvError::Empty) => { + log::debug!("waiting for reply"); + true + } + Err(crossbeam_channel::TryRecvError::Disconnected) => { + log::warn!("channel disconnected"); + false + } + } + } + BackingStatus::Ready(r) => { + let now = SystemTime::now(); + if extend { + log::debug!("processing ready {r:?}"); + r.timestamp = now; + log::debug!("timestamp renewed"); + } else if now.duration_since(r.timestamp).unwrap() > BACKING_TIMEOUT { + log::debug!("processing ready {r:?}"); + log::info!("Backing Id {} Timed Out", r.backing_id); + // everything below this is just for extra logging. + let (tx, rx) = crossbeam_channel::bounded(1); + r.reply_sender = Some(tx); + *backing_status = BackingStatus::Closed(ClosedBackingId { reply: rx }); + } + true // ready remains ready or transitions to closed. either way, it remains in the cache. + } + BackingStatus::Closed(d) => { + // all of this is just for the extra logging + log::debug!("processing closed {d:?}"); + match d.reply.try_recv() { + Ok(Ok(value)) => { + log::debug!("ok {value:?}"); + false + } + Ok(Err(e)) => { + log::error!("error {e}"); + false + } + Err(crossbeam_channel::TryRecvError::Empty) => { + log::debug!("waiting for reply"); + true + } + Err(crossbeam_channel::TryRecvError::Disconnected) => { + log::warn!("channel disconnected"); + false + } + } + } } } } @@ -154,10 +255,43 @@ impl Filesystem for PassthroughFs { Ok(config) } + #[cfg(feature = "abi-7-11")] + fn init_notification_sender( + &mut self, + sender: Sender, + ) -> bool { + log::info!("init_notification_sender"); + self.notification_sender = Some(sender); + true + } + + // It is not generally safe to contact the kernel to obtain a backing id + // while the kernel is waiting for a response to an open operation in progress. + // Therefore, this example requests the backing id on lookup instead of on open. #[allow(clippy::cast_sign_loss)] fn lookup(&mut self, _req: RequestMeta, parent: u64, name: &Path) -> Result { log::info!("lookup(name={name:?})"); if parent == 1 && name.to_str() == Some("passthrough") { + if self.get_update_backing_status(2).is_none() { + log::info!("new pending backing id request"); + if let Some(sender) = &self.notification_sender { + let (tx, rx) = crossbeam_channel::bounded(1); + let file = File::open("/etc/profile").unwrap(); + let fd = std::os::unix::io::AsRawFd::as_raw_fd(&file); + if let Err(e) = sender.send(Notification::OpenBacking((fd as u32, Some(tx)))) { + log::error!("failed to send OpenBacking notification: {e}"); + } else { + let backing_id = PendingBackingId { + reply: rx, + _file: Arc::new(file), + }; + self.backing_cache + .insert(2, BackingStatus::Pending(backing_id)); + } + } else { + log::warn!("unable to request a backing id. no notification sender available"); + } + } Ok(Entry { ino: self.passthrough_file_attr.ino, generation: None, @@ -186,36 +320,48 @@ impl Filesystem for PassthroughFs { if ino != 2 { return Err(Errno::ENOENT); } + // Check if a backing id is ready for this file + let backing_id_option = if let Some(BackingStatus::Ready(ready_backing_id)) = + self.get_update_backing_status(ino) + { + Some(ready_backing_id.backing_id) + } else { + //TODO: return Err(Errno::EAGAIN); + None + }; + let fh = self.next_fh(); + // TODO: track file handles + log::info!("open: fh {fh}, backing_id_option {backing_id_option:?}"); + Ok(Open { + fh, + flags: consts::FOPEN_PASSTHROUGH, + backing_id: backing_id_option, + }) + } - let (fh, id) = self - .backing_cache - .get_or(ino, || { - let _file = File::open("/etc/os-release")?; - // TODO: Implement opening the backing file and returning appropriate - // information, possibly including a BackingId within the Open struct, - // or handle it through other means if fd-passthrough is intended here. - Err(std::io::Error::new(std::io::ErrorKind::Other, "TODO: passthrough open not fully implemented")) - }) - .unwrap(); - - eprintln!(" -> opened_passthrough({fh:?}, 0, {id:?});\n"); - // TODO: Ensure fd-passthrough is correctly set up if intended. - // The Open struct would carry necessary info. - // TODO: implement flags for Open struct - Ok(Open{fh, flags: 0 }) + // The heartbeat function is called periodically by the FUSE session. + // We use it to ensure that the cache entries have accurate timestamps. + fn heartbeat(&mut self) -> Result { + if let Some(notifier) = self.notification_sender.clone() { + self.backing_cache + .retain(|_, v| PassthroughFs::update_backing_status(v, ¬ifier, false)); + } + Ok(fuser::FsStatus::Ready) } - fn release( + // This deliberately unimplemented read() function proves that the example demonstrates passthrough. + // If a user is able to read the file, it could only have been via the kernel. + fn read<'a>( &mut self, _req: RequestMeta, _ino: u64, - fh: u64, + _fh: u64, + _offset: i64, + _size: u32, _flags: i32, _lock_owner: Option, - _flush: bool, - ) -> Result<(), Errno> { - self.backing_cache.put(fh); - Ok(()) + ) -> Result, Errno> { + unimplemented!(); } #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] @@ -241,6 +387,19 @@ impl Filesystem for PassthroughFs { Ok(DirentList::Empty) } } + + fn release( + &mut self, + _req: RequestMeta, + _ino: u64, + _fh: u64, + _flags: i32, + _lock_owner: Option, + _flush: bool, + ) -> Result<(), Errno> { + // TODO: mark fh as unused + Ok(()) + } } fn main() { @@ -279,5 +438,89 @@ fn main() { } let fs = PassthroughFs::new(); - fuser::mount2(fs, mountpoint, &options).unwrap(); + let mut session = fuser::Session::new(fs, Path::new(mountpoint), &options).unwrap(); + if let Err(e) = session.run_with_notifications() { + // Since there is no graceful shutdown button, an error here is inevitable. + log::info!("Session ended with error: {e}"); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::path::PathBuf; + + fn dummy_meta() -> RequestMeta { + RequestMeta { unique: 0, uid: 1000, gid: 1000, pid: 2000 } + } + + #[test] + fn test_lookup_heartbeat_cycle() { + let mut fs = PassthroughFs::new(); + let (tx, rx) = crossbeam_channel::unbounded(); + fs.init_notification_sender(tx); + + // Should react to lookup with a pending entry and a notification + fs.lookup(dummy_meta(), 1, &PathBuf::from("passthrough")).unwrap(); + assert_eq!(fs.backing_cache.len(), 1); + assert!(matches!( + fs.backing_cache.get(&2).unwrap(), + BackingStatus::Pending(_) + )); + let notification = rx.try_recv().unwrap(); + let (fd, sender) = match notification { + Notification::OpenBacking(d) => d, + _ => panic!("unexpected notification"), + }; + assert!(fd > 0); + let sender = sender.unwrap(); + + // Heartbeat should not do anything yet + fs.heartbeat().unwrap(); + assert_eq!(fs.backing_cache.len(), 1); + assert!(matches!( + fs.backing_cache.get(&2).unwrap(), + BackingStatus::Pending(_) + )); + + // Simulate the kernel replying to the open backing request + sender.send(Ok(123)).unwrap(); + + // Heartbeat should now trigger the transition to ready + fs.heartbeat().unwrap(); + assert_eq!(fs.backing_cache.len(), 1); + assert!(matches!( + fs.backing_cache.get(&2).unwrap(), + BackingStatus::Ready(_) + )); + + // Open the file + let open = fs.open(dummy_meta(), 2, 0).unwrap(); + assert_eq!(open.flags, consts::FOPEN_PASSTHROUGH); + assert_eq!(open.backing_id, Some(123)); + + // Wait for timeout + std::thread::sleep(BACKING_TIMEOUT); + + // Heartbeat should now trigger the transition to closed + fs.heartbeat().unwrap(); + assert_eq!(fs.backing_cache.len(), 1); + assert!(matches!( + fs.backing_cache.get(&2).unwrap(), + BackingStatus::Closed(_) + )); + + // Simulate the kernel replying to the close backing request + let notification = rx.try_recv().unwrap(); + let (backing_id, sender) = match notification { + Notification::CloseBacking(d) => d, + _ => panic!("unexpected notification"), + }; + assert_eq!(backing_id, 123); + sender.unwrap().send(Ok(0)).unwrap(); + + // Heartbeat should now trigger dropping the entry + fs.heartbeat().unwrap(); + assert_eq!(fs.backing_cache.len(), 0); + } } diff --git a/examples/poll.rs b/examples/poll.rs index 0a664a82..bb80f7ff 100644 --- a/examples/poll.rs +++ b/examples/poll.rs @@ -150,7 +150,7 @@ impl Filesystem for FSelFS { }; Ok(Entry { ino: FSelData::idx_to_ino(idx), - generation: Some(0), + generation: None, file_ttl: Duration::ZERO, attr: self.data.filestat(idx), attr_ttl: Duration::ZERO, @@ -252,6 +252,7 @@ impl Filesystem for FSelFS { Ok(Open { fh: idx.into(), // Using idx as file handle flags: FOPEN_DIRECT_IO | FOPEN_NONSEEKABLE, + backing_id: None, }) } diff --git a/examples/simple.rs b/examples/simple.rs index 98236999..b1892854 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -1393,6 +1393,7 @@ impl Filesystem for SimpleFS { return Ok(Open { fh: self.allocate_next_file_handle(read, write), flags: open_flags, + backing_id: None, }); } return Err(Errno::EACCES); @@ -1523,6 +1524,7 @@ impl Filesystem for SimpleFS { return Ok(Open { fh: self.allocate_next_file_handle(read, write), flags: open_flags, + backing_id: None, }); } return Err(Errno::EACCES); @@ -1805,6 +1807,7 @@ impl Filesystem for SimpleFS { Open { fh: self.allocate_next_file_handle(read, write), flags: 0, + backing_id: None, }, )); } diff --git a/src/channel.rs b/src/channel.rs index 4d0d4269..65987d3d 100644 --- a/src/channel.rs +++ b/src/channel.rs @@ -9,10 +9,9 @@ use std::{ }; use libc::{c_int, c_void, size_t}; - -#[cfg(feature = "abi-7-40")] -use crate::passthrough::BackingId; use crate::reply::ReplySender; +#[cfg(feature = "abi-7-40")] +use crate::ll::ioctl::{ioctl_close_backing, ioctl_open_backing}; /// A raw communication channel to the FUSE kernel driver #[derive(Debug)] @@ -119,9 +118,19 @@ impl ReplySender for ChannelSender { Ok(()) } } +} + +impl ChannelSender { + /// Registers a file descriptor with the kernel. + /// If the kernel accepts, it returns a backing ID. + #[cfg(feature = "abi-7-40")] + pub fn open_backing(&self, fd: u32) -> std::io::Result { + ioctl_open_backing(&self.0, fd) + } + /// Deregisters a backing ID. #[cfg(feature = "abi-7-40")] - fn open_backing(&self, fd: BorrowedFd<'_>) -> std::io::Result { - BackingId::create(&self.0, fd) + pub fn close_backing(&self, backing_id: u32) -> std::io::Result { + ioctl_close_backing(&self.0, backing_id) } } diff --git a/src/lib.rs b/src/lib.rs index 7cda0c2d..1b695400 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,8 +38,6 @@ pub use notify::Store; pub use notify::Delete; #[cfg(feature = "abi-7-11")] pub use reply::Ioctl; -#[cfg(feature = "abi-7-40")] -pub use passthrough::BackingId; #[cfg(target_os = "macos")] pub use reply::XTimes; pub use reply::{Bytes, Entry, FileAttr, FileType, Dirent, DirentList, DirentPlusList, Open, Statfs, Xattr, Lock}; @@ -58,8 +56,6 @@ mod ll; mod mnt; #[cfg(feature = "abi-7-11")] mod notify; -#[cfg(feature = "abi-7-40")] -mod passthrough; mod reply; mod request; mod session; diff --git a/src/ll/ioctl.rs b/src/ll/ioctl.rs new file mode 100644 index 00000000..a3448f15 --- /dev/null +++ b/src/ll/ioctl.rs @@ -0,0 +1,61 @@ +//! This module contains functions for interacting with the FUSE device through ioctls. +// TODO: fix all these non camel case types +#![allow(non_camel_case_types)] + +use std::fs::File; +use std::os::fd::AsRawFd; +use std::sync::Arc; + +// The `fuse_backing_map_out` struct is used to pass information about a backing file +// descriptor to the kernel. +#[repr(C)] +pub struct fuse_backing_map_out { + pub fd: u32, + pub flags: u32, + pub padding: u64, +} + +const FUSE_DEV_IOC_MAGIC: u8 = 229; +const FUSE_DEV_IOC_BACKING_OPEN: u8 = 1; +const FUSE_DEV_IOC_BACKING_CLOSE: u8 = 2; + +// This ioctl is used to register a backing file descriptor with the kernel. +// The kernel will return a backing ID that can be used to refer to the file descriptor in +// subsequent operations. +nix::ioctl_write_ptr!( + fuse_dev_ioc_backing_open, + FUSE_DEV_IOC_MAGIC, + FUSE_DEV_IOC_BACKING_OPEN, + fuse_backing_map_out +); + +// This ioctl is used to deregister a backing file descriptor. +nix::ioctl_write_ptr!( + fuse_dev_ioc_backing_close, + FUSE_DEV_IOC_MAGIC, + FUSE_DEV_IOC_BACKING_CLOSE, + u32 +); + +pub(crate) fn ioctl_open_backing( + channel: &Arc, + fd: u32, + ) -> std::io::Result { + let map = fuse_backing_map_out { + fd, + flags: 0, + padding: 0, + }; + let id = unsafe { fuse_dev_ioc_backing_open(channel.as_raw_fd(), &map) } + ?; + Ok(id as u32) +} + +pub(crate) fn ioctl_close_backing( + channel: &Arc, + backing_id: u32, + ) -> std::io::Result { + let code = unsafe { fuse_dev_ioc_backing_close(channel.as_raw_fd(), &backing_id) } + ?; + Ok(code as u32) +} \ No newline at end of file diff --git a/src/ll/mod.rs b/src/ll/mod.rs index 932ac7ec..30a42ab4 100644 --- a/src/ll/mod.rs +++ b/src/ll/mod.rs @@ -4,6 +4,8 @@ mod argument; pub mod fuse_abi; #[cfg(feature = "abi-7-11")] pub(crate) mod notify; +#[cfg(feature = "abi-7-40")] +pub(crate) mod ioctl; pub(crate) mod reply; mod request; diff --git a/src/notify.rs b/src/notify.rs index 95c1da6e..033fd419 100644 --- a/src/notify.rs +++ b/src/notify.rs @@ -88,6 +88,12 @@ pub enum Notification { /// An inode deletion notification #[cfg(feature = "abi-7-18")] Delete((Delete, Option>>)), + /// A request to register a file descriptor and receive a backing ID + #[cfg(feature = "abi-7-40")] + OpenBacking((u32, Option>>)), + /// A request to close a backing ID + #[cfg(feature = "abi-7-40")] + CloseBacking((u32, Option>>)), /// (Internal) Disable notifications for this session Stop } @@ -102,6 +108,7 @@ impl From for Notification {fn from(notification: InvalInode) -> Sel impl From for Notification {fn from(notification: Store) -> Self{Notification::Store((notification, None))}} #[cfg(feature = "abi-7-18")] impl From for Notification {fn from(notification: Delete) -> Self{Notification::Delete((notification, None))}} +//TODO: strong typing on fd(u32) and backing_id(u32) to enable From<> trait /// A handle by which the application can send notifications to the server #[derive(Debug, Clone)] @@ -184,6 +191,30 @@ impl Notifier { } res }, + #[cfg(feature = "abi-7-40")] + Notification::OpenBacking((data, sender)) => { + let res = self.open_backing(data); + if let Some(sender) = sender { + if let Err(SendError(res)) = sender.send(res) { + log::warn!("OpenBacking notification reply {res:?} could not be delivered."); + return res.map(|_|{} ); + } + return Ok(()); + } + res.map(|_|{} ) + }, + #[cfg(feature = "abi-7-40")] + Notification::CloseBacking((data, sender)) => { + let res = self.close_backing(data); + if let Some(sender) = sender { + if let Err(SendError(res)) = sender.send(res) { + log::warn!("CloseBacking notification reply {res:?} could not be delivered."); + return res.map(|_|{} ); + } + return Ok(()); + } + res.map(|_|{} ) + }, // For completeness Notification::Stop => Ok(()) } @@ -238,6 +269,19 @@ impl Notifier { x => x, } } + + #[cfg(feature = "abi-7-40")] + /// Registers a file descriptor with the kernel for passthrough. + /// If the kernel accepts, it returns a backing ID. + pub fn open_backing(&self, fd: u32) -> io::Result { + self.0.open_backing(fd) + } + + #[cfg(feature = "abi-7-40")] + /// Deregisters a backing ID. + pub fn close_backing(&self, backing_id: u32) -> io::Result { + self.0.close_backing(backing_id) + } fn send(&self, code: notify_code, notification: &NotificationBuf<'_>) -> io::Result<()> { notification diff --git a/src/passthrough.rs b/src/passthrough.rs deleted file mode 100644 index 0c36c724..00000000 --- a/src/passthrough.rs +++ /dev/null @@ -1,74 +0,0 @@ -use std::fs::File; -use std::os::fd::{AsFd, AsRawFd}; -use std::sync::{Arc, Weak}; - -#[repr(C)] -struct fuse_backing_map { - fd: u32, - flags: u32, - padding: u64, -} - -const FUSE_DEV_IOC_MAGIC: u8 = 229; -const FUSE_DEV_IOC_BACKING_OPEN: u8 = 1; -const FUSE_DEV_IOC_BACKING_CLOSE: u8 = 2; - -nix::ioctl_write_ptr!( - fuse_dev_ioc_backing_open, - FUSE_DEV_IOC_MAGIC, - FUSE_DEV_IOC_BACKING_OPEN, - fuse_backing_map -); - -nix::ioctl_write_ptr!( - fuse_dev_ioc_backing_close, - FUSE_DEV_IOC_MAGIC, - FUSE_DEV_IOC_BACKING_CLOSE, - u32 -); - -/// A reference to a previously opened fd intended to be used for passthrough -/// -/// You can create these via `ReplyOpen::open_backing()` and send them via -/// `ReplyOpen::opened_passthrough()`. -/// -/// When working with backing IDs you need to ensure that they live "long enough". A good practice -/// is to create them in the Filesystem::open() impl, store them in the struct of your Filesystem -/// impl, then drop them in the Filesystem::release() impl. Dropping them immediately after -/// sending them in the Filesystem::open() impl can lead to the kernel returning EIO when userspace -/// attempts to access the file. -/// -/// This is implemented as a safe wrapper around the backing_id field of the fuse_backing_map -/// struct used by the ioctls involved in fd passthrough. It is created by performing a -/// FUSE_DEV_IOC_BACKING_OPEN ioctl on an fd and has a Drop trait impl which makes a matching -/// FUSE_DEV_IOC_BACKING_CLOSE call. It holds a weak reference on the fuse channel to allow it to -/// make that call (if the channel hasn't already been closed). -#[derive(Debug)] -pub struct BackingId { - channel: Weak, - /// The backing_id field passed to and from the kernel - pub(crate) backing_id: u32, -} - -impl BackingId { - pub(crate) fn create(channel: &Arc, fd: impl AsFd) -> std::io::Result { - let map = fuse_backing_map { - fd: fd.as_fd().as_raw_fd() as u32, - flags: 0, - padding: 0, - }; - let id = unsafe { fuse_dev_ioc_backing_open(channel.as_raw_fd(), &map) }?; - Ok(Self { - channel: Arc::downgrade(channel), - backing_id: id as u32, - }) - } -} - -impl Drop for BackingId { - fn drop(&mut self) { - if let Some(ch) = self.channel.upgrade() { - let _ = unsafe { fuse_dev_ioc_backing_close(ch.as_raw_fd(), &self.backing_id) }; - } - } -} diff --git a/src/reply.rs b/src/reply.rs index e07d97c6..727dc6c4 100644 --- a/src/reply.rs +++ b/src/reply.rs @@ -8,14 +8,10 @@ use crate::{Container, Errno, KernelConfig}; use crate::ll::{self, reply::DirentBuf}; #[cfg(feature = "abi-7-21")] use crate::ll::reply::{DirentPlusBuf}; -#[cfg(feature = "abi-7-40")] -use crate::{consts::FOPEN_PASSTHROUGH, passthrough::BackingId}; #[allow(unused_imports)] use log::{error, warn, info, debug}; use std::fmt; use std::io::IoSlice; -#[cfg(feature = "abi-7-40")] -use std::os::fd::BorrowedFd; use std::time::{Duration, SystemTime}; use zerocopy::IntoBytes; #[cfg(feature = "serializable")] @@ -24,11 +20,7 @@ use serde::{Deserialize, Serialize}; /// Generic reply callback to send data pub(crate) trait ReplySender: Send + Sync + Unpin + 'static { /// Send data. - fn send(&self, data: &[IoSlice<'_>]) -> std::io::Result<()>; - /// Open a backing file - #[cfg(feature = "abi-7-40")] - fn open_backing(&self, fd: BorrowedFd<'_>) -> std::io::Result; -} + fn send(&self, data: &[IoSlice<'_>]) -> std::io::Result<()>;} impl fmt::Debug for Box { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { @@ -364,9 +356,11 @@ impl ReplyHandler { /// Reply to a request with a newly opened file handle pub fn opened(self, open: Open) { - #[cfg(feature = "abi-7-40")] - assert_eq!(open.flags & FOPEN_PASSTHROUGH, 0); - self.send_ll(&ll::Response::new_open(ll::FileHandle(open.fh), open.flags, 0)); + self.send_ll(&ll::Response::new_open( + ll::FileHandle(open.fh), + open.flags, + open.backing_id.unwrap_or(0), + )); } /// Reply to a request with the number of bytes written @@ -383,15 +377,13 @@ impl ReplyHandler { /// Reply to a request with a newle created file entry and its newly open file handle pub fn created(self, entry: Entry, open: Open) { - #[cfg(feature = "abi-7-40")] - assert_eq!(open.flags & FOPEN_PASSTHROUGH, 0); self.send_ll(&ll::Response::new_create( &entry.file_ttl, &entry.attr.into(), ll::Generation(entry.generation.unwrap_or(1)), ll::FileHandle(open.fh), open.flags, - 0, + open.backing_id.unwrap_or(0), )); } @@ -602,11 +594,6 @@ mod test { assert_eq!(self.expected, v); Ok(()) } - - #[cfg(feature = "abi-7-40")] - fn open_backing(&self, _fd: BorrowedFd<'_>) -> std::io::Result { - unreachable!() - } } #[test] @@ -836,17 +823,23 @@ mod test { #[test] fn reply_open() { + #[cfg(feature = "abi-7-40")] + let backing_byte = 0x44; + #[cfg(not(feature = "abi-7-40"))] + let backing_byte = 0x00; let sender = AssertSender { expected: vec![ 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00, 0x22, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, + backing_byte, 0x00, 0x00, 0x00, ], }; let replyhandler: ReplyHandler = ReplyHandler::new(0xdeadbeef, sender); - replyhandler.opened( - Open { fh: 0x1122, flags: 0x33} - ); + replyhandler.opened(Open { + fh: 0x1122, + flags: 0x33, + backing_id: Some(u32::from(backing_byte)), + }); } #[test] @@ -891,6 +884,10 @@ mod test { #[test] fn reply_create() { + #[cfg(feature = "abi-7-40")] + let backing_byte = 0x44; + #[cfg(not(feature = "abi-7-40"))] + let backing_byte = 0x00; let mut expected = if cfg!(target_os = "macos") { vec![ 0xa8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, @@ -904,7 +901,8 @@ mod test { 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0xa4, 0x81, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x99, 0x00, 0x00, 0x00, 0xbb, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + backing_byte, 0x00, 0x00, 0x00, ] } else { vec![ @@ -918,7 +916,8 @@ mod test { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0xa4, 0x81, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0xbb, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + backing_byte, 0x00, 0x00, 0x00, ] }; @@ -962,7 +961,8 @@ mod test { }, Open { fh: 0xbb, - flags: 0x0f + flags: 0x0f, + backing_id: Some(u32::from(backing_byte)), } ); } @@ -1193,11 +1193,6 @@ mod test { self.send(()).unwrap(); Ok(()) } - - #[cfg(feature = "abi-7-40")] - fn open_backing(&self, _fd: BorrowedFd<'_>) -> std::io::Result { - unreachable!() - } } #[test] diff --git a/tests/test_passthrough.sh b/tests/test_passthrough.sh index 0224af33..111be92b 100755 --- a/tests/test_passthrough.sh +++ b/tests/test_passthrough.sh @@ -34,7 +34,7 @@ for x in $(seq 10); do sleep 1 done -expected="$(sha256sum - < /usr/lib/os-release)" +expected="$(sha256sum - < /etc/profile)" # Check that it's equal to the underlying file test "$(sha256sum - < "${mnt}/passthrough")" = "${expected}" From 9cc620e5c51ffe7067d635105c64625110c9f2e2 Mon Sep 17 00:00:00 2001 From: Richard Lawrence Date: Mon, 21 Jul 2025 10:43:52 -0500 Subject: [PATCH 13/19] Draft changelog entry announcing new features. --- CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e66b018a..b26fd6a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # FUSE for Rust - Changelog +## release candidate 0.16.0 - 2025-07-21 (pending) +* **Major API Refactor**: The `Filesystem` trait methods have been refactored to return `Result` instead of using `Reply` objects for callbacks. + * All `Filesystem` trait methods that previously accepted a `reply: ReplyT` parameter now return a `Result`, where `T` is a struct containing the success data for that operation. + * The `Request` object passed to `Filesystem` methods has been replaced with `RequestMeta`, a smaller struct containing only the request's metadata (uid, gid, pid, unique id). The full request parsing is now handled internally. + * A generic enum `Container` has been implemented to enable flexible ownership models in returned data. + * Additional public structs are introduced to simplify handling of request data, response data, and errors. + * This change unifies the implementation of `Filesystem` methods and brings them more in line with Idiomatic Rust. + * Examples and tests have been updated to match this new API. A few unrelated bugs in examples and tests have been fixed. + * Notifications and their results now pass through crossbeam_channels that are safely handled in a synchronous loop. + * A new `heartbeat()` operation enables a single-threaded execution model to monitor the passage of time. + * Idiomatic implementation of Passthrough and Notify have been completed. + * Unit tests have been added to most examples, enabled by the new callback-free API. + * Feature gates and clippy configuration are more consistently applied. + * Minor fixes for better MacOS support. + * Improved documentation throughout. + ## 0.15.1 - 2024-11-27 * Fix crtime related panic that could occur on MacOS. See PR #322 for details. From 5e4b0ff4e46a21d189035e2eec5433197433b4c6 Mon Sep 17 00:00:00 2001 From: Richard Lawrence Date: Mon, 21 Jul 2025 10:52:00 -0500 Subject: [PATCH 14/19] Revert bad clippy suggestion in hello::tests. Thinking about using Bytes in Notification structs. --- examples/hello.rs | 6 +++--- src/notify.rs | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/examples/hello.rs b/examples/hello.rs index a7e47b5b..7d220db5 100644 --- a/examples/hello.rs +++ b/examples/hello.rs @@ -275,21 +275,21 @@ mod test { assert_eq!(entries_slice.len(), 3, "Root directory should contain exactly 3 entries"); // Check entry 0: "." - let entry0_data = entries_slice[0]; + let entry0_data = &entries_slice[0]; assert_eq!(entry0_data.name.as_ref(), OsStr::new(".").as_bytes(), "First entry should be '.'"); assert_eq!(entry0_data.ino, 1, "Inode for '.' should be 1"); assert_eq!(entry0_data.offset, 1, "Offset for '.' should be 1"); assert_eq!(entry0_data.kind, FileType::Directory, "'.' should be a directory"); // Check entry 1: ".." - let entry1_data = entries_slice[1]; + let entry1_data = &entries_slice[1]; assert_eq!(entry1_data.name.as_ref(), OsStr::new("..").as_bytes(), "Second entry should be '..'"); assert_eq!(entry1_data.ino, 1, "Inode for '..' should be 1"); assert_eq!(entry1_data.offset, 2, "Offset for '..' should be 2"); assert_eq!(entry1_data.kind, FileType::Directory, "'..' should be a directory"); // Check entry 2: "hello.txt" - let entry2_data = entries_slice[2]; + let entry2_data = &entries_slice[2]; assert_eq!(entry2_data.name.as_ref(), OsStr::new("hello.txt").as_bytes(), "Third entry should be 'hello.txt'"); assert_eq!(entry2_data.ino, 2, "Inode for 'hello.txt' should be 2"); assert_eq!(entry2_data.offset, 3, "Offset for 'hello.txt' should be 3"); diff --git a/src/notify.rs b/src/notify.rs index 033fd419..a2bb0e8b 100644 --- a/src/notify.rs +++ b/src/notify.rs @@ -32,6 +32,7 @@ pub struct InvalEntry { pub parent: u64, /// Name: the file name of the invalid entry pub name: OsString + // TODO: maybe Bytes<'_> instead? } /// Invalid inode notification to be sent to the kernel @@ -56,6 +57,7 @@ pub struct Store { pub offset: u64, /// The new metadata pub data: Vec + // TODO: maybe Bytes<'_> instead? } /// Deleted file notification to be sent to the kernel @@ -68,6 +70,7 @@ pub struct Delete { pub ino: u64, /// Name: the file name of the deleted entry pub name: OsString + // TODO: maybe Bytes<'_> instead? } /// The list of supported notification types From f0d79de9e5c430b10dbbdb99025fd87604fe994a Mon Sep 17 00:00:00 2001 From: Richard Lawrence Date: Mon, 21 Jul 2025 11:39:33 -0500 Subject: [PATCH 15/19] Check for non-directory in simple example. --- examples/simple.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/examples/simple.rs b/examples/simple.rs index b1892854..0e453449 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -806,6 +806,10 @@ impl Filesystem for SimpleFS { } }; + if parent_attrs.kind != FileKind::Directory { + return Err(Errno::ENOTDIR); + } + if !check_access( parent_attrs.uid, parent_attrs.gid, @@ -884,6 +888,10 @@ impl Filesystem for SimpleFS { } }; + if parent_attrs.kind != FileKind::Directory { + return Err(Errno::ENOTDIR); + } + if !check_access( parent_attrs.uid, parent_attrs.gid, @@ -1073,6 +1081,10 @@ impl Filesystem for SimpleFS { } }; + if parent_attrs.kind != FileKind::Directory { + return Err(Errno::ENOTDIR); + } + if !check_access( parent_attrs.uid, parent_attrs.gid, @@ -1181,6 +1193,10 @@ impl Filesystem for SimpleFS { } }; + if new_parent_attrs.kind != FileKind::Directory { + return Err(Errno::ENOTDIR); + } + if !check_access( new_parent_attrs.uid, new_parent_attrs.gid, @@ -1748,6 +1764,10 @@ impl Filesystem for SimpleFS { } }; + if parent_attrs.kind != FileKind::Directory { + return Err(Errno::ENOTDIR); + } + if !check_access( parent_attrs.uid, parent_attrs.gid, From 5e25789b866b21eb281fb26903616312b536e178 Mon Sep 17 00:00:00 2001 From: Richard Lawrence Date: Mon, 21 Jul 2025 23:22:26 -0500 Subject: [PATCH 16/19] More tactful error handling on background session join. --- src/session.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/session.rs b/src/session.rs index 54328dec..a0e310b5 100644 --- a/src/session.rs +++ b/src/session.rs @@ -479,7 +479,10 @@ impl BackgroundSession { // Unmount the filesystem drop(_mount); // Stop the background thread - main_loop_guard.join().unwrap().unwrap(); + let res = main_loop_guard.join() + .expect("Failed to join the background thread"); + // An error is expected, since the thread was active when the unmount occured. + info!("Session loop end with result {res:?}."); } /// Returns an object that can be used to send notifications to the kernel From 0df9d3492887f100708f3dffbc6b6e6551b0faab Mon Sep 17 00:00:00 2001 From: Richard Lawrence Date: Wed, 30 Jul 2025 13:41:48 -0500 Subject: [PATCH 17/19] Backticks in Container::to_vec() docstring --- src/container/utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/container/utils.rs b/src/container/utils.rs index 2cd2424b..f08036f2 100644 --- a/src/container/utils.rs +++ b/src/container/utils.rs @@ -88,7 +88,7 @@ impl Container<'_, T> { self.len() == 0 } - /// Converts the container to an owned Vec. + /// Converts the container to an owned `Vec`. /// This will most likely be a copy. /// Returns an empty vector if the source data is unavailable. #[must_use] From f643488eda405aa77874c35e7c0019a56b9601a9 Mon Sep 17 00:00:00 2001 From: Richard Lawrence Date: Thu, 31 Jul 2025 20:10:00 -0500 Subject: [PATCH 18/19] Standardized Derive traits for easier unit testing. --- src/lib.rs | 4 ++-- src/ll/mod.rs | 6 +++--- src/ll/request.rs | 16 ++++++++-------- src/notify.rs | 12 ++++++------ src/reply.rs | 20 ++++++++++---------- src/request.rs | 2 +- src/session.rs | 2 +- 7 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 1b695400..a464d49d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -90,7 +90,7 @@ const fn default_init_flags(capabilities: u64) -> u64 { } } -#[derive(Debug)] +#[derive(Copy, Clone, Debug, PartialEq)] /// Target of a `forget` or `batch_forget` operation. pub struct Forget { /// Inode of the file to be forgotten. @@ -276,7 +276,7 @@ impl KernelConfig { } } -#[derive(Debug)] +#[derive(Copy, Clone, PartialEq, Debug)] /// This enum is an optional way for the Filesystem to report its status to a Session thread. pub enum FsStatus { /// Default may be used when the Filesystem does not implement a status diff --git a/src/ll/mod.rs b/src/ll/mod.rs index 30a42ab4..a9f086eb 100644 --- a/src/ll/mod.rs +++ b/src/ll/mod.rs @@ -14,7 +14,7 @@ use std::{convert::TryInto, num::NonZeroI32, time::SystemTime}; pub use reply::Response; pub use request::{AnyRequest, FileHandle, INodeNo, Lock, Operation, Request, RequestId, Version}; -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)] /// Possible input arguments for atime & mtime, which can either be set to a specified time, /// or to the current time pub enum TimeOrNow { @@ -39,7 +39,7 @@ macro_rules! errno { } /// Represents an error code to be returned to the caller -#[derive(Debug, PartialEq)] +#[derive(Copy, Clone, PartialEq, Debug)] pub struct Errno(pub NonZeroI32); impl Errno { /// Operation not permitted @@ -268,7 +268,7 @@ impl From for i32 { /// mount time). So if the file system reuses an inode after it has been /// deleted, it must assign a new, previously unused generation number to the /// inode at the same time. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)] pub struct Generation(pub u64); impl From for u64 { fn from(fh: Generation) -> Self { diff --git a/src/ll/request.rs b/src/ll/request.rs index d7360a8f..02215a95 100644 --- a/src/ll/request.rs +++ b/src/ll/request.rs @@ -36,7 +36,7 @@ pub enum RequestError { /// 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)] +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)] #[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))] pub struct RequestId(pub u64); impl From for u64 { @@ -68,7 +68,7 @@ impl From for u64 { /// We implement conversion from [`INodeNo`] to [u64] but not vice-versa because /// not all [u64]s are valid [`INodeNo`]s, but the reverse is true. So to produce /// a [`INodeNo`] from a [u64] we must be explicit. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)] #[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))] pub struct INodeNo(pub u64); impl From for u64 { @@ -112,7 +112,7 @@ impl From for u64 { /// We implement conversion from `FileHandle` to u64 but not vice-versa because /// not all u64s are valid `FileHandles`, but the reverse is true. So to produce /// a `FileHandle` from a u64 we must be explicit. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)] #[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))] pub struct FileHandle(pub u64); @@ -131,7 +131,7 @@ impl From for u64 { /// We implement conversion from [`LockOwner`] to [`u64`] but not vice-versa /// because all `LockOwners` are valid [`u64`]s, but not vice-versa. So to produce /// a [`LockOwner`] from a [`u64`] we must be explicit. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)] #[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))] pub struct LockOwner(pub u64); @@ -141,7 +141,7 @@ impl From for u64 { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)] pub struct Lock { // Unfortunately this can't be a std::ops::Range because Range is not Copy: // https://github.com/rust-lang/rfcs/issues/2848 @@ -161,7 +161,7 @@ impl Lock { } /// A newtype for ABI version -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)] #[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))] pub struct Version(pub u32, pub u32); impl Version { @@ -179,7 +179,7 @@ impl Display for Version { } /// Represents a filename in a directory -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)] pub struct FilenameInDir<'a> { /// The Inode number of the directory pub dir: INodeNo, @@ -1513,7 +1513,7 @@ mod op { /// Copy the specified range from the source inode to the destination inode #[cfg(feature = "abi-7-28")] - #[derive(Debug, Clone, Copy)] + #[derive(Copy, Clone, Debug)] pub struct CopyFileRangeFile { pub inode: INodeNo, /// The value set by the [`Open`] method. See [`FileHandle`]. diff --git a/src/notify.rs b/src/notify.rs index a2bb0e8b..48338917 100644 --- a/src/notify.rs +++ b/src/notify.rs @@ -16,7 +16,7 @@ use crate::{ /// Poll event data to be sent to the kernel #[cfg(feature = "abi-7-11")] -#[derive(Debug, Copy, Clone)] +#[derive(Copy, Clone, PartialEq, Debug)] pub struct Poll { /// Poll handle: the unique idenifier from a previous poll request pub ph: u64, @@ -26,7 +26,7 @@ pub struct Poll { /// Invalid entry notification to be sent to the kernel #[cfg(feature = "abi-7-12")] -#[derive(Debug, Clone)] +#[derive(Clone, PartialEq, Debug)] pub struct InvalEntry { /// Parent: the inode of the parent of the invalid entry pub parent: u64, @@ -37,7 +37,7 @@ pub struct InvalEntry { /// Invalid inode notification to be sent to the kernel #[cfg(feature = "abi-7-12")] -#[derive(Debug, Copy, Clone)] +#[derive(Copy, Clone, PartialEq, Debug)] pub struct InvalInode { /// Inode with invalid metadata pub ino: u64, @@ -49,7 +49,7 @@ pub struct InvalInode { /// Store inode notification to be sent to the kernel #[cfg(feature = "abi-7-15")] -#[derive(Debug, Clone)] +#[derive(Clone, PartialEq, Debug)] pub struct Store { /// ino: the inode to be updated pub ino: u64, @@ -62,7 +62,7 @@ pub struct Store { /// Deleted file notification to be sent to the kernel #[cfg(feature = "abi-7-18")] -#[derive(Debug, Clone)] +#[derive(Clone, PartialEq, Debug)] pub struct Delete { /// Parent: the inode of the parent directory that contained the deleted entry pub parent: u64, @@ -114,7 +114,7 @@ impl From for Notification {fn from(notification: Delete) -> Sel //TODO: strong typing on fd(u32) and backing_id(u32) to enable From<> trait /// A handle by which the application can send notifications to the server -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub(crate) struct Notifier(ChannelSender); impl Notifier { diff --git a/src/reply.rs b/src/reply.rs index 727dc6c4..6bf87c6a 100644 --- a/src/reply.rs +++ b/src/reply.rs @@ -86,7 +86,7 @@ impl Drop for ReplyHandler { } /// File types -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)] #[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))] pub enum FileType { /// Named pipe (`S_IFIFO`) @@ -106,7 +106,7 @@ pub enum FileType { } /// File attributes -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] #[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))] pub struct FileAttr { /// Unique number for this file @@ -141,7 +141,7 @@ pub struct FileAttr { pub flags: u32, } -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] #[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))] /// An entry in the kernel's file cache pub struct Entry { @@ -157,7 +157,7 @@ pub struct Entry { pub attr_ttl: Duration, } -#[derive(Debug, Clone, Copy)] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] #[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))] /// Open file handle response data pub struct Open { @@ -172,7 +172,7 @@ pub struct Open { /// A container for bytes, implementing flexible ownership. pub type Bytes<'a> = Container<'a, u8>; -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] #[cfg_attr(feature = "serializable", derive(Serialize, Deserialize))] /// A single directory entry. /// The `'name` lifetime parameter is associated with the `name` field if it is a borrowed value. @@ -194,7 +194,7 @@ pub type DirentList<'dir, 'name> = Container<'dir, Dirent<'name>>; pub type DirentPlusList<'dir, 'name> = Container<'dir, (Dirent<'name>, Entry)>; #[cfg(target_os = "macos")] -#[derive(Debug)] +#[derive(Copy, Clone, PartialEq, Debug)] /// Xtimes response data pub struct XTimes { /// Backup time @@ -203,7 +203,7 @@ pub struct XTimes { pub crtime: SystemTime } -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, PartialEq, Debug)] /// Statfs response data pub struct Statfs { /// Total blocks (in units of frsize) @@ -224,7 +224,7 @@ pub struct Statfs { pub frsize: u32 } -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, PartialEq, Debug)] /// File lock response data pub struct Lock { /// start of locked byte range @@ -242,7 +242,7 @@ pub struct Lock { /// `Xattr` represents the response for extended attribute operations (`getxattr`, `listxattr`). /// It can either indicate the size of the attribute data or provide the data itself /// using `Bytes` for flexible ownership. -#[derive(Debug)] +#[derive(Clone, Debug)] pub enum Xattr<'a> { /// Indicates the size of the extended attribute data. Used when the caller /// provides a zero-sized buffer to query the required buffer size. @@ -253,7 +253,7 @@ pub enum Xattr<'a> { } #[cfg(feature = "abi-7-11")] -#[derive(Debug)] +#[derive(Clone, Debug)] /// File io control reponse data pub struct Ioctl<'a> { /// Result of the ioctl operation diff --git a/src/request.rs b/src/request.rs index d2376047..29ca7a0f 100644 --- a/src/request.rs +++ b/src/request.rs @@ -34,7 +34,7 @@ pub struct Request<'a> { } /// Request metadata structure -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, PartialEq, Debug)] pub struct RequestMeta { /// The unique identifier of this request pub unique: u64, diff --git a/src/session.rs b/src/session.rs index a0e310b5..88ce62b5 100644 --- a/src/session.rs +++ b/src/session.rs @@ -39,7 +39,7 @@ const BUFFER_SIZE: usize = MAX_WRITE_SIZE + 4096; /// This value is used to prevent a busy loop in the synchronous run with notification const SYNC_SLEEP_INTERVAL: std::time::Duration = std::time::Duration::from_millis(5); -#[derive(Default, Debug, Eq, PartialEq)] +#[derive(Default, Eq, PartialEq, Debug)] /// How requests should be filtered based on the calling UID. pub enum SessionACL { /// Allow requests from any user. Corresponds to the `allow_other` mount option. From 55bc56cf7e1820646e771307f6299923baa97427 Mon Sep 17 00:00:00 2001 From: Richard Lawrence Date: Sat, 2 Aug 2025 06:47:02 -0500 Subject: [PATCH 19/19] Feature gates: `"threaded"` and `"no-rc"` (both on by default). BackgroundSession functions are gated by `"threaded"` to support single-threaded compilation for non-Send Filesystems. Container `Rc` variants are gated by `"no-rc"` so that Container is Send when it needs to be. --- Cargo.toml | 4 +- src/container/core.rs | 22 ++++++++++- src/container/tests.rs | 84 +++++++++++++++++++++++++----------------- src/container/utils.rs | 12 ++++++ src/lib.rs | 11 ++++-- src/session.rs | 10 ++++- 6 files changed, 102 insertions(+), 41 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5885e7f6..34054217 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,9 @@ nix = { version = "0.29.0", features = ["poll", "fs", "ioctl"] } pkg-config = { version = "0.3.14", optional = true } [features] -default = [] +default = ["threaded"] +threaded = ["no-rc"] +no-rc = [] libfuse = ["pkg-config"] serializable = ["serde"] macfuse-4-compat = [] diff --git a/src/container/core.rs b/src/container/core.rs index 1c338019..342a6f29 100644 --- a/src/container/core.rs +++ b/src/container/core.rs @@ -1,10 +1,11 @@ use std::sync::{Arc,Mutex,RwLock}; +#[cfg(not(feature = "no-rc"))] use std::rc::Rc; use std::borrow::Cow; -use std::cell::RefCell; +#[cfg(not(feature = "no-rc"))] +use std::cell::{Ref, RefCell}; use std::ops::Deref; use std::sync::{MutexGuard, RwLockReadGuard, PoisonError}; -use std::cell::{Ref}; #[derive(Debug)] /// A generic container enum that provides flexible ownership models for unsized data types. @@ -21,6 +22,7 @@ pub enum Container<'a, T: Clone> { Ref(&'a [T]), /// A borrowed slice with copy-on-write. Cow(Cow<'a, [T]>), + #[cfg(not(feature = "no-rc"))] /// A reusable, fixed-size slice. Rc(Rc<[T]>), /// A shared, fixed-size slice. @@ -35,8 +37,10 @@ pub enum Container<'a, T: Clone> { CowBox(Cow<'a, Box<[T]>>), /// A borrowed, immutable, heap-allocated vactor, with copy-on-write. CowVec(Cow<'a, Vec>), + #[cfg(not(feature = "no-rc"))] /// A reusable, fixed-size, heap-allocated slice. RcBox(Rc>), + #[cfg(not(feature = "no-rc"))] /// A reusable, immutable, heap-allocated vector. RcVec(Rc>), /// A shared, fixed-size, heap-allocated slice. @@ -44,8 +48,10 @@ pub enum Container<'a, T: Clone> { /// A shared, immutable, heap-allocated vector. ArcVec(Arc>), // ----- Locking Variants ----- + #[cfg(not(feature = "no-rc"))] /// A reusable, replaceable, heap-allocated slice. RcRefCellBox(Rc>>), + #[cfg(not(feature = "no-rc"))] /// A reusable, growable, heap-allocated vector. RcRefCellVec(Rc>>), /// A shared, fixed-size, replacable, heap-allocated slice. @@ -69,8 +75,10 @@ pub enum Borrow<'a, T> { /// A borrowed reference to a slice. Slice(&'a [T]), // ----- Locking Variants ----- + #[cfg(not(feature = "no-rc"))] /// A borrowed reference to a reusable, replaceable, heap-allocated slice. RcRefCellBox(Ref<'a, Box<[T]>>), + #[cfg(not(feature = "no-rc"))] /// A borrowed reference to a reusable, growable, heap-allocated vector. RcRefCellVec(Ref<'a, Vec>), /// A borrowed reference to a shared, fixed-size, replacable, heap-allocated slice. @@ -90,7 +98,9 @@ impl Deref for Borrow<'_, T> { match self { Borrow::Empty => &[], Borrow::Slice(value) => value, + #[cfg(not(feature = "no-rc"))] Borrow::RcRefCellBox(value) => value, + #[cfg(not(feature = "no-rc"))] Borrow::RcRefCellVec(value) => value, Borrow::ArcMutexBox(value) => value, Borrow::ArcMutexVec(value) => value, @@ -127,6 +137,7 @@ impl Container<'_, T> { Container::Vec(value) => Ok(Borrow::Slice(value.as_ref())), Container::Ref(value) => Ok(Borrow::Slice(value)), Container::Cow(value) => Ok(Borrow::Slice(value.as_ref())), + #[cfg(not(feature = "no-rc"))] Container::Rc(value) => Ok(Borrow::Slice(value.as_ref())), Container::Arc(value) => Ok(Borrow::Slice(value.as_ref())), // ----- Compound Variants ----- @@ -134,12 +145,16 @@ impl Container<'_, T> { Container::RefVec(value) => Ok(Borrow::Slice(value.as_ref())), Container::CowBox(value) => Ok(Borrow::Slice(value.as_ref().as_ref())), Container::CowVec(value) => Ok(Borrow::Slice(value.as_ref().as_ref())), + #[cfg(not(feature = "no-rc"))] Container::RcBox(value) => Ok(Borrow::Slice(value.as_ref().as_ref())), + #[cfg(not(feature = "no-rc"))] Container::RcVec(value) => Ok(Borrow::Slice(value.as_ref().as_ref())), Container::ArcBox(value) => Ok(Borrow::Slice(value.as_ref().as_ref())), Container::ArcVec(value) => Ok(Borrow::Slice(value.as_ref().as_ref())), // ----- Locking Variants ----- + #[cfg(not(feature = "no-rc"))] Container::RcRefCellBox(value) => Ok(Borrow::RcRefCellBox(value.borrow())), + #[cfg(not(feature = "no-rc"))] Container::RcRefCellVec(value) => Ok(Borrow::RcRefCellVec(value.borrow())), Container::ArcMutexBox(value) => Ok(Borrow::ArcMutexBox(value.lock()?)), Container::ArcMutexVec(value) => Ok(Borrow::ArcMutexVec(value.lock()?)), @@ -169,6 +184,7 @@ impl Container<'_, T> { Container::Vec(value) => Ok(value.as_ref()), Container::Ref(value) => Ok(value), Container::Cow(value) => Ok(value.as_ref()), + #[cfg(not(feature = "no-rc"))] Container::Rc(value) => Ok(value.as_ref()), Container::Arc(value) => Ok(value.as_ref()), // ----- Compound Variants ----- @@ -176,7 +192,9 @@ impl Container<'_, T> { Container::RefVec(value) => Ok(value.as_ref()), Container::CowBox(value) => Ok(value.as_ref().as_ref()), Container::CowVec(value) => Ok(value.as_ref().as_ref()), + #[cfg(not(feature = "no-rc"))] Container::RcBox(value) => Ok(value.as_ref().as_ref()), + #[cfg(not(feature = "no-rc"))] Container::RcVec(value) => Ok(value.as_ref().as_ref()), Container::ArcBox(value) => Ok(value.as_ref().as_ref()), Container::ArcVec(value) => Ok(value.as_ref().as_ref()), diff --git a/src/container/tests.rs b/src/container/tests.rs index 1d093b20..fe002f6e 100644 --- a/src/container/tests.rs +++ b/src/container/tests.rs @@ -4,6 +4,8 @@ use crate::container::Container; use std::sync::Arc; +#[cfg(not(feature = "no-rc"))] +use std::rc::Rc; #[cfg(test)] mod u8 { @@ -80,7 +82,6 @@ mod u8 { fn owned_variants() { let data_box_orig: Box<[u8]> = Box::new([1, 2, 3]); let data_vec_orig: Vec = vec![4, 5, 6]; - let data_rc_orig: std::rc::Rc<[u8]> = std::rc::Rc::new([7, 8, 9]); let data_arc_orig: Arc<[u8]> = Arc::new([10, 11, 12]); // Container::Box @@ -99,21 +100,28 @@ mod u8 { let container_cow_owned_box_as_vec = Container::Cow(std::borrow::Cow::Owned(data_box_orig.to_vec())); assert_eq!(&*container_cow_owned_box_as_vec.borrow(), data_box_orig.as_ref()); - // Container::Rc - let container_rc = Container::from(data_rc_orig.clone()); - assert_eq!(&*container_rc.borrow(), data_rc_orig.as_ref()); + #[cfg(not(feature = "no-rc"))] + { + // Container::Rc + let data_rc_orig: Rc<[u8]> = Rc::new([7, 8, 9]); + let container_rc = Container::from(data_rc_orig.clone()); + assert_eq!(&*container_rc.borrow(), data_rc_orig.as_ref()); + } // Container::Arc let container_arc = Container::from(data_arc_orig.clone()); assert_eq!(&*container_arc.borrow(), data_arc_orig.as_ref()); - // Container::RcBox - let container_rc_box = Container::RcBox(std::rc::Rc::new(data_box_orig.clone())); - assert_eq!(&*container_rc_box.borrow(), data_box_orig.as_ref()); + #[cfg(not(feature = "no-rc"))] + { + // Container::RcBox + let container_rc_box = Container::RcBox(Rc::new(data_box_orig.clone())); + assert_eq!(&*container_rc_box.borrow(), data_box_orig.as_ref()); - // Container::RcVec - let container_rc_vec = Container::RcVec(std::rc::Rc::new(data_vec_orig.clone())); - assert_eq!(&*container_rc_vec.borrow(), data_vec_orig.as_slice()); + // Container::RcVec + let container_rc_vec = Container::RcVec(Rc::new(data_vec_orig.clone())); + assert_eq!(&*container_rc_vec.borrow(), data_vec_orig.as_slice()); + } // Container::ArcBox let container_arc_box = Container::ArcBox(Arc::new(data_box_orig.clone())); @@ -128,21 +136,24 @@ mod u8 { fn locking_variants_read() { let data_box_orig: Box<[u8]> = Box::new([1, 2, 3]); let data_vec_orig: Vec = vec![4, 5, 6]; + + #[cfg(not(feature = "no-rc"))] + { + // Container::RcRefCellBox + let rc_ref_cell_box = Rc::new(std::cell::RefCell::new(data_box_orig.clone())); + let container_rc_ref_cell_box: Container<'static, u8> = Container::from(rc_ref_cell_box.clone()); + match container_rc_ref_cell_box.try_borrow() { + Ok(guard) => assert_eq!(&*guard, data_box_orig.as_ref()), + Err(_) => panic!("Expected Ok for RcRefCellBox get_slice"), + } - // Container::RcRefCellBox - let rc_ref_cell_box = std::rc::Rc::new(std::cell::RefCell::new(data_box_orig.clone())); - let container_rc_ref_cell_box: Container<'static, u8> = Container::from(rc_ref_cell_box.clone()); - match container_rc_ref_cell_box.try_borrow() { - Ok(guard) => assert_eq!(&*guard, data_box_orig.as_ref()), - Err(_) => panic!("Expected Ok for RcRefCellBox get_slice"), - } - - // Container::RcRefCellVec - let rc_ref_cell_vec = std::rc::Rc::new(std::cell::RefCell::new(data_vec_orig.clone())); - let container_rc_ref_cell_vec: Container<'static, u8> = Container::from(rc_ref_cell_vec.clone()); - match container_rc_ref_cell_vec.try_borrow() { - Ok(guard) => assert_eq!(&*guard, data_vec_orig.as_slice()), - Err(_) => panic!("Expected Ok for RcRefCellVec get_slice"), + // Container::RcRefCellVec + let rc_ref_cell_vec = Rc::new(std::cell::RefCell::new(data_vec_orig.clone())); + let container_rc_ref_cell_vec: Container<'static, u8> = Container::from(rc_ref_cell_vec.clone()); + match container_rc_ref_cell_vec.try_borrow() { + Ok(guard) => assert_eq!(&*guard, data_vec_orig.as_slice()), + Err(_) => panic!("Expected Ok for RcRefCellVec get_slice"), + }; // this weird semicolon is needed because of lifetimes } // Container::ArcMutexBox @@ -183,15 +194,18 @@ mod u8 { let data_box_orig: Box<[u8]> = Box::new([1, 2, 3]); let data_vec_orig: Vec = vec![4, 5, 6]; - // Container::RcRefCellBox - let rc_ref_cell_box = std::rc::Rc::new(std::cell::RefCell::new(data_box_orig.clone())); - let container_rc_ref_cell_box: Container<'static, u8> = Container::from(rc_ref_cell_box); - assert!(container_rc_ref_cell_box.try_as_ref().is_err()); - - // Container::RcRefCellVec - let rc_ref_cell_vec = std::rc::Rc::new(std::cell::RefCell::new(data_vec_orig.clone())); - let container_rc_ref_cell_vec: Container<'static, u8> = Container::from(rc_ref_cell_vec); - assert!(container_rc_ref_cell_vec.try_as_ref().is_err()); + #[cfg(not(feature = "no-rc"))] + { + // Container::RcRefCellBox + let rc_ref_cell_box = Rc::new(std::cell::RefCell::new(data_box_orig.clone())); + let container_rc_ref_cell_box: Container<'static, u8> = Container::from(rc_ref_cell_box); + assert!(container_rc_ref_cell_box.try_as_ref().is_err()); + + // Container::RcRefCellVec + let rc_ref_cell_vec = Rc::new(std::cell::RefCell::new(data_vec_orig.clone())); + let container_rc_ref_cell_vec: Container<'static, u8> = Container::from(rc_ref_cell_vec); + assert!(container_rc_ref_cell_vec.try_as_ref().is_err()); + } // Container::ArcMutexBox let arc_mutex_box = Arc::new(std::sync::Mutex::new(data_box_orig.clone())); @@ -229,7 +243,9 @@ mod string { use std::os::unix::ffi::OsStrExt; use std::ffi::{OsStr, OsString}; use std::sync::{Arc, Mutex, RwLock}; + #[cfg(not(feature = "no-rc"))] use std::cell::RefCell; + #[cfg(not(feature = "no-rc"))] use std::rc::Rc; use std::borrow::Cow; @@ -285,6 +301,7 @@ mod string { Container::Cow(Cow::Borrowed(bytes)), Container::Cow(Cow::Owned(bytes.to_vec())), Container::Arc(Arc::from(bytes)), + #[cfg(not(feature = "no-rc"))] Container::Rc(Rc::from(bytes)), ] } @@ -293,6 +310,7 @@ mod string { vec![ Container::ArcMutexVec(Arc::new(Mutex::new(bytes.to_vec()))), Container::ArcRwLockVec(Arc::new(RwLock::new(bytes.to_vec()))), + #[cfg(not(feature = "no-rc"))] Container::RcRefCellVec(Rc::new(RefCell::new(bytes.to_vec()))), ] } diff --git a/src/container/utils.rs b/src/container/utils.rs index f08036f2..4e69b905 100644 --- a/src/container/utils.rs +++ b/src/container/utils.rs @@ -1,7 +1,9 @@ use super::core::Container; use std::sync::{Arc,Mutex,RwLock}; +#[cfg(not(feature = "no-rc"))] use std::rc::Rc; use std::borrow::Cow; +#[cfg(not(feature = "no-rc"))] use std::cell::RefCell; // --- From Raw --- @@ -12,6 +14,7 @@ impl From> for Container<'_, T> {fn from(value: Box<[T]>) -> impl From> for Container<'_, T> {fn from(value: Vec) -> Self {Container::Vec(value)}} impl<'a, T: Clone> From<&'a [T]> for Container<'a, T> {fn from(value: &'a [T]) -> Self {Container::Ref(value)}} impl<'a, T: Clone> From> for Container<'a, T> {fn from(value: Cow<'a, [T]>) -> Self {Container::Cow(value)}} +#[cfg(not(feature = "no-rc"))] impl From> for Container<'_, T> {fn from(value: Rc<[T]>) -> Self {Container::Rc(value)}} impl From> for Container<'_, T> {fn from(value: Arc<[T]>) -> Self {Container::Arc(value)}} // ----- Compound Variants ----- @@ -19,12 +22,16 @@ impl<'a, T: Clone> From<&'a Box<[T]>> for Container<'a, T> {fn from(value: &'a B impl<'a, T: Clone> From<&'a Vec> for Container<'a, T> {fn from(value: &'a Vec) -> Self {Container::RefVec(value)}} impl<'a, T: Clone> From>> for Container<'a, T> {fn from(value: Cow<'a, Box<[T]>>) -> Self {Container::CowBox(value)}} impl<'a, T: Clone> From>> for Container<'a, T> {fn from(value: Cow<'a, Vec>) -> Self {Container::CowVec(value)}} +#[cfg(not(feature = "no-rc"))] impl From>> for Container<'_, T> {fn from(value: Rc>) -> Self {Container::RcBox(value)}} +#[cfg(not(feature = "no-rc"))] impl From>> for Container<'_, T> {fn from(value: Rc>) -> Self {Container::RcVec(value)}} impl From>> for Container<'_, T> {fn from(value: Arc>) -> Self {Container::ArcBox(value)}} impl From>> for Container<'_, T> {fn from(value: Arc>) -> Self {Container::ArcVec(value)}} // ----- Locking Variants ----- +#[cfg(not(feature = "no-rc"))] impl From>>> for Container<'_, T> {fn from(value: Rc>>) -> Self {Container::RcRefCellBox(value)}} +#[cfg(not(feature = "no-rc"))] impl From>>> for Container<'_, T> {fn from(value: Rc>>) -> Self {Container::RcRefCellVec(value)}} impl From>>> for Container<'_, T> {fn from(value: Arc>>) -> Self {Container::ArcMutexBox(value)}} impl From>>> for Container<'_, T> {fn from(value: Arc>>) -> Self {Container::ArcMutexVec(value)}} @@ -46,6 +53,7 @@ impl Clone for Container<'_, T> { Container::Vec(value) => Container::Vec(value.clone()), Container::Ref(value) => Container::Ref(value), Container::Cow(value) => Container::Cow(value.clone()), + #[cfg(not(feature = "no-rc"))] Container::Rc(value) => Container::Rc(value.clone()), Container::Arc(value) => Container::Arc(value.clone()), // ----- Compound Variants ----- @@ -53,12 +61,16 @@ impl Clone for Container<'_, T> { Container::RefVec(value) => Container::RefVec(value), Container::CowBox(value) => Container::CowBox(value.clone()), Container::CowVec(value) => Container::CowVec(value.clone()), + #[cfg(not(feature = "no-rc"))] Container::RcBox(value) => Container::RcBox(value.clone()), + #[cfg(not(feature = "no-rc"))] Container::RcVec(value) => Container::RcVec(value.clone()), Container::ArcBox(value) => Container::ArcBox(value.clone()), Container::ArcVec(value) => Container::ArcVec(value.clone()), // ----- Locking Variants ----- + #[cfg(not(feature = "no-rc"))] Container::RcRefCellBox(value) => Container::RcRefCellBox(value.clone()), + #[cfg(not(feature = "no-rc"))] Container::RcRefCellVec(value) => Container::RcRefCellVec(value.clone()), Container::ArcMutexBox(value) => Container::ArcMutexBox(value.clone()), Container::ArcMutexVec(value) => Container::ArcMutexVec(value.clone()), diff --git a/src/lib.rs b/src/lib.rs index a464d49d..2cad1768 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,7 +19,9 @@ use std::ffi::OsStr; use std::io; use std::path::Path; use std::time::{Duration, SystemTime}; -use std::{convert::AsRef, io::ErrorKind}; +use std::convert::AsRef; +#[cfg(feature = "threaded")] +use std::io::ErrorKind; #[allow(clippy::wildcard_imports)] // avoid duplicating feature gates use crate::ll::fuse_abi::consts::*; pub use crate::ll::fuse_abi::FUSE_ROOT_ID; @@ -42,9 +44,10 @@ pub use reply::Ioctl; pub use reply::XTimes; pub use reply::{Bytes, Entry, FileAttr, FileType, Dirent, DirentList, DirentPlusList, Open, Statfs, Xattr, Lock}; pub use request::RequestMeta; -pub use session::{BackgroundSession, Session, SessionACL, SessionUnmounter}; +pub use session::{Session, SessionACL, SessionUnmounter}; +#[cfg(feature = "threaded")] +pub use session::BackgroundSession; pub use container::{Container, Borrow}; - #[cfg(feature = "abi-7-28")] use std::cmp::max; #[cfg(feature = "abi-7-13")] @@ -1052,6 +1055,7 @@ pub fn mount2>( /// typically starting with `"-o"`. For example: `&[OsStr::new("-o"), OsStr::new("auto_unmount")]`. /// # Errors /// Error if the session is not started. +#[cfg(feature = "threaded")] #[deprecated(note = "Use `spawn_mount2` instead, which takes a slice of `MountOption` enums for better type safety and clarity.")] pub fn spawn_mount<'a, FS: Filesystem + Send + 'static + 'a, P: AsRef>( filesystem: FS, @@ -1079,6 +1083,7 @@ pub fn spawn_mount<'a, FS: Filesystem + Send + 'static + 'a, P: AsRef>( /// This is the recommended way to mount a FUSE filesystem in the background. /// # Errors /// Error if the session is not started. +#[cfg(feature = "threaded")] pub fn spawn_mount2<'a, FS: Filesystem + Send + 'static + 'a, P: AsRef>( filesystem: FS, mountpoint: P, diff --git a/src/session.rs b/src/session.rs index 88ce62b5..f10f290e 100644 --- a/src/session.rs +++ b/src/session.rs @@ -9,12 +9,14 @@ use libc::{EAGAIN, EINTR, ENODEV, ENOENT}; #[allow(unused_imports)] use log::{debug, info, warn, error}; use nix::unistd::geteuid; -use std::fmt; use std::os::fd::{AsFd, BorrowedFd, OwnedFd}; use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex}; -use std::thread::{self, JoinHandle}; use std::io; +#[cfg(feature = "threaded")] +use std::thread::{self, JoinHandle}; +#[cfg(feature = "threaded")] +use std::fmt; use crate::ll::fuse_abi as abi; use crate::request::Request; @@ -410,6 +412,7 @@ fn aligned_sub_buf(buf: &mut [u8], alignment: usize) -> &mut [u8] { /// A session can be run synchronously in the current thread using `run()` or spawned into a /// background thread using `spawn()`. +#[cfg(feature = "threaded")] impl Session { /// Run the session loop in a background thread pub fn spawn(self) -> io::Result { @@ -431,6 +434,7 @@ impl Drop for Session { } /// The background session data structure +#[cfg(feature = "threaded")] pub struct BackgroundSession { /// Thread guard of the main session loop pub main_loop_guard: JoinHandle>, @@ -441,6 +445,7 @@ pub struct BackgroundSession { _mount: Option, } +#[cfg(feature = "threaded")] impl BackgroundSession { /// Create a new background session for the given session by running its /// session loop in a background thread. If the returned handle is dropped, @@ -495,6 +500,7 @@ impl BackgroundSession { // replace with #[derive(Debug)] if Debug ever gets implemented for // thread_scoped::JoinGuard +#[cfg(feature = "threaded")] impl fmt::Debug for BackgroundSession { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { let mut builder = f.debug_struct("BackgroundSession");