//! Resolves virtual paths before routing them to mounted layers. use crate::*; use std::{collections::BTreeMap, io::ErrorKind as E}; struct Mount { layer: Arc, readonly: bool, } /// A per-process mount namespace. Mount the root before use. pub struct Mounts { mounts: BTreeMap, observer: observe::Observer, } impl Default for Mounts { fn default() -> Self { Self { mounts: BTreeMap::new(), observer: observe::Observer::new(|_| {}), } } } impl Mounts { /// Add or replace a mount. Read-only mounts reject every mutation. pub fn mount(&mut self, path: &str, layer: Arc, readonly: bool) { let path = resolve("/", path); let layer = self.observer.layer(layer, &path, Process::default()); self.mounts.insert(path, Mount { layer, readonly }); } pub(crate) async fn metadata_file(&self, path: &str, follow: bool) -> Result> { let (m, p) = self.route(path, true)?; m.layer .open( &p, Open { follow, ..Open::default() }, ) .await } fn route(&self, path: &str, write: bool) -> Result<(&Mount, String)> { let path = resolve("/", path); let (base, m) = self .mounts .iter() .filter(|(p, _)| p.as_str() == "/" || path == **p || path.starts_with(&format!("{p}/"))) .max_by_key(|(p, _)| p.len()) .ok_or_else(|| error(E::NotFound))?; if write && m.readonly { return Err(error(E::ReadOnlyFilesystem)); } let rest = if base == "/" { path } else { path[base.len()..].into() }; Ok((m, if rest.is_empty() { "/".into() } else { rest })) } } #[async_trait] impl Layer for Mounts { fn observer(&self) -> Option { Some(self.observer.clone()) } async fn stat(&self, path: &str, follow: bool) -> Result { let (m, p) = self.route(path, false)?; m.layer.stat(&p, follow).await } async fn readlink(&self, path: &str) -> Result { let (m, p) = self.route(path, false)?; m.layer.readlink(&p).await } async fn list(&self, path: &str) -> Result> { let path = resolve("/", path); let (m, p) = self.route(&path, false)?; let mut entries = m.layer.list(&p).await?; let prefix = if path == "/" { "/".into() } else { format!("{path}/") }; for mount in self.mounts.keys() { if let Some(rest) = mount.strip_prefix(&prefix) && !rest.is_empty() { let name = rest.split('/').next().unwrap(); entries.retain(|e| e.name != name); entries.push(Entry { name: name.into(), directory: true, symlink: false, }); } } entries.sort_by(|a, b| a.name.cmp(&b.name)); Ok(entries) } async fn open(&self, path: &str, options: Open) -> Result> { let (m, p) = self.route(path, options.write || options.create || options.truncate)?; let file = m.layer.open(&p, options).await?; if m.readonly { Ok(Arc::new(crate::readonly::Readonly(file))) } else { Ok(file) } } async fn mkdir(&self, path: &str) -> Result<()> { let (m, p) = self.route(path, true)?; m.layer.mkdir(&p).await } async fn remove_file(&self, path: &str) -> Result<()> { let (m, p) = self.route(path, true)?; m.layer.remove_file(&p).await } async fn remove_dir(&self, path: &str) -> Result<()> { let (m, p) = self.route(path, true)?; m.layer.remove_dir(&p).await } async fn rename(&self, from: &str, to: &str) -> Result<()> { let (a, p) = self.route(from, true)?; let (b, q) = self.route(to, true)?; if !std::ptr::eq(a, b) { return Err(error(E::CrossesDevices)); } a.layer.rename(&p, &q).await } }