ahrightos-fs 0.1.0

Portable filesystem, block-device, and IPC primitives for the AhRightOS teaching kernel.
Documentation
//! 内存文件系统(RamFS)
//!
//! 将所有数据存储在RAM中的简单文件系统

use super::file::{File, FileError, FileType, SeekFrom};
use super::inode::{permissions, Inode};
use alloc::collections::BTreeMap;
use alloc::string::String;
use alloc::sync::Arc;
use alloc::vec::Vec;
use spin::Mutex;

// Teaching crate logging is intentionally a no-op here; kernel-specific
// console output stays in the executable crate.
#[allow(unused_macros)]
macro_rules! fs_log {
    ($($arg:tt)*) => {};
}

/// RamFS Inode结构
///
/// 存储文件元数据和数据
pub struct RamInode {
    ino: usize,          // Inode编号
    file_type: FileType, // 文件类型
    mode: u32,           // 权限位
    size: usize,         // 文件大小
    created: u64,        // 创建时间
    modified: u64,       // 修改时间
    nlinks: usize,       // 硬链接计数

    // 文件数据(用于普通文件)
    data: Vec<u8>,

    // 目录项(用于目录)
    entries: BTreeMap<String, Arc<Mutex<RamInode>>>,
}

impl RamInode {
    /// 创建新的文件inode
    pub fn new_file(ino: usize) -> Self {
        fs_log!("Creating new file inode #{}", ino);
        RamInode {
            ino,
            file_type: FileType::RegularFile,
            mode: permissions::S_DEFAULT_FILE, // 0o644
            size: 0,
            created: 0,
            modified: 0,
            nlinks: 1,
            data: Vec::new(),
            entries: BTreeMap::new(),
        }
    }

    /// 创建新的目录inode
    pub fn new_directory(ino: usize) -> Self {
        fs_log!("Creating new directory inode #{}", ino);
        RamInode {
            ino,
            file_type: FileType::Directory,
            mode: permissions::S_DEFAULT_DIR, // 0o755
            size: 0,
            created: 0,
            modified: 0,
            nlinks: 1,
            data: Vec::new(),
            entries: BTreeMap::new(),
        }
    }

    /// Return file metadata for stat-like callers.
    pub fn metadata(&self) -> super::file::FileMetadata {
        super::file::FileMetadata {
            file_type: self.file_type,
            size: self.size,
            mode: self.mode,
            created: self.created,
            modified: self.modified,
        }
    }

    /// Return the inode link count.
    pub fn nlinks(&self) -> usize {
        self.nlinks
    }

    /// 从文件的指定位置读取数据
    ///
    /// # 参数
    /// * `offset` - 起始位置
    /// * `buf` - 读取缓冲区
    ///
    /// # 返回值
    /// * Ok(n) - 成功读取n字节
    /// * Err(e) - 读取失败
    pub fn read_at(&self, offset: usize, buf: &mut [u8]) -> Result<usize, FileError> {
        // 检查文件类型
        if self.file_type != FileType::RegularFile {
            return Err(FileError::IsDirectory);
        }

        if offset >= self.data.len() {
            return Ok(0);
        }

        let end = core::cmp::min(offset.saturating_add(buf.len()), self.data.len());
        let n = end - offset;
        buf[..n].copy_from_slice(&self.data[offset..end]);
        Ok(n)
    }

    /// 向文件的指定位置写入数据
    ///
    /// # 参数
    /// * `offset` - 起始位置
    /// * `buf` - 要写入的数据
    ///
    /// # 返回值
    /// * Ok(n) - 成功写入n字节
    /// * Err(e) - 写入失败
    pub fn write_at(&mut self, offset: usize, buf: &[u8]) -> Result<usize, FileError> {
        if self.file_type != FileType::RegularFile {
            return Err(FileError::IsDirectory);
        }

        let end = offset
            .checked_add(buf.len())
            .ok_or(FileError::InvalidOperation)?;
        if end > self.data.len() {
            self.data.resize(end, 0);
        }

        self.data[offset..end].copy_from_slice(buf);
        self.size = self.data.len();
        self.modified += 1;
        Ok(buf.len())
    }

