//! ramfs: a file system that lives entirely on the heap (like Linux's ramfs / //! tmpfs without the size limit). The simplest possible `FileSystem`: a tree //! of nodes; files are a `Vec`, directories a sorted map name -> node. use super::{DirEntry, FileSystem, Inode, InodeKind}; use crate::errno::Errno; use alloc::{collections::BTreeMap, string::String, sync::Arc, vec::Vec}; use spin::Mutex; pub struct RamFs { root: Arc, } impl RamFs { pub fn new() -> Arc { Arc::new(RamFs { root: Arc::new(RamInode::Dir(Mutex::new(BTreeMap::new()))) }) } } impl FileSystem for RamFs { fn name(&self) -> &'static str { "ramfs" } fn root(&self) -> Arc { self.root.clone() } } pub enum RamInode { File(Mutex>), Dir(Mutex>>), } impl Inode for RamInode { fn kind(&self) -> InodeKind { match self { RamInode::File(_) => InodeKind::File, RamInode::Dir(_) => InodeKind::Directory, } } fn size(&self) -> usize { match self { RamInode::File(data) => data.lock().len(), RamInode::Dir(entries) => entries.lock().len(), } } fn read_at(&self, offset: usize, buf: &mut [u8]) -> Result { let RamInode::File(data) = self else { return Err(Errno::EISDIR) }; let data = data.lock(); if offset >= data.len() { return Ok(0); // EOF } let n = buf.len().min(data.len() - offset); buf[..n].copy_from_slice(&data[offset..offset + n]); Ok(n) } fn write_at(&self, offset: usize, buf: &[u8]) -> Result { let RamInode::File(data) = self else { return Err(Errno::EISDIR) }; let mut data = data.lock(); let end = offset.checked_add(buf.len()).ok_or(Errno::EINVAL)?; if data.len() < end { data.resize(end, 0); // writing past the end grows the file (gap = zeros) } data[offset..end].copy_from_slice(buf); Ok(buf.len()) } fn lookup(&self, name: &str) -> Result, Errno> { let RamInode::Dir(entries) = self else { return Err(Errno::ENOTDIR) }; let node = entries.lock().get(name).cloned().ok_or(Errno::ENOENT)?; Ok(node) } fn create(&self, name: &str, kind: InodeKind) -> Result, Errno> { let RamInode::Dir(entries) = self else { return Err(Errno::ENOTDIR) }; if name.is_empty() || name.contains('/') || name.len() > 255 { return Err(Errno::EINVAL); } let mut entries = entries.lock(); if entries.contains_key(name) { return Err(Errno::EEXIST); } let node = Arc::new(match kind { InodeKind::File => RamInode::File(Mutex::new(Vec::new())), InodeKind::Directory => RamInode::Dir(Mutex::new(BTreeMap::new())), InodeKind::Symlink | InodeKind::Device => return Err(Errno::EOPNOTSUPP), // ramfs has neither }); entries.insert(String::from(name), node.clone()); Ok(node) } fn list(&self) -> Result, Errno> { let RamInode::Dir(entries) = self else { return Err(Errno::ENOTDIR) }; let entries = entries.lock(); Ok(entries.iter().map(|(name, node)| DirEntry { name: name.clone(), kind: node.kind() }).collect()) } fn truncate(&self) -> Result<(), Errno> { let RamInode::File(data) = self else { return Err(Errno::EISDIR) }; data.lock().clear(); Ok(()) } fn remove(&self, name: &str) -> Result<(), Errno> { let RamInode::Dir(entries) = self else { return Err(Errno::ENOTDIR) }; let mut entries = entries.lock(); let node = entries.get(name).ok_or(Errno::ENOENT)?; if let RamInode::Dir(children) = &**node { if !children.lock().is_empty() { return Err(Errno::ENOTEMPTY); } } entries.remove(name); Ok(()) } }