//! 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). //! Stage 17 adds symlinks, hard links (two directory entries holding the //! same `Arc`), owners (chown) and renames that just move the `Arc`. use super::{now_timespec, DirEntry, FileSystem, Inode, InodeKind, StatFs, MODE_SET, RAMFS_MAGIC}; 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() } /// A ramfs can grow until memory runs out, so that's its "size" (Linux's /// ramfs says 0 for everything; numbers are friendlier to `df`). fn statfs(&self) -> StatFs { let (total, free) = crate::memory::frame_totals(); StatFs { magic: RAMFS_MAGIC, block_size: 4096, blocks: total, free_blocks: free, avail_blocks: free, files: total, free_files: free, name_max: 255, read_only: false, } } } pub enum Node { File(Mutex>), Dir(Mutex>>), /// A symlink's target (stage 17). Symlink(String), /// A Unix socket's name (stage 18): no contents. Socket, } /// 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], /// Owner and group (stage 17: chown; new files get the creator's). uid: u32, gid: u32, /// Directory entries naming this node (hard links; directories count /// their subdirectories' ".." on top, see `nlink`). links: u32, } 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], uid: 0, gid: 0, links: 1 }) } } fn entries(&self) -> Result<&Mutex>>, Errno> { match &self.node { Node::Dir(entries) => Ok(entries), _ => Err(Errno::ENOTDIR), } } /// A directory entry pointing at `node` went away. fn unlinked(node: &RamInode) { let mut meta = node.meta.lock(); meta.links = meta.links.saturating_sub(1); meta.times[2] = now_timespec(); } /// The status-change time (ctime) is now. fn changed(&self) { self.meta.lock().times[2] = now_timespec(); } /// 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, Node::Symlink(_) => InodeKind::Symlink, Node::Socket => InodeKind::Socket, } } fn size(&self) -> usize { match &self.node { Node::File(data) => data.lock().len(), Node::Dir(entries) => entries.lock().len(), Node::Symlink(target) => target.len(), Node::Socket => 0, } } fn readlink(&self) -> Result { match &self.node { Node::Symlink(target) => Ok(target.clone()), _ => Err(Errno::EINVAL), } } 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) }; check_name(name)?; 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::Socket => Node::Socket, InodeKind::Symlink | InodeKind::Device => return Err(Errno::EOPNOTSUPP), // symlinks: `symlink` })); entries.insert(String::from(name), node.clone()); drop(entries); self.touch(); 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(), ino: node.ino_number() }).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); } } if let Some(node) = entries.remove(name) { Self::unlinked(&node); } drop(entries); self.touch(); Ok(()) } // --- stage 17 ---------------------------------------------------------- fn ino(&self) -> Option { Some(self.ino_number()) } fn nlink(&self) -> Option { let links = self.meta.lock().links as u64; Some(match &self.node { // "." plus each subdirectory's ".." (like a disk file system) Node::Dir(entries) => links + 1 + entries.lock().values().filter(|n| matches!(n.node, Node::Dir(_))).count() as u64, _ => links, }) } fn owner(&self) -> (u32, u32) { let meta = self.meta.lock(); (meta.uid, meta.gid) } fn set_owner(&self, uid: Option, gid: Option) -> Result<(), Errno> { let mut meta = self.meta.lock(); if let Some(uid) = uid { meta.uid = uid; } if let Some(gid) = gid { meta.gid = gid; } meta.times[2] = now_timespec(); Ok(()) } fn symlink(&self, name: &str, target: &str) -> Result, Errno> { check_name(name)?; let mut entries = self.entries()?.lock(); if entries.contains_key(name) { return Err(Errno::EEXIST); } let node = Arc::new(RamInode::new(Node::Symlink(String::from(target)))); entries.insert(String::from(name), node.clone()); drop(entries); self.touch(); Ok(node) } fn link(&self, name: &str, target: &Arc) -> Result<(), Errno> { check_name(name)?; let node = super::downcast::(target).ok_or(Errno::EXDEV)?; let mut entries = self.entries()?.lock(); if entries.contains_key(name) { return Err(Errno::EEXIST); } { let mut meta = node.meta.lock(); meta.links += 1; meta.times[2] = now_timespec(); } entries.insert(String::from(name), node); drop(entries); self.touch(); Ok(()) } /// Move the `Arc` from one map to the other. With two directories, the /// two locks are taken in address order, so two renames going opposite /// ways can't each hold one lock and wait for the other. fn rename_entry(&self, old: &str, new_dir: &Arc, new: &str) -> Result<(), Errno> { check_name(new)?; let target = super::downcast::(new_dir).ok_or(Errno::EXDEV)?; let same = core::ptr::eq(self, &*target); let (src, dst) = (self.entries()?, target.entries()?); let replaced; if same { let mut map = src.lock(); let node = map.remove(old).ok_or(Errno::ENOENT)?; replaced = map.insert(String::from(new), node); } else { let self_first = (self as *const RamInode) < Arc::as_ptr(&target); let (mut a, mut b) = if self_first { let a = src.lock(); (a, dst.lock()) } else { let b = dst.lock(); (src.lock(), b) }; let node = a.remove(old).ok_or(Errno::ENOENT)?; node.changed(); replaced = b.insert(String::from(new), node); } if let Some(gone) = replaced { Self::unlinked(&gone); } self.touch(); target.touch(); Ok(()) } fn as_any(self: Arc) -> Option> { Some(self) } } impl RamInode { /// Inode number: the node's address (unique while it exists). fn ino_number(&self) -> u64 { self as *const RamInode as u64 } } fn check_name(name: &str) -> Result<(), Errno> { match name { "" | "." | ".." => Err(Errno::EINVAL), _ if name.contains('/') => Err(Errno::EINVAL), _ if name.len() > 255 => Err(Errno::ENAMETOOLONG), _ => Ok(()), } }