diff --git a/CHANGELOG.md b/CHANGELOG.md index e66b018a..3b35e652 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # FUSE for Rust - Changelog +## 0.16.0 - 2025-06-24 +* **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. + * 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. + * Idiomatic implementation of Passthrough and Notify are TODO items. + ## 0.15.1 - 2024-11-27 * Fix crtime related panic that could occur on MacOS. See PR #322 for details. diff --git a/examples/hello.rs b/examples/hello.rs index c4150495..d4d75465 100644 --- a/examples/hello.rs +++ b/examples/hello.rs @@ -1,10 +1,9 @@ use clap::{crate_version, Arg, ArgAction, Command}; use fuser::{ - FileAttr, FileType, Filesystem, MountOption, ReplyAttr, ReplyData, ReplyDirectory, ReplyEntry, - Request, + Filesystem, MountOption, Attr, DirEntry, + Entry, Errno, RequestMeta, FileType, FileAttr }; -use libc::ENOENT; -use std::ffi::OsStr; +use std::ffi::{OsStr, OsString}; use std::time::{Duration, UNIX_EPOCH}; const TTL: Duration = Duration::from_secs(1); // 1 second @@ -50,66 +49,68 @@ const HELLO_TXT_ATTR: FileAttr = FileAttr { struct HelloFS; 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); + fn lookup(&mut self, _req: RequestMeta, parent: u64, name: OsString) -> Result { + if parent == 1 && name == OsStr::new("hello.txt") { + Ok(Entry{attr: HELLO_TXT_ATTR, ttl: TTL, generation: 0}) } 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 { match ino { - 1 => reply.attr(&TTL, &HELLO_DIR_ATTR), - 2 => reply.attr(&TTL, &HELLO_TXT_ATTR), - _ => reply.error(ENOENT), + 1 => Ok(Attr{attr: HELLO_DIR_ATTR, ttl: TTL,}), + 2 => Ok(Attr{attr: HELLO_TXT_ATTR, ttl: TTL,}), + _ => Err(Errno::ENOENT), } } fn read( &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..]); + Ok(HELLO_TXT_CONTENT.as_bytes()[offset as usize..].to_vec()) } else { - reply.error(ENOENT); + Err(Errno::ENOENT) } } fn readdir( &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"), + DirEntry { ino: 1, offset: 1, kind: FileType::Directory, name: OsString::from(".") }, + DirEntry { ino: 1, offset: 2, kind: FileType::Directory, name: OsString::from("..") }, + DirEntry { ino: 2, offset: 3, kind: FileType::RegularFile, name: OsString::from("hello.txt") }, ]; - 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; - } + let mut result = Vec::new(); + for entry in entries.into_iter().skip(offset as usize) { + // example loop where additional logic could be inserted + result.push(entry); } - reply.ok(); + Ok(result) } } @@ -147,3 +148,65 @@ fn main() { } fuser::mount2(HelloFS, mountpoint, &options).unwrap(); } + +#[cfg(test)] +mod test { + use fuser::{Filesystem, RequestMeta, Errno, FileType}; + use std::ffi::OsString; + + fn dummy_meta() -> RequestMeta { + RequestMeta { unique: 0, uid: 1000, gid: 1000, pid: 2000 } + } + + #[test] + fn test_lookup_hello_txt() { + let mut hellofs = super::HelloFS {}; + let req = dummy_meta(); + let result = hellofs.lookup(req, 1, OsString::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 {}; + 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), "Hello World!\n", "Content of hello.txt should be 'Hello World!\\n'"); + } + } + + #[test] + fn test_readdir_root() { + let mut hellofs = super::HelloFS {}; + 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) = result { + assert_eq!(entries.len(), 3, "Root directory should contain exactly 3 entries"); + assert_eq!(entries[0].name, OsString::from("."), "First entry should be '.'"); + assert_eq!(entries[0].ino, 1, "Inode for '.' should be 1"); + assert_eq!(entries[1].name, OsString::from(".."), "Second entry should be '..'"); + assert_eq!(entries[1].ino, 1, "Inode for '..' should be 1"); + assert_eq!(entries[2].name, OsString::from("hello.txt"), "Third entry should be 'hello.txt'"); + assert_eq!(entries[2].ino, 2, "Inode for 'hello.txt' should be 2"); + } + } + + #[test] + fn test_create_fails_readonly() { + let mut hellofs = super::HelloFS {}; + let req = dummy_meta(); + let result = hellofs.create(req, 1, OsString::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..3d1ef155 100644 --- a/examples/ioctl.rs +++ b/examples/ioctl.rs @@ -4,12 +4,10 @@ use clap::{crate_version, Arg, ArgAction, Command}; use fuser::{ - FileAttr, FileType, Filesystem, MountOption, ReplyAttr, ReplyData, ReplyDirectory, ReplyEntry, - Request, + FileAttr, FileType, Filesystem, MountOption, RequestMeta, Entry, Attr, Ioctl, Errno, DirEntry, }; -use libc::{EINVAL, ENOENT}; use log::debug; -use std::ffi::OsStr; +use std::ffi::{OsStr, OsString}; use std::time::{Duration, UNIX_EPOCH}; const TTL: Duration = Duration::from_secs(1); // 1 second @@ -70,82 +68,82 @@ 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); + fn lookup(&mut self, _req: RequestMeta, parent: u64, name: OsString) -> Result { + if parent == 1 && name == OsStr::new("fioc") { + Ok(Entry { + attr: self.fioc_file_attr, + ttl: TTL, + generation: 0, + }) } 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 { match ino { - 1 => reply.attr(&TTL, &self.root_attr), - 2 => reply.attr(&TTL, &self.fioc_file_attr), - _ => reply.error(ENOENT), + 1 => Ok(Attr { attr: self.root_attr, ttl: TTL,}), + 2 => Ok(Attr { attr: self.fioc_file_attr, ttl: TTL,}), + _ => Err(Errno::ENOENT), } } fn read( &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..]) + Ok(self.content[offset as usize..].to_vec()) } else { - reply.error(ENOENT); + Err(Errno::ENOENT) } } fn readdir( &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, "fioc"), + DirEntry { ino: 1, offset: 1, kind: FileType::Directory, name: OsString::from(".") }, + DirEntry { ino: 1, offset: 2, kind: FileType::Directory, name: OsString::from("..") }, + DirEntry { ino: 2, offset: 3, kind: FileType::RegularFile, name: OsString::from("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; - } + let mut result = Vec::new(); + for entry in entries.into_iter().skip(offset as usize) { + // example loop where additional logic could be inserted + result.push(entry); } - reply.ok(); + Ok(result) } + #[cfg(feature = "abi-7-11")] fn ioctl( &mut self, - _req: &Request<'_>, + _req: RequestMeta, ino: u64, _fh: u64, _flags: u32, cmd: u32, - in_data: &[u8], + in_data: Vec, _out_size: u32, - reply: fuser::ReplyIoctl, - ) { + ) -> Result { 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 +152,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: size_bytes.to_vec(), + }) } FIOC_SET_SIZE => { - let new_size = usize::from_ne_bytes(in_data.try_into().unwrap()); + let new_size = usize::from_ne_bytes(in_data.as_slice().try_into().unwrap()); self.content = vec![0_u8; new_size]; - reply.ioctl(0, &[]); + Ok(Ioctl { + result: 0, + data: vec![], + }) } _ => { 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..c23c0998 100644 --- a/examples/notify_inval_entry.rs +++ b/examples/notify_inval_entry.rs @@ -8,7 +8,7 @@ // licensed under the terms of the GNU GPLv2. use std::{ - ffi::OsStr, + ffi::{OsStr, OsString}, sync::{ atomic::{AtomicU64, Ordering::SeqCst}, Arc, Mutex, @@ -17,13 +17,10 @@ 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, + Attr, DirEntry, Entry, Errno, FileAttr, FileType, Filesystem, Forget, MountOption, RequestMeta, FUSE_ROOT_ID }; struct ClockFS<'a> { @@ -32,7 +29,7 @@ struct ClockFS<'a> { timeout: Duration, } -impl<'a> ClockFS<'a> { +impl ClockFS<'_> { const FILE_INO: u64 = 2; fn get_filename(&self) -> String { @@ -67,58 +64,64 @@ 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: OsString) -> 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 { + attr, + ttl: self.timeout, + generation: 0, + }), + 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 { match ClockFS::stat(ino) { - Some(a) => reply.attr(&self.timeout, &a), - None => reply.error(ENOENT), + Some(attr) => Ok(Attr { + attr, + ttl: self.timeout, + }), + None => Err(Errno::ENOENT), } } fn readdir( &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(); + let mut entries = Vec::new(); + if offset == 0 { + entries.push(DirEntry { + ino: ClockFS::FILE_INO, + offset: 1, // Next offset is 1 + kind: FileType::RegularFile, + name: OsString::from(self.get_filename()), + }); } + // If offset is > 0, we've already returned the single entry, so return an empty vector. + Ok(entries) } } diff --git a/examples/notify_inval_inode.rs b/examples/notify_inval_inode.rs index 84f1418f..73ee11eb 100644 --- a/examples/notify_inval_inode.rs +++ b/examples/notify_inval_inode.rs @@ -8,7 +8,7 @@ use std::{ convert::TryInto, - ffi::OsStr, + ffi::{OsStr, OsString}, sync::{ atomic::{AtomicU64, Ordering::SeqCst}, Arc, Mutex, @@ -17,13 +17,10 @@ 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, Attr, DirEntry, Entry, Errno, FileAttr, FileType, Filesystem, Forget, MountOption, Open, RequestMeta, FUSE_ROOT_ID }; struct ClockFS<'a> { @@ -31,7 +28,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 +63,118 @@ 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: OsString) -> 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 { + attr, + ttl: Duration::MAX, // Effectively infinite TTL + generation: 0, + }), + 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 { match self.stat(ino) { - Some(a) => reply.attr(&Duration::MAX, &a), - None => reply.error(ENOENT), + Some(attr) => Ok(Attr { + attr, + ttl: Duration::MAX, // Effectively infinite TTL + }), + None => Err(Errno::ENOENT), } } fn readdir( &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(); + let mut entries = Vec::new(); + if offset == 0 { + entries.push(DirEntry { + ino: ClockFS::FILE_INO, + offset: 1, // Next offset + kind: FileType::RegularFile, + name: OsString::from(Self::FILE_NAME), + }); } + // If offset is > 0, we've already returned the single entry, so return an empty vector. + Ok(entries) } - 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( &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; - }; - eprintln!("read returning {} bytes at offset {}", end - start, offset); - reply.data(&filedata[start..end]); + let dlen: i64 = filedata.len().try_into().map_err(|_| Errno::EIO)?; // EIO if size doesn't fit i64 + + let start_offset: usize = offset.try_into().map_err(|_| Errno::EINVAL)?; + if start_offset > filedata.len() { + return Ok(Vec::new()); // Read past EOF + } + + let end_offset: 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 actual_end = std::cmp::min(end_offset, filedata.len()); + + eprintln!( + "read returning {} bytes at offset {}", + actual_end.saturating_sub(start_offset), + offset + ); + Ok(filedata[start_offset..actual_end].to_vec()) } } diff --git a/examples/null.rs b/examples/null.rs index 6b4feecd..ef0cdff3 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::ffi::OsString; + + 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, OsString::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, OsString::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..a7d0ac54 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, FileAttr, FileType, Filesystem, KernelConfig, MountOption, Attr, DirEntry, + Entry, Open, Errno, RequestMeta, }; -use libc::ENOENT; use std::collections::HashMap; -use std::ffi::{c_int, OsStr}; +use std::ffi::{OsString}; use std::fs::File; use std::rc::{Rc, Weak}; use std::time::{Duration, UNIX_EPOCH}; @@ -139,73 +138,85 @@ impl PassthroughFs { impl Filesystem for PassthroughFs { fn init( &mut self, - _req: &Request, - config: &mut KernelConfig, - ) -> std::result::Result<(), c_int> { + _req: RequestMeta, + config: KernelConfig, + ) -> Result { + let mut config = config; config.add_capabilities(consts::FUSE_PASSTHROUGH).unwrap(); 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: OsString) -> Result { if parent == 1 && name.to_str() == Some("passthrough") { - reply.entry(&TTL, &self.passthrough_file_attr, 0); + Ok(Entry { + attr: self.passthrough_file_attr, + ttl: TTL, + generation: 0, + }) } 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 { match ino { - 1 => reply.attr(&TTL, &self.root_attr), - 2 => reply.attr(&TTL, &self.passthrough_file_attr), - _ => reply.error(ENOENT), + 1 => Ok(Attr{attr: self.root_attr, ttl: TTL}), + 2 => Ok(Attr{attr: self.passthrough_file_attr, ttl: 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( &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![ @@ -213,14 +224,18 @@ impl Filesystem for PassthroughFs { (1, FileType::Directory, ".."), (2, FileType::RegularFile, "passthrough"), ]; + let mut result=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; - } + result.push(DirEntry { + ino: entry.0, + offset: i as i64 + 1, + kind: entry.1, + name: OsString::from(entry.2), + }); } - reply.ok(); + Ok(result) } } diff --git a/examples/poll.rs b/examples/poll.rs index a86e0879..ca4f368f 100644 --- a/examples/poll.rs +++ b/examples/poll.rs @@ -7,10 +7,12 @@ // 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, + os::unix::ffi::{OsStrExt, OsStringExt}, // for converting to and from sync::{ atomic::{AtomicU64, Ordering::SeqCst}, Arc, Mutex, @@ -19,11 +21,9 @@ 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, + FileAttr, FileType, MountOption, RequestMeta, Entry, Attr, DirEntry, Open, Errno, FUSE_ROOT_ID, }; const NUMFILES: u8 = 16; @@ -81,27 +81,29 @@ impl FSelFS { } impl fuser::Filesystem for FSelFS { - fn lookup(&mut self, _req: &Request, parent: u64, name: &OsStr, reply: fuser::ReplyEntry) { + fn lookup(&mut self, _req: RequestMeta, parent: u64, name: OsString) -> Result { if parent != FUSE_ROOT_ID || name.len() != 1 { - reply.error(ENOENT); - return; + return Err(Errno::ENOENT); } - let name = name.as_bytes(); + let name_bytes = name.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 { + attr: self.get_data().filestat(idx), + ttl: Duration::ZERO, + generation: 0, + }) } - fn getattr(&mut self, _req: &Request, ino: u64, _fh: Option, reply: fuser::ReplyAttr) { + fn getattr(&mut self, _req: RequestMeta, ino: u64, _fh: Option) -> Result { if ino == FUSE_ROOT_ID { let a = FileAttr { ino: FUSE_ROOT_ID, @@ -120,150 +122,142 @@ impl fuser::Filesystem for FSelFS { flags: 0, blksize: 0, }; - reply.attr(&Duration::ZERO, &a); - return; + return Ok(Attr { ttl: Duration::ZERO, attr: a }); } let idx = FSelData::ino_to_idx(ino); if idx < NUMFILES { - reply.attr(&Duration::ZERO, &self.get_data().filestat(idx)); + Ok(Attr { + attr: self.get_data().filestat(idx), + ttl: Duration::ZERO, + }) } else { - reply.error(ENOENT); + Err(Errno::ENOENT) } } fn readdir( &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::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, + let name_bytes = vec![ascii_char_val]; // Byte vector (but just one byte) + let name = OsString::from_vec(name_bytes); + entries.push(DirEntry { + ino: FSelData::idx_to_ino(idx), + offset: (idx + 1).into(), + kind: FileType::RegularFile, name, - ) { - break; - } + }); + // TODO: compare to _max_bytes; stop if full. } - - reply.ok(); + Ok(entries) } - 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( &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()); + Ok(data) } + #[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 = { @@ -271,7 +265,7 @@ impl fuser::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]; @@ -288,8 +282,7 @@ impl fuser::Filesystem for FSelFS { 0 } }; - - reply.poll(revents); + Ok(revents) } } @@ -342,3 +335,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..f5af767a 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -9,9 +9,7 @@ use fuser::consts::FUSE_HANDLE_KILLPRIV; // 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, - FUSE_ROOT_ID, + Attr, DirEntry, Entry, Errno, Filesystem, Forget, KernelConfig, MountOption, Open, RequestMeta, Statfs, TimeOrNow, Xattr, FUSE_ROOT_ID }; #[cfg(feature = "abi-7-26")] use log::info; @@ -20,7 +18,7 @@ use log::{error, LevelFilter}; use serde::{Deserialize, Serialize}; use std::cmp::min; use std::collections::BTreeMap; -use std::ffi::OsStr; +use std::ffi::{OsStr, OsString}; use std::fs::{File, OpenOptions}; use std::io::{BufRead, BufReader, ErrorKind, Read, Seek, SeekFrom, Write}; use std::os::raw::c_int; @@ -130,16 +128,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 +147,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 +164,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); @@ -221,7 +221,7 @@ impl From for fuser::FileAttr { fuser::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 +248,7 @@ struct SimpleFS { next_file_handle: AtomicU64, direct_io: bool, suid_support: bool, + usermode: bool } impl SimpleFS { @@ -255,6 +256,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 +265,7 @@ impl SimpleFS { next_file_handle: AtomicU64::new(1), direct_io, suid_support, + usermode, } } #[cfg(not(feature = "abi-7-26"))] @@ -272,6 +275,7 @@ impl SimpleFS { next_file_handle: AtomicU64::new(1), direct_io, suid_support: false, + usermode, } } } @@ -431,7 +435,7 @@ impl SimpleFS { Ok(attrs) } - fn lookup_name(&self, parent: u64, name: &OsStr) -> Result { + fn lookup_name(&self, parent: u64, name: &OsString) -> Result { let entries = self.get_directory_content(parent)?; if let Some((inode, _)) = entries.get(name.as_bytes()) { return self.get_inode(*inode); @@ -442,13 +446,13 @@ impl SimpleFS { fn insert_link( &self, - req: &Request, + req: RequestMeta, parent: u64, - name: &OsStr, + name: OsString, inode: u64, kind: FileKind, ) -> Result<(), c_int> { - if self.lookup_name(parent, name).is_ok() { + if self.lookup_name(parent, &name).is_ok() { return Err(libc::EEXIST); } @@ -458,8 +462,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 +483,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 +514,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 +525,92 @@ 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) { + fn destroy(&mut self) {} + + fn lookup(&mut self, req: RequestMeta, parent: u64, name: OsString) -> Result { if name.len() > MAX_NAME_LENGTH as usize { - reply.error(libc::ENAMETOOLONG); - return; + 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) { + Ok(attrs) => Ok(Entry { + attr: attrs.into(), + ttl: Duration::new(0,0), + generation: 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 { + match self.get_inode(ino) { + Ok(inode) => + Ok(Attr { + attr: inode.into(), + ttl: 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 { 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 +620,31 @@ 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(Attr { + attr: attrs.into(), + ttl: 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 +652,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(Attr { attr: attrs.into(), ttl: 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(Attr { attr: current_attrs.into(), ttl: 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 +714,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 +741,47 @@ 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(Attr { attr: final_attrs.into(), ttl: Duration::new(0,0), }) } - fn readlink(&mut self, _req: &Request, inode: u64, reply: ReplyData) { + fn readlink(&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(_) => Ok(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: OsString, + 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 +790,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).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 +808,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 +833,51 @@ 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(); + let mut entries = self.get_directory_content(parent).map_err(Errno::from_i32)?; entries.insert(name.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 { + attr: attrs.into(), + ttl: Duration::new(0,0), + generation: 0, + }) } fn mkdir( &mut self, - req: &Request, + req: RequestMeta, parent: u64, - name: &OsStr, - mut mode: u32, - _umask: u32, - reply: ReplyEntry, - ) { + name: OsString, + 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).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 +885,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 +914,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 +927,30 @@ 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(); + let mut entries = self.get_directory_content(parent).map_err(Errno::from_i32)?; entries.insert(name.as_bytes().to_vec(), (inode, FileKind::Directory)); self.write_directory_content(parent, entries); - reply.entry(&Duration::new(0, 0), &attrs.into(), 0); + Ok(Entry { + attr: attrs.into(), + ttl: Duration::new(0,0), + generation: 0, + }) } - fn unlink(&mut self, req: &Request, parent: u64, name: &OsStr, reply: ReplyEmpty) { + fn unlink(&mut self, req: RequestMeta, parent: u64, name: OsString) -> 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) { 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 +958,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(); @@ -954,52 +988,52 @@ impl Filesystem for SimpleFS { entries.remove(name.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: OsString) -> 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) { 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 +1045,20 @@ impl Filesystem for SimpleFS { self.write_inode(&attrs); self.gc_inode(&attrs); - let mut entries = self.get_directory_content(parent).unwrap(); + let mut entries = self.get_directory_content(parent).map_err(Errno::from_i32)?; entries.remove(name.as_bytes()); self.write_directory_content(parent, entries); - reply.ok(); + Ok(()) } fn symlink( &mut self, - req: &Request, + req: RequestMeta, parent: u64, - link_name: &OsStr, - target: &Path, - reply: ReplyEntry, - ) { + link_name: OsString, + target: PathBuf, + ) -> Result { debug!( "symlink() called with {:?} {:?} {:?}", parent, link_name, target @@ -1033,8 +1066,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 +1074,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 +1095,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) { - reply.error(error_code); - return; + return Err(Errno::from_i32(error_code)); } self.write_inode(&attrs); @@ -1082,39 +1112,40 @@ impl Filesystem for SimpleFS { .create(true) .truncate(true) .open(path) - .unwrap(); - file.write_all(target.as_os_str().as_bytes()).unwrap(); + .map_err(|_| Errno::EIO)?; + file.write_all(target.as_os_str().as_bytes()).map_err(|_| Errno::EIO)?; - reply.entry(&Duration::new(0, 0), &attrs.into(), 0); + Ok(Entry { + attr: attrs.into(), + ttl: Duration::new(0,0), + generation: 0, + }) } fn rename( &mut self, - req: &Request, + req: RequestMeta, parent: u64, - name: &OsStr, + name: OsString, new_parent: u64, - new_name: &OsStr, + new_name: OsString, 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) { 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 +1153,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,45 +1180,42 @@ 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) { + 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) { 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(), (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(), (new_inode_attrs.inode, new_inode_attrs.kind), @@ -1209,31 +1234,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) { + 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,18 +1265,17 @@ 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(); + if let Ok(mut existing_inode_attrs) = self.lookup_name(new_parent, &new_name) { + let mut entries = self.get_directory_content(new_parent).map_err(Errno::from_i32)?; entries.remove(new_name.as_bytes()); self.write_directory_content(new_parent, entries); @@ -1270,11 +1289,11 @@ impl Filesystem for SimpleFS { self.gc_inode(&existing_inode_attrs); } - let mut entries = self.get_directory_content(parent).unwrap(); + let mut entries = self.get_directory_content(parent).map_err(Errno::from_i32)?; entries.remove(name.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(), (inode_attrs.inode, inode_attrs.kind), @@ -1291,22 +1310,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: OsString, + ) -> Result { debug!( "link() called for {}, {}, {:?}", inode, new_parent, new_name @@ -1314,28 +1332,30 @@ 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); + 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{ + ttl:Duration::new(0, 0), + attr: attrs.into(), + generation: 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 +1368,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 +1378,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( &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,35 +1424,33 @@ 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(buffer) } else { - reply.error(libc::ENOENT); + Err(Errno::ENOENT) } } fn write( &mut self, - _req: &Request, + _req: RequestMeta, inode: u64, fh: u64, offset: i64, - data: &[u8], + data: Vec, _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 +1467,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 +1502,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,147 +1512,140 @@ 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( &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, Err(error_code) => { - reply.error(error_code); - return; + return Err(Errno::from_i32(error_code)); } }; + let mut result=Vec::new(); 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; - } + result.push(DirEntry { + ino: *inode, + offset: offset + index as i64 + 1, + kind: (*file_type).into(), + name: OsStr::from_bytes(name).to_owned(), + }); + // TODO stop if bytes > _max_bytes } - - reply.ok(); + Ok(result) } 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], + key: OsString, + value: Vec, _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( &mut self, - request: &Request<'_>, + request: RequestMeta, inode: u64, - key: &OsStr, + key: OsString, size: u32, - reply: ReplyXattr, - ) { + ) -> Result { if let Ok(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()) { 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); + return Ok(Xattr::Data(data.to_owned())); } 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(&mut self, _req: RequestMeta, inode: u64, size: u32) -> Result { if let Ok(attrs) = self.get_inode(inode) { let mut bytes = vec![]; // Convert to concatenated null-terminated strings @@ -1647,67 +1654,63 @@ impl Filesystem for SimpleFS { bytes.push(0); } 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)); } 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: OsString) -> 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: OsString, 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).is_ok() { + return Err(Errno::EEXIST); } let (read, write) = match flags & libc::O_ACCMODE { @@ -1716,16 +1719,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 +1734,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 +1759,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); @@ -1778,26 +1778,29 @@ impl Filesystem for SimpleFS { 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 { + attr: attrs.into(), + ttl: Duration::new(0, 0), + generation: 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 +1815,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 +1832,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 +1866,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 +1991,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 +2020,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 +2055,7 @@ fn main() { data_dir, matches.get_flag("direct-io"), matches.get_flag("suid"), + matches.get_flag("user") ), mountpoint, &options, @@ -2055,8 +2064,125 @@ 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::*; + use fuser::{Filesystem, RequestMeta, Errno, TimeOrNow}; + use std::ffi::OsString; + use std::path::PathBuf; + use std::collections::BTreeMap; + use std::fs::File; + use std::io::Write; + + fn dummy_meta() -> RequestMeta { + RequestMeta { unique: 0, uid: 1000, gid: 1000, pid: 2000 } + } + + // Mock setup for SimpleFS to avoid real filesystem operations + 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) = result { + assert_eq!(attr.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, OsString::from("testlink"), target.clone()); + 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, 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/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 d0e0f493..4d3ed69c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,14 +6,13 @@ #![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")] use serde::{Deserialize, Serialize}; -use std::ffi::OsStr; +use std::ffi::{OsStr, OsString}; use std::io; -use std::path::Path; +use std::path::{Path, PathBuf}; #[cfg(feature = "abi-7-23")] use std::time::Duration; use std::time::SystemTime; @@ -24,24 +23,18 @@ 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-11")] +pub use reply::Ioctl; #[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 request::Request; +pub use reply::XTimes; +pub use reply::{Entry, Attr, DirEntry, Open, Statfs, Xattr, Lock}; +pub use ll::Errno; +pub use request::RequestMeta; pub use session::{BackgroundSession, Session, SessionACL, SessionUnmounter}; #[cfg(feature = "abi-7-28")] use std::cmp::max; @@ -72,7 +65,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 @@ -144,6 +138,17 @@ pub struct FileAttr { 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)] pub struct KernelConfig { @@ -325,12 +330,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<(), c_int> { - 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. @@ -338,245 +345,253 @@ 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) { + /// 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 ); - reply.error(ENOSYS); + Err(Errno::ENOSYS) } /// 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, reply: ReplyAttr) { + /// 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 { warn!( "[Not Implemented] getattr(ino: {:#x?}, fh: {:#x?})", ino, fh ); - reply.error(ENOSYS); + Err(Errno::ENOSYS) } /// Set file attributes. + /// The method should return `Ok(Attr)` with the updated file attributes, or `Err(Errno)` otherwise. fn setattr( &mut self, - _req: &Request<'_>, + 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, - reply: ReplyAttr, - ) { + crtime: Option, + chgtime: Option, + bkuptime: Option, + flags: Option + ) -> Result { 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) { + /// The method should return `Ok(Vec)` with the link target, or `Err(Errno)` otherwise. + fn readlink(&mut self, req: RequestMeta, ino: u64) -> Result, Errno> { warn!("[Not Implemented] readlink(ino: {:#x?})", ino); - reply.error(ENOSYS); + 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: &Request<'_>, + req: RequestMeta, parent: u64, - name: &OsStr, + name: OsString, mode: u32, umask: u32, rdev: u32, - reply: ReplyEntry, - ) { + ) -> Result { 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. + /// The method should return `Ok(Entry)` with the new directory's attributes, or `Err(Errno)` otherwise. fn mkdir( &mut self, - _req: &Request<'_>, + req: RequestMeta, parent: u64, - name: &OsStr, + name: OsString, mode: u32, umask: u32, - reply: ReplyEntry, - ) { + ) -> Result { 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) { + /// 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, ); - reply.error(ENOSYS); + Err(Errno::ENOSYS) } /// Remove a directory. - fn rmdir(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEmpty) { + /// 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, ); - reply.error(ENOSYS); + Err(Errno::ENOSYS) } /// 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: &Request<'_>, + req: RequestMeta, parent: u64, - link_name: &OsStr, - target: &Path, - reply: ReplyEntry, - ) { + link_name: OsString, + target: PathBuf, + ) -> Result { warn!( "[Not Implemented] symlink(parent: {:#x?}, link_name: {:?}, target: {:?})", parent, link_name, target, ); - reply.error(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: &Request<'_>, + req: RequestMeta, parent: u64, - name: &OsStr, + name: OsString, newparent: u64, - newname: &OsStr, + newname: OsString, 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. + /// The method should return `Ok(Entry)` with the new link's attributes, or `Err(Errno)` otherwise. fn link( &mut self, - _req: &Request<'_>, + req: RequestMeta, ino: u64, newparent: u64, - newname: &OsStr, - reply: ReplyEntry, - ) { + newname: OsString, + ) -> Result { warn!( "[Not Implemented] link(ino: {:#x?}, newparent: {:#x?}, newname: {:?})", ino, newparent, newname ); - reply.error(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: &Request<'_>, _ino: u64, _flags: i32, reply: ReplyOpen) { - reply.opened(0, 0); + /// 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) } /// 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. + /// The method should return `Ok(Vec)` with the read data, or `Err(Errno)` otherwise. /// - /// flags: these are the file flags, such as O_SYNC. Only supported with ABI >= 7.9 - /// lock_owner: 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( &mut self, - _req: &Request<'_>, + req: RequestMeta, ino: u64, fh: u64, offset: i64, 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. /// 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: &Request<'_>, + req: RequestMeta, ino: u64, fh: u64, offset: i64, - data: &[u8], + data: Vec, write_flags: u32, flags: i32, lock_owner: Option, - reply: ReplyWrite, - ) { + ) -> Result { warn!( "[Not Implemented] write(ino: {:#x?}, fh: {}, offset: {}, data.len(): {}, \ write_flags: {:#x?}, flags: {:#x?}, lock_owner: {:?})", @@ -588,248 +603,274 @@ pub trait Filesystem { flags, lock_owner ); - reply.error(ENOSYS); + Err(Errno::ENOSYS) } /// 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: &Request<'_>, ino: u64, fh: u64, lock_owner: u64, reply: ReplyEmpty) { + /// 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 ); - reply.error(ENOSYS); + Err(Errno::ENOSYS) } /// 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: &Request<'_>, - _ino: u64, - _fh: u64, - _flags: i32, - _lock_owner: Option, - _flush: bool, - reply: ReplyEmpty, - ) { - reply.ok(); + 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: &Request<'_>, ino: u64, fh: u64, datasync: bool, reply: ReplyEmpty) { + /// 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 ); - reply.error(ENOSYS); + Err(Errno::ENOSYS) } /// 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: &Request<'_>, _ino: u64, _flags: i32, reply: ReplyOpen) { - reply.opened(0, 0); + /// 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. - /// 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. + /// The filesystem should return a buffer filled with directory entries. The buffer + /// must not exceed the `max_bytes` parameter. An empty buffer 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. + /// The method should return `Ok(Vec)` with the directory entries, or `Err(Errno)` otherwise. fn readdir( &mut self, - _req: &Request<'_>, + req: RequestMeta, ino: u64, fh: u64, offset: i64, - reply: ReplyDirectory, - ) { + 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 ); - reply.error(ENOSYS); + 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. + /// Similar to `readdir`, but also returns the attributes of each directory entry. + /// The filesystem should return a buffer filled with directory entries and their attributes. + /// The buffer must not exceed the `max_bytes` parameter. An empty buffer 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. + /// The method should return `Ok(Vec<(DirEntry, Entry)>)` with the directory entries and their attributes, or `Err(Errno)` otherwise. + #[cfg(feature = "abi-7-21")] fn readdirplus( &mut self, - _req: &Request<'_>, + req: RequestMeta, ino: u64, fh: u64, offset: i64, - reply: ReplyDirectoryPlus, - ) { + 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 ); - reply.error(ENOSYS); + Err(Errno::ENOSYS) } /// 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: &Request<'_>, - _ino: u64, - _fh: u64, - _flags: i32, - reply: ReplyEmpty, - ) { - reply.ok(); + 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: &Request<'_>, + req: RequestMeta, 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); + /// 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: 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: &Request<'_>, + req: RequestMeta, ino: u64, - name: &OsStr, - _value: &[u8], + name: OsString, + value: Vec, 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. - /// 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. + /// 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(Vec)`. + /// If the value does not fit, `Err(Errno::ERANGE)` should be returned. + /// The method should return `Ok(Xattr)` on success, or `Err(Errno)` otherwise. fn getxattr( &mut self, - _req: &Request<'_>, + req: RequestMeta, ino: u64, - name: &OsStr, + name: OsString, size: u32, - reply: ReplyXattr, - ) { + ) -> Result { 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) { + /// 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(Vec)`. + /// If the list does not fit, `Err(Errno::ERANGE)` should be returned. + /// The method should return `Ok(Xattr)` on success, or `Err(Errno)` otherwise. + fn listxattr( + &mut self, + req: RequestMeta, + ino: u64, + size: u32, + ) -> Result { 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) { + /// The method should return `Ok(())` on success, or `Err(Errno)` otherwise. + fn removexattr( + &mut self, + req: RequestMeta, + ino: u64, + name: OsString, + ) -> 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' + /// 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) { + /// 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); - reply.error(ENOSYS); + Err(Errno::ENOSYS) } /// 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: &Request<'_>, + req: RequestMeta, parent: u64, - name: &OsStr, + name: OsString, mode: u32, umask: u32, flags: i32, - reply: ReplyCreate, - ) { + ) -> Result<(Entry,Open), 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. + /// The method should return `Ok(Lock)` with the lock information, or `Err(Errno)` otherwise. fn getlk( &mut self, - _req: &Request<'_>, + req: RequestMeta, ino: u64, fh: u64, lock_owner: u64, @@ -837,26 +878,26 @@ pub trait Filesystem { end: u64, typ: i32, pid: u32, - reply: ReplyLock, - ) { + ) -> Result { 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. - /// 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: &Request<'_>, + req: RequestMeta, ino: u64, fh: u64, lock_owner: u64, @@ -865,39 +906,40 @@ 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) { + /// 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, ); - reply.error(ENOSYS); + Err(Errno::ENOSYS) } - /// control device + /// Control device. + /// The method should return `Ok(Ioctl)` with the ioctl result, or `Err(Errno)` otherwise. + #[cfg(feature = "abi-7-11")] fn ioctl( &mut self, - _req: &Request<'_>, + req: RequestMeta, ino: u64, fh: u64, flags: u32, cmd: u32, - in_data: &[u8], + in_data: Vec, out_size: u32, - reply: ReplyIoctl, - ) { + ) -> Result { warn!( "[Not Implemented] ioctl(ino: {:#x?}, fh: {}, flags: {}, cmd: {}, \ in_data.len(): {}, out_size: {})", @@ -908,68 +950,70 @@ pub trait Filesystem { in_data.len(), out_size, ); - reply.error(ENOSYS); + 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, - _req: &Request<'_>, + req: RequestMeta, ino: u64, fh: u64, - ph: PollHandle, + ph: u64, events: u32, flags: u32, - reply: ReplyPoll, - ) { + ) -> Result { 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 + /// Preallocate or deallocate space to a file. + /// The method should return `Ok(())` on success, or `Err(Errno)` otherwise. fn fallocate( &mut self, - _req: &Request<'_>, + req: RequestMeta, ino: u64, fh: u64, 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 + /// 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: &Request<'_>, + req: RequestMeta, ino: u64, fh: u64, offset: i64, whence: i32, - reply: ReplyLseek, - ) { + ) -> Result { 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 + /// 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: &Request<'_>, + req: RequestMeta, ino_in: u64, fh_in: u64, offset_in: i64, @@ -978,59 +1022,63 @@ pub trait Filesystem { offset_out: i64, len: u64, flags: u32, - reply: ReplyWrite, - ) { + ) -> 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 ); - reply.error(ENOSYS); + 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: &Request<'_>, name: &OsStr, reply: ReplyEmpty) { + fn setvolname(&mut self, req: RequestMeta, name: OsStr) -> Result<(), Errno> { warn!("[Not Implemented] setvolname(name: {:?})", name); - reply.error(ENOSYS); + 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: &Request<'_>, + req: RequestMeta, parent: u64, - name: &OsStr, + name: OsString, newparent: u64, - newname: &OsStr, - options: u64, - reply: ReplyEmpty, - ) { + newname: OsString, + options: u64 + ) -> 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 + /// 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: &Request<'_>, ino: u64, reply: ReplyXTimes) { + fn getxtimes(&mut self, req: RequestMeta, ino: u64) -> Result { warn!("[Not Implemented] getxtimes(ino: {:#x?})", ino); - reply.error(ENOSYS); + Err(Errno::ENOSYS) } } /// 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, @@ -1041,9 +1089,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, @@ -1053,12 +1105,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, @@ -1072,13 +1129,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/ll/fuse_abi.rs b/src/ll/fuse_abi.rs index 96cea927..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}; @@ -136,10 +139,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 +538,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/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/reply.rs b/src/ll/reply.rs index cada68ca..5cca5d67 100644 --- a/src/ll/reply.rs +++ b/src/ll/reply.rs @@ -222,6 +222,7 @@ impl<'a> Response<'a> { } // TODO: Are you allowed to send data while result != 0? + #[cfg(feature = "abi-7-11")] pub(crate) fn new_ioctl(result: i32, data: &[IoSlice<'_>]) -> Self { let r = abi::fuse_ioctl_out { result, @@ -257,6 +258,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) @@ -438,6 +440,7 @@ impl DirEntList { } } +#[cfg(feature = "abi-7-21")] #[derive(Debug)] pub struct DirEntryPlus> { #[allow(unused)] // We use `attr.ino` instead @@ -450,6 +453,7 @@ pub struct DirEntryPlus> { attr_valid: Duration, } +#[cfg(feature = "abi-7-21")] impl> DirEntryPlus { pub fn new( ino: INodeNo, @@ -473,8 +477,11 @@ impl> DirEntryPlus { } /// Used to respond to [ReadDir] requests. +#[cfg(feature = "abi-7-21")] #[derive(Debug)] pub struct DirEntPlusList(EntListBuf); + +#[cfg(feature = "abi-7-21")] impl From for Response<'_> { fn from(l: DirEntPlusList) -> Self { assert!(l.0.buf.len() <= l.0.max_size); @@ -482,6 +489,7 @@ impl From for Response<'_> { } } +#[cfg(feature = "abi-7-21")] impl DirEntPlusList { pub(crate) fn new(max_size: usize) -> Self { Self(EntListBuf::new(max_size)) diff --git a/src/ll/request.rs b/src/ll/request.rs index 01c28eda..9f85e7ec 100644 --- a/src/ll/request.rs +++ b/src/ll/request.rs @@ -265,8 +265,6 @@ macro_rules! impl_request { } mod op { - use crate::ll::Response; - use super::{ super::{argument::ArgumentIterator, TimeOrNow}, FilenameInDir, Request, @@ -282,7 +280,6 @@ mod op { path::Path, time::{Duration, SystemTime}, }; - use zerocopy::IntoBytes; /// Look up a directory entry by name and get its attributes. /// @@ -318,7 +315,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 @@ -336,7 +333,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)) @@ -353,7 +350,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, @@ -473,7 +470,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, @@ -657,7 +654,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 } @@ -676,7 +673,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) @@ -780,7 +777,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 } @@ -812,7 +809,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) @@ -912,7 +909,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. @@ -957,7 +954,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) @@ -973,7 +970,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 { @@ -988,45 +985,6 @@ mod op { 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. @@ -1045,7 +1003,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 @@ -1059,7 +1017,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) @@ -1081,7 +1039,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) @@ -1112,7 +1070,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) @@ -1130,7 +1088,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) @@ -1157,7 +1115,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) @@ -1175,7 +1133,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) @@ -1198,7 +1156,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 } @@ -1284,7 +1242,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) } @@ -1299,7 +1257,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 } @@ -1313,11 +1271,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")] @@ -1330,7 +1283,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] } @@ -1364,7 +1317,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) @@ -1418,6 +1371,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 /// @@ -1431,7 +1403,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) @@ -1460,7 +1432,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) @@ -1524,7 +1496,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) @@ -1556,7 +1528,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 { @@ -1974,7 +1946,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()), @@ -2190,7 +2162,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!( @@ -2237,8 +2209,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 @@ -2248,8 +2221,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 @@ -2259,6 +2233,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 @@ -2304,7 +2316,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"), } } @@ -2312,7 +2327,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/reply.rs b/src/reply.rs index 5b779551..bb6287db 100644 --- a/src/reply.rs +++ b/src/reply.rs @@ -1,39 +1,36 @@ //! 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). +//! 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::ll::{ self, - reply::{DirEntPlusList, DirEntryPlus}, - Generation, -}; -use crate::ll::{ - reply::{DirEntList, DirEntOffset, DirEntry}, + reply::{DirEntList, DirEntOffset}, + reply::DirEntry as ll_DirEntry, INodeNo, }; +#[cfg(feature = "abi-7-21")] +use crate::ll::reply::{DirEntPlusList, DirEntryPlus}; +#[cfg(feature = "abi-7-21")] +use crate::ll::Generation; #[cfg(feature = "abi-7-40")] use crate::{consts::FOPEN_PASSTHROUGH, passthrough::BackingId}; -use libc::c_int; use log::{error, warn}; -use std::convert::AsRef; -use std::ffi::OsStr; +use std::ffi::OsString; use std::fmt; use std::io::IoSlice; #[cfg(feature = "abi-7-40")] use std::os::fd::BorrowedFd; use std::time::Duration; - +use zerocopy::IntoBytes; #[cfg(target_os = "macos")] use std::time::SystemTime; -use crate::{FileAttr, FileType}; +use crate::{FileAttr, FileType, KernelConfig}; /// 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 +44,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 +77,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,589 +101,334 @@ impl Drop for ReplyRaw { } } -/// -/// Empty reply -/// -#[derive(Debug)] -pub struct ReplyEmpty { - reply: ReplyRaw, -} - -impl Reply for ReplyEmpty { - fn new(unique: u64, sender: S) -> ReplyEmpty { - ReplyEmpty { - reply: Reply::new(unique, sender), - } - } -} +// +// Structs for managing response data +// -impl ReplyEmpty { - /// Reply to a request with nothing - pub fn ok(self) { - self.reply.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); - } -} - -/// -/// 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 - 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); - } +/// File attribute response data +pub struct Attr { + /// Describes a file + pub attr: FileAttr, + /// Time to live for the attribute + pub ttl: Duration } -/// -/// Entry reply -/// #[derive(Debug)] -pub struct ReplyEntry { - reply: ReplyRaw, +/// File entry response data +pub struct Entry { + /// Describes a file + pub attr: FileAttr, + /// Time to live for the entry + pub ttl: Duration, + /// The generation of the entry + pub generation: u64 } -impl Reply for ReplyEntry { - fn new(unique: u64, sender: S) -> ReplyEntry { - ReplyEntry { - reply: Reply::new(unique, sender), - } - } +#[derive(Debug)] //TODO #[derive(Copy)] +/// 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 } -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 -/// +#[cfg(target_os = "macos")] #[derive(Debug)] -pub struct ReplyAttr { - reply: ReplyRaw, +/// 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 } -impl Reply for ReplyAttr { - fn new(unique: u64, sender: S) -> ReplyAttr { - ReplyAttr { - reply: Reply::new(unique, sender), - } - } +#[derive(Debug)] +/// Directory listing response data +pub struct DirEntry { + /// file inode number + pub ino: u64, + /// entry number in directory + pub offset: i64, + /// kind of file + pub kind: FileType, + /// name of file + pub name: OsString +} + +#[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, } -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); - } +#[derive(Debug)] +/// Extended attribute response data +pub enum Xattr{ + /// Reply to a request with the size of the xattr. + Size(u32), + /// Reply to a request with the data in the xattr. + Data(Vec) } -/// -/// XTimes Reply -/// -#[cfg(target_os = "macos")] +#[cfg(feature = "abi-7-11")] #[derive(Debug)] -pub struct ReplyXTimes { - reply: ReplyRaw, +/// File io control reponse data +pub struct Ioctl { + /// Result of the ioctl operation + pub result: i32, + /// Data to be returned with the ioctl operation + pub data: Vec } -#[cfg(target_os = "macos")] -impl Reply for ReplyXTimes { - fn new(unique: u64, sender: S) -> ReplyXTimes { - ReplyXTimes { - reply: Reply::new(unique, sender), - } - } -} +// +// Methods to reply to a request for each kind of data +// -#[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)) - } +impl ReplyHandler { - /// 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 Ok + pub fn ok(self) { + self.send_ll(&ll::Response::new_empty()); } -} -/// -/// Open Reply -/// -#[derive(Debug)] -pub struct ReplyOpen { - reply: ReplyRaw, -} - -impl Reply for ReplyOpen { - fn new(unique: u64, sender: S) -> ReplyOpen { - ReplyOpen { - reply: Reply::new(unique, sender), - } + /// Reply to a general request with an error code + pub fn error(self, err: ll::Errno) { + self.send_ll(&ll::Response::new_error(err)); } -} -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)) + /// Reply to a general request with data + 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, capabilities: u64, config: KernelConfig) { + let flags = capabilities & config.requested; // use requested features and reported as capable + + 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())); } - /// 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 a file entry + pub fn entry(self, entry: Entry) { + self.send_ll(&ll::Response::new_entry( + ll::INodeNo(entry.attr.ino), + ll::Generation(entry.generation), + &entry.attr.into(), + entry.ttl, + entry.ttl, + )); } - /// 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 a file attributes + pub fn attr(self, attr: Attr) { + self.send_ll(&ll::Response::new_attr(&attr.ttl, &attr.attr.into())); } - /// Reply to a request with the given error code - pub fn error(self, err: c_int) { - self.reply.error(err); + #[cfg(target_os = "macos")] + /// Reply to a request with xtimes attributes + pub fn xtimes(self, xtimes: XTimes) { + self.send_ll(&ll::Response::new_xtimes(xtimes.bkuptime, xtimes.crtime)) } -} - -/// -/// Write Reply -/// -#[derive(Debug)] -pub struct ReplyWrite { - reply: ReplyRaw, -} -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, 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)) } -} -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, - bfree: u64, - bavail: u64, - files: u64, - ffree: u64, - bsize: u32, - namelen: u32, - frsize: u32, + statfs: Statfs ) { - self.reply.send_ll(&ll::Response::new_statfs( - blocks, bfree, bavail, files, ffree, bsize, namelen, frsize, + 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 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) { + /// 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!(flags & FOPEN_PASSTHROUGH, 0); - self.reply.send_ll(&ll::Response::new_create( - ttl, - &attr.into(), - ll::Generation(generation), - ll::FileHandle(fh), - flags, + assert_eq!(open.flags & FOPEN_PASSTHROUGH, 0); + self.send_ll(&ll::Response::new_create( + &entry.ttl, + &entry.attr.into(), + ll::Generation(entry.generation), + ll::FileHandle(open.fh), + open.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 a file lock + 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 the given error code - pub fn error(self, err: c_int) { - self.reply.error(err); - } -} - -/// -/// Bmap Reply -/// -#[derive(Debug)] -pub struct ReplyBmap { - reply: ReplyRaw, -} - -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)) - } - - /// 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), - } + self.send_ll(&ll::Response::new_bmap(block)) } -} -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)])); + #[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, &[IoSlice::new(ioctl.data.as_ref())])); } - /// 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")] -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), - } + self.send_ll(&ll::Response::new_poll(revents)) } - /// 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), + /// Reply to a request with a filled directory buffer + pub fn dir( + self, + entries: Vec, + size: usize + ) { + let mut buf = DirEntList::new(size); + for item in entries.into_iter() { + let full= buf.push(&ll_DirEntry::new( + INodeNo(item.ino), + DirEntOffset(item.offset), + item.kind, + item.name + )); + if full { + break; + } } + self.send_ll(&buf.into()); } - /// 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, - )) - } - - /// 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 the given error code - pub fn error(self, err: c_int) { - self.reply.error(err); - } -} - -/// -/// Xattr reply -/// -#[derive(Debug)] -pub struct ReplyXattr { - reply: ReplyRaw, -} - -impl Reply for ReplyXattr { - fn new(unique: u64, sender: S) -> ReplyXattr { - ReplyXattr { - reply: Reply::new(unique, sender), + #[cfg(feature = "abi-7-21")] + // Reply to a request with a filled directory plus buffer + pub fn dirplus( + self, + entries: Vec<(DirEntry, Entry)>, + size: usize + ) { + let mut buf = DirEntPlusList::new(size); + for (item, plus) in entries.into_iter() { + let full = buf.push(&DirEntryPlus::new( + INodeNo(item.ino), + Generation(plus.generation), + DirEntOffset(item.offset), + item.name, + plus.ttl, + plus.attr.into(), + plus.ttl, + )); + if full { + break; + } } - } -} - -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)) + self.send_ll(&buf.into()); } - /// 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 extended attributes. + 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 given error code. - pub fn error(self, err: c_int) { - self.reply.error(err); + /// 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)) } -} - -/// -/// 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), - } + /// Reply to a request with the data in an xattr result. + pub fn xattr_data(self, data: Vec) { + self.send_ll(&ll::Response::new_slice(&data)) } -} -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)] @@ -765,8 +503,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 +515,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 +528,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,8 +540,8 @@ 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] @@ -842,7 +581,7 @@ mod test { expected[0] = (expected.len()) as u8; let sender = AssertSender { expected }; - let reply: ReplyEntry = 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 { @@ -862,7 +601,13 @@ mod test { flags: 0x99, blksize: 0xbb, }; - reply.entry(&ttl, &attr, 0xaa); + replyhandler.entry( + Entry{ + attr: attr, + ttl: ttl, + generation: 0xaa + } + ); } #[test] @@ -899,7 +644,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 +664,9 @@ mod test { flags: 0x99, blksize: 0xbb, }; - reply.attr(&ttl, &attr); + replyhandler.attr( + Attr { attr: attr, ttl: ttl} + ); } #[test] @@ -932,9 +679,14 @@ 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( + XTimes{ + bkuptime: time, + crtime: time, + } + ); } #[test] @@ -946,8 +698,10 @@ 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( + Open { fh: 0x1122, flags: 0x33} + ); } #[test] @@ -958,8 +712,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 +729,19 @@ 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( + Statfs{ + blocks: 0x11, + bfree: 0x22, + bavail: 0x33, + files: 0x44, + ffree: 0x55, + bsize: 0x66, + namelen: 0x77, + frsize: 0x88 + } + ); } #[test] @@ -1008,7 +773,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, ] }; @@ -1022,7 +787,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 +807,17 @@ mod test { flags: 0x99, blksize: 0xdd, }; - reply.created(&ttl, &attr, 0xaa, 0xbb, 0xcc); + replyhandler.created( + Entry { + attr: attr, + ttl: ttl, + generation: 0xaa + }, + Open { + fh: 0xbb, + flags: 0x0f + } + ); } #[test] @@ -1054,8 +829,15 @@ 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( + Lock { + start: 0x11, + end: 0x22, + typ: 0x33, + pid: 0x44 + } + ); } #[test] @@ -1066,8 +848,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,12 +864,25 @@ 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!( + DirEntry { + ino: 0xaabb, + offset: 1, + kind: FileType::Directory, + name: OsString::from("hello"), + }, + DirEntry { + ino: 0xccdd, + offset: 2, + kind: FileType::RegularFile, + name: OsString::from("world.rs"), + } + ); + replyhandler.dir(entries, std::mem::size_of::()*128); } + #[test] fn reply_xattr_size() { let sender = AssertSender { @@ -1096,8 +891,8 @@ 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); + replyhandler.xattr(Xattr::Size(0x12345678)); } #[test] @@ -1108,8 +903,8 @@ 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); + replyhandler.xattr(Xattr::Data([0x11, 0x22, 0x33, 0x44].to_vec())); } impl super::ReplySender for SyncSender<()> { @@ -1127,9 +922,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..7e5af8ff 100644 --- a/src/request.rs +++ b/src/request.rs @@ -5,23 +5,20 @@ //! //! 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 _; -#[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")] use crate::PollHandle; -use crate::{ll, KernelConfig}; +use crate::{ll, KernelConfig, Forget}; /// Request data structure #[derive(Debug)] @@ -30,9 +27,26 @@ pub struct Request<'a> { ch: ChannelSender, /// Request raw data #[allow(unused)] - data: &'a [u8], + data: &'a [u8], // TODO Vec /// 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> { @@ -46,100 +60,62 @@ impl<'a> Request<'a> { } }; - Some(Self { ch, data, request }) + 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, 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 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) - } - } + let op = op_result.unwrap(); - fn dispatch_req( - &self, - se: &mut Session, - ) -> Result>, Errno> { - let op = self.request.operation().map_err(|_| Errno::ENOSYS)?; // 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(_) => {} - _ => { - return Err(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(_) => {} - _ => { - return Err(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(_) => {} - _ => { - return Err(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) => { @@ -147,17 +123,23 @@ 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); + 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()); - // Call filesystem init method and give it a chance to return an error - se.filesystem - .init(self, &mut config) - .map_err(Errno::from_i32)?; + 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 + let config = match se.filesystem.init(self.meta, config) { + Ok(config) => config, + 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,59 +153,89 @@ 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); + match self.request.reply_err(Errno::EIO) + .with_iovec( + self.request.unique(), + |iov| self.ch.send(iov) + ) + { + Ok(()) => {} + Err(err) => { + error!("Failed to send uninitialised error: {}", err); + } + }; } // Filesystem destroyed - ll::Operation::Destroy(x) => { + 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( - self, + let response = se.filesystem.lookup( + self.meta, self.request.nodeid().into(), - x.name().as_ref(), - self.reply(), - ); + x.name().into() + ); + match response { + Ok(entry) => { + self.replyhandler.entry(entry) + }, + Err(err)=>{ + self.replyhandler.error(err) + } + } } 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")] - se.filesystem.getattr( - self, + let response = se.filesystem.getattr( + self.meta, 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.meta, + self.request.nodeid().into(), + None, + ); + match response { + Ok(attr)=> { + self.replyhandler.attr(attr) + } + Err(err)=>{ + self.replyhandler.error(err) + } + } } ll::Operation::SetAttr(x) => { - se.filesystem.setattr( - self, + let response = se.filesystem.setattr( + self.meta, self.request.nodeid().into(), x.mode(), x.uid(), @@ -236,240 +248,448 @@ impl<'a> Request<'a> { x.crtime(), x.chgtime(), x.bkuptime(), - x.flags(), - self.reply(), + x.flags() ); + match response { + Ok(attr)=> { + self.replyhandler.attr(attr) + } + 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.meta, + 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( - self, + let response = se.filesystem.mknod( + self.meta, self.request.nodeid().into(), - x.name().as_ref(), + x.name().into(), 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( - self, + let response = se.filesystem.mkdir( + self.meta, self.request.nodeid().into(), - x.name().as_ref(), + x.name().into(), 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( - self, + let response = se.filesystem.unlink( + self.meta, 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( - self, + let response = se.filesystem.rmdir( + self.meta, 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::SymLink(x) => { - se.filesystem.symlink( - self, + let response = se.filesystem.symlink( + self.meta, self.request.nodeid().into(), - x.link_name().as_ref(), - Path::new(x.target()), - self.reply(), + x.link_name().into(), + x.target().into() ); + match response { + Ok(entry)=> { + self.replyhandler.entry(entry) + } + Err(err)=>{ + self.replyhandler.error(err) + } + } } ll::Operation::Rename(x) => { - se.filesystem.rename( - self, + let response = se.filesystem.rename( + self.meta, self.request.nodeid().into(), - x.src().name.as_ref(), + x.src().name.into(), x.dest().dir.into(), - x.dest().name.as_ref(), - 0, - self.reply(), + x.dest().name.into(), + 0 ); + match response { + Ok(())=> { + self.replyhandler.ok() + } + Err(err)=>{ + self.replyhandler.error(err) + } + } } ll::Operation::Link(x) => { - se.filesystem.link( - self, + let response = se.filesystem.link( + self.meta, x.inode_no().into(), self.request.nodeid().into(), - x.dest().name.as_ref(), - self.reply(), + x.dest().name.into() ); + 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.meta, + 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( - self, + let response = se.filesystem.read( + self.meta, 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) + } + Err(err)=>{ + self.replyhandler.error(err) + } + } } ll::Operation::Write(x) => { - se.filesystem.write( - self, + let response = se.filesystem.write( + self.meta, self.request.nodeid().into(), x.file_handle().into(), x.offset(), - x.data(), + x.data().to_vec(), 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( - self, + let response = se.filesystem.flush( + self.meta, 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( - self, + let response = se.filesystem.release( + self.meta, 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( - self, + let response = se.filesystem.fsync( + self.meta, 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.meta, + 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( - self, + let response = se.filesystem.readdir( + self.meta, self.request.nodeid().into(), x.file_handle().into(), x.offset(), - ReplyDirectory::new( - self.request.unique().into(), - self.ch.clone(), - x.size() as usize, - ), + x.size() ); + match response { + Ok(entries)=> { + self.replyhandler.dir(entries, x.size() as usize) + } + Err(err)=>{ + self.replyhandler.error(err) + } + } } ll::Operation::ReleaseDir(x) => { - se.filesystem.releasedir( - self, + let response = se.filesystem.releasedir( + self.meta, 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( - self, + let response = se.filesystem.fsyncdir( + self.meta, 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.meta, + 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( - self, + let response = se.filesystem.setxattr( + self.meta, self.request.nodeid().into(), - x.name(), - x.value(), + x.name().into(), + x.value().into(), 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( - self, + let response = se.filesystem.getxattr( + self.meta, self.request.nodeid().into(), - x.name(), - x.size_u32(), - self.reply(), + x.name().into(), + 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.meta, + 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( - self, + let response = se.filesystem.removexattr( + self.meta, self.request.nodeid().into(), - x.name(), - self.reply(), + x.name().into() ); + 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.meta, + 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( - self, + let response = se.filesystem.create( + self.meta, self.request.nodeid().into(), - x.name().as_ref(), + x.name().into(), 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( - self, + let response = se.filesystem.getlk( + self.meta, self.request.nodeid().into(), x.file_handle().into(), x.lock_owner().into(), 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( - self, + let response = se.filesystem.setlk( + self.meta, self.request.nodeid().into(), x.file_handle().into(), x.lock_owner().into(), @@ -477,13 +697,20 @@ 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( - self, + let response = se.filesystem.setlk( + self.meta, self.request.nodeid().into(), x.file_handle().into(), x.lock_owner().into(), @@ -491,114 +718,169 @@ 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( - self, + let response = se.filesystem.bmap( + self.meta, 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( - self, + let response = se.filesystem.ioctl( + self.meta, self.request.nodeid().into(), x.file_handle().into(), x.flags(), x.command(), - x.in_data(), - x.out_size(), - self.reply(), + x.in_data().to_vec(), + 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( - self, + let response = se.filesystem.poll( + self.meta, self.request.nodeid().into(), x.file_handle().into(), - ph, + ph.into(), 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) => { - 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) => { - se.filesystem.fallocate( - self, + let response = se.filesystem.fallocate( + self.meta, 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( - self, + let response = se.filesystem.readdirplus( + self.meta, self.request.nodeid().into(), x.file_handle().into(), x.offset(), - ReplyDirectoryPlus::new( - self.request.unique().into(), - self.ch.clone(), - x.size() as usize, - ), + x.size() ); + match response { + Ok(entries)=> { + self.replyhandler.dirplus(entries, x.size() as usize) + } + Err(err)=>{ + self.replyhandler.error(err) + } + } } #[cfg(feature = "abi-7-23")] ll::Operation::Rename2(x) => { - se.filesystem.rename( - self, + let response = se.filesystem.rename( + 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.flags(), - self.reply(), + x.to().name.into(), + 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( - self, + let response = se.filesystem.lseek( + self.meta, 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( - self, + let response = se.filesystem.copy_file_range( + self.meta, i.inode.into(), i.file_handle.into(), i.offset, @@ -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() } }