//! Bounded exact-path symlink resolution inside a process-rooted namespace. use crate::os_vfs_paths::{Error as PathError, MountNamespace, Path, Route, MAX_PATH_BYTES}; pub const MAX_SYMLINK_HOPS: u8 = 40; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { InvalidLink, Path(PathError), Eloop(u8), } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Link { at: Path, target: [u8; MAX_PATH_BYTES], target_len: u16, } impl Link { pub fn new(namespace: &MountNamespace, source: &[u8], target: &[u8]) -> Result { if target.is_empty() || target.len() > MAX_PATH_BYTES || !target.starts_with(b"/") { return Err(Error::InvalidLink); } let at = namespace.resolve(source).map_err(Error::Path)?.path; namespace.resolve(target).map_err(Error::Path)?; let mut bytes = [0; MAX_PATH_BYTES]; bytes[..target.len()].copy_from_slice(target); Ok(Self { at, target: bytes, target_len: target.len() as u16 }) } fn target(&self) -> &[u8] { &self.target[..usize::from(self.target_len)] } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Resolution { pub route: Route, pub hops: u8, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct SymlinkTable { links: [Link; 2], } impl SymlinkTable { pub fn new(first: Link, second: Link) -> Result { if first.at == second.at { return Err(Error::InvalidLink); } Ok(Self { links: [first, second] }) } pub fn resolve(&self, namespace: &MountNamespace, raw: &[u8]) -> Result { let mut route = namespace.resolve(raw).map_err(Error::Path)?; let mut hops = 0; loop { let link = match self.links.iter().find(|link| link.at == route.path) { Some(link) => link, None => return Ok(Resolution { route, hops }), }; if hops == MAX_SYMLINK_HOPS + 1 { return Err(Error::Eloop(hops)); } route = namespace.resolve(link.target()).map_err(Error::Path)?; hops += 1; } } }