120 lines
3.6 KiB
Rust
120 lines
3.6 KiB
Rust
use anyhow::{anyhow, Context, Result};
|
|
use base64::{engine::general_purpose as b64, Engine as _};
|
|
use duct::cmd;
|
|
use std::path::{Path, PathBuf};
|
|
|
|
#[derive(Debug)]
|
|
pub struct TempDir {
|
|
path: PathBuf,
|
|
removed: bool,
|
|
}
|
|
|
|
impl TempDir {
|
|
pub fn new() -> Result<TempDir> {
|
|
let mut rbuf = [0u8; 10];
|
|
getrandom::getrandom(&mut rbuf).map_err(|_| anyhow!("Failed to get randomness"))?;
|
|
let path = PathBuf::from(format!("/var/run/soft/{}", b64::URL_SAFE_NO_PAD.encode(rbuf)));
|
|
log::trace!("Creating temporary directory '{}'", path.display());
|
|
std::fs::create_dir(&path).with_context(|| format!("Failed to create temporary directory '{}'", path.display()))?;
|
|
Ok(TempDir { path, removed: false })
|
|
}
|
|
|
|
pub fn cleanup(&mut self) -> Result<()> {
|
|
log::trace!("Removing temporary directory '{}'", self.path.display());
|
|
std::fs::remove_dir(&self.path)
|
|
.with_context(|| format!("Failed to remove temporary directory '{}'", self.path.display()))?;
|
|
self.removed = true;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn path(&self) -> &Path {
|
|
&self.path
|
|
}
|
|
}
|
|
|
|
impl Drop for TempDir {
|
|
fn drop(&mut self) {
|
|
if !self.removed {
|
|
if let Err(e) = self.cleanup() {
|
|
log::error!("While cleaning up temporary directory '{}': {:?}", self.path.display(), e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
enum MountPoint {
|
|
Existing(PathBuf),
|
|
Temp(TempDir),
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct Mount {
|
|
point: MountPoint,
|
|
was: (String, PathBuf),
|
|
unmounted: bool,
|
|
}
|
|
|
|
impl Mount {
|
|
pub fn path(&self) -> &Path {
|
|
match &self.point {
|
|
MountPoint::Existing(p) => &p,
|
|
MountPoint::Temp(td) => &td.path,
|
|
}
|
|
}
|
|
|
|
pub fn mount_tmpfs(to: PathBuf) -> Result<Mount> {
|
|
Self::mount(Path::new("tmpfs"), to, "tmpfs", "rw")
|
|
}
|
|
|
|
pub fn mount_bind(from: &Path, to: PathBuf, rw: bool) -> Result<Mount> {
|
|
Self::mount(from, to, "nullfs", if rw { "rw" } else { "ro" })
|
|
}
|
|
|
|
pub fn mount_safe_dev(to: PathBuf) -> Result<Mount> {
|
|
Self::mount(Path::new("devfs"), to, "devfs", "ruleset=4")
|
|
}
|
|
|
|
pub fn mount_temp(from: &Path, fs: &str, opts: &str) -> Result<Mount> {
|
|
let td = TempDir::new()?;
|
|
let mut m = Self::mount(from, td.path().to_owned(), fs, opts)?;
|
|
m.point = MountPoint::Temp(td);
|
|
Ok(m)
|
|
}
|
|
|
|
pub fn mount(from: &Path, to: PathBuf, fs: &str, opts: &str) -> Result<Mount> {
|
|
log::trace!("Mounting {}: '{}' to '{}'", fs, from.display(), to.display());
|
|
cmd!("mount", "-t", fs, "-o", opts, from, &to)
|
|
.stdout_null()
|
|
.run()
|
|
.with_context(|| format!("Failed to mount {}: '{}' to '{}'", fs, from.display(), to.display()))?;
|
|
Ok(Mount {
|
|
point: MountPoint::Existing(to),
|
|
was: (fs.to_owned(), from.to_owned()),
|
|
unmounted: false,
|
|
})
|
|
}
|
|
|
|
pub fn unmount(&mut self) -> Result<()> {
|
|
log::trace!("Unmounting '{}' (was {} '{}')", self.path().display(), self.was.0, self.was.1.display());
|
|
cmd!("umount", "-f", self.path())
|
|
.run()
|
|
.with_context(|| format!("Failed to unmount '{}'", self.path().display()))?;
|
|
if let MountPoint::Temp(td) = &mut self.point {
|
|
td.cleanup()?;
|
|
}
|
|
self.unmounted = true;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl Drop for Mount {
|
|
fn drop(&mut self) {
|
|
if !self.unmounted {
|
|
if let Err(e) = self.unmount() {
|
|
log::error!("While cleaning up mount '{}': {:?}", self.path().display(), e);
|
|
}
|
|
}
|
|
}
|
|
}
|