command_stream/zx/
kill.rs1use super::error::ZxError;
4
5pub const SIGTERM: &str = "SIGTERM";
7
8pub fn validate_pid(pid: &str) -> Result<u32, ZxError> {
10 if pid.is_empty() || !pid.bytes().all(|b| b.is_ascii_digit()) {
11 return Err(ZxError::new(format!("Invalid pid: {pid}")));
12 }
13 pid.parse::<u32>()
14 .map_err(|_| ZxError::new(format!("Invalid pid: {pid}")))
15}
16
17#[cfg(unix)]
19pub fn parse_signal(signal: &str) -> Result<nix::sys::signal::Signal, ZxError> {
20 use nix::sys::signal::Signal;
21 use std::str::FromStr;
22 let unknown = || ZxError::new(format!("Unknown signal: {signal}"));
23 if let Ok(num) = signal.parse::<i32>() {
24 return Signal::try_from(num).map_err(|_| unknown());
25 }
26 let upper = signal.to_ascii_uppercase();
27 let name = if upper.starts_with("SIG") {
28 upper
29 } else {
30 format!("SIG{upper}")
31 };
32 Signal::from_str(&name).map_err(|_| unknown())
33}
34
35#[cfg(unix)]
37pub fn signal_name(num: i32) -> Option<String> {
38 nix::sys::signal::Signal::try_from(num)
39 .ok()
40 .map(|s| s.as_str().to_string())
41}
42
43#[cfg(not(unix))]
45pub fn signal_name(_num: i32) -> Option<String> {
46 None
47}
48
49#[cfg(unix)]
51pub fn descendants(pid: u32) -> Vec<u32> {
52 let listing = std::process::Command::new("ps")
53 .args(["-A", "-o", "pid=", "-o", "ppid="])
54 .stderr(std::process::Stdio::null())
55 .output();
56 let Ok(listing) = listing else {
57 return Vec::new();
58 };
59 let table: Vec<(u32, u32)> = String::from_utf8_lossy(&listing.stdout)
60 .lines()
61 .filter_map(|line| {
62 let mut cols = line.split_whitespace();
63 let child = cols.next()?.parse().ok()?;
64 let parent = cols.next()?.parse().ok()?;
65 Some((child, parent))
66 })
67 .collect();
68 let mut found = Vec::new();
69 let mut queue = vec![pid];
70 while let Some(parent) = queue.pop() {
71 for &(child, ppid) in &table {
72 if ppid == parent && child != pid && !found.contains(&child) {
73 found.push(child);
74 queue.push(child);
75 }
76 }
77 }
78 found
79}
80
81#[cfg(not(unix))]
83pub fn descendants(_pid: u32) -> Vec<u32> {
84 Vec::new()
85}
86
87#[cfg(unix)]
89pub fn kill_tree(pid: u32, signal: &str) -> Result<(), ZxError> {
90 use nix::sys::signal::kill as send;
91 use nix::unistd::Pid;
92 let sig = parse_signal(signal)?;
93 let raw = i32::try_from(pid).map_err(|_| ZxError::new(format!("Invalid pid: {pid}")))?;
94 let children = descendants(pid);
95 if send(Pid::from_raw(-raw), sig).is_err() {
100 let _ = send(Pid::from_raw(raw), sig);
101 }
102 for child in children {
103 if let Ok(child) = i32::try_from(child) {
104 let _ = send(Pid::from_raw(child), sig);
105 }
106 }
107 Ok(())
108}
109
110#[cfg(not(unix))]
112pub fn kill_tree(pid: u32, _signal: &str) -> Result<(), ZxError> {
113 let status = std::process::Command::new("taskkill")
114 .args(["/pid", &pid.to_string(), "/t", "/f"])
115 .stdout(std::process::Stdio::null())
116 .stderr(std::process::Stdio::null())
117 .status()?;
118 if status.success() {
119 Ok(())
120 } else {
121 Err(ZxError::new(format!("taskkill failed for pid {pid}")))
122 }
123}
124
125pub async fn kill(pid: impl ToString, signal: Option<&str>) -> Result<(), ZxError> {
130 let pid = validate_pid(&pid.to_string())?;
131 let signal = match signal {
132 Some(s) => s.to_string(),
133 None => super::shell::current_options().kill_signal,
134 };
135 tokio::task::spawn_blocking(move || kill_tree(pid, &signal))
136 .await
137 .map_err(|e| ZxError::new(e.to_string()))?
138}