ahrightos-fs 0.1.0

Portable filesystem, block-device, and IPC primitives for the AhRightOS teaching kernel.
Documentation
//! VFS path resolution layer
//!
//! Provides unified path-to-file resolution across RamFS and mounted BlkFS
//! instances. Centralizes mount-table prefix matching (deduplicated from the
//! three syscall-impl call sites) and directory traversal for both filesystem
//! types.

use super::file::{File, FileError, FileType};
use super::inode::Inode;
use super::manager::{MountPoint, FS_MANAGER};
use super::{BlkFS, BlkInode, RamInode};
use alloc::string::String;
use alloc::sync::Arc;
use alloc::vec::Vec;
use spin::Mutex;

// ── Path utilities ──────────────────────────────────────────────

/// Split a normalized path (no leading `/`) into components.
pub fn split_path(path: &str) -> Vec<&str> {
    path.split('/').filter(|s| !s.is_empty()).collect()
}

// ── Mount resolution ────────────────────────────────────────────

/// Find the longest mount-point whose prefix matches `normalized`.
/// Returns `(MountPoint, remainder_path_after_prefix)` or `None` for RamFS.
fn find_mount(normalized: &str) -> Option<(MountPoint, String)> {
    let mgr = FS_MANAGER.lock();

    let mount = mgr
        .mounts
        .iter()
        .filter(|m| {
            let mp = m.path.trim_start_matches('/');
            if mp.is_empty() {
                return false;
            }
            normalized == mp
                || normalized.starts_with(mp) && normalized.as_bytes().get(mp.len()) == Some(&b'/')
        })
        .max_by_key(|m| m.path.len())?;

    let mp = mount.path.trim_start_matches('/');
    let remainder = if normalized.len() > mp.len() {
        normalized[mp.len() + 1..].trim_start_matches('/').into()
    } else {
        String::new()
    };

    // Clone out of the lock before returning
    let result = MountPoint {
        path: mount.path.clone(),
        fs_type: mount.fs_type,
        blkfs: mount.blkfs.clone(),
        virtualfs: mount.virtualfs.clone(),
    };
    Some((result, remainder))
}

// ── RamFS traversal ──────────────────────────────────────────────

/// Resolve a path within RamFS, returning the target inode.
pub fn ramfs_resolve(path: &str) -> Result<Arc<Mutex<RamInode>>, FileError> {
    let mgr = FS_MANAGER.lock();
    let ramfs = mgr.ramfs();
    let parts = split_path(path.trim_start_matches('/'));
    if parts.is_empty() {
        return Ok(mgr.root());
    }
    let mut current = mgr.root();
    for &part in &parts {
        current = ramfs.lookup(current, part)?;
    }
    Ok(current)
}

/// Resolve parent directory + filename within RamFS.
pub fn ramfs_parent_and_name(path: &str) -> Result<(Arc<Mutex<RamInode>>, String), FileError> {
    let parts = split_path(path.trim_start_matches('/'));
    if parts.is_empty() {
        return Err(FileError::InvalidOperation);
    }
    let filename = String::from(parts[parts.len() - 1]);
    let mgr = FS_MANAGER.lock();
    let ramfs = mgr.ramfs();
    let mut parent = mgr.root();
    for &part in parts.iter().take(parts.len().saturating_sub(1)) {
        parent = ramfs.lookup(parent, part)?;
    }
    Ok((parent, filename))
}

// ── BlkFS traversal ──────────────────────────────────────────────

/// Resolve a path within BlkFS, returning the target inode.
pub fn blkfs_resolve(blkfs: &BlkFS, path: &str) -> Result<BlkInode, FileError> {
    let parts = split_path(path);
    if parts.is_empty() {
        return blkfs.root_inode();
    }
    let mut current = blkfs.root_inode()?;
    for &part in &parts {
        current = blkfs.lookup(&current, part)?;
    }
    Ok(current)
}

/// Resolve parent directory + filename within BlkFS.
pub fn blkfs_parent_and_name(blkfs: &BlkFS, path: &str) -> Result<(BlkInode, String), FileError> {
    let parts = split_path(path);
    if parts.is_empty() {
        return Err(FileError::InvalidOperation);
    }
    let filename = String::from(parts[parts.len() - 1]);
    let mut parent = blkfs.root_inode()?;
    for &part in parts.iter().take(parts.len().saturating_sub(1)) {
        parent = blkfs.lookup(&parent, part)?;
    }
    Ok((parent, filename))
}

// ── Unified VFS operations ──────────────────────────────────────

/// Open an existing file by path, routing through mount table.
pub fn open_file(path: &str) -> Result<Arc<Mutex<dyn File>>, FileError> {
    let normalized = path.trim_start_matches('/');
    if normalized.is_empty() {
        return Err(FileError::IsDirectory);
    }

    if let Some((mount, remainder)) = find_mount(normalized) {
        if let Some(virtualfs) = mount.virtualfs {
            return virtualfs.open_file(&remainder);
        }
        let blkfs = mount.blkfs.ok_or(FileError::InvalidOperation)?;
        if remainder.is_empty() {
            return Err(FileError::IsDirectory);
        }
        let inode = blkfs_resolve(&blkfs, &remainder)?;
        if inode.file_type() == FileType::Directory {
            return Err(FileError::IsDirectory);
        }
        let file = blkfs.open_file(inode)?;
        Ok(Arc::new(Mutex::new(file)))
    } else {
        let inode = ramfs_resolve(normalized)?;
        {
            let locked = inode.lock();
            if locked.file_type() == FileType::Directory {
                return Err(FileError::IsDirectory);
            }
        }
        let ramfs = FS_MANAGER.lock().ramfs();
        let file = ramfs.open_file(inode)?;
        Ok(Arc::new(Mutex::new(file)))
    }
}

