//! 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, InvalidMount, NoMount, 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 starts_with(&self, prefix: &Path) -> bool { if self.depth < prefix.depth { return false; } self.components[..usize::from(prefix.depth)] == prefix.components[..usize::from(prefix.depth)] } 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 Mount { pub at: Path, pub volume: u8, } impl Mount { pub fn new(at: Path, volume: u8) -> Result { if volume == 0 { return Err(Error::InvalidMount); } Ok(Self { at, volume }) } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Route { pub path: Path, pub volume: u8, pub relative_depth: u8, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct MountNamespace { pub process_root: Path, pub cwd: Path, pub mounts: [Mount; 2], pub generation: u64, } impl MountNamespace { pub fn new(process_root: Path, first: Mount, second: Mount) -> Result { if first.volume == second.volume || first.at == second.at || first.at.depth <= process_root.depth || second.at.depth <= process_root.depth || !first.at.starts_with(&process_root) || !second.at.starts_with(&process_root) { return Err(Error::InvalidMount); } Ok(Self { process_root, cwd: process_root, mounts: [first, second], 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('/') { self.process_root } else { self.cwd }; for component in text.split('/') { match component { "" | "." => {}, ".." => { if candidate.depth <= self.process_root.depth { return Err(Error::AboveRoot); } candidate.pop()?; } value => candidate.push(value.as_bytes())?, } } let selected = self.mounts.iter() .filter(|mount| candidate.starts_with(&mount.at)) .max_by_key(|mount| mount.at.depth) .ok_or(Error::NoMount)?; Ok(Route { path: candidate, volume: selected.volume, relative_depth: candidate.depth - selected.at.depth }) } pub fn chdir(&mut self, raw: &[u8]) -> Result { let route = self.resolve(raw)?; let generation = self.generation.checked_add(1).ok_or(Error::Overflow)?; self.cwd = route.path; self.generation = generation; Ok(route) } } #[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) } }