running_process_platform_internal/platform_linux/
process_inspect.rs1use std::io;
4use std::os::fd::{AsRawFd, FromRawFd, OwnedFd};
5use std::path::PathBuf;
6
7use crate::platform::process::{ProcessInspectError, ProcessInspectErrorKind};
8
9pub struct ProcessLiveness {
17 pid: u32,
18 pid_fd: Option<OwnedFd>,
19}
20
21impl std::fmt::Debug for ProcessLiveness {
22 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28 f.debug_struct("ProcessLiveness")
29 .field("pid", &self.pid)
30 .finish_non_exhaustive()
31 }
32}
33
34impl ProcessLiveness {
35 pub fn open(pid: u32) -> Result<Self, ProcessInspectError> {
37 validate_pid(pid)?;
38 if !process_exists(pid) {
39 return Err(not_found());
40 }
41 Ok(Self {
42 pid,
43 pid_fd: try_pidfd_open(pid)?,
44 })
45 }
46
47 pub fn pid(&self) -> u32 {
49 self.pid
50 }
51
52 pub fn is_alive(&self) -> bool {
54 match self.pid_fd.as_ref() {
55 Some(pid_fd) => pidfd_is_alive(pid_fd),
56 None => process_exists(self.pid),
57 }
58 }
59}
60
61pub fn process_executable_path(pid: u32) -> Result<PathBuf, io::Error> {
63 std::fs::read_link(format!("/proc/{pid}/exe"))
64}
65
66pub fn process_signal_terminate(pid: u32) -> Result<(), ProcessInspectError> {
68 signal(pid, libc::SIGTERM)
69}
70
71pub fn process_force_kill(pid: u32) -> Result<(), ProcessInspectError> {
73 signal(pid, libc::SIGKILL)
74}
75
76fn signal(pid: u32, signal: libc::c_int) -> Result<(), ProcessInspectError> {
77 let native_pid = validate_pid(pid)?;
78 let rc = unsafe { libc::kill(native_pid, signal) };
80 if rc == 0 {
81 Ok(())
82 } else {
83 Err(ProcessInspectError::last_os_error(
84 ProcessInspectErrorKind::Host,
85 ))
86 }
87}
88
89fn process_exists(pid: u32) -> bool {
94 let Ok(native_pid) = validate_pid(pid) else {
95 return false;
96 };
97 let rc = unsafe { libc::kill(native_pid, 0) };
99 if rc == 0 {
100 return true;
101 }
102 matches!(io::Error::last_os_error().raw_os_error(), Some(libc::EPERM))
103}
104
105fn validate_pid(pid: u32) -> Result<libc::pid_t, ProcessInspectError> {
106 if pid == 0 || pid > libc::pid_t::MAX as u32 {
107 Err(ProcessInspectError::stated(
108 ProcessInspectErrorKind::InvalidPid,
109 "pid outside the range this host issues",
110 ))
111 } else {
112 Ok(pid as libc::pid_t)
113 }
114}
115
116fn try_pidfd_open(pid: u32) -> Result<Option<OwnedFd>, ProcessInspectError> {
123 let raw = unsafe { libc::syscall(libc::SYS_pidfd_open, pid as libc::pid_t, 0_u32) };
125 if raw >= 0 {
126 return Ok(Some(unsafe { OwnedFd::from_raw_fd(raw as i32) }));
129 }
130
131 match io::Error::last_os_error().raw_os_error() {
132 Some(libc::ESRCH) => Err(not_found()),
133 _ => Ok(None),
134 }
135}
136
137fn pidfd_is_alive(pid_fd: &OwnedFd) -> bool {
139 let mut poll_fd = libc::pollfd {
140 fd: pid_fd.as_raw_fd(),
141 events: libc::POLLIN,
142 revents: 0,
143 };
144 let rc = unsafe { libc::poll(&mut poll_fd, 1, 0) };
147 rc == 0
148}
149
150fn not_found() -> ProcessInspectError {
151 ProcessInspectError::stated(ProcessInspectErrorKind::NotFound, "no such process")
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157
158 #[test]
161 fn pid_zero_is_never_valid() {
162 let error = ProcessLiveness::open(0).expect_err("pid 0");
163 assert_eq!(error.kind, ProcessInspectErrorKind::InvalidPid);
164 assert!(!process_exists(0));
165 }
166
167 #[test]
169 fn this_process_is_alive_and_locatable() {
170 let me = std::process::id();
171 let handle = ProcessLiveness::open(me).expect("open self");
172 assert_eq!(handle.pid(), me);
173 assert!(handle.is_alive());
174 assert_eq!(
175 process_executable_path(me).expect("exe"),
176 std::env::current_exe().expect("current_exe")
177 );
178 }
179
180 #[test]
184 fn a_dead_process_reports_dead() {
185 let child = std::process::Command::new("/bin/sh")
186 .args(["-c", "exit 0"])
187 .spawn()
188 .expect("spawn");
189 let pid = child.id();
190 let handle = ProcessLiveness::open(pid).expect("open child");
191 let mut child = child;
192 child.wait().expect("reap");
193 assert!(!handle.is_alive(), "a reaped child must report dead");
194 }
195}
196
197pub fn process_same_executable_path(actual: &std::path::Path, expected: &std::path::Path) -> bool {
205 let resolve =
206 |path: &std::path::Path| std::fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf());
207 resolve(actual) == resolve(expected)
208}
209
210#[cfg(test)]
211mod path_tests {
212 use super::*;
213 use std::path::Path;
214
215 #[test]
217 fn case_distinguishes_two_images() {
218 assert!(!process_same_executable_path(
219 Path::new("/tmp/Daemon"),
220 Path::new("/tmp/daemon"),
221 ));
222 }
223
224 #[test]
226 fn a_path_matches_itself() {
227 assert!(process_same_executable_path(
228 Path::new("/tmp/rp-does-not-exist/daemon"),
229 Path::new("/tmp/rp-does-not-exist/daemon"),
230 ));
231 let me = std::env::current_exe().expect("current_exe");
232 assert!(process_same_executable_path(&me, &me));
233 }
234}