Skip to main content

ralph/lock/
pid.rs

1//! PID liveness detection.
2//!
3//! Responsibilities:
4//! - Provide tri-state PID liveness helpers.
5//! - Encapsulate platform-specific process existence checks.
6//!
7//! Not handled here:
8//! - Lock acquisition or cleanup policy.
9//! - Owner metadata parsing.
10//!
11//! Invariants/assumptions:
12//! - Indeterminate liveness is treated conservatively by callers.
13
14/// Tri-state PID liveness result.
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum PidLiveness {
17    Running,
18    NotRunning,
19    Indeterminate,
20}
21
22impl PidLiveness {
23    pub fn is_definitely_not_running(self) -> bool {
24        matches!(self, Self::NotRunning)
25    }
26
27    pub fn is_running_or_indeterminate(self) -> bool {
28        matches!(self, Self::Running | Self::Indeterminate)
29    }
30}
31
32pub fn pid_liveness(pid: u32) -> PidLiveness {
33    match pid_is_running(pid) {
34        Some(true) => PidLiveness::Running,
35        Some(false) => PidLiveness::NotRunning,
36        None => PidLiveness::Indeterminate,
37    }
38}
39
40#[cfg(windows)]
41fn pid_exists_via_toolhelp(pid: u32) -> Option<bool> {
42    use windows_sys::Win32::Foundation::{CloseHandle, INVALID_HANDLE_VALUE};
43    use windows_sys::Win32::System::Diagnostics::ToolHelp::{
44        CreateToolhelp32Snapshot, PROCESSENTRY32, Process32First, Process32Next, TH32CS_SNAPPROCESS,
45    };
46
47    unsafe {
48        let snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
49        if snapshot == INVALID_HANDLE_VALUE {
50            log::debug!(
51                "CreateToolhelp32Snapshot failed for PID existence check, error: {}",
52                windows_sys::Win32::Foundation::GetLastError()
53            );
54            return None;
55        }
56
57        let result = {
58            let mut entry: PROCESSENTRY32 = std::mem::zeroed();
59            entry.dwSize = std::mem::size_of::<PROCESSENTRY32>() as u32;
60
61            if Process32First(snapshot, &mut entry) == 0 {
62                log::debug!(
63                    "Process32First failed, error: {}",
64                    windows_sys::Win32::Foundation::GetLastError()
65                );
66                None
67            } else {
68                let mut found = false;
69                loop {
70                    if entry.th32ProcessID == pid {
71                        found = true;
72                        break;
73                    }
74                    if Process32Next(snapshot, &mut entry) == 0 {
75                        break;
76                    }
77                }
78                Some(found)
79            }
80        };
81
82        CloseHandle(snapshot);
83        result
84    }
85}
86
87pub fn pid_is_running(pid: u32) -> Option<bool> {
88    #[cfg(unix)]
89    {
90        let result = unsafe { libc::kill(pid as i32, 0) };
91        if result == 0 {
92            return Some(true);
93        }
94        let error = std::io::Error::last_os_error();
95        if error.raw_os_error() == Some(libc::ESRCH) {
96            return Some(false);
97        }
98        None
99    }
100
101    #[cfg(windows)]
102    {
103        use windows_sys::Win32::Foundation::{
104            CloseHandle, ERROR_ACCESS_DENIED, ERROR_INVALID_PARAMETER,
105        };
106        use windows_sys::Win32::System::Threading::{OpenProcess, PROCESS_QUERY_INFORMATION};
107
108        unsafe {
109            let handle = OpenProcess(PROCESS_QUERY_INFORMATION, 0, pid);
110            if handle != 0 {
111                CloseHandle(handle);
112                Some(true)
113            } else {
114                let error = windows_sys::Win32::Foundation::GetLastError();
115                if error == ERROR_INVALID_PARAMETER {
116                    Some(false)
117                } else if error == ERROR_ACCESS_DENIED {
118                    log::debug!(
119                        "OpenProcess({}) failed with ERROR_ACCESS_DENIED, falling back to ToolHelp enumeration",
120                        pid
121                    );
122                    pid_exists_via_toolhelp(pid)
123                } else {
124                    log::debug!(
125                        "OpenProcess({}) failed with unexpected error: {}",
126                        pid,
127                        error
128                    );
129                    None
130                }
131            }
132        }
133    }
134
135    #[cfg(not(any(unix, windows)))]
136    {
137        let _ = pid;
138        None
139    }
140}