use std::fs;
use std::path::{Path, PathBuf};
use crate::error::Context;
use crate::{Error, Result};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Overlay {
pub lower: Vec<PathBuf>,
pub upper: PathBuf,
pub work: PathBuf,
pub merged: PathBuf,
pub userxattr: bool,
}
impl Overlay {
pub fn prepare(lower: Vec<PathBuf>, dir: &Path) -> Result<Self> {
let ov = Self {
lower,
upper: dir.join("upper"),
work: dir.join("work"),
merged: dir.join("merged"),
userxattr: false,
};
ov.create()?;
Ok(ov)
}
pub fn for_layers(layers: &[PathBuf], dir: &Path, xattr: crate::OverlayXattr) -> Result<Self> {
let mut ov = Self::prepare(Self::stack(layers), dir)?;
ov.userxattr = xattr == crate::OverlayXattr::User;
Ok(ov)
}
pub fn stack(layers: &[PathBuf]) -> Vec<PathBuf> {
let mut lower: Vec<PathBuf> = Vec::with_capacity(layers.len());
for l in layers.iter().rev() {
if !lower.contains(l) {
lower.push(l.clone());
}
}
lower
}
pub fn create(&self) -> Result<()> {
if self.lower.is_empty() {
return Err(Error::Invalid("an overlay needs a lower directory".into()));
}
for d in [&self.upper, &self.work, &self.merged] {
fs::create_dir_all(d).ctx(|| format!("creating {}", d.display()))?;
}
Ok(())
}
pub fn mount_data(&self) -> String {
let lower: Vec<String> = self.lower.iter().map(|p| escape(p)).collect();
let mut data = format!(
"lowerdir={},upperdir={},workdir={}",
lower.join(":"),
escape(&self.upper),
escape(&self.work)
);
if self.userxattr {
data.push_str(",userxattr");
}
data
}
}
fn escape(p: &Path) -> String {
let s = p.display().to_string();
let mut out = String::with_capacity(s.len());
for c in s.chars() {
if matches!(c, '\\' | ',' | ':') {
out.push('\\');
}
out.push(c);
}
out
}