use crate::error::Result;
use std::path::{Path, PathBuf};
pub struct Vfs {
fs_root: PathBuf,
tmp_dir: PathBuf,
}
impl Vfs {
pub fn new(fs_root: impl Into<PathBuf>) -> Self {
let fs_root = fs_root.into();
let tmp_dir = std::env::temp_dir().join("npcrs");
let _ = std::fs::create_dir_all(&tmp_dir);
Self { fs_root, tmp_dir }
}
pub fn resolve(&self, vfs_path: &str) -> VfsResolution {
let path = vfs_path.trim_start_matches('/');
if path.is_empty() || path == "/" {
return VfsResolution::Directory(vec![
"fs".into(),
"proc".into(),
"dev".into(),
"sys".into(),
"mem".into(),
"tmp".into(),
]);
}
let parts: Vec<&str> = path.splitn(2, '/').collect();
let mount = parts[0];
let rest = parts.get(1).unwrap_or(&"");
match mount {
"fs" => {
let real_path = if rest.is_empty() {
self.fs_root.clone()
} else {
self.fs_root.join(rest)
};
VfsResolution::RealPath(real_path)
}
"tmp" => {
let real_path = if rest.is_empty() {
self.tmp_dir.clone()
} else {
self.tmp_dir.join(rest)
};
VfsResolution::RealPath(real_path)
}
"proc" => VfsResolution::Virtual(VirtualNode::Proc(rest.to_string())),
"dev" => VfsResolution::Virtual(VirtualNode::Dev(rest.to_string())),
"sys" => VfsResolution::Virtual(VirtualNode::Sys(rest.to_string())),
"mem" => VfsResolution::Virtual(VirtualNode::Mem(rest.to_string())),
_ => VfsResolution::NotFound,
}
}
pub fn read_file(&self, vfs_path: &str) -> Result<String> {
match self.resolve(vfs_path) {
VfsResolution::RealPath(path) => {
std::fs::read_to_string(&path).map_err(|e| crate::error::NpcError::FileLoad {
path: path.display().to_string(),
source: e,
})
}
VfsResolution::Virtual(node) => Ok(format!("[virtual: {:?}]", node)),
VfsResolution::Directory(entries) => Ok(entries.join("\n")),
VfsResolution::NotFound => Err(crate::error::NpcError::Other(format!(
"ENOENT: {} not found",
vfs_path
))),
}
}
pub fn write_file(&self, vfs_path: &str, content: &str) -> Result<()> {
match self.resolve(vfs_path) {
VfsResolution::RealPath(path) => {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).map_err(|e| {
crate::error::NpcError::FileLoad {
path: parent.display().to_string(),
source: e,
}
})?;
}
std::fs::write(&path, content).map_err(|e| crate::error::NpcError::FileLoad {
path: path.display().to_string(),
source: e,
})
}
VfsResolution::Virtual(_) => Err(crate::error::NpcError::Other(
"EROFS: cannot write to virtual node".to_string(),
)),
_ => Err(crate::error::NpcError::Other(format!(
"ENOENT: {} not found",
vfs_path
))),
}
}
pub fn list_dir(&self, vfs_path: &str) -> Result<Vec<String>> {
match self.resolve(vfs_path) {
VfsResolution::RealPath(path) => {
let entries = std::fs::read_dir(&path)
.map_err(|e| crate::error::NpcError::FileLoad {
path: path.display().to_string(),
source: e,
})?
.filter_map(|e| e.ok())
.map(|e| e.file_name().to_string_lossy().to_string())
.collect();
Ok(entries)
}
VfsResolution::Directory(entries) => Ok(entries),
_ => Ok(Vec::new()),
}
}
pub fn fs_root(&self) -> &Path {
&self.fs_root
}
}
#[derive(Debug)]
pub enum VfsResolution {
RealPath(PathBuf),
Virtual(VirtualNode),
Directory(Vec<String>),
NotFound,
}
#[derive(Debug)]
pub enum VirtualNode {
Proc(String),
Dev(String),
Sys(String),
Mem(String),
}