Files
sausage/vfs/src/mounts.rs
T
surma 151550242b sausage-vfs: review fixes (fallible growth, symlink flags, stream cancel, sized reads, timestamps, error kinds, linker shadowing)
API: Layer::stat(path, follow), Layer::readlink, Open::follow,
Stat::symlink and Entry::symlink. Process::idle documents the shutdown
order: abort, await the task, await idle, release locks.
2026-10-23 00:49:46 +01:00

115 lines
3.8 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.
#[derive(Default)]
pub struct Mounts {
mounts: BTreeMap<String, Mount>,
}
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) {
self.mounts
.insert(resolve("/", 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 {
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
}
}