use alloc::{format, string::String, sync::Arc, vec::Vec};
use axerrno::{ax_err, AxError, AxResult};
use axfs_vfs::{VfsError, VfsNodeAttr, VfsNodeOps, VfsNodeRef, VfsNodeType, VfsOps, VfsResult};
use axsync::Mutex;
use lazy_init::LazyInit;
use crate::api::FileType;
static CURRENT_DIR_PATH: Mutex<String> = Mutex::new(String::new());
static CURRENT_DIR: LazyInit<Mutex<VfsNodeRef>> = LazyInit::new();
pub struct MountPoint {
path: &'static str,
fs: Arc<dyn VfsOps>,
}
struct RootDirectory {
main_fs: Arc<dyn VfsOps>,
mounts: Vec<MountPoint>,
}
static ROOT_DIR: LazyInit<Arc<RootDirectory>> = LazyInit::new();
impl MountPoint {
pub fn new(path: &'static str, fs: Arc<dyn VfsOps>) -> Self {
Self { path, fs }
}
}
impl Drop for MountPoint {
fn drop(&mut self) {
self.fs.umount().ok();
}
}
impl RootDirectory {
pub const fn new(main_fs: Arc<dyn VfsOps>) -> Self {
Self {
main_fs,
mounts: Vec::new(),
}
}
pub fn mount(&mut self, path: &'static str, fs: Arc<dyn VfsOps>) -> AxResult {
if path == "/" {
return ax_err!(InvalidInput, "cannot mount root filesystem");
}
if !path.starts_with('/') {
return ax_err!(InvalidInput, "mount path must start with '/'");
}
if self.mounts.iter().any(|mp| mp.path == path) {
return ax_err!(InvalidInput, "mount point already exists");
}
match self.main_fs.root_dir().lookup(path) {
Ok(_) => {}
Err(err_code) => {
if err_code == VfsError::NotFound {
self.main_fs.root_dir().create(path, FileType::Dir)?;
}
}
}
fs.mount(path, self.main_fs.root_dir().lookup(path)?)?;
self.mounts.push(MountPoint::new(path, fs));
Ok(())
}
pub fn _umount(&mut self, path: &str) {
self.mounts.retain(|mp| mp.path != path);
}
pub fn contains(&self, path: &str) -> bool {
self.mounts.iter().any(|mp| mp.path == path)
}
fn lookup_mounted_fs<F, T>(&self, path: &str, f: F) -> AxResult<T>
where
F: FnOnce(Arc<dyn VfsOps>, &str) -> AxResult<T>,
{
debug!("lookup at root: {}", path);
let path = path.trim_matches('/');
if let Some(rest) = path.strip_prefix("./") {
return self.lookup_mounted_fs(rest, f);
}
let mut idx = 0;
let mut max_len = 0;
for (i, mp) in self.mounts.iter().enumerate() {
if path.starts_with(&mp.path[1..]) && mp.path.len() - 1 > max_len {
max_len = mp.path.len() - 1;
idx = i;
}
}
if max_len == 0 {
f(self.main_fs.clone(), path) } else {
f(self.mounts[idx].fs.clone(), &path[max_len..]) }
}
}
impl VfsNodeOps for RootDirectory {
axfs_vfs::impl_vfs_dir_default! {}
fn get_attr(&self) -> VfsResult<VfsNodeAttr> {
self.main_fs.root_dir().get_attr()
}
fn lookup(self: Arc<Self>, path: &str) -> VfsResult<VfsNodeRef> {
self.lookup_mounted_fs(path, |fs, rest_path| fs.root_dir().lookup(rest_path))
}
fn create(&self, path: &str, ty: VfsNodeType) -> VfsResult {
self.lookup_mounted_fs(path, |fs, rest_path| {
if rest_path.is_empty() {
Ok(()) } else {
fs.root_dir().create(rest_path, ty)
}
})
}
fn remove(&self, path: &str) -> VfsResult {
self.lookup_mounted_fs(path, |fs, rest_path| {
if rest_path.is_empty() {
ax_err!(PermissionDenied) } else {
fs.root_dir().remove(rest_path)
}
})
}
fn rename(&self, src_path: &str, dst_path: &str) -> VfsResult {
self.lookup_mounted_fs(src_path, |fs, rest_path| {
if rest_path.is_empty() {
ax_err!(PermissionDenied) } else {
fs.root_dir().rename(rest_path, dst_path)
}
})
}
}
pub(crate) fn init_rootfs(mount_points: Vec<MountPoint>) {
let main_fs = mount_points.get(0).expect("No filesystem found").fs.clone();
let mut root_dir = RootDirectory::new(main_fs);
for mp in mount_points.iter().skip(1) {
let path = mp.path;
let vfsops = mp.fs.clone();
let message = format!("failed to mount filesystem at {}", path);
info!("mounting {}", path);
root_dir.mount(path, vfsops).expect(&message);
}
ROOT_DIR.init_by(Arc::new(root_dir));
CURRENT_DIR.init_by(Mutex::new(ROOT_DIR.clone()));
*CURRENT_DIR_PATH.lock() = "/".into();
}
fn parent_node_of(dir: Option<&VfsNodeRef>, path: &str) -> VfsNodeRef {
if path.starts_with('/') {
ROOT_DIR.clone()
} else {
dir.cloned().unwrap_or_else(|| CURRENT_DIR.lock().clone())
}
}
pub(crate) fn absolute_path(path: &str) -> AxResult<String> {
if path.starts_with('/') {
Ok(axfs_vfs::path::canonicalize(path))
} else {
let path = CURRENT_DIR_PATH.lock().clone() + path;
Ok(axfs_vfs::path::canonicalize(&path))
}
}
pub(crate) fn lookup(dir: Option<&VfsNodeRef>, path: &str) -> AxResult<VfsNodeRef> {
if path.is_empty() {
return ax_err!(NotFound);
}
let node = parent_node_of(dir, path).lookup(path)?;
if path.ends_with('/') && !node.get_attr()?.is_dir() {
ax_err!(NotADirectory)
} else {
Ok(node)
}
}
pub(crate) fn create_file(dir: Option<&VfsNodeRef>, path: &str) -> AxResult<VfsNodeRef> {
if path.is_empty() {
return ax_err!(NotFound);
} else if path.ends_with('/') {
return ax_err!(NotADirectory);
}
let parent = parent_node_of(dir, path);
parent.create(path, VfsNodeType::File)?;
parent.lookup(path)
}
pub(crate) fn create_dir(dir: Option<&VfsNodeRef>, path: &str) -> AxResult {
match lookup(dir, path) {
Ok(_) => ax_err!(AlreadyExists),
Err(AxError::NotFound) => parent_node_of(dir, path).create(path, VfsNodeType::Dir),
Err(e) => Err(e),
}
}
pub(crate) fn remove_file(dir: Option<&VfsNodeRef>, path: &str) -> AxResult {
let node = lookup(dir, path)?;
let attr = node.get_attr()?;
if attr.is_dir() {
ax_err!(IsADirectory)
} else if !attr.perm().owner_writable() {
ax_err!(PermissionDenied)
} else {
parent_node_of(dir, path).remove(path)
}
}
pub(crate) fn remove_dir(dir: Option<&VfsNodeRef>, path: &str) -> AxResult {
if path.is_empty() {
return ax_err!(NotFound);
}
let path_check = path.trim_matches('/');
if path_check.is_empty() {
return ax_err!(DirectoryNotEmpty); } else if path_check == "."
|| path_check == ".."
|| path_check.ends_with("/.")
|| path_check.ends_with("/..")
{
return ax_err!(InvalidInput);
}
if ROOT_DIR.contains(&absolute_path(path)?) {
return ax_err!(PermissionDenied);
}
let node = lookup(dir, path)?;
let attr = node.get_attr()?;
if !attr.is_dir() {
ax_err!(NotADirectory)
} else if !attr.perm().owner_writable() {
ax_err!(PermissionDenied)
} else {
parent_node_of(dir, path).remove(path)
}
}
pub(crate) fn current_dir() -> AxResult<String> {
Ok(CURRENT_DIR_PATH.lock().clone())
}
pub(crate) fn set_current_dir(path: &str) -> AxResult {
let mut abs_path = absolute_path(path)?;
if !abs_path.ends_with('/') {
abs_path += "/";
}
if abs_path == "/" {
*CURRENT_DIR.lock() = ROOT_DIR.clone();
*CURRENT_DIR_PATH.lock() = "/".into();
return Ok(());
}
let node = lookup(None, &abs_path)?;
let attr = node.get_attr()?;
if !attr.is_dir() {
ax_err!(NotADirectory)
} else if !attr.perm().owner_executable() {
ax_err!(PermissionDenied)
} else {
*CURRENT_DIR.lock() = node;
*CURRENT_DIR_PATH.lock() = abs_path;
Ok(())
}
}
pub(crate) fn rename(old: &str, new: &str) -> AxResult {
if parent_node_of(None, new).lookup(new).is_ok() {
warn!("dst file already exist, now remove it");
remove_file(None, new)?;
}
parent_node_of(None, old).rename(old, new)
}