d94cb2df4c
- Fix trailing whitespace in kex.rs and s3.rs - Add missing KexProposal import in kex_complete.rs - Auto-fix clippy warnings across all crates - All 153 tests pass
406 lines
12 KiB
Rust
406 lines
12 KiB
Rust
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<HashMap<u64, String>>, // fileid -> node_id
|
|
reverse_map: Mutex<HashMap<String, u64>>, // node_id -> fileid
|
|
next_id: Mutex<u64>,
|
|
}
|
|
|
|
impl MarkBaseNFSBackend {
|
|
pub fn new(user_id: String, db_path: std::path::PathBuf) -> anyhow::Result<Self> {
|
|
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<String> {
|
|
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<fileid3, nfsstat3> {
|
|
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<fattr3, nfsstat3> {
|
|
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<fattr3, nfsstat3> {
|
|
Err(nfsstat3::NFS3ERR_ROFS)
|
|
}
|
|
|
|
async fn read(
|
|
&self,
|
|
id: fileid3,
|
|
offset: u64,
|
|
count: u32,
|
|
) -> Result<(Vec<u8>, 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<fattr3, nfsstat3> {
|
|
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<fileid3, nfsstat3> {
|
|
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<ReadDirResult, nfsstat3> {
|
|
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<i64>)> = 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<i64>>(3)
|
|
.map(|file_size| (node_id, label, node_type, file_size))
|
|
})
|
|
})
|
|
})
|
|
})
|
|
.map_err(|_| nfsstat3::NFS3ERR_SERVERFAULT)?
|
|
.collect::<Result<Vec<_>, _>>()
|
|
.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<i64>>(3)
|
|
.map(|file_size| (node_id, label, node_type, file_size))
|
|
})
|
|
})
|
|
})
|
|
})
|
|
.map_err(|_| nfsstat3::NFS3ERR_SERVERFAULT)?
|
|
.collect::<Result<Vec<_>, _>>()
|
|
.map_err(|_| nfsstat3::NFS3ERR_SERVERFAULT)?
|
|
};
|
|
|
|
let mut entries: Vec<DirEntry> = 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<nfspath3, nfsstat3> {
|
|
Err(nfsstat3::NFS3ERR_NOTSUPP)
|
|
}
|
|
}
|