use std::io::{self, Read};
use std::process::{Command, Output, Stdio};
use std::time::{Duration, Instant};
pub fn run_command_with_timeout(
command: &str,
args: &[&str],
timeout: Duration,
) -> io::Result<Output> {
let mut child = Command::new(command)
.args(args)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?;
let start = Instant::now();
let poll_interval = Duration::from_millis(10);
loop {
match child.try_wait() {
Ok(Some(status)) => {
let mut stdout = Vec::new();
let mut stderr = Vec::new();
if let Some(mut out) = child.stdout.take() {
let _ = out.read_to_end(&mut stdout);
}
if let Some(mut err) = child.stderr.take() {
let _ = err.read_to_end(&mut stderr);
}
return Ok(Output {
status,
stdout,
stderr,
});
}
Ok(None) => {
if start.elapsed() >= timeout {
let _ = child.kill();
let _ = child.wait(); return Err(io::Error::new(
io::ErrorKind::TimedOut,
format!("Command '{command}' timed out after {timeout:?}"),
));
}
std::thread::sleep(poll_interval);
}
Err(e) => {
let _ = child.kill();
let _ = child.wait();
return Err(e);
}
}
}
}
pub fn run_command_fast_fail(command: &str, args: &[&str]) -> io::Result<Output> {
let timeout = if is_container_environment() {
Duration::from_millis(500) } else {
Duration::from_secs(2) };
run_command_with_timeout(command, args, timeout)
}
fn is_container_environment() -> bool {
std::path::Path::new("/.dockerenv").exists()
|| std::path::Path::new("/run/.containerenv").exists()
|| std::env::var("KUBERNETES_SERVICE_HOST").is_ok()
|| std::env::var("CONTAINER_RUNTIME").is_ok()
|| check_cgroup_container()
}
fn check_cgroup_container() -> bool {
if let Ok(contents) = std::fs::read_to_string("/proc/self/cgroup") {
contents.contains("/docker/")
|| contents.contains("/lxc/")
|| contents.contains("/kubepods/")
|| contents.contains("/containerd/")
} else {
false
}
}