use super::error::ZxError;
pub const SIGTERM: &str = "SIGTERM";
pub fn validate_pid(pid: &str) -> Result<u32, ZxError> {
if pid.is_empty() || !pid.bytes().all(|b| b.is_ascii_digit()) {
return Err(ZxError::new(format!("Invalid pid: {pid}")));
}
pid.parse::<u32>()
.map_err(|_| ZxError::new(format!("Invalid pid: {pid}")))
}
#[cfg(unix)]
pub fn parse_signal(signal: &str) -> Result<nix::sys::signal::Signal, ZxError> {
use nix::sys::signal::Signal;
use std::str::FromStr;
let unknown = || ZxError::new(format!("Unknown signal: {signal}"));
if let Ok(num) = signal.parse::<i32>() {
return Signal::try_from(num).map_err(|_| unknown());
}
let upper = signal.to_ascii_uppercase();
let name = if upper.starts_with("SIG") {
upper
} else {
format!("SIG{upper}")
};
Signal::from_str(&name).map_err(|_| unknown())
}
#[cfg(unix)]
pub fn signal_name(num: i32) -> Option<String> {
nix::sys::signal::Signal::try_from(num)
.ok()
.map(|s| s.as_str().to_string())
}
#[cfg(not(unix))]
pub fn signal_name(_num: i32) -> Option<String> {
None
}
#[cfg(unix)]
pub fn descendants(pid: u32) -> Vec<u32> {
let listing = std::process::Command::new("ps")
.args(["-A", "-o", "pid=", "-o", "ppid="])
.stderr(std::process::Stdio::null())
.output();
let Ok(listing) = listing else {
return Vec::new();
};
let table: Vec<(u32, u32)> = String::from_utf8_lossy(&listing.stdout)
.lines()
.filter_map(|line| {
let mut cols = line.split_whitespace();
let child = cols.next()?.parse().ok()?;
let parent = cols.next()?.parse().ok()?;
Some((child, parent))
})
.collect();
let mut found = Vec::new();
let mut queue = vec![pid];
while let Some(parent) = queue.pop() {
for &(child, ppid) in &table {
if ppid == parent && child != pid && !found.contains(&child) {
found.push(child);
queue.push(child);
}
}
}
found
}
#[cfg(not(unix))]
pub fn descendants(_pid: u32) -> Vec<u32> {
Vec::new()
}
#[cfg(unix)]
pub fn kill_tree(pid: u32, signal: &str) -> Result<(), ZxError> {
use nix::sys::signal::kill as send;
use nix::unistd::Pid;
let sig = parse_signal(signal)?;
let raw = i32::try_from(pid).map_err(|_| ZxError::new(format!("Invalid pid: {pid}")))?;
let children = descendants(pid);
if send(Pid::from_raw(-raw), sig).is_err() {
let _ = send(Pid::from_raw(raw), sig);
}
for child in children {
if let Ok(child) = i32::try_from(child) {
let _ = send(Pid::from_raw(child), sig);
}
}
Ok(())
}
#[cfg(not(unix))]
pub fn kill_tree(pid: u32, _signal: &str) -> Result<(), ZxError> {
let status = std::process::Command::new("taskkill")
.args(["/pid", &pid.to_string(), "/t", "/f"])
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()?;
if status.success() {
Ok(())
} else {
Err(ZxError::new(format!("taskkill failed for pid {pid}")))
}
}
pub async fn kill(pid: impl ToString, signal: Option<&str>) -> Result<(), ZxError> {
let pid = validate_pid(&pid.to_string())?;
let signal = match signal {
Some(s) => s.to_string(),
None => super::shell::current_options().kill_signal,
};
tokio::task::spawn_blocking(move || kill_tree(pid, &signal))
.await
.map_err(|e| ZxError::new(e.to_string()))?
}