use std::ffi::OsStr;
use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use arcbox_core::host_mount::{MountInfo, MountTable};
use super::host::{Host, run_checked};
use super::steps::{Outcome, skipped};
pub(super) fn remove_host_mounts(
host: &dyn Host,
root: &Path,
ours: fn(&MountInfo) -> bool,
) -> Result<Outcome> {
if root.symlink_metadata().is_err() {
return Ok(skipped("absent"));
}
let listing = run_checked(host, "/sbin/mount", &[])?;
let table = MountTable::parse(&String::from_utf8_lossy(&listing.stdout));
let mut mounts = table.under(root);
if let Some(info) = table.at(root) {
mounts.push((canonical_root(root)?, info));
}
let mut kept = Vec::new();
let mut foreign = Vec::new();
for (mountpoint, info) in mounts {
if ours(&info) {
run_checked(
host,
"/sbin/umount",
&[OsStr::new("-f"), mountpoint.as_os_str()],
)?;
} else {
kept.push(format!(
"{} ({}) at {}",
info.source,
info.fstype,
mountpoint.display()
));
foreign.push(mountpoint);
}
}
remove_empty_directories(&canonical_root(root)?, &foreign, &mut kept)?;
if kept.is_empty() {
Ok(Outcome::Done)
} else {
Ok(skipped(format!("left alone: {}", kept.join(", "))))
}
}
fn canonical_root(root: &Path) -> Result<PathBuf> {
let parent = root
.parent()
.with_context(|| format!("{} has no parent", root.display()))?;
let parent = std::fs::canonicalize(parent)
.with_context(|| format!("could not resolve {}", parent.display()))?;
let name = root
.file_name()
.with_context(|| format!("{} has no name", root.display()))?;
Ok(parent.join(name))
}
fn remove_empty_directories(
dir: &Path,
foreign: &[PathBuf],
kept: &mut Vec<String>,
) -> Result<bool> {
let entries =
std::fs::read_dir(dir).with_context(|| format!("could not read {}", dir.display()))?;
let mut own_content = false;
let mut kept_below = false;
for entry in entries {
let entry = entry.with_context(|| format!("could not read {}", dir.display()))?;
let path = entry.path();
if !entry.file_type()?.is_dir() {
own_content = true;
} else if foreign.contains(&path) || !remove_empty_directories(&path, foreign, kept)? {
kept_below = true;
}
}
if own_content {
kept.push(dir.display().to_string());
}
if own_content || kept_below {
return Ok(false);
}
std::fs::remove_dir(dir).with_context(|| format!("could not remove {}", dir.display()))?;
Ok(true)
}