use alloc::collections::BTreeMap;
use alloc::sync::{Arc, Weak};
use alloc::{string::String, vec::Vec};
use axfs_vfs::{VfsDirEntry, VfsNodeAttr, VfsNodeOps, VfsNodeRef, VfsNodeType};
use axfs_vfs::{VfsError, VfsResult};
use spin::RwLock;
use crate::file::FileNode;
use crate::Interrupts;
pub struct DirNode {
this: Weak<DirNode>,
parent: RwLock<Weak<dyn VfsNodeOps>>,
children: RwLock<BTreeMap<String, VfsNodeRef>>,
}
impl DirNode {
pub(super) fn new(parent: Option<Weak<dyn VfsNodeOps>>) -> Arc<Self> {
Arc::new_cyclic(|this| Self {
this: this.clone(),
parent: RwLock::new(parent.unwrap_or_else(|| Weak::<Self>::new())),
children: RwLock::new(BTreeMap::new()),
})
}
pub(super) fn set_parent(&self, parent: Option<&VfsNodeRef>) {
*self.parent.write() = parent.map_or(Weak::<Self>::new() as _, Arc::downgrade);
}
pub fn get_entries(&self) -> Vec<String> {
self.children.read().keys().cloned().collect()
}
pub fn exist(&self, name: &str) -> bool {
self.children.read().contains_key(name)
}
pub fn create_node(&self, name: &str, ty: VfsNodeType) -> VfsResult {
if self.exist(name) {
log::error!("AlreadyExists {}", name);
return Err(VfsError::AlreadyExists);
}
let node: VfsNodeRef = match ty {
VfsNodeType::File => {
if name == "interrupts" {
Arc::new(Interrupts)
} else {
Arc::new(FileNode::new())
}
}
VfsNodeType::Dir => Self::new(Some(self.this.clone())),
_ => return Err(VfsError::Unsupported),
};
self.children.write().insert(name.into(), node);
Ok(())
}
pub fn remove_node(&self, name: &str) -> VfsResult {
let mut children = self.children.write();
let node = children.get(name).ok_or(VfsError::NotFound)?;
if let Some(dir) = node.as_any().downcast_ref::<DirNode>() {
if !dir.children.read().is_empty() {
return Err(VfsError::DirectoryNotEmpty);
}
}
children.remove(name);
Ok(())
}
}
impl VfsNodeOps for DirNode {
fn get_attr(&self) -> VfsResult<VfsNodeAttr> {
Ok(VfsNodeAttr::new_dir(4096, 0))
}
fn parent(&self) -> Option<VfsNodeRef> {
self.parent.read().upgrade()
}
fn lookup(self: Arc<Self>, path: &str) -> VfsResult<VfsNodeRef> {
let (name, rest) = split_path(path);
let node = match name {
"" | "." => Ok(self.clone() as VfsNodeRef),
".." => self.parent().ok_or(VfsError::NotFound),
_ => self
.children
.read()
.get(name)
.cloned()
.ok_or(VfsError::NotFound),
}?;
if let Some(rest) = rest {
node.lookup(rest)
} else {
Ok(node)
}
}
fn read_dir(&self, start_idx: usize, dirents: &mut [VfsDirEntry]) -> VfsResult<usize> {
let children = self.children.read();
let mut children = children.iter().skip(start_idx.max(2) - 2);
for (i, ent) in dirents.iter_mut().enumerate() {
match i + start_idx {
0 => *ent = VfsDirEntry::new(".", VfsNodeType::Dir),
1 => *ent = VfsDirEntry::new("..", VfsNodeType::Dir),
_ => {
if let Some((name, node)) = children.next() {
*ent = VfsDirEntry::new(name, node.get_attr().unwrap().file_type());
} else {
return Ok(i);
}
}
}
}
Ok(dirents.len())
}
fn create(&self, path: &str, ty: VfsNodeType) -> VfsResult {
log::debug!("create {:?} at ramfs: {}", ty, path);
let (name, rest) = split_path(path);
if let Some(rest) = rest {
match name {
"" | "." => self.create(rest, ty),
".." => self.parent().ok_or(VfsError::NotFound)?.create(rest, ty),
_ => {
let subdir = self
.children
.read()
.get(name)
.ok_or(VfsError::NotFound)?
.clone();
subdir.create(rest, ty)
}
}
} else if name.is_empty() || name == "." || name == ".." {
Ok(()) } else {
self.create_node(name, ty)
}
}
fn remove(&self, path: &str) -> VfsResult {
log::debug!("remove at ramfs: {}", path);
let (name, rest) = split_path(path);
if let Some(rest) = rest {
match name {
"" | "." => self.remove(rest),
".." => self.parent().ok_or(VfsError::NotFound)?.remove(rest),
_ => {
let subdir = self
.children
.read()
.get(name)
.ok_or(VfsError::NotFound)?
.clone();
subdir.remove(rest)
}
}
} else if name.is_empty() || name == "." || name == ".." {
Err(VfsError::InvalidInput) } else {
self.remove_node(name)
}
}
axfs_vfs::impl_vfs_dir_default! {}
}
fn split_path(path: &str) -> (&str, Option<&str>) {
let trimmed_path = path.trim_start_matches('/');
trimmed_path.find('/').map_or((trimmed_path, None), |n| {
(&trimmed_path[..n], Some(&trimmed_path[n + 1..]))
})
}