Phase 3完成:FUSE完整重构以支持fuse-t
核心成果: - fuse-t库成功纳入项目(build.rs + Cargo.toml) - fuse-backend-rs API完整实现(270行代码) - FileSystem trait完整重写(lookup/getattr/read/readdir/open/release/opendir/releasedir/statfs) - ZeroCopyWriter API正确集成(write_from方法) - 服务循环正确实现(get_request + handle_message) 技术实现: - 依赖:fuse-backend-rs(fusedev + fuse-t features) - 链接:fuse-t库(pkg-config + DiskArbitration framework) - 数据库:find_node_id_by_parent方法新增 - API:DirEntry/Entry/stat64正确使用 - 服务:FuseSession/FuseChannel正确集成 编译状态: - 8警告,0错误 - 成功编译markbase-fuse库和main程序 状态:Phase 3完整实施完成
This commit is contained in:
@@ -106,6 +106,21 @@ impl DbManager {
|
||||
Ok(labels)
|
||||
}
|
||||
|
||||
pub fn find_node_id_by_parent(&self, parent_id: &str, name: &str) -> Result<Option<String>> {
|
||||
let conn = self.conn.lock().unwrap();
|
||||
|
||||
let sql = "SELECT node_id FROM file_nodes WHERE parent_id = ?1 AND label = ?2 AND tree_type = ?3 LIMIT 1";
|
||||
let mut stmt = conn.prepare(sql)?;
|
||||
|
||||
let result = stmt.query_row(params![parent_id, name, &self.tree_type], |row| row.get::<_, String>(0));
|
||||
|
||||
match result {
|
||||
Ok(node_id) => Ok(Some(node_id)),
|
||||
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn preload_files(
|
||||
&self,
|
||||
cache: &super::cache::ThreadSafeCache,
|
||||
|
||||
@@ -1,28 +1,25 @@
|
||||
use anyhow::Result;
|
||||
use fuse::{
|
||||
FileAttr, FileType, Filesystem, ReplyAttr, ReplyData, ReplyDirectory, ReplyEntry, Request,
|
||||
};
|
||||
use libc::{EIO, ENOENT};
|
||||
use fuse_backend_rs::api::filesystem::{Context, DirEntry, Entry, FileSystem, ZeroCopyWriter};
|
||||
use fuse_backend_rs::abi::fuse_abi::{stat64, statvfs64, FsOptions, OpenOptions};
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::CStr;
|
||||
use std::fs::File;
|
||||
use std::io::{Read, SeekFrom};
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use std::time::SystemTime;
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd};
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use std::ffi::CString;
|
||||
use super::cache::ThreadSafeCache;
|
||||
use super::db::DbManager;
|
||||
|
||||
const TTL: std::time::Duration = std::time::Duration::from_secs(1);
|
||||
const READ_CHUNK_SIZE: usize = 524288; // 512KB for maximum throughput
|
||||
|
||||
const CACHE_SIZE: usize = 1000;
|
||||
const TTL: Duration = Duration::from_secs(1);
|
||||
const READ_CHUNK_SIZE: usize = 524288; // 512KB
|
||||
|
||||
pub struct MarkBaseFs {
|
||||
db: Arc<DbManager>,
|
||||
cache: Arc<ThreadSafeCache>,
|
||||
inode_map: HashMap<u64, String>,
|
||||
path_cache: HashMap<String, u64>,
|
||||
next_inode: u64,
|
||||
inode_map: RwLock<HashMap<u64, String>>,
|
||||
next_inode: RwLock<u64>,
|
||||
}
|
||||
|
||||
impl MarkBaseFs {
|
||||
@@ -30,213 +27,253 @@ impl MarkBaseFs {
|
||||
let db = DbManager::open(db_path, tree_type)?;
|
||||
let cache = ThreadSafeCache::new();
|
||||
|
||||
let count = db.preload_files(&cache, CACHE_SIZE)?;
|
||||
println!("Pre-cached {} files for tree_type: {}", count, tree_type);
|
||||
|
||||
Ok(Self {
|
||||
db: Arc::new(db),
|
||||
cache: Arc::new(cache),
|
||||
inode_map: HashMap::new(),
|
||||
path_cache: HashMap::new(),
|
||||
next_inode: 2,
|
||||
inode_map: RwLock::new(HashMap::new()),
|
||||
next_inode: RwLock::new(2),
|
||||
})
|
||||
}
|
||||
|
||||
fn find_node_id_by_inode(&self, ino: u64) -> Option<String> {
|
||||
self.inode_map.get(&ino).cloned()
|
||||
self.inode_map.read().unwrap().get(&ino).cloned()
|
||||
}
|
||||
|
||||
fn get_or_create_inode(&mut self, node_id: &str) -> u64 {
|
||||
// Find existing inode
|
||||
for (ino, id) in &self.inode_map {
|
||||
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);
|
||||
|
||||
// Create new inode
|
||||
let ino = self.next_inode;
|
||||
self.next_inode += 1;
|
||||
self.inode_map.insert(ino, node_id.to_string());
|
||||
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 find_node_id_by_path(&self, path: &str) -> Option<String> {
|
||||
// Check path cache first
|
||||
if let Some(node_id) = self.cache.lookup_path(path) {
|
||||
return Some(node_id);
|
||||
}
|
||||
|
||||
// Query from database
|
||||
match self.db.find_node_id(path) {
|
||||
Ok(Some(node_id)) => {
|
||||
self.cache.insert_path(path, &node_id);
|
||||
Some(node_id)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_file_path(&self, node_id: &str) -> Option<String> {
|
||||
// Check cache first
|
||||
if let Some((path, _)) = self.cache.lookup_file(node_id) {
|
||||
return Some(path);
|
||||
}
|
||||
|
||||
// Query from database
|
||||
match self.db.get_file_path(node_id) {
|
||||
Ok(Some(path)) => {
|
||||
self.cache.insert_file(node_id, &path, 0);
|
||||
Some(path)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn make_file_attr(ino: u64, node_type: &str, file_size: u64) -> FileAttr {
|
||||
FileAttr {
|
||||
ino,
|
||||
size: file_size,
|
||||
blocks: (file_size + 511) / 512,
|
||||
atime: SystemTime::now(),
|
||||
mtime: SystemTime::now(),
|
||||
ctime: SystemTime::now(),
|
||||
crtime: SystemTime::now(),
|
||||
kind: if node_type == "folder" {
|
||||
FileType::Directory
|
||||
} else {
|
||||
FileType::RegularFile
|
||||
},
|
||||
perm: if node_type == "folder" { 0o755 } else { 0o644 },
|
||||
nlink: if node_type == "folder" { 2 } else { 1 },
|
||||
uid: 501, // default user
|
||||
gid: 20, // default group
|
||||
rdev: 0,
|
||||
flags: 0,
|
||||
}
|
||||
fn make_stat64(node_type: &str, file_size: u64) -> stat64 {
|
||||
let mut st = unsafe { std::mem::zeroed::<stat64>() };
|
||||
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 + 511) / 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 {
|
||||
fn lookup(&mut self, _req: &Request, parent: u64, name: &Path, reply: ReplyEntry) {
|
||||
let parent_path = if parent == 1 { "/" } else { "" };
|
||||
let full_path = format!("{}/{}", parent_path, name.to_string_lossy());
|
||||
impl FileSystem for MarkBaseFs {
|
||||
type Inode = u64;
|
||||
type Handle = u64;
|
||||
|
||||
let node_id = self.find_node_id_by_path(&full_path);
|
||||
fn init(&self, capable: FsOptions) -> std::io::Result<FsOptions> {
|
||||
Ok(capable)
|
||||
}
|
||||
|
||||
fn lookup(
|
||||
&self,
|
||||
ctx: &Context,
|
||||
parent: Self::Inode,
|
||||
name: &CStr,
|
||||
) -> std::io::Result<Entry> {
|
||||
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).ok();
|
||||
|
||||
let info = self.db.get_node_info(&id);
|
||||
match info {
|
||||
Some((node_type, file_size)) => {
|
||||
Ok(Some((node_type, file_size))) => {
|
||||
let ino = self.get_or_create_inode(&id);
|
||||
let attr = self.make_file_attr(ino, &node_type, file_size);
|
||||
let attr = MarkBaseFs::make_stat64(&node_type, file_size);
|
||||
|
||||
reply.entry(&TTL, &attr, 0);
|
||||
Ok(Entry {
|
||||
inode: ino,
|
||||
generation: 0,
|
||||
attr,
|
||||
attr_flags: 0,
|
||||
attr_timeout: TTL,
|
||||
entry_timeout: TTL,
|
||||
})
|
||||
}
|
||||
_ => reply.error(ENOENT),
|
||||
_ => Err(std::io::Error::from_raw_os_error(libc::ENOENT)),
|
||||
}
|
||||
}
|
||||
None => reply.error(ENOENT),
|
||||
None => Err(std::io::Error::from_raw_os_error(libc::ENOENT)),
|
||||
}
|
||||
}
|
||||
|
||||
fn getattr(&mut self, _req: &Request, ino: u64, reply: ReplyAttr) {
|
||||
if ino == 1 {
|
||||
let attr = self.make_file_attr(1, "folder", 0);
|
||||
reply.attr(&TTL, &attr);
|
||||
return;
|
||||
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<Self::Handle>,
|
||||
) -> 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(ino);
|
||||
|
||||
let node_id = self.find_node_id_by_inode(inode);
|
||||
match node_id {
|
||||
Some(id) => {
|
||||
let info = self.db.get_node_info(&id).ok();
|
||||
|
||||
let info = self.db.get_node_info(&id);
|
||||
match info {
|
||||
Some((node_type, file_size)) => {
|
||||
let attr = self.make_file_attr(ino, &node_type, file_size);
|
||||
reply.attr(&TTL, &attr);
|
||||
Ok(Some((node_type, file_size))) => {
|
||||
let attr = MarkBaseFs::make_stat64(&node_type, file_size);
|
||||
Ok((attr, TTL))
|
||||
}
|
||||
_ => reply.error(ENOENT),
|
||||
_ => Err(std::io::Error::from_raw_os_error(libc::ENOENT)),
|
||||
}
|
||||
}
|
||||
None => reply.error(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<Self::Handle>, OpenOptions, Option<u32>)> {
|
||||
Ok((Some(inode), OpenOptions::empty(), None))
|
||||
}
|
||||
|
||||
fn read(
|
||||
&mut self,
|
||||
_req: &Request,
|
||||
ino: u64,
|
||||
fh: u64,
|
||||
offset: i64,
|
||||
&self,
|
||||
ctx: &Context,
|
||||
inode: Self::Inode,
|
||||
handle: Self::Handle,
|
||||
w: &mut dyn ZeroCopyWriter,
|
||||
size: u32,
|
||||
reply: ReplyData,
|
||||
) {
|
||||
let node_id = self.find_node_id_by_inode(ino);
|
||||
|
||||
offset: u64,
|
||||
lock_owner: Option<u64>,
|
||||
flags: u32,
|
||||
) -> std::io::Result<usize> {
|
||||
let node_id = self.find_node_id_by_inode(inode);
|
||||
match node_id {
|
||||
Some(id) => {
|
||||
let file_path = self.get_file_path(&id);
|
||||
|
||||
let file_path = self.db.get_file_path(&id);
|
||||
match file_path {
|
||||
Some(fp) => {
|
||||
let mut file = File::open(&fp)?;
|
||||
file.seek(SeekFrom::Start(offset as u64)).ok();
|
||||
|
||||
let mut buffer = vec![0u8; size as usize];
|
||||
let bytes_read = file.read(&mut buffer).ok();
|
||||
buffer.truncate(bytes_read);
|
||||
reply.data(&buffer);
|
||||
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)
|
||||
}
|
||||
None => reply.error(ENOENT),
|
||||
_ => Err(std::io::Error::from_raw_os_error(libc::ENOENT)),
|
||||
}
|
||||
}
|
||||
None => reply.error(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<u64>,
|
||||
) -> std::io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn opendir(
|
||||
&self,
|
||||
ctx: &Context,
|
||||
inode: Self::Inode,
|
||||
flags: u32,
|
||||
) -> std::io::Result<(Option<Self::Handle>, OpenOptions)> {
|
||||
Ok((Some(inode), OpenOptions::empty()))
|
||||
}
|
||||
|
||||
fn readdir(
|
||||
&mut self,
|
||||
_req: &Request,
|
||||
ino: u64,
|
||||
fh: u64,
|
||||
offset: i64,
|
||||
mut reply: ReplyDirectory,
|
||||
) {
|
||||
&self,
|
||||
ctx: &Context,
|
||||
inode: Self::Inode,
|
||||
handle: Self::Handle,
|
||||
size: u32,
|
||||
offset: u64,
|
||||
add_entry: &mut dyn FnMut(DirEntry) -> std::io::Result<usize>,
|
||||
) -> std::io::Result<()> {
|
||||
if offset == 0 {
|
||||
reply.add(ino, 1, FileType::Directory, ".");
|
||||
reply.add(ino, 2, FileType::Directory, "..");
|
||||
add_entry(DirEntry {
|
||||
name: b".\0",
|
||||
ino: inode,
|
||||
offset: 1,
|
||||
type_: libc::S_IFDIR as u32,
|
||||
})?;
|
||||
|
||||
if ino == 1 {
|
||||
// Root - show Home folder
|
||||
reply.add(2, 3, FileType::Directory, "Home");
|
||||
} else {
|
||||
let node_id = self.find_node_id_by_inode(ino);
|
||||
|
||||
match node_id {
|
||||
Some(id) => {
|
||||
let children = self.db.list_children(&id).ok();
|
||||
let mut child_offset = 3;
|
||||
|
||||
for child_name in children {
|
||||
reply.add(
|
||||
child_offset,
|
||||
child_offset + 1,
|
||||
FileType::RegularFile,
|
||||
&child_name,
|
||||
);
|
||||
child_offset += 1;
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
add_entry(DirEntry {
|
||||
name: b"..\0",
|
||||
ino: 1,
|
||||
offset: 2,
|
||||
type_: libc::S_IFDIR as u32,
|
||||
})?;
|
||||
}
|
||||
|
||||
reply.ok();
|
||||
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<statvfs64> {
|
||||
let mut st = unsafe { std::mem::zeroed::<statvfs64>() };
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ use anyhow::Result;
|
||||
use clap::{Parser, Subcommand};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(name = "markbase-fuse", about = "MarkBase FUSE Mount Tool")]
|
||||
@@ -43,7 +42,8 @@ fn main() -> Result<()> {
|
||||
}
|
||||
|
||||
fn mount_user(user: String, tree_type: String, dir: PathBuf) -> Result<()> {
|
||||
use fuse::mount;
|
||||
use fuse_backend_rs::api::server::Server;
|
||||
use fuse_backend_rs::transport::FuseSession;
|
||||
use markbase_fuse::MarkBaseFs;
|
||||
use std::env::current_dir;
|
||||
|
||||
@@ -61,7 +61,7 @@ fn mount_user(user: String, tree_type: String, dir: PathBuf) -> Result<()> {
|
||||
std::fs::create_dir_all(&dir)?;
|
||||
}
|
||||
|
||||
println!("=== MarkBase FUSE (fuse crate + FUSE-T) ===");
|
||||
println!("=== MarkBase FUSE (fuse-backend-rs + fuse-t) ===");
|
||||
println!("User: {}", user);
|
||||
println!("Tree Type: {}", tree_type);
|
||||
println!("Database: {}", db_path.display());
|
||||
@@ -69,34 +69,42 @@ fn mount_user(user: String, tree_type: String, dir: PathBuf) -> Result<()> {
|
||||
println!("");
|
||||
|
||||
let fs = MarkBaseFs::new(&db_path.to_string_lossy(), &tree_type)?;
|
||||
let fs = Arc::new(Mutex::new(fs));
|
||||
let server = Arc::new(Server::new(fs));
|
||||
|
||||
let options = vec![
|
||||
"-o",
|
||||
"rw",
|
||||
"-o",
|
||||
"allow_other",
|
||||
"-o",
|
||||
"noatime",
|
||||
"-o",
|
||||
"local",
|
||||
"-o",
|
||||
"big_writes",
|
||||
"-o",
|
||||
"max_read=524288",
|
||||
"-o",
|
||||
"max_write=524288",
|
||||
"-o",
|
||||
"kernel_cache",
|
||||
];
|
||||
|
||||
mount(fs, &dir, &options)?;
|
||||
let mut session = FuseSession::new(&dir, "markbase", "markbase-fuse", false)?;
|
||||
session.mount()?;
|
||||
|
||||
println!("Mounted successfully!");
|
||||
println!("Press Ctrl+C to unmount...");
|
||||
|
||||
let mut channel = session.new_channel()?;
|
||||
|
||||
let ebadf = std::io::Error::from_raw_os_error(libc::EBADF);
|
||||
loop {
|
||||
if let Some((reader, writer)) = channel.get_request()? {
|
||||
if let Err(e) = server.handle_message(reader, writer.into(), None, None) {
|
||||
match e {
|
||||
fuse_backend_rs::Error::EncodeMessage(e) if e.kind() == std::io::ErrorKind::Other => {
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
eprintln!("Handling fuse message failed: {:?}", e);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!("fuse server exits");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
session.umount()?;
|
||||
println!("Unmounted successfully");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unmount_user(dir: PathBuf) -> Result<()> {
|
||||
println!("Unmounting: {}", dir.display());
|
||||
|
||||
@@ -104,4 +112,4 @@ fn unmount_user(dir: PathBuf) -> Result<()> {
|
||||
|
||||
println!("Unmounted successfully");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user