Skip to main content

oci_image/
snapshot.rs

1use std::fs;
2use std::path::{Path, PathBuf};
3
4use crate::error::Context;
5use crate::{Error, Result};
6
7/// The directories of one overlay mount: read-only lower layers (top first),
8/// and the container's own upper, work and merged directories.
9///
10/// [`Overlay::prepare`] only creates directories; mounting is the caller's
11/// (`mount -t overlay overlay -o <mount_data> <merged>`, or the equivalent
12/// `mount(2)`), since it needs privileges this crate does not assume. The
13/// mount data must fit one page (4096 bytes); with long store paths and many
14/// layers, mount from inside the store with relative lower paths.
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct Overlay {
17    pub lower: Vec<PathBuf>,
18    pub upper: PathBuf,
19    pub work: PathBuf,
20    pub merged: PathBuf,
21    /// Mount with `userxattr` (markers in `user.overlay.*`, user namespaces).
22    pub userxattr: bool,
23}
24
25impl Overlay {
26    /// Create `<dir>/{upper,work,merged}` over `lower` (topmost first).
27    pub fn prepare(lower: Vec<PathBuf>, dir: &Path) -> Result<Self> {
28        let ov = Self {
29            lower,
30            upper: dir.join("upper"),
31            work: dir.join("work"),
32            merged: dir.join("merged"),
33            userxattr: false,
34        };
35        ov.create()?;
36        Ok(ov)
37    }
38
39    /// An overlay over image layers given bottom first (as
40    /// [`crate::LayerStore::image_layers`] returns them), with the xattr
41    /// namespace the layers were unpacked with.
42    pub fn for_layers(layers: &[PathBuf], dir: &Path, xattr: crate::OverlayXattr) -> Result<Self> {
43        let mut ov = Self::prepare(Self::stack(layers), dir)?;
44        ov.userxattr = xattr == crate::OverlayXattr::User;
45        Ok(ov)
46    }
47
48    /// The lowerdir order for image layers given bottom first: top first,
49    /// and a layer that repeats only at its topmost place (overlayfs refuses
50    /// a lower directory twice; the upper copy re-applies everything the
51    /// lower one did).
52    pub fn stack(layers: &[PathBuf]) -> Vec<PathBuf> {
53        let mut lower: Vec<PathBuf> = Vec::with_capacity(layers.len());
54        for l in layers.iter().rev() {
55            if !lower.contains(l) {
56                lower.push(l.clone());
57            }
58        }
59        lower
60    }
61
62    /// Create the upper, work and merged directories of an overlay laid out
63    /// by the caller.
64    pub fn create(&self) -> Result<()> {
65        if self.lower.is_empty() {
66            return Err(Error::Invalid("an overlay needs a lower directory".into()));
67        }
68        for d in [&self.upper, &self.work, &self.merged] {
69            fs::create_dir_all(d).ctx(|| format!("creating {}", d.display()))?;
70        }
71        Ok(())
72    }
73
74    /// The overlayfs mount data, with `\`, `,` and `:` in paths escaped.
75    pub fn mount_data(&self) -> String {
76        let lower: Vec<String> = self.lower.iter().map(|p| escape(p)).collect();
77        let mut data = format!(
78            "lowerdir={},upperdir={},workdir={}",
79            lower.join(":"),
80            escape(&self.upper),
81            escape(&self.work)
82        );
83        if self.userxattr {
84            data.push_str(",userxattr");
85        }
86        data
87    }
88}
89
90fn escape(p: &Path) -> String {
91    let s = p.display().to_string();
92    let mut out = String::with_capacity(s.len());
93    for c in s.chars() {
94        if matches!(c, '\\' | ',' | ':') {
95            out.push('\\');
96        }
97        out.push(c);
98    }
99    out
100}