use std::collections::HashMap; use std::sync::Mutex; use std::time::SystemTime; use async_trait::async_trait; use nfsserve::nfs::*; use nfsserve::vfs::{DirEntry, NFSFileSystem, ReadDirResult, VFSCapabilities}; use crate::nfs::markbase_fs::MarkBaseFS; pub struct MarkBaseNFSBackend { fs: MarkBaseFS, id_map: Mutex>, // fileid -> node_id reverse_map: Mutex>, // node_id -> fileid next_id: Mutex, } impl MarkBaseNFSBackend { pub fn new(user_id: String, db_path: std::path::PathBuf) -> anyhow::Result { let fs = MarkBaseFS::new(user_id, db_path)?; Ok(MarkBaseNFSBackend { fs, id_map: Mutex::new(HashMap::new()), reverse_map: Mutex::new(HashMap::new()), next_id: Mutex::new(2), // 1 is root }) } fn allocate_id(&self, node_id: &str) -> u64 { let mut reverse_map = self.reverse_map.lock().unwrap(); if let Some(id) = reverse_map.get(node_id) { return *id; } let mut next_id = self.next_id.lock().unwrap(); let id = *next_id; *next_id += 1; reverse_map.insert(node_id.to_string(), id); self.id_map.lock().unwrap().insert(id, node_id.to_string()); id } fn get_node_id(&self, fileid: u64) -> Option { self.id_map.lock().unwrap().get(&fileid).cloned() } fn get_fileid_from_node(&self, node_id: &str) -> u64 { self.reverse_map .lock() .unwrap() .get(node_id) .copied() .unwrap_or_else(|| self.allocate_id(node_id)) } } #[async_trait] impl NFSFileSystem for MarkBaseNFSBackend { fn capabilities(&self) -> VFSCapabilities { VFSCapabilities::ReadOnly } fn root_dir(&self) -> fileid3 { 1 } async fn lookup(&self, dirid: fileid3, filename: &filename3) -> Result { let dir_node_id = if dirid == 1 { "root".to_string() } else { self.get_node_id(dirid).ok_or(nfsstat3::NFS3ERR_STALE)? }; let filename_str = String::from_utf8_lossy(filename).to_string(); let conn = self .fs .conn .lock() .map_err(|_| nfsstat3::NFS3ERR_SERVERFAULT)?; let query = if dir_node_id == "root" { "SELECT node_id FROM file_nodes WHERE parent_id IS NULL AND label = ?1" } else { "SELECT node_id FROM file_nodes WHERE parent_id = ?1 AND label = ?2" }; let node_id: String = if dir_node_id == "root" { conn.query_row(query, [&filename_str], |row| row.get(0)) .map_err(|_| nfsstat3::NFS3ERR_NOENT)? } else { conn.query_row(query, [dir_node_id, filename_str], |row| row.get(0)) .map_err(|_| nfsstat3::NFS3ERR_NOENT)? }; Ok(self.get_fileid_from_node(&node_id)) } async fn getattr(&self, id: fileid3) -> Result { if id == 1 { return Ok(fattr3 { ftype: ftype3::NF3DIR, mode: 0o755, nlink: 1, uid: 0, gid: 0, size: 0, used: 0, rdev: specdata3 { specdata1: 0, specdata2: 0, }, fsid: 0, fileid: 1, atime: nfstime3 { seconds: 0, nseconds: 0, }, mtime: nfstime3 { seconds: 0, nseconds: 0, }, ctime: nfstime3 { seconds: 0, nseconds: 0, }, }); } let node_id = self.get_node_id(id).ok_or(nfsstat3::NFS3ERR_STALE)?; let conn = self .fs .conn .lock() .map_err(|_| nfsstat3::NFS3ERR_SERVERFAULT)?; let (node_type, file_size): (String, i64) = conn .query_row( "SELECT node_type, file_size FROM file_nodes WHERE node_id = ?1", [&node_id], |row| Ok((row.get::<_, String>(0)?, row.get::<_, i64>(1)?)), ) .map_err(|_| nfsstat3::NFS3ERR_NOENT)?; let type_ = if node_type == "folder" { ftype3::NF3DIR } else { ftype3::NF3REG }; let now = SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) .unwrap() .as_secs(); Ok(fattr3 { ftype: type_, mode: if node_type == "folder" { 0o755 } else { 0o644 }, nlink: 1, uid: 0, gid: 0, size: file_size as u64, used: file_size as u64, rdev: specdata3 { specdata1: 0, specdata2: 0, }, fsid: 0, fileid: id, atime: nfstime3 { seconds: now as u32, nseconds: 0, }, mtime: nfstime3 { seconds: now as u32, nseconds: 0, }, ctime: nfstime3 { seconds: now as u32, nseconds: 0, }, }) } async fn setattr(&self, _id: fileid3, _setattr: sattr3) -> Result { Err(nfsstat3::NFS3ERR_ROFS) } async fn read( &self, id: fileid3, offset: u64, count: u32, ) -> Result<(Vec, bool), nfsstat3> { let node_id = self.get_node_id(id).ok_or(nfsstat3::NFS3ERR_STALE)?; let conn = self .fs .conn .lock() .map_err(|_| nfsstat3::NFS3ERR_SERVERFAULT)?; let aliases_json: String = conn .query_row( "SELECT aliases_json FROM file_nodes WHERE node_id = ?1", [&node_id], |row| row.get(0), ) .map_err(|_| nfsstat3::NFS3ERR_NOENT)?; let aliases: serde_json::Value = serde_json::from_str(&aliases_json).map_err(|_| nfsstat3::NFS3ERR_SERVERFAULT)?; let file_path = aliases["path"].as_str().ok_or(nfsstat3::NFS3ERR_NOENT)?; let file_data = std::fs::read(file_path).map_err(|_| nfsstat3::NFS3ERR_IO)?; let file_size = file_data.len() as u64; let start = offset.min(file_size) as usize; let end = (offset + count as u64).min(file_size) as usize; let data = file_data[start..end].to_vec(); let eof = end >= file_size as usize; Ok((data, eof)) } async fn write(&self, _id: fileid3, _offset: u64, _data: &[u8]) -> Result { Err(nfsstat3::NFS3ERR_ROFS) } async fn create( &self, _dirid: fileid3, _filename: &filename3, _attr: sattr3, ) -> Result<(fileid3, fattr3), nfsstat3> { Err(nfsstat3::NFS3ERR_ROFS) } async fn create_exclusive( &self, _dirid: fileid3, _filename: &filename3, ) -> Result { Err(nfsstat3::NFS3ERR_ROFS) } async fn mkdir( &self, _dirid: fileid3, _dirname: &filename3, ) -> Result<(fileid3, fattr3), nfsstat3> { Err(nfsstat3::NFS3ERR_ROFS) } async fn remove(&self, _dirid: fileid3, _filename: &filename3) -> Result<(), nfsstat3> { Err(nfsstat3::NFS3ERR_ROFS) } async fn rename( &self, _from_dirid: fileid3, _from_filename: &filename3, _to_dirid: fileid3, _to_filename: &filename3, ) -> Result<(), nfsstat3> { Err(nfsstat3::NFS3ERR_ROFS) } async fn readdir( &self, dirid: fileid3, start_after: fileid3, max_entries: usize, ) -> Result { let dir_node_id = if dirid == 1 { "root".to_string() } else { self.get_node_id(dirid).ok_or(nfsstat3::NFS3ERR_STALE)? }; let conn = self .fs .conn .lock() .map_err(|_| nfsstat3::NFS3ERR_SERVERFAULT)?; let query = if dir_node_id == "root" { "SELECT node_id, label, node_type, file_size FROM file_nodes WHERE parent_id IS NULL" } else { "SELECT node_id, label, node_type, file_size FROM file_nodes WHERE parent_id = ?1" }; let mut stmt = conn .prepare(query) .map_err(|_| nfsstat3::NFS3ERR_SERVERFAULT)?; let rows: Vec<(String, String, String, Option)> = if dir_node_id == "root" { stmt.query_map([], |row| { row.get::<_, String>(0).and_then(|node_id| { row.get::<_, String>(1).and_then(|label| { row.get::<_, String>(2).and_then(|node_type| { row.get::<_, Option>(3) .map(|file_size| (node_id, label, node_type, file_size)) }) }) }) }) .map_err(|_| nfsstat3::NFS3ERR_SERVERFAULT)? .collect::, _>>() .map_err(|_| nfsstat3::NFS3ERR_SERVERFAULT)? } else { stmt.query_map([&dir_node_id.as_str()], |row| { row.get::<_, String>(0).and_then(|node_id| { row.get::<_, String>(1).and_then(|label| { row.get::<_, String>(2).and_then(|node_type| { row.get::<_, Option>(3) .map(|file_size| (node_id, label, node_type, file_size)) }) }) }) }) .map_err(|_| nfsstat3::NFS3ERR_SERVERFAULT)? .collect::, _>>() .map_err(|_| nfsstat3::NFS3ERR_SERVERFAULT)? }; let mut entries: Vec = Vec::new(); let mut started = start_after == 0; for row in rows { let (node_id, label, node_type, file_size_opt) = row; let file_size = file_size_opt.unwrap_or(0); let fileid = self.get_fileid_from_node(&node_id); if !started && fileid == start_after { started = true; continue; } if started && entries.len() < max_entries { let attr = fattr3 { ftype: if node_type == "folder" { ftype3::NF3DIR } else { ftype3::NF3REG }, mode: if node_type == "folder" { 0o755 } else { 0o644 }, nlink: 1, uid: 0, gid: 0, size: file_size as u64, used: file_size as u64, rdev: specdata3 { specdata1: 0, specdata2: 0, }, fsid: 0, fileid, atime: nfstime3 { seconds: 0, nseconds: 0, }, mtime: nfstime3 { seconds: 0, nseconds: 0, }, ctime: nfstime3 { seconds: 0, nseconds: 0, }, }; entries.push(DirEntry { fileid, name: nfsserve::nfs::nfsstring(label.into_bytes()), attr, }); } } Ok(ReadDirResult { entries, end: true }) } async fn symlink( &self, _dirid: fileid3, _linkname: &filename3, _symlink: &nfspath3, _attr: &sattr3, ) -> Result<(fileid3, fattr3), nfsstat3> { Err(nfsstat3::NFS3ERR_ROFS) } async fn readlink(&self, _id: fileid3) -> Result { Err(nfsstat3::NFS3ERR_NOTSUPP) } }