use std::process::{Command, Stdio};
pub fn reap_stale_containers() {
let Some(cli) = detect_cli() else {
return;
};
let reaped = reap_orphans(&cli, &["ps", "-a"], |id| {
vec!["rm".to_string(), "-f".to_string(), id.to_string()]
});
if reaped > 0 {
tracing::info!(count = reaped, "reaped orphaned backing containers");
}
let reaped_networks = reap_orphans(&cli, &["network", "ls"], |id| {
vec!["network".to_string(), "rm".to_string(), id.to_string()]
});
if reaped_networks > 0 {
tracing::info!(count = reaped_networks, "reaped orphaned backing networks");
}
}
fn reap_orphans(cli: &str, list_args: &[&str], remove_argv: impl Fn(&str) -> Vec<String>) -> usize {
let mut args: Vec<&str> = list_args.to_vec();
args.extend_from_slice(&[
"--filter",
"label=fakecloud-instance",
"--format",
"{{.ID}} {{.Label \"fakecloud-instance\"}}",
]);
let output = match Command::new(cli).args(&args).stderr(Stdio::null()).output() {
Ok(o) if o.status.success() => o,
_ => return 0,
};
let self_pid = std::process::id();
let listing = String::from_utf8_lossy(&output.stdout);
let mut reaped = 0usize;
for line in listing.lines() {
let Some((id, label)) = line.split_once(' ') else {
continue;
};
let Some(pid_str) = label.strip_prefix("fakecloud-") else {
continue;
};
let Ok(pid) = pid_str.parse::<u32>() else {
continue;
};
if pid == self_pid || pid_alive(pid) {
continue;
}
let removed = Command::new(cli)
.args(remove_argv(id))
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.map(|s| s.success())
.unwrap_or(false);
if removed {
reaped += 1;
}
}
reaped
}
fn detect_cli() -> Option<String> {
if let Ok(cli) = std::env::var("FAKECLOUD_CONTAINER_CLI") {
return cli_works(&cli).then_some(cli);
}
if cli_works("docker") {
return Some("docker".to_string());
}
if cli_works("podman") {
return Some("podman".to_string());
}
None
}
fn cli_works(cli: &str) -> bool {
Command::new(cli)
.arg("info")
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.map(|s| s.success())
.unwrap_or(false)
}
#[cfg(unix)]
pub fn pid_alive(pid: u32) -> bool {
let rc = unsafe { libc::kill(pid as libc::pid_t, 0) };
if rc == 0 {
return true;
}
std::io::Error::last_os_error().raw_os_error() == Some(libc::EPERM)
}
#[cfg(not(unix))]
pub fn pid_alive(_pid: u32) -> bool {
true
}
#[cfg(all(test, unix))]
mod tests {
use super::{cli_works, pid_alive};
#[test]
fn self_is_alive() {
assert!(pid_alive(std::process::id()));
}
#[test]
fn init_is_alive() {
assert!(pid_alive(1));
}
#[test]
fn huge_pid_is_dead() {
assert!(!pid_alive(u32::MAX - 1));
}
#[test]
fn cli_works_false_for_unknown_binary() {
assert!(!cli_works("definitely-not-a-real-cli-name-xyz123"));
}
}