//! Serialized, bounded in-memory namespace transactions. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { Capacity, InvalidName, ParentMissing, NotDirectory, Exists, Quota, NoSpace, NotFound, Cycle, DirectoryLink, LinkOverflow, InvalidLinkCount } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Kind { File, Directory } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct LinkCount(u16); impl LinkCount { pub fn new(value: u16) -> Result { if value == 0 { return Err(Error::InvalidLinkCount); } Ok(Self(value)) } pub const fn value(&self) -> u16 { self.0 } pub fn add(&mut self) -> Result<(), Error> { let next = self.0.checked_add(1).ok_or(Error::LinkOverflow)?; self.0 = next; Ok(()) } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Entry { pub inode: u8, pub parent: u8, pub kind: Kind, name: [u8; 16], name_length: u8, } impl Entry { pub fn name(&self) -> &[u8] { &self.name[..usize::from(self.name_length)] } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Namespace { entries: [Option; N], inode_bitmap: u64, quota: usize, } impl Namespace { pub const ROOT: u8 = 1; pub fn new(quota: usize) -> Result { if N == 0 || N > 62 || quota > N { return Err(Error::Capacity); } Ok(Self {entries: [None; N], inode_bitmap: 3, quota}) } pub fn used(&self) -> usize { self.entries.iter().flatten().count() } pub const fn allocated_bits(&self) -> u64 { self.inode_bitmap } pub fn lookup(&self, parent: u8, name: &[u8]) -> Option { self.entries.iter().flatten() .find(|entry| entry.parent == parent && entry.name() == name).copied() } fn parent_directory(&self, inode: u8) -> Result<(), Error> { if inode == Self::ROOT { return Ok(()); } match self.entries.iter().flatten().find(|entry| entry.inode == inode) { Some(entry) if entry.kind == Kind::Directory => Ok(()), Some(_) => Err(Error::NotDirectory), None => Err(Error::ParentMissing), } } fn validate_name(name: &[u8]) -> Result<(), Error> { if name.is_empty() || name.len() > 16 || name == b"." || name == b".." || name.contains(&b'/') || name.contains(&0) || core::str::from_utf8(name).is_err() { return Err(Error::InvalidName); } Ok(()) } pub fn create(&mut self, parent: u8, name: &[u8], kind: Kind) -> Result { Self::validate_name(name)?; self.parent_directory(parent)?; if self.lookup(parent, name).is_some() { return Err(Error::Exists); } let slot = self.entries.iter().position(Option::is_none).ok_or(Error::NoSpace)?; let inode = (2u8..64).find(|id| self.inode_bitmap & (1u64 << id) == 0) .ok_or(Error::NoSpace)?; let mut staged = *self; staged.inode_bitmap |= 1u64 << inode; // Quota failure after private reservation must not publish the bitmap. if self.used() >= self.quota { return Err(Error::Quota); } let mut stored_name = [0; 16]; stored_name[..name.len()].copy_from_slice(name); staged.entries[slot] = Some(Entry {inode, parent, kind, name: stored_name, name_length: name.len() as u8}); *self = staged; Ok(inode) } /// Rename within this namespace without replacement. Identity and all /// allocation bits are retained; directory ancestry is checked first. pub fn rename(&mut self, from_parent: u8, from_name: &[u8], to_parent: u8, to_name: &[u8]) -> Result<(), Error> { Self::validate_name(from_name)?; Self::validate_name(to_name)?; self.parent_directory(from_parent)?; self.parent_directory(to_parent)?; let slot = self.entries.iter().position(|entry| entry.is_some_and(|entry| entry.parent == from_parent && entry.name() == from_name)) .ok_or(Error::NotFound)?; let source = self.entries[slot].ok_or(Error::NotFound)?; if from_parent == to_parent && from_name == to_name { return Ok(()); } if self.lookup(to_parent, to_name).is_some() { return Err(Error::Exists); } if source.kind == Kind::Directory { let mut ancestor = to_parent; let mut reached_root = false; for _ in 0..=N { if ancestor == source.inode { return Err(Error::Cycle); } if ancestor == Self::ROOT { reached_root = true; break; } ancestor = self.entries.iter().flatten() .find(|entry| entry.inode == ancestor) .ok_or(Error::ParentMissing)?.parent; } if !reached_root { return Err(Error::Cycle); } } let mut staged = *self; let mut replacement = source; replacement.parent = to_parent; replacement.name = [0; 16]; replacement.name[..to_name.len()].copy_from_slice(to_name); replacement.name_length = to_name.len() as u8; staged.entries[slot] = Some(replacement); *self = staged; Ok(()) } pub fn link_count(&self, inode: u8) -> u16 { self.entries.iter().flatten().filter(|entry| entry.inode == inode).count() as u16 } pub fn hardlink(&mut self, from_parent: u8, from_name: &[u8], to_parent: u8, to_name: &[u8]) -> Result { Self::validate_name(from_name)?; Self::validate_name(to_name)?; self.parent_directory(from_parent)?; self.parent_directory(to_parent)?; let source = self.lookup(from_parent, from_name).ok_or(Error::NotFound)?; if source.kind == Kind::Directory { return Err(Error::DirectoryLink); } if self.lookup(to_parent, to_name).is_some() { return Err(Error::Exists); } if self.used() >= self.quota { return Err(Error::Quota); } let slot = self.entries.iter().position(Option::is_none).ok_or(Error::NoSpace)?; let mut count = LinkCount::new(self.link_count(source.inode))?; count.add()?; let mut staged = *self; let mut alias = source; alias.parent = to_parent; alias.name = [0; 16]; alias.name[..to_name.len()].copy_from_slice(to_name); alias.name_length = to_name.len() as u8; staged.entries[slot] = Some(alias); *self = staged; Ok(count.value()) } }