//! 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. //! Since stage 16 every node also remembers its permission bits (chmod) //! and its three times (utimensat, and writes update the modification time). use super::{now_timespec, DirEntry, FileSystem, Inode, InodeKind, MODE_SET}; 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::new(Node::Dir(Mutex::new(BTreeMap::new())))) }) } } impl FileSystem for RamFs { fn name(&self) -> &'static str { "ramfs" } fn root(&self) -> Arc { self.root.clone() } } pub enum Node { File(Mutex>), Dir(Mutex>>), } /// What `stat` shows besides the contents (stage 16). struct Meta { /// 0 = never chmod'ed (stat shows the default 0o644 / 0o755); /// otherwise the bits | MODE_SET. mode: u32, /// atime, mtime, ctime as [seconds, nanoseconds]. times: [[i64; 2]; 3], } pub struct RamInode { node: Node, meta: Mutex, } impl RamInode { fn new(node: Node) -> RamInode { let now = now_timespec(); RamInode { node, meta: Mutex::new(Meta { mode: 0, times: [now; 3] }) } } /// The contents changed: new modification and status-change time. fn touch(&self) { let now = now_timespec(); let mut meta = self.meta.lock(); meta.times[1] = now; meta.times[2] = now; } } impl Inode for RamInode { fn kind(&self) -> InodeKind { match &self.node { Node::File(_) => InodeKind::File, Node::Dir(_) => InodeKind::Directory, } } fn size(&self) -> usize { match &self.node { Node::File(data) => data.lock().len(), Node::Dir(entries) => entries.lock().len(), } } fn read_at(&self, offset: usize, buf: &mut [u8]) -> Result { let Node::File(data) = &self.node 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 Node::File(data) = &self.node 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); drop(data); self.touch(); Ok(buf.len()) } fn lookup(&self, name: &str) -> Result, Errno> { let Node::Dir(entries) = &self.node 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 Node::Dir(entries) = &self.node 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(RamInode::new(match kind { InodeKind::File => Node::File(Mutex::new(Vec::new())), InodeKind::Directory => Node::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 Node::Dir(entries) = &self.node 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 Node::File(data) = &self.node else { return Err(Errno::EISDIR) }; data.lock().clear(); self.touch(); Ok(()) } fn set_len(&self, len: usize) -> Result<(), Errno> { let Node::File(data) = &self.node else { return Err(Errno::EISDIR) }; data.lock().resize(len, 0); // shorter: cut; longer: zeros self.touch(); Ok(()) } fn mode(&self) -> u32 { self.meta.lock().mode } fn set_mode(&self, mode: u32) -> Result<(), Errno> { let mut meta = self.meta.lock(); meta.mode = (mode & 0o7777) | MODE_SET; meta.times[2] = now_timespec(); Ok(()) } fn times(&self) -> Option<[[i64; 2]; 3]> { Some(self.meta.lock().times) } fn set_times(&self, atime: Option<[i64; 2]>, mtime: Option<[i64; 2]>) -> Result<(), Errno> { let mut meta = self.meta.lock(); if let Some(t) = atime { meta.times[0] = t; } if let Some(t) = mtime { meta.times[1] = t; } meta.times[2] = now_timespec(); Ok(()) } fn remove(&self, name: &str) -> Result<(), Errno> { let Node::Dir(entries) = &self.node else { return Err(Errno::ENOTDIR) }; let mut entries = entries.lock(); let node = entries.get(name).ok_or(Errno::ENOENT)?; if let Node::Dir(children) = &node.node { if !children.lock().is_empty() { return Err(Errno::ENOTEMPTY); } } entries.remove(name); Ok(()) } }