diff --git a/.github/workflows/cargo-test.yml b/.github/workflows/cargo-test.yml index 9fd674b..cf1ec0d 100644 --- a/.github/workflows/cargo-test.yml +++ b/.github/workflows/cargo-test.yml @@ -16,7 +16,7 @@ jobs: - rust_toolchain: stable deny_warnings: --deny warnings - rust_toolchain: nightly - deny_warnings: + deny_warnings: true steps: - name: Set up FUSE run: ${{matrix.fuse_install}} diff --git a/.gitignore b/.gitignore index a9d37c5..738792e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ target Cargo.lock +.history diff --git a/Cargo.toml b/Cargo.toml index f7d10e0..e39cb29 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,10 +11,13 @@ readme = "README.md" edition = "2018" [dependencies] -fuser = "0.11" +tracing = "0.1" libc = "0.2" -log = "0.4" -threadpool = "1.0" +threadpool = "1.8.1" + +[dependencies.fuser] +version="0.11" +features=[ "abi-7-31", "libfuse" ] [workspace] members = [".", "example"] diff --git a/README.md b/README.md index 1954be5..a3093ef 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ Some random notes on the implementation: * write * flush * fsync + * ioctl * Other calls run synchronously on the main thread because either it is expected that they will complete quickly and/or they require mutating internal state of the InodeTranslator and I want to avoid needing locking in there. * The inode/path translation is always done on the main thread. * It might be a good idea to limit the number of concurrent read and write operations in flight. I'm not sure yet how many outstanding read/write requests FUSE will issue though, so it might be a non-issue. diff --git a/example/Cargo.toml b/example/Cargo.toml index fe86e2a..303cd77 100644 --- a/example/Cargo.toml +++ b/example/Cargo.toml @@ -2,8 +2,8 @@ name = "passthrufs" version = "0.1.0" authors = ["William R. Fraser "] -edition = "2018" workspace = ".." +edition = "2018" [dependencies] libc = "0.2" diff --git a/example/src/main.rs b/example/src/main.rs index a61b2c5..b3504d2 100644 --- a/example/src/main.rs +++ b/example/src/main.rs @@ -46,7 +46,7 @@ fn main() { target: args[1].clone(), }; - let fuse_args = [OsStr::new("-o"), OsStr::new("fsname=passthrufs")]; + let fuse_args = [OsStr::new("-o"), OsStr::new("fsname=passthrufs,auto_unmount")]; fuse_mt::mount(fuse_mt::FuseMT::new(filesystem, 1), &args[2], &fuse_args[..]).unwrap(); } diff --git a/src/fusemt.rs b/src/fusemt.rs index 3a08d97..5a6c709 100644 --- a/src/fusemt.rs +++ b/src/fusemt.rs @@ -2,19 +2,25 @@ // operations to multiple threads. // // Copyright (c) 2016-2022 by William R. Fraser -// +// Copyright (C) 2019-2022 Ahmed Masud. + use std::ffi::OsStr; use std::path::{Path, PathBuf}; use std::sync::Arc; use std::time::SystemTime; -use fuser::TimeOrNow; + use threadpool::ThreadPool; +use tracing::{debug, error}; + +use crate:: { + directory_cache::*, + inode_table::*, + types::* +}; +use fuser::TimeOrNow; -use crate::directory_cache::*; -use crate::inode_table::*; -use crate::types::*; trait IntoRequestInfo { fn info(&self) -> RequestInfo; @@ -26,7 +32,7 @@ impl<'a> IntoRequestInfo for fuser::Request<'a> { unique: self.unique(), uid: self.uid(), gid: self.gid(), - pid: self.pid(), + pid: self.pid() as i32, } } } @@ -47,6 +53,7 @@ fn fuse_fileattr(attr: FileAttr, ino: u64) -> fuser::FileAttr { gid: attr.gid, rdev: attr.rdev, blksize: 4096, // TODO + // padding: 0, flags: attr.flags, } } @@ -89,7 +96,8 @@ impl FuseMT { f() } else { if self.threads.is_none() { - debug!("initializing threadpool with {} threads", self.num_threads); + debug!("initializing threadpool with {} threads", + self.num_threads); self.threads = Some(ThreadPool::new(self.num_threads)); } self.threads.as_ref().unwrap().execute(f); @@ -117,7 +125,6 @@ impl fuser::Filesystem for FuseMT { debug!("init"); self.target.init(req.info()) } - fn destroy(&mut self) { debug!("destroy"); self.target.destroy(); @@ -480,6 +487,37 @@ impl fuser::Filesystem for FuseMT { }); } + fn ioctl( + &mut self, + req: &fuser::Request<'_>, + ino: u64, + fh: u64, + flags: u32, + cmd: u32, + in_data: &[u8], + out_size: u32, + reply: fuser::ReplyIoctl + ) { + let path = get_path!(self, ino, reply); + debug!("ioctl: {:?} {:#x} {:#x}", path, flags, cmd); + let target = self.target.clone(); + let req_info = req.info(); + let data_buf = Vec::from(in_data); + self.threadpool_run(move || { + target.ioctl(req_info, &path, fh, flags, cmd, &data_buf, + |result| { + match result { + Ok(data) => reply.ioctl(data.0, + &data.1[..out_size as usize]), + Err(e) => reply.error(e), + } + CallbackResult { + _private: std::marker::PhantomData {}, + } + }); + }); + } + fn flush( &mut self, req: &fuser::Request<'_>, @@ -826,7 +864,27 @@ impl fuser::Filesystem for FuseMT { // bmap - #[cfg(target_os = "macos")] + + fn fallocate( + &mut self, + req: &fuser::Request<'_>, + ino: u64, + fh: u64, + offset: i64, + length: i64, + mode: i32, + reply: fuser::ReplyEmpty, + ) { + let path = get_path!(self, ino, reply); + debug!("fallocate: {:?}, offset={:?}, length={:?}, mode={:#o}", + path, offset, length, mode); + match self.target.fallocate(req.info(), &path, fh, offset, length, mode) { + Ok(()) => reply.ok(), + Err(e) => reply.error(e), + } + } + + #[cfg(target_os = "macos")] fn setvolname( &mut self, req: &fuser::Request<'_>, diff --git a/src/inode_table.rs b/src/inode_table.rs index 50c82cd..b8fdc62 100644 --- a/src/inode_table.rs +++ b/src/inode_table.rs @@ -10,6 +10,7 @@ use std::collections::hash_map::Entry::*; use std::hash::{Hash, Hasher}; use std::path::{Path, PathBuf}; use std::sync::Arc; +use tracing::{debug, error}; pub type Inode = u64; pub type Generation = u64; diff --git a/src/lib.rs b/src/lib.rs index c9ec0e7..5a15f6b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,7 +17,8 @@ #![deny(rust_2018_idioms)] #[macro_use] -extern crate log; +extern crate libc; + mod directory_cache; mod fusemt; @@ -26,6 +27,7 @@ mod types; pub const VERSION: &str = env!("CARGO_PKG_VERSION"); + pub use fuser::FileType; pub use crate::fusemt::*; pub use crate::types::*; diff --git a/src/types.rs b/src/types.rs index f93174a..e1ce497 100644 --- a/src/types.rs +++ b/src/types.rs @@ -17,7 +17,7 @@ pub struct RequestInfo { /// The group ID of the process making the request. pub gid: u32, /// The process ID of the process making the request. - pub pid: u32, + pub pid: i32, } /// A directory entry. @@ -99,6 +99,11 @@ pub enum Xattr { Data(Vec), } +#[derive(Clone, Debug)] +pub struct BmapBlock { + pub block: u64, +} + #[cfg(target_os = "macos")] #[derive(Clone, Debug)] pub struct XTimes { @@ -116,6 +121,9 @@ pub type ResultWrite = Result; pub type ResultStatfs = Result; pub type ResultCreate = Result; pub type ResultXattr = Result; +pub type ResultBmap = Result; +pub type ResultLseek = Result; +pub type ResultIOCTL<'a> = Result<(i32, &'a [u8]), libc::c_int>; #[cfg(target_os = "macos")] pub type ResultXTimes = Result; @@ -471,6 +479,95 @@ pub trait FilesystemMT { // bmap + /// Test for a POSIX file lock. + #[allow(clippy::too_many_arguments)] + fn getlk(&self, _req: &RequestInfo, _path: &Path, _fh: u64, + _lock_owner: u64, _start: u64, _end: u64, _typ: i32, _pid: u32) -> ResultEmpty { + Err(libc::ENOSYS) + } + + /// Acquire, modify or release a POSIX file lock. + /// 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 + /// implemented, the kernel will still allow file locking to work locally. + /// Hence these are only interesting for network filesystems and similar. + #[allow(clippy::too_many_arguments)] + fn setlk(&self, + _req: RequestInfo, + _path: &Path, + _fh: Option, _lock_owner: u64, + _start: u64, _end: u64, + _typ: i32, _pid: u32, + _sleep: bool)-> ResultEmpty { + Err(libc::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(&self, + _req: RequestInfo, + _path: &Path, + _blocksize: u32, + _idx: u64) -> ResultBmap { + Err(libc::ENOSYS) + } + + /// control device + #[allow(clippy::too_many_arguments)] + fn ioctl( + &self, + _req: RequestInfo, + _path: &Path, + _fh: u64, _flags: u32, + _cmd: u32, + _in_data: &[u8], + callback: impl FnOnce(ResultIOCTL<'_>) -> + CallbackResult) -> CallbackResult { + callback(Err(libc::ENOSYS)) + } + + /// Preallocate or deallocate space to a file + fn fallocate( + &self, + _req: RequestInfo, + _path: &Path, + _fh: u64, _offset: i64, + _length: i64, _mode: i32) -> ResultEmpty { + Err(libc::ENOSYS) + } + + /// Reposition read/write file offset + fn lseek( + &self, + _req: RequestInfo, + _fh: u64, + _offset: i64, + _whence: i32 + ) -> ResultLseek { + Err(libc::ENOSYS) + } + + /// Copy the specified range from the source inode to the destination inode + #[allow(clippy::too_many_arguments)] + fn copy_file_range( + &self, + _req: RequestInfo, + _path_in: &Path, + _fh_in: Option, + _offset_in: i64, + _path_out: &Path, + _fh_out: Option, + _offset_out: i64, + _len: u64, + _flags: u32, + ) -> ResultWrite { + Err(libc::ENOSYS) + } + + /// macOS only: Rename the volume. /// /// * `name`: new name for the volume