Skip to main content

prt_core/core/
killer.rs

1//! Process termination (SIGTERM / SIGKILL).
2//!
3//! Provides a safe wrapper around `nix::sys::signal::kill` with a
4//! pre-check that the target process is still alive.
5
6use anyhow::{Context, Result};
7use nix::sys::signal::{self, Signal};
8use nix::unistd::Pid;
9
10/// Send a signal to a process.
11///
12/// - `force = false` → SIGTERM (graceful shutdown)
13/// - `force = true`  → SIGKILL (immediate kill)
14///
15/// Returns an error if the process no longer exists or the signal fails.
16pub fn kill_process(pid: u32, force: bool) -> Result<()> {
17    if !is_running(pid) {
18        anyhow::bail!("process {pid} is no longer running");
19    }
20    let sig = if force {
21        Signal::SIGKILL
22    } else {
23        Signal::SIGTERM
24    };
25    signal::kill(Pid::from_raw(pid as i32), sig)
26        .with_context(|| format!("failed to send {sig} to pid {pid}"))
27}
28
29/// Check if a process with the given PID is alive.
30///
31/// Uses `kill(pid, 0)` — sends no signal but checks process existence.
32pub fn is_running(pid: u32) -> bool {
33    signal::kill(Pid::from_raw(pid as i32), None).is_ok()
34}
35
36#[cfg(test)]
37mod tests {
38    use super::*;
39
40    #[test]
41    fn current_process_is_running() {
42        assert!(is_running(std::process::id()));
43    }
44
45    #[test]
46    fn nonexistent_process_is_not_running() {
47        assert!(!is_running(4_000_000));
48    }
49
50    #[test]
51    fn kill_nonexistent_process_returns_error() {
52        assert!(kill_process(4_000_000, false).is_err());
53    }
54
55    #[test]
56    fn kill_error_message_contains_pid() {
57        let err = kill_process(4_000_000, false).unwrap_err();
58        assert!(err.to_string().contains("4000000"));
59    }
60}