//! What the root salvage ([`crate::rootfs`]) asks of the host: free space, //! reflinks, and the locks that keep two salvages apart. //! //! The space and reflink calls sit behind [`Backend`] so a test can set the //! free space and make a clone fail. use std::fs::{File, OpenOptions}; use std::io; use std::os::unix::fs::OpenOptionsExt as _; use std::path::Path; use std::sync::Mutex; use anyhow::{Context as _, Result}; /// Where the per-container lock files live. A root-owned runtime directory /// (`RuntimeDirectory=` of `hive-priv.service`), so no container can write it. pub(crate) const LOCK_DIR: &str = "/run/hive-priv"; /// Held by the one salvage that is copying data at any moment. pub(crate) static ONE_AT_A_TIME: Mutex<()> = Mutex::new(()); /// Size and free space of a filesystem, in bytes. `free` is what an /// unprivileged writer could still use. #[derive(Debug, Clone, Copy)] pub(crate) struct FsSpace { pub(crate) free: u64, pub(crate) total: u64, } /// The host calls the salvage makes. pub(crate) trait Backend { /// Space on the filesystem holding `path`. fn space(&self, path: &Path) -> io::Result; /// Make `dst`, an empty file, share `src`'s data blocks. An error leaves /// `dst` as it was: the filesystem cannot clone, or `src` and `dst` are on /// different ones. fn clone_file(&self, src: &File, dst: &File) -> io::Result<()>; } /// The real filesystem: `statvfs(2)` and `ioctl(FICLONE)`. pub(crate) struct Os; impl Backend for Os { fn space(&self, path: &Path) -> io::Result { let st = rustix::fs::statvfs(path)?; Ok(FsSpace { free: st.f_bavail.saturating_mul(st.f_frsize), total: st.f_blocks.saturating_mul(st.f_frsize), }) } fn clone_file(&self, src: &File, dst: &File) -> io::Result<()> { Ok(rustix::fs::ioctl_ficlone(dst, src)?) } } /// An exclusive `flock(2)` on a container's lock file, released on drop. pub(crate) struct ContainerLock { _file: File, } impl ContainerLock { /// Wait for the lock of `name` in `dir`, creating the file if needed. /// /// # Errors /// Returns an error when the directory or file cannot be created or the /// lock call fails. pub(crate) fn acquire(dir: &Path, name: &str) -> Result { std::fs::create_dir_all(dir).with_context(|| format!("create {}", dir.display()))?; let path = dir.join(format!("salvage-{name}.lock")); let file = OpenOptions::new() .write(true) .create(true) .truncate(false) .mode(0o600) .custom_flags(libc::O_NOFOLLOW) .open(&path) .with_context(|| format!("open {}", path.display()))?; file.lock() .with_context(|| format!("lock {}", path.display()))?; Ok(Self { _file: file }) } }