Skip to main content

aft/bash_background/
process.rs

1/// Shared process-termination helpers for both foreground bash and background
2/// bash tasks. Extracted to avoid duplication between `commands/bash.rs` and
3/// `bash_background/registry.rs`.
4///
5/// Termination is graceful-first: SIGTERM + 3-second grace period, then
6/// SIGKILL on Unix. On Windows, `taskkill /T /F` kills the entire process tree.
7use std::process::Child;
8#[cfg(windows)]
9use std::process::{Command, Stdio};
10#[cfg(unix)]
11use std::thread;
12use std::time::Duration;
13#[cfg(unix)]
14use std::time::Instant;
15
16pub const TERMINATE_GRACE: Duration = Duration::from_secs(2);
17
18#[cfg(unix)]
19pub fn terminate_process(child: &mut Child) {
20    let pgid = child.id() as i32;
21    terminate_pgid(pgid, Some(child));
22}
23
24#[cfg(unix)]
25pub fn terminate_pgid(pgid: i32, mut child: Option<&mut Child>) {
26    unsafe {
27        libc::killpg(pgid, libc::SIGTERM);
28    }
29    let grace_started = Instant::now();
30    while grace_started.elapsed() < TERMINATE_GRACE {
31        if let Some(child) = child.as_deref_mut() {
32            if matches!(child.try_wait(), Ok(Some(_))) {
33                return;
34            }
35        }
36        thread::sleep(Duration::from_millis(50));
37    }
38    unsafe {
39        libc::killpg(pgid, libc::SIGKILL);
40    }
41}
42
43#[cfg(windows)]
44pub fn terminate_process(child: &mut Child) {
45    terminate_pid(child.id());
46}
47
48#[cfg(windows)]
49pub fn terminate_pid(pid: u32) {
50    let pid = pid.to_string();
51    let _ = Command::new("taskkill")
52        .args(["/PID", &pid, "/T", "/F"])
53        .stdout(Stdio::null())
54        .stderr(Stdio::null())
55        .status();
56}
57
58#[cfg(unix)]
59pub fn is_process_alive(pid: u32) -> bool {
60    let Ok(pid) = i32::try_from(pid) else {
61        return false;
62    };
63    if pid <= 0 {
64        return false;
65    }
66    (unsafe { libc::kill(pid, 0) == 0 })
67        || std::io::Error::last_os_error().raw_os_error() == Some(libc::EPERM)
68}
69
70#[cfg(windows)]
71pub fn is_process_alive(pid: u32) -> bool {
72    use std::ffi::c_void;
73
74    type Handle = *mut c_void;
75
76    extern "system" {
77        fn OpenProcess(dwDesiredAccess: u32, bInheritHandle: i32, dwProcessId: u32) -> Handle;
78        fn GetExitCodeProcess(hProcess: Handle, lpExitCode: *mut u32) -> i32;
79        fn CloseHandle(hObject: Handle) -> i32;
80    }
81
82    const FALSE: i32 = 0;
83    const PROCESS_QUERY_LIMITED_INFORMATION: u32 = 0x1000;
84    const STILL_ACTIVE: u32 = 0x103;
85
86    if pid == 0 {
87        return false;
88    }
89
90    unsafe {
91        let handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
92        if handle.is_null() {
93            return false;
94        }
95        let mut exit_code = 0;
96        let ok = GetExitCodeProcess(handle, &mut exit_code) != 0 && exit_code == STILL_ACTIVE;
97        let _ = CloseHandle(handle);
98        ok
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105
106    #[test]
107    fn is_process_alive_returns_true_for_self() {
108        assert!(is_process_alive(std::process::id()));
109    }
110
111    #[test]
112    fn is_process_alive_returns_false_for_dead_pid() {
113        #[cfg(unix)]
114        let mut child = std::process::Command::new("/bin/sh")
115            .args(["-c", "true"])
116            .spawn()
117            .expect("spawn true");
118
119        #[cfg(windows)]
120        let mut child = std::process::Command::new("cmd.exe")
121            .args(["/D", "/C", "exit 0"])
122            .spawn()
123            .expect("spawn cmd");
124
125        let pid = child.id();
126        child.wait().expect("wait for child");
127
128        assert!(!is_process_alive(pid));
129    }
130}