//! Bounded UTF-8 path components and transactional CWD publication. pub const MAX_PATH_BYTES: usize = 256; pub const MAX_COMPONENT_BYTES: usize = 64; pub const MAX_COMPONENTS: usize = 16; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { EmptyInput, InvalidUtf8, TooLong, TooDeep, AboveRoot, InvalidComponent, Overflow, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Component { bytes: [u8; MAX_COMPONENT_BYTES], len: u8, } impl Component { const EMPTY: Self = Self { bytes: [0; MAX_COMPONENT_BYTES], len: 0 }; 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 Path { components: [Component; MAX_COMPONENTS], depth: u8, } impl Path { pub const fn root() -> Self { Self { components: [Component::EMPTY; MAX_COMPONENTS], depth: 0 } } pub const fn depth(&self) -> u8 { self.depth } pub fn component(&self, index: usize) -> Option<&str> { if index < usize::from(self.depth) { Some(self.components[index].as_str()) } else { None } } fn push(&mut self, bytes: &[u8]) -> Result<(), Error> { if bytes.len() > MAX_COMPONENT_BYTES { return Err(Error::TooLong); } if usize::from(self.depth) == MAX_COMPONENTS { return Err(Error::TooDeep); } let mut component = Component::EMPTY; component.bytes[..bytes.len()].copy_from_slice(bytes); component.len = bytes.len() as u8; self.components[usize::from(self.depth)] = component; self.depth += 1; Ok(()) } fn pop(&mut self) -> Result<(), Error> { if self.depth == 0 { return Err(Error::AboveRoot); } self.depth -= 1; self.components[usize::from(self.depth)] = Component::EMPTY; Ok(()) } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct PathState { pub cwd: Path, pub generation: u64, } impl PathState { pub const fn new() -> Self { Self { cwd: Path::root(), generation: 0 } } pub fn resolve(&self, raw: &[u8]) -> Result { if raw.is_empty() { return Err(Error::EmptyInput); } if raw.len() > MAX_PATH_BYTES { return Err(Error::TooLong); } let text = core::str::from_utf8(raw).map_err(|_| Error::InvalidUtf8)?; if text.as_bytes().contains(&0) { return Err(Error::InvalidComponent); } let mut candidate = if text.starts_with('/') { Path::root() } else { self.cwd }; for component in text.split('/') { match component { "" | "." => {}, ".." => candidate.pop()?, value => candidate.push(value.as_bytes())?, } } Ok(candidate) } pub fn chdir(&mut self, raw: &[u8]) -> Result { let candidate = self.resolve(raw)?; let generation = self.generation.checked_add(1).ok_or(Error::Overflow)?; self.cwd = candidate; self.generation = generation; Ok(candidate) } }