#[cfg(feature = "vfs")]
use alloc::vec;
use alloc::{
borrow::{Cow, ToOwned},
collections::vec_deque::VecDeque,
string::String,
sync::{Arc, Weak},
vec::Vec,
};
#[cfg(feature = "vfs")]
use core::sync::atomic::AtomicU64;
#[cfg(feature = "vfs")]
use core::sync::atomic::Ordering;
use ax_io::{Read, Write};
#[cfg(feature = "vfs")]
use axfs_ng_vfs::Mountpoint;
use axfs_ng_vfs::{
Location, Metadata, NodePermission, NodeType, VfsError, VfsResult,
path::{Component, Components, Path, PathBuf},
};
use spin::Once;
use crate::{
file::File,
os::sync::{IrqMutex, SleepMutex as Mutex},
};
pub const SYMLINKS_MAX: usize = 40;
pub static ROOT_FS_CONTEXT: Once<FsContext> = Once::new();
static FS_REGISTRY: IrqMutex<Vec<Weak<Mutex<FsContext>>>> = IrqMutex::new(Vec::new());
#[cfg(feature = "vfs")]
static MOUNT_NAMESPACE_ID: AtomicU64 = AtomicU64::new(1);
fn register_fs_context(ctx: &Arc<Mutex<FsContext>>) {
let mut registry = FS_REGISTRY.lock();
registry.retain(|weak| weak.upgrade().is_some());
registry.push(Arc::downgrade(ctx));
}
#[cfg(feature = "vfs")]
pub fn is_mount_busy(mp: &Arc<Mountpoint>) -> bool {
let refs: Vec<Arc<Mutex<FsContext>>> = {
let mut registry = FS_REGISTRY.lock();
registry.retain(|weak| weak.upgrade().is_some());
registry.iter().filter_map(|weak| weak.upgrade()).collect()
};
for ctx_arc in refs {
let ctx = ctx_arc.lock();
if !ctx.mount_namespace_contains(mp) {
continue;
}
if Arc::ptr_eq(ctx.root_dir().mountpoint(), mp)
|| Arc::ptr_eq(ctx.current_dir().mountpoint(), mp)
{
return true;
}
}
false
}
#[cfg(feature = "vfs")]
#[derive(Debug, Clone)]
pub struct MountNamespace {
id: u64,
root_mount: Arc<Mountpoint>,
}
#[cfg(feature = "vfs")]
impl MountNamespace {
fn new(root_mount: Arc<Mountpoint>) -> Self {
Self {
id: MOUNT_NAMESPACE_ID.fetch_add(1, Ordering::Relaxed),
root_mount,
}
}
pub fn id(&self) -> u64 {
self.id
}
pub fn root_mount(&self) -> &Arc<Mountpoint> {
&self.root_mount
}
fn clone_namespace(&self) -> Arc<Self> {
Arc::new(Self::new(self.root_mount.clone_tree()))
}
fn contains_mountpoint(&self, mountpoint: &Arc<Mountpoint>) -> bool {
let mut stack = vec![self.root_mount.clone()];
while let Some(current) = stack.pop() {
if Arc::ptr_eq(¤t, mountpoint) {
return true;
}
stack.extend(current.children());
}
false
}
}
scope_local::scope_local! {
pub static FS_CONTEXT: Arc<Mutex<FsContext>> = {
let ctx = Arc::new(Mutex::new(
ROOT_FS_CONTEXT
.get()
.expect("Root FS context not initialized")
.clone(),
));
register_fs_context(&ctx);
ctx
};
}
pub struct ReadDirEntry {
pub name: String,
pub ino: u64,
pub node_type: NodeType,
pub offset: u64,
}
#[derive(Debug, Clone)]
pub struct FsContext {
#[cfg(feature = "vfs")]
mnt_ns: Arc<MountNamespace>,
root_dir: Location,
current_dir: Location,
}
impl FsContext {
pub fn new(root_dir: Location) -> Self {
#[cfg(feature = "vfs")]
{
let mnt_ns = Arc::new(MountNamespace::new(root_dir.mountpoint().clone()));
Self::new_in_namespace(mnt_ns, root_dir)
}
#[cfg(not(feature = "vfs"))]
{
Self {
root_dir: root_dir.clone(),
current_dir: root_dir,
}
}
}
#[cfg(feature = "vfs")]
fn new_in_namespace(mnt_ns: Arc<MountNamespace>, root_dir: Location) -> Self {
Self {
root_dir: root_dir.clone(),
current_dir: root_dir,
mnt_ns,
}
}
#[cfg(feature = "vfs")]
pub fn mount_namespace(&self) -> &Arc<MountNamespace> {
&self.mnt_ns
}
#[cfg(feature = "vfs")]
fn mount_namespace_contains(&self, mountpoint: &Arc<Mountpoint>) -> bool {
self.mnt_ns.contains_mountpoint(mountpoint)
}
pub fn root_dir(&self) -> &Location {
&self.root_dir
}
pub fn current_dir(&self) -> &Location {
&self.current_dir
}
pub fn set_current_dir(&mut self, current_dir: Location) -> VfsResult<()> {
current_dir.check_is_dir()?;
self.current_dir = current_dir;
Ok(())
}
pub fn with_current_dir(&self, current_dir: Location) -> VfsResult<Self> {
current_dir.check_is_dir()?;
Ok(Self {
root_dir: self.root_dir.clone(),
current_dir,
#[cfg(feature = "vfs")]
mnt_ns: self.mnt_ns.clone(),
})
}
#[cfg(feature = "vfs")]
pub fn unshare_mount_namespace(&mut self) -> VfsResult<()> {
let new_ns = self.mnt_ns.clone_namespace();
self.set_mount_namespace(new_ns)
}
#[cfg(feature = "vfs")]
pub fn set_mount_namespace(&mut self, new_ns: Arc<MountNamespace>) -> VfsResult<()> {
let root_path = self.root_dir.absolute_path()?;
let current_path = self.current_dir.absolute_path()?;
let new_root_loc = new_ns.root_mount().root_location();
let resolver = Self::new_in_namespace(new_ns.clone(), new_root_loc);
let root_dir = resolver.resolve(root_path)?;
let current_dir = resolver.resolve(current_path)?;
self.mnt_ns = new_ns;
self.root_dir = root_dir;
self.current_dir = current_dir;
Ok(())
}
pub fn try_resolve_symlink(
&self,
loc: Location,
follow_count: &mut usize,
) -> VfsResult<Location> {
if loc.node_type() != NodeType::Symlink {
return Ok(loc);
}
if *follow_count >= SYMLINKS_MAX {
return Err(VfsError::FilesystemLoop);
}
*follow_count += 1;
let target = loc.read_link()?;
if target.is_empty() {
return Err(VfsError::NotFound);
}
self.resolve_components(PathBuf::from(target).components(), follow_count)
}
fn lookup(&self, dir: &Location, name: &str, follow_count: &mut usize) -> VfsResult<Location> {
let loc = dir.lookup_no_follow(name)?;
self.with_current_dir(dir.clone())?
.try_resolve_symlink(loc, follow_count)
}
fn resolve_components(
&self,
components: Components,
follow_count: &mut usize,
) -> VfsResult<Location> {
let mut dir = self.current_dir.clone();
for comp in components {
match comp {
Component::CurDir => {}
Component::ParentDir => {
dir = dir.parent().unwrap_or_else(|| self.root_dir.clone());
}
Component::RootDir => {
dir = self.root_dir.clone();
}
Component::Normal(name) => {
dir = self.lookup(&dir, name, follow_count)?;
}
}
}
Ok(dir)
}
fn resolve_inner<'a>(
&self,
path: &'a Path,
follow_count: &mut usize,
) -> VfsResult<(Location, Option<&'a str>)> {
let entry_name = path.file_name();
let mut components = path.components();
if entry_name.is_some() {
components.next_back();
}
let dir = self.resolve_components(components, follow_count)?;
dir.check_is_dir()?;
Ok((dir, entry_name))
}
pub fn resolve(&self, path: impl AsRef<Path>) -> VfsResult<Location> {
let mut follow_count = 0;
let (dir, name) = self.resolve_inner(path.as_ref(), &mut follow_count)?;
match name {
Some(name) => self.lookup(&dir, name, &mut follow_count),
None => Ok(dir),
}
}
pub fn resolve_no_follow(&self, path: impl AsRef<Path>) -> VfsResult<Location> {
let (dir, name) = self.resolve_inner(path.as_ref(), &mut 0)?;
match name {
Some(name) => dir.lookup_no_follow(name),
None => Ok(dir),
}
}
pub fn resolve_parent<'a>(&self, path: &'a Path) -> VfsResult<(Location, Cow<'a, str>)> {
let (dir, name) = self.resolve_inner(path, &mut 0)?;
if let Some(name) = name {
Ok((dir, Cow::Borrowed(name)))
} else if let Some(parent) = dir.parent() {
Ok((parent, dir.name().into_owned().into()))
} else {
Err(VfsError::InvalidInput)
}
}
pub fn resolve_nonexistent<'a>(&self, path: &'a Path) -> VfsResult<(Location, &'a str)> {
let (dir, name) = self.resolve_inner(path, &mut 0)?;
if let Some(name) = name {
Ok((dir, name))
} else {
Err(VfsError::InvalidInput)
}
}
pub fn metadata(&self, path: impl AsRef<Path>) -> VfsResult<Metadata> {
self.resolve(path)?.metadata()
}
pub fn read(&self, path: impl AsRef<Path>) -> VfsResult<Vec<u8>> {
let mut buf = Vec::new();
let file = File::open(self, path.as_ref())?;
(&file).read_to_end(&mut buf)?;
Ok(buf)
}
pub fn read_to_string(&self, path: impl AsRef<Path>) -> VfsResult<String> {
String::from_utf8(self.read(path)?).map_err(|_| VfsError::InvalidData)
}
pub fn write(&self, path: impl AsRef<Path>, buf: impl AsRef<[u8]>) -> VfsResult<()> {
let file = File::create(self, path.as_ref())?;
(&file).write_all(buf.as_ref())?;
Ok(())
}
pub fn read_dir(&self, path: impl AsRef<Path>) -> VfsResult<ReadDir> {
let dir = self.resolve(path)?;
Ok(ReadDir {
dir,
buf: VecDeque::new(),
offset: 0,
ended: false,
})
}
pub fn remove_file(&self, path: impl AsRef<Path>) -> VfsResult<()> {
let entry = self.resolve_no_follow(path.as_ref())?;
entry
.parent()
.ok_or(VfsError::IsADirectory)?
.unlink(&entry.name(), false)
}
pub fn remove_dir(&self, path: impl AsRef<Path>) -> VfsResult<()> {
let entry = self.resolve_no_follow(path.as_ref())?;
let dir = entry.entry().as_dir()?;
if dir.has_children()? {
return Err(VfsError::DirectoryNotEmpty);
}
entry
.parent()
.ok_or(VfsError::ResourceBusy)?
.unlink(&entry.name(), true)
}
pub fn rename(&self, from: impl AsRef<Path>, to: impl AsRef<Path>) -> VfsResult<()> {
let (src_dir, src_name) = self.resolve_parent(from.as_ref())?;
let (dst_dir, dst_name) = self.resolve_parent(to.as_ref())?;
src_dir.rename(&src_name, &dst_dir, &dst_name)
}
pub fn create_dir(
&self,
path: impl AsRef<Path>,
mode: NodePermission,
uid: u32,
gid: u32,
) -> VfsResult<Location> {
let path = path.as_ref();
if path.as_str().is_empty() {
return Err(VfsError::NotFound);
}
let (dir, name) = match self.resolve_nonexistent(path) {
Ok(pair) => pair,
Err(VfsError::InvalidInput) => {
return match self.resolve(path) {
Ok(loc) if loc.node_type() == NodeType::Directory => {
Err(VfsError::AlreadyExists)
}
Ok(_) => Err(VfsError::NotADirectory),
Err(e) => Err(e),
};
}
Err(e) => return Err(e),
};
dir.create(name, NodeType::Directory, mode, uid, gid)
}
pub fn link(
&self,
old_path: impl AsRef<Path>,
new_path: impl AsRef<Path>,
) -> VfsResult<Location> {
let old = self.resolve(old_path.as_ref())?;
let (new_dir, new_name) = self.resolve_nonexistent(new_path.as_ref())?;
new_dir.link(new_name, &old)
}
pub fn symlink(
&self,
target: impl AsRef<str>,
link_path: impl AsRef<Path>,
uid: u32,
gid: u32,
) -> VfsResult<Location> {
let (dir, name) = self.resolve_nonexistent(link_path.as_ref())?;
if dir.lookup_no_follow(name).is_ok() {
return Err(VfsError::AlreadyExists);
}
let symlink = dir.create(name, NodeType::Symlink, NodePermission::default(), uid, gid)?;
symlink.entry().as_file()?.set_symlink(target.as_ref())?;
Ok(symlink)
}
pub fn canonicalize(&self, path: impl AsRef<Path>) -> VfsResult<PathBuf> {
self.resolve(path.as_ref())?.absolute_path()
}
pub fn pivot_root(&mut self, new_root: Location, put_old: Location) -> VfsResult<()> {
let old_root = self.root_dir.clone();
let old_root_mp = self.root_dir.mountpoint().clone();
let new_root_mp = new_root.mountpoint().clone();
old_root_mp.pivot_mount(&new_root_mp, &put_old)?;
let new_root_loc = new_root_mp.root_location();
self.root_dir = new_root_loc.clone();
if old_root.ptr_eq(&self.current_dir) {
self.current_dir = new_root_loc;
}
Ok(())
}
pub fn propagate_pivot_root(old_root: &Location, new_root: &Location) {
let refs: Vec<Arc<Mutex<FsContext>>> = {
let mut registry = FS_REGISTRY.lock();
registry.retain(|weak| weak.upgrade().is_some());
registry.iter().filter_map(|weak| weak.upgrade()).collect()
};
for ctx_arc in refs {
let mut ctx = ctx_arc.lock();
let update_root = old_root.ptr_eq(&ctx.root_dir);
let update_cwd = old_root.ptr_eq(&ctx.current_dir);
if update_root {
ctx.root_dir = new_root.clone();
}
if update_cwd {
ctx.current_dir = new_root.clone();
}
}
}
}
pub struct ReadDir {
dir: Location,
buf: VecDeque<ReadDirEntry>,
offset: u64,
ended: bool,
}
impl ReadDir {
pub const BUF_SIZE: usize = 128;
}
impl Iterator for ReadDir {
type Item = VfsResult<ReadDirEntry>;
fn next(&mut self) -> Option<Self::Item> {
if self.ended {
return None;
}
if self.buf.is_empty() {
self.buf.clear();
let result = self.dir.read_dir(
self.offset,
&mut |name: &str, ino: u64, node_type: NodeType, offset: u64| {
self.buf.push_back(ReadDirEntry {
name: name.to_owned(),
ino,
node_type,
offset,
});
self.offset = offset;
self.buf.len() < Self::BUF_SIZE
},
);
if self.buf.is_empty() {
if let Err(err) = result {
return Some(Err(err));
}
self.ended = true;
return None;
}
}
self.buf.pop_front().map(Ok)
}
}