/// Create a new regular file by path, routing through mount table.
pub fn create_file(path: &str) -> Result<Arc<Mutex<dyn File>>, FileError> {
    let normalized = path.trim_start_matches('/');
    if normalized.is_empty() {
        return Err(FileError::InvalidOperation);
    }

    if let Some((mount, remainder)) = find_mount(normalized) {
        if let Some(virtualfs) = mount.virtualfs {
            return virtualfs.create_file(&remainder);
        }
        let blkfs = mount.blkfs.ok_or(FileError::InvalidOperation)?;
        if remainder.is_empty() {
            return Err(FileError::InvalidOperation);
        }
        let (mut parent, filename) = blkfs_parent_and_name(&blkfs, &remainder)?;
        let inode = blkfs.create_file(&mut parent, &filename)?;
        let file = blkfs.open_file(inode)?;
        Ok(Arc::new(Mutex::new(file)))
    } else {
        let (parent, filename) = ramfs_parent_and_name(normalized)?;
        let ramfs = FS_MANAGER.lock().ramfs();
        let inode = ramfs.create_file(parent, filename)?;
        let file = ramfs.open_file(inode)?;
        Ok(Arc::new(Mutex::new(file)))
    }
}

/// Create a directory by path, routing through mount table.
pub fn create_dir(path: &str) -> Result<(), FileError> {
    let normalized = path.trim_start_matches('/');
    if normalized.is_empty() {
        return Err(FileError::InvalidOperation);
    }

    if let Some((mount, remainder)) = find_mount(normalized) {
        if let Some(virtualfs) = mount.virtualfs {
            return virtualfs.create_dir(&remainder);
        }
        let blkfs = mount.blkfs.ok_or(FileError::InvalidOperation)?;
        if remainder.is_empty() {
            return Err(FileError::InvalidOperation);
        }
        let (mut parent, dirname) = blkfs_parent_and_name(&blkfs, &remainder)?;
        blkfs.create_directory(&mut parent, &dirname)?;
        Ok(())
    } else {
        let (parent, dirname) = ramfs_parent_and_name(normalized)?;
        let ramfs = FS_MANAGER.lock().ramfs();
        ramfs.create_directory(parent, dirname)?;
        Ok(())
    }
}

/// Remove a file (unlink) by path, routing through mount table.
pub fn remove_file(path: &str) -> Result<(), FileError> {
    let normalized = path.trim_start_matches('/');
    if normalized.is_empty() {
        return Err(FileError::InvalidOperation);
    }

    if let Some((mount, remainder)) = find_mount(normalized) {
        if let Some(virtualfs) = mount.virtualfs {
            return virtualfs.remove_file(&remainder);
        }
        let blkfs = mount.blkfs.ok_or(FileError::InvalidOperation)?;
        if remainder.is_empty() {
            return Err(FileError::InvalidOperation);
        }
        let (mut parent, filename) = blkfs_parent_and_name(&blkfs, &remainder)?;
        parent.remove_entry(&filename)?;
        Ok(())
    } else {
        let (parent, filename) = ramfs_parent_and_name(normalized)?;
        let mut parent_locked = parent.lock();
        parent_locked.remove_entry(&filename)?;
        Ok(())
    }
}

/// Get file metadata (stat) by path, routing through mount table.
pub fn stat_path(path: &str) -> Result<super::file::FileMetadata, FileError> {
    let normalized = path.trim_start_matches('/');
    if normalized.is_empty() {
        return Err(FileError::InvalidOperation);
    }

    if let Some((mount, remainder)) = find_mount(normalized) {
        if let Some(virtualfs) = mount.virtualfs {
            return virtualfs.stat_path(&remainder);
        }
        let blkfs = mount.blkfs.ok_or(FileError::InvalidOperation)?;
        let inode = if remainder.is_empty() {
            blkfs.root_inode()?
        } else {
            blkfs_resolve(&blkfs, &remainder)?
        };
        Ok(super::file::FileMetadata {
            file_type: super::inode::Inode::file_type(&inode),
            size: super::inode::Inode::size(&inode),
            mode: super::inode::Inode::mode(&inode),
            created: 0,
            modified: 0,
        })
    } else {
        let inode = ramfs_resolve(normalized)?;
        let locked = inode.lock();
        Ok(locked.metadata())
    }
}

/// List directory entries (readdir) by path.
pub fn readdir_path(path: &str) -> Result<alloc::vec::Vec<alloc::string::String>, FileError> {
    let normalized = path.trim_start_matches('/');
    if normalized.is_empty() {
        return Err(FileError::InvalidOperation);
    }

    if let Some((mount, remainder)) = find_mount(normalized) {
        if let Some(virtualfs) = mount.virtualfs {
            return virtualfs.readdir_path(&remainder);
        }
        let blkfs = mount.blkfs.ok_or(FileError::InvalidOperation)?;
        let inode = if remainder.is_empty() {
            blkfs.root_inode()?
        } else {
            blkfs_resolve(&blkfs, &remainder)?
        };
        if inode.file_type() != super::file::FileType::Directory {
            return Err(FileError::NotDirectory);
        }
        inode.list_entries()
    } else {
        let inode = ramfs_resolve(normalized)?;
        let locked = inode.lock();
        if locked.file_type() != super::file::FileType::Directory {
            return Err(FileError::NotDirectory);
        }
        locked.list_entries()
    }
}