use anyhow::Result; use fuse_backend_rs::abi::fuse_abi::{stat64, statvfs64, FsOptions, OpenOptions}; use fuse_backend_rs::api::filesystem::{Context, DirEntry, Entry, FileSystem, ZeroCopyWriter}; use std::collections::HashMap; use std::ffi::CStr; use std::fs::File; use std::io::Read; use std::os::unix::io::{AsRawFd, FromRawFd}; use std::sync::{Arc, RwLock}; use std::time::{Duration, SystemTime}; use super::cache::ThreadSafeCache; use super::db::DbManager; const TTL: Duration = Duration::from_secs(1); const READ_CHUNK_SIZE: usize = 524288; // 512KB pub struct MarkBaseFs { db: Arc, cache: Arc, inode_map: RwLock>, next_inode: RwLock, } impl MarkBaseFs { pub fn new(db_path: &str, tree_type: &str) -> Result { let db = DbManager::open(db_path, tree_type)?; let cache = ThreadSafeCache::new(); Ok(Self { db: Arc::new(db), cache: Arc::new(cache), inode_map: RwLock::new(HashMap::new()), next_inode: RwLock::new(2), }) } fn find_node_id_by_inode(&self, ino: u64) -> Option { self.inode_map.read().unwrap().get(&ino).cloned() } fn get_or_create_inode(&self, node_id: &str) -> u64 { let map = self.inode_map.read().unwrap(); for (ino, id) in &*map { if id == node_id { return *ino; } } drop(map); let mut next = self.next_inode.write().unwrap(); let ino = *next; *next += 1; let mut map = self.inode_map.write().unwrap(); map.insert(ino, node_id.to_string()); ino } fn make_stat64(node_type: &str, file_size: u64) -> stat64 { let mut st = unsafe { std::mem::zeroed::() }; st.st_ino = 0; st.st_mode = if node_type == "folder" { 0o755 } else { 0o644 }; st.st_nlink = if node_type == "folder" { 2 } else { 1 }; st.st_uid = 501; st.st_gid = 20; st.st_size = file_size as i64; st.st_blocks = file_size.div_ceil(512) as i64; st.st_blksize = 4096; let now = SystemTime::now(); st.st_atime = now .duration_since(SystemTime::UNIX_EPOCH) .unwrap() .as_secs() as i64; st.st_mtime = st.st_atime; st.st_ctime = st.st_atime; st } } impl FileSystem for MarkBaseFs { type Inode = u64; type Handle = u64; fn init(&self, capable: FsOptions) -> std::io::Result { Ok(capable) } fn lookup(&self, _ctx: &Context, parent: Self::Inode, name: &CStr) -> std::io::Result { let name_str = name.to_string_lossy(); let node_id = if parent == 1 { self.db .find_node_id(&format!("/{}", name_str)) .ok() .flatten() } else { let parent_id = self.find_node_id_by_inode(parent); match parent_id { Some(pid) => self .db .find_node_id_by_parent(&pid, &name_str) .ok() .flatten(), None => None, } }; match node_id { Some(id) => { let info = self.db.get_node_info(&id); match info { Ok(Some((node_type, file_size))) => { let ino = self.get_or_create_inode(&id); let attr = MarkBaseFs::make_stat64(&node_type, file_size); Ok(Entry { inode: ino, generation: 0, attr, attr_flags: 0, attr_timeout: TTL, entry_timeout: TTL, }) } _ => Err(std::io::Error::from_raw_os_error(libc::ENOENT)), } } None => Err(std::io::Error::from_raw_os_error(libc::ENOENT)), } } fn forget(&self, _ctx: &Context, inode: Self::Inode, _count: u64) { let mut map = self.inode_map.write().unwrap(); map.remove(&inode); } fn getattr( &self, _ctx: &Context, inode: Self::Inode, _handle: Option, ) -> std::io::Result<(stat64, Duration)> { if inode == 1 { let attr = MarkBaseFs::make_stat64("folder", 0); return Ok((attr, TTL)); } let node_id = self.find_node_id_by_inode(inode); match node_id { Some(id) => { let info = self.db.get_node_info(&id); match info { Ok(Some((node_type, file_size))) => { let attr = MarkBaseFs::make_stat64(&node_type, file_size); Ok((attr, TTL)) } _ => Err(std::io::Error::from_raw_os_error(libc::ENOENT)), } } None => Err(std::io::Error::from_raw_os_error(libc::ENOENT)), } } fn open( &self, _ctx: &Context, inode: Self::Inode, _flags: u32, _fuse_flags: u32, ) -> std::io::Result<(Option, OpenOptions, Option)> { Ok((Some(inode), OpenOptions::empty(), None)) } fn read( &self, _ctx: &Context, inode: Self::Inode, _handle: Self::Handle, w: &mut dyn ZeroCopyWriter, size: u32, offset: u64, _lock_owner: Option, _flags: u32, ) -> std::io::Result { let node_id = self.find_node_id_by_inode(inode); match node_id { Some(id) => { let file_path = self.db.get_file_path(&id); match file_path { Ok(Some(fp)) => { let file = File::open(&fp)?; let fd = file.as_raw_fd(); let f = unsafe { File::from_raw_fd(fd) }; let mut f = std::mem::ManuallyDrop::new(f); w.write_from(&mut *f, size as usize, offset) } _ => Err(std::io::Error::from_raw_os_error(libc::ENOENT)), } } None => Err(std::io::Error::from_raw_os_error(libc::ENOENT)), } } fn release( &self, _ctx: &Context, _inode: Self::Inode, _flags: u32, _handle: Self::Handle, _flush: bool, _flock_release: bool, _lock_owner: Option, ) -> std::io::Result<()> { Ok(()) } fn opendir( &self, _ctx: &Context, inode: Self::Inode, _flags: u32, ) -> std::io::Result<(Option, OpenOptions)> { Ok((Some(inode), OpenOptions::empty())) } fn readdir( &self, _ctx: &Context, inode: Self::Inode, _handle: Self::Handle, _size: u32, offset: u64, add_entry: &mut dyn FnMut(DirEntry) -> std::io::Result, ) -> std::io::Result<()> { if offset == 0 { add_entry(DirEntry { name: b".\0", ino: inode, offset: 1, type_: libc::S_IFDIR as u32, })?; add_entry(DirEntry { name: b"..\0", ino: 1, offset: 2, type_: libc::S_IFDIR as u32, })?; } Ok(()) } fn releasedir( &self, _ctx: &Context, _inode: Self::Inode, _flags: u32, _handle: Self::Handle, ) -> std::io::Result<()> { Ok(()) } fn statfs(&self, _ctx: &Context, _inode: Self::Inode) -> std::io::Result { let mut st = unsafe { std::mem::zeroed::() }; st.f_bsize = 4096; st.f_blocks = 1000000; st.f_bfree = 500000; st.f_bavail = 500000; st.f_files = 1000; st.f_ffree = 500; st.f_favail = 500; st.f_namemax = 255; Ok(st) } }