    /// 将文件截断到指定大小
    ///
    /// # 参数
    /// * `size` - 新的文件大小
    ///
    /// # 示例
    /// - truncate(0): 清空文件
    /// - truncate(100): 将文件设置为100字节
    pub fn truncate(&mut self, size: usize) -> Result<(), FileError> {
        if self.file_type != FileType::RegularFile {
            return Err(FileError::IsDirectory);
        }

        self.data.resize(size, 0);
        self.size = size;
        self.modified += 1;
        Ok(())
    }

    /// 向目录添加条目
    ///
    /// # 参数
    /// * `name` - 条目名称
    /// * `inode` - Inode引用
    ///
    /// # 错误
    /// * NotDirectory - 不是目录
    /// * AlreadyExists - 条目已存在
    pub fn add_entry(
        &mut self,
        name: String,
        inode: Arc<Mutex<RamInode>>,
    ) -> Result<(), FileError> {
        // Step 1: 检查文件类型
        if self.file_type != FileType::Directory {
            return Err(FileError::NotDirectory);
        }
        // Step 2: 检查是否已存在同名条目
        if self.entries.contains_key(&name) {
            return Err(FileError::AlreadyExists);
        }
        // Step 3: 插入新条目
        self.entries.insert(name, inode);
        self.size = self.entries.len();
        self.modified += 1;
        // Step 4: 返回成功
        Ok(())
    }

    /// 在目录中查找条目
    ///
    /// # 参数
    /// * `name` - 要查找的条目名称
    ///
    /// # 返回值
    /// * Ok(inode) - 找到的条目
    /// * Err(NotFound) - 条目不存在
    pub fn lookup(&self, name: &str) -> Result<Arc<Mutex<RamInode>>, FileError> {
        if self.file_type != FileType::Directory {
            return Err(FileError::NotDirectory);
        }

        self.entries.get(name).cloned().ok_or(FileError::NotFound)
    }

    /// 从目录中删除条目
    ///
    /// # 参数
    /// * `name` - 要删除的条目名称
    ///
    /// # 注意
    /// 只删除目录项,不删除inode
    /// (当引用计数为0时,Inode会自动释放)
    pub fn remove_entry(&mut self, name: &str) -> Result<(), FileError> {
        if self.file_type != FileType::Directory {
            return Err(FileError::NotDirectory);
        }

        self.entries.remove(name).ok_or(FileError::NotFound)?;
        self.size = self.entries.len();
        self.modified += 1;
        Ok(())
    }

    /// 列出目录中的所有条目
    ///
    /// # 返回值
    /// 条目名称的向量(按字母顺序排序)
    pub fn list_entries(&self) -> Result<Vec<String>, FileError> {
        if self.file_type != FileType::Directory {
            return Err(FileError::NotDirectory);
        }

        Ok(self.entries.keys().cloned().collect())
    }
}

/// 实现Inode trait
impl Inode for RamInode {
    fn ino(&self) -> usize {
        self.ino
    }

    fn file_type(&self) -> FileType {
        self.file_type
    }

    fn size(&self) -> usize {
        self.size
    }

    fn mode(&self) -> u32 {
        self.mode
    }
}

/// RamFS文件句柄
///
/// 包装RamInode并维护当前offset
pub struct RamFile {
    inode: Arc<Mutex<RamInode>>,
    offset: usize, // 当前读写位置
}

impl RamFile {
    pub fn new(inode: Arc<Mutex<RamInode>>) -> Self {
        RamFile { inode, offset: 0 }
    }
}

