//! Bounded read-only directory and inode metadata validation. pub const BLOCK_BYTES: u64 = 512; pub const MAX_NAME_BYTES: usize = 24; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { InvalidName, InvalidIdentity, InvalidKind, InvalidRights, CorruptInodeSize, NoEntry, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Kind { Directory, Regular } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Name { bytes: [u8; MAX_NAME_BYTES], len: u8, } impl Name { pub fn new(raw: &[u8]) -> Result { if raw.is_empty() || raw.len() > MAX_NAME_BYTES || raw.contains(&0) || raw.contains(&b'/') || core::str::from_utf8(raw).is_err() { return Err(Error::InvalidName); } let mut name = Self { bytes: [0; MAX_NAME_BYTES], len: raw.len() as u8 }; name.bytes[..raw.len()].copy_from_slice(raw); Ok(name) } pub fn as_str(&self) -> &str { core::str::from_utf8(&self.bytes[..usize::from(self.len)]).unwrap() } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Inode { pub number: u64, pub kind: Kind, pub permissions: u16, pub uid: u32, pub gid: u32, pub size: u64, pub allocated_bytes: u64, } impl Inode { fn validate(&self) -> Result<(), Error> { if self.number == 0 { return Err(Error::InvalidIdentity); } if self.size > self.allocated_bytes || self.allocated_bytes % BLOCK_BYTES != 0 { return Err(Error::CorruptInodeSize); } let allowed = match self.kind { Kind::Directory => 0o555, Kind::Regular => 0o444, }; if self.permissions != allowed { return Err(Error::InvalidRights); } Ok(()) } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Stat { pub inode: u64, pub kind: Kind, pub permissions: u16, pub uid: u32, pub gid: u32, pub size: u64, pub blocks512: u64, } #[repr(C)] #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct StatWire { pub inode: u64, pub size: u64, pub blocks512: u64, pub kind: u32, pub permissions: u32, pub uid: u32, pub gid: u32, } pub const STAT_WIRE_BYTES: usize = core::mem::size_of::(); impl From for StatWire { fn from(value: Stat) -> Self { Self { inode: value.inode, size: value.size, blocks512: value.blocks512, kind: match value.kind { Kind::Directory => 1, Kind::Regular => 2 }, permissions: u32::from(value.permissions), uid: value.uid, gid: value.gid } } } impl From for Stat { fn from(value: Inode) -> Self { Self { inode: value.number, kind: value.kind, permissions: value.permissions, uid: value.uid, gid: value.gid, size: value.size, blocks512: value.allocated_bytes / BLOCK_BYTES } } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Catalog { root: Inode, names: [Name; 2], files: [Inode; 2], } impl Catalog { pub fn new(root: Inode, first_name: Name, first: Inode, second_name: Name, second: Inode) -> Result { root.validate()?; first.validate()?; second.validate()?; if root.kind != Kind::Directory || first.kind != Kind::Regular || second.kind != Kind::Regular { return Err(Error::InvalidKind); } if root.number == first.number || root.number == second.number || first.number == second.number { return Err(Error::InvalidIdentity); } if first_name == second_name { return Err(Error::InvalidName); } Ok(Self { root, names: [first_name, second_name], files: [first, second] }) } pub fn stat_root(&self) -> Stat { self.root.into() } pub fn stat_file(&self, raw_name: &[u8]) -> Result { let name = Name::new(raw_name)?; let index = self.names.iter().position(|entry| *entry == name) .ok_or(Error::NoEntry)?; Ok(self.files[index].into()) } }