use std::collections::HashMap;
use std::process::Command;
use std::time::Duration;
const DOCKER_TIMEOUT_SECS: u64 = 2;
pub fn resolve_container_names(pids: &[u32]) -> HashMap<u32, String> {
if pids.is_empty() {
return HashMap::new();
}
docker_resolve(pids)
.or_else(|| podman_resolve(pids))
.unwrap_or_default()
}
pub fn has_containers(names: &HashMap<u32, String>) -> bool {
!names.is_empty()
}
fn docker_resolve(pids: &[u32]) -> Option<HashMap<u32, String>> {
let output = run_with_timeout(
"docker",
&["ps", "--no-trunc", "--format", "{{.ID}} {{.Names}}"],
)?;
if output.is_empty() {
return Some(HashMap::new());
}
let containers: Vec<(String, String)> = output
.lines()
.filter_map(|line| {
let mut parts = line.splitn(2, ' ');
let id = parts.next()?.trim().to_string();
let name = parts.next()?.trim().to_string();
if id.is_empty() || name.is_empty() {
None
} else {
Some((id, name))
}
})
.collect();
if containers.is_empty() {
return Some(HashMap::new());
}
let mut result = HashMap::new();
for (id, name) in &containers {
if let Some(container_pid) = get_container_pid("docker", id) {
if pids.contains(&container_pid) {
result.insert(container_pid, name.clone());
}
}
}
Some(result)
}
fn podman_resolve(pids: &[u32]) -> Option<HashMap<u32, String>> {
let output = run_with_timeout(
"podman",
&["ps", "--no-trunc", "--format", "{{.ID}} {{.Names}}"],
)?;
if output.is_empty() {
return Some(HashMap::new());
}
let containers: Vec<(String, String)> = output
.lines()
.filter_map(|line| {
let mut parts = line.splitn(2, ' ');
let id = parts.next()?.trim().to_string();
let name = parts.next()?.trim().to_string();
if id.is_empty() || name.is_empty() {
None
} else {
Some((id, name))
}
})
.collect();
if containers.is_empty() {
return Some(HashMap::new());
}
let mut result = HashMap::new();
for (id, name) in &containers {
if let Some(container_pid) = get_container_pid("podman", id) {
if pids.contains(&container_pid) {
result.insert(container_pid, name.clone());
}
}
}
Some(result)
}
fn get_container_pid(runtime: &str, container_id: &str) -> Option<u32> {
let output = run_with_timeout(
runtime,
&["inspect", "--format", "{{.State.Pid}}", container_id],
)?;
output.trim().parse().ok().filter(|&pid: &u32| pid > 0)
}
fn run_with_timeout(cmd: &str, args: &[&str]) -> Option<String> {
let mut child = Command::new(cmd)
.args(args)
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::null())
.spawn()
.ok()?;
let timeout = Duration::from_secs(DOCKER_TIMEOUT_SECS);
let start = std::time::Instant::now();
loop {
match child.try_wait() {
Ok(Some(status)) => {
if !status.success() {
return None;
}
let mut out = String::new();
if let Some(mut stdout) = child.stdout.take() {
use std::io::Read;
let _ = stdout.read_to_string(&mut out);
}
return if out.is_empty() { None } else { Some(out) };
}
Ok(None) => {
if start.elapsed() > timeout {
let _ = child.kill();
return None;
}
std::thread::sleep(Duration::from_millis(50));
}
Err(_) => return None,
}
}
}
#[cfg(test)]
fn parse_ps_line(line: &str) -> Option<(String, String)> {
let mut parts = line.splitn(2, ' ');
let id = parts.next()?.trim().to_string();
let name = parts.next()?.trim().to_string();
if id.is_empty() || name.is_empty() {
None
} else {
Some((id, name))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_ps_line_valid() {
let (id, name) = parse_ps_line("abc123def456 my-nginx").unwrap();
assert_eq!(id, "abc123def456");
assert_eq!(name, "my-nginx");
}
#[test]
fn parse_ps_line_with_spaces_in_name() {
let (id, name) = parse_ps_line("abc123 my container").unwrap();
assert_eq!(id, "abc123");
assert_eq!(name, "my container");
}
#[test]
fn parse_ps_line_empty_returns_none() {
assert!(parse_ps_line("").is_none());
assert!(parse_ps_line(" ").is_none());
}
#[test]
fn parse_ps_line_no_name_returns_none() {
assert!(parse_ps_line("abc123").is_none());
}
#[test]
fn resolve_empty_pids() {
let result = resolve_container_names(&[]);
assert!(result.is_empty());
}
#[test]
fn has_containers_empty() {
assert!(!has_containers(&HashMap::new()));
}
#[test]
fn has_containers_with_data() {
let mut m = HashMap::new();
m.insert(1, "nginx".to_string());
assert!(has_containers(&m));
}
}