Files
sausage/vfs/src/mounts.rs
T

127 lines
4.2 KiB
Rust

//! Resolves virtual paths before routing them to mounted layers.
use crate::*;
use std::{collections::BTreeMap, io::ErrorKind as E};
struct Mount {
layer: Arc<dyn Layer>,
readonly: bool,
}
/// A per-process mount namespace. Mount the root before use.
pub struct Mounts {
mounts: BTreeMap<String, Mount>,
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<dyn Layer>, 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<Arc<dyn File>> {
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<observe::Observer> {
Some(self.observer.clone())
}
async fn stat(&self, path: &str, follow: bool) -> Result<Stat> {
let (m, p) = self.route(path, false)?;
m.layer.stat(&p, follow).await
}
async fn readlink(&self, path: &str) -> Result<String> {
let (m, p) = self.route(path, false)?;
m.layer.readlink(&p).await
}
async fn list(&self, path: &str) -> Result<Vec<Entry>> {
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<Arc<dyn File>> {
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
}
}