#[cfg(target_os = "linux")]
use std::path::Path;
#[cfg(target_os = "linux")]
pub fn reap_orphaned_box(box_id: &str) {
reap_orphaned_box_in(&a3s_box_core::dirs_home(), box_id);
}
#[cfg(target_os = "linux")]
fn reap_orphaned_box_in(home_dir: &Path, box_id: &str) {
let box_dir = home_dir.join("boxes").join(box_id);
if !box_dir.exists() {
return;
}
let killed = kill_orphaned_shim(box_id);
wait_for_exit(&killed, std::time::Duration::from_secs(5));
let merged = box_dir.join("merged");
if merged.exists() {
if let Err(error) = crate::rootfs::overlay::overlay_unmount(&merged) {
tracing::warn!(
box_id = %box_id,
path = %merged.display(),
error = %error,
"Failed to unmount orphaned box overlay during crash recovery"
);
}
}
if let Err(error) = std::fs::remove_dir_all(&box_dir) {
if error.kind() != std::io::ErrorKind::NotFound {
tracing::warn!(
box_id = %box_id,
path = %box_dir.display(),
error = %error,
"Failed to remove orphaned box directory during crash recovery"
);
}
}
let _ = std::fs::remove_dir(format!("/sys/fs/cgroup/a3s-box/{box_id}"));
if !killed.is_empty() {
tracing::info!(box_id = %box_id, "Reaped orphaned sandbox microVM after CRI restart");
}
}
#[cfg(target_os = "linux")]
fn wait_for_exit(pids: &[i32], timeout: std::time::Duration) {
if pids.is_empty() {
return;
}
let step = std::time::Duration::from_millis(50);
let mut remaining = (timeout.as_millis() / step.as_millis().max(1)) as u32;
while remaining > 0 {
let any_alive = pids.iter().any(|&pid| unsafe { libc::kill(pid, 0) } == 0);
if !any_alive {
return;
}
std::thread::sleep(step);
remaining -= 1;
}
}
#[cfg(not(target_os = "linux"))]
pub fn reap_orphaned_box(_box_id: &str) {}
#[cfg(all(test, not(target_os = "linux")))]
mod tests {
use super::*;
#[test]
fn reap_orphaned_box_is_noop_on_non_linux() {
reap_orphaned_box("non-linux-noop");
}
}
#[cfg(target_os = "linux")]
fn kill_orphaned_shim(box_id: &str) -> Vec<i32> {
let Ok(entries) = std::fs::read_dir("/proc") else {
return Vec::new();
};
let mut killed = Vec::new();
for entry in entries.flatten() {
let name = entry.file_name();
let Some(pid) = name.to_str().and_then(|s| s.parse::<i32>().ok()) else {
continue;
};
let Ok(cmdline) = std::fs::read(Path::new("/proc").join(name).join("cmdline")) else {
continue;
};
let cmdline = String::from_utf8_lossy(&cmdline);
if cmdline.contains("a3s-box-shim") && cmdline.contains(box_id) {
unsafe {
libc::kill(pid, libc::SIGKILL);
}
tracing::info!(box_id = %box_id, pid, "Killed orphaned shim during crash recovery");
killed.push(pid);
}
}
killed
}
#[cfg(all(test, target_os = "linux"))]
mod tests {
use super::*;
#[test]
fn test_reap_removes_box_dir() {
let home = tempfile::tempdir().unwrap();
let box_id = "reap-test-no-such-shim-uuid";
let box_dir = home.path().join("boxes").join(box_id);
std::fs::create_dir_all(box_dir.join("logs")).unwrap();
std::fs::write(box_dir.join("logs/shim.stdout.log"), b"x").unwrap();
assert!(box_dir.exists());
reap_orphaned_box_in(home.path(), box_id);
assert!(!box_dir.exists(), "orphaned box dir should be removed");
}
#[test]
fn test_reap_absent_box_is_noop() {
let home = tempfile::tempdir().unwrap();
reap_orphaned_box_in(home.path(), "absent-box-uuid");
}
}