/// 实现File trait
impl File for RamFile {
    /// 从当前offset读取
    fn read(&mut self, buf: &mut [u8]) -> Result<usize, FileError> {
        let n = self.inode.lock().read_at(self.offset, buf)?;
        self.offset += n; // 更新offset
        Ok(n)
    }

    /// 向当前offset写入
    fn write(&mut self, buf: &[u8]) -> Result<usize, FileError> {
        let n = self.inode.lock().write_at(self.offset, buf)?;
        self.offset += n; // 更新offset
        Ok(n)
    }

    /// 移动到新位置
    fn seek(&mut self, pos: SeekFrom) -> Result<usize, FileError> {
        let size = self.inode.lock().size();
        let new_offset = match pos {
            SeekFrom::Start(offset) => offset,
            SeekFrom::Current(delta) if delta >= 0 => self.offset.saturating_add(delta as usize),
            SeekFrom::Current(delta) => self.offset.saturating_sub((-delta) as usize),
            SeekFrom::End(delta) if delta >= 0 => size.saturating_add(delta as usize),
            SeekFrom::End(delta) => size.saturating_sub((-delta) as usize),
        };

        self.offset = new_offset;
        Ok(self.offset)
    }
}

/// RamFS文件系统
///
/// 管理inode分配和根目录
pub struct RamFS {
    root: Arc<Mutex<RamInode>>, // 根目录
    next_ino: Mutex<usize>,     // 下一个可用的inode号
}

impl RamFS {
    pub fn new() -> Self {
        let root = Arc::new(Mutex::new(RamInode::new_directory(1)));
        RamFS {
            root,
            next_ino: Mutex::new(2), // 1已分配给root
        }
    }

    fn alloc_ino(&self) -> usize {
        let mut next = self.next_ino.lock();
        let ino = *next;
        *next += 1;
        ino
    }

    pub fn root(&self) -> Arc<Mutex<RamInode>> {
        self.root.clone()
    }

    /// 在目录中创建文件
    ///
    /// # 参数
    /// * `parent` - 父目录inode
    /// * `name` - 文件名
    ///
    /// # 返回值
    /// 新文件的inode
    pub fn create_file(
        &self,
        parent: Arc<Mutex<RamInode>>,
        name: String,
    ) -> Result<Arc<Mutex<RamInode>>, FileError> {
        let ino = self.alloc_ino();
        let inode = Arc::new(Mutex::new(RamInode::new_file(ino)));
        parent.lock().add_entry(name, inode.clone())?;
        Ok(inode)
    }

    /// 在父目录中创建目录
    pub fn create_directory(
        &self,
        parent: Arc<Mutex<RamInode>>,
        name: String,
    ) -> Result<Arc<Mutex<RamInode>>, FileError> {
        let ino = self.alloc_ino();
        let inode = Arc::new(Mutex::new(RamInode::new_directory(ino)));
        parent.lock().add_entry(name, inode.clone())?;
        Ok(inode)
    }

    /// 删除文件或目录
    ///
    /// # 注意
    /// 删除目录前必须为空
    pub fn remove(&self, parent: Arc<Mutex<RamInode>>, name: &str) -> Result<(), FileError> {
        parent.lock().remove_entry(name)
    }

    /// 在目录中查找文件
    pub fn lookup(
        &self,
        parent: Arc<Mutex<RamInode>>,
        name: &str,
    ) -> Result<Arc<Mutex<RamInode>>, FileError> {
        parent.lock().lookup(name)
    }

    /// 打开文件用于读写
    ///
    /// # 错误
    /// * IsDirectory - 不能将目录作为文件打开
    pub fn open_file(&self, inode: Arc<Mutex<RamInode>>) -> Result<RamFile, FileError> {
        let file_type = inode.lock().file_type();
        if file_type != FileType::RegularFile {
            return Err(FileError::IsDirectory);
        }
        Ok(RamFile::new(inode))
    }
}

impl Default for RamFS {
    fn default() -> Self {
        Self::new()
    }
}