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 #[cfg(any(feature = "independent-spawn", test))]
38 pub(crate) fn open_pinned(pid: u32) -> io::Result<Self> {
39 if pid == 0 || pid > libc::pid_t::MAX as u32 {
40 return Err(io::Error::from(io::ErrorKind::InvalidInput));
41 }
42 let raw = unsafe { libc::syscall(libc::SYS_pidfd_open, pid as libc::pid_t, 0_u32) };
44 if raw < 0 {
45 let error = io::Error::last_os_error();
46 return Err(match error.raw_os_error() {
47 Some(libc::ENOSYS | libc::EINVAL) => io::Error::new(
48 io::ErrorKind::Unsupported,
49 "kernel-pinned process control is unavailable",
50 ),
51 _ => error,
52 });
53 }
54 let pid_fd = unsafe { OwnedFd::from_raw_fd(raw as i32) };
56 Ok(Self {
57 pid,
58 pid_fd: Some(pid_fd),
59 })
60 }
61
62 #[cfg(any(feature = "independent-spawn", test))]
64 pub(crate) fn signal_pinned(&self, signal: i32) -> io::Result<()> {
65 let fd = self.pid_fd.as_ref().ok_or_else(|| {
66 io::Error::new(
67 io::ErrorKind::Unsupported,
68 "process identity is not kernel-pinned",
69 )
70 })?;
71 let result = unsafe {
74 libc::syscall(
75 libc::SYS_pidfd_send_signal,
76 fd.as_raw_fd(),
77 signal,
78 std::ptr::null::<libc::siginfo_t>(),
79 0_u32,
80 )
81 };
82 if result < 0 {
83 Err(io::Error::last_os_error())
84 } else {
85 Ok(())
86 }
87 }
88
89 pub fn open(pid: u32) -> Result<Self, ProcessInspectError> {
91 validate_pid(pid)?;
92 if !process_exists(pid) {
93 return Err(not_found());
94 }
95 Ok(Self {
96 pid,
97 pid_fd: try_pidfd_open(pid)?,
98 })
99 }
100
101 pub fn pid(&self) -> u32 {
103 self.pid
104 }
105
106 pub fn is_alive(&self) -> bool {
108 match self.pid_fd.as_ref() {
109 Some(pid_fd) => pidfd_is_alive(pid_fd),
110 None => process_exists(self.pid),
111 }
112 }
113}
114
115pub fn process_executable_path(pid: u32) -> Result<PathBuf, io::Error> {
117 std::fs::read_link(format!("/proc/{pid}/exe"))
118}
119
120pub fn process_signal_terminate(pid: u32) -> Result<(), ProcessInspectError> {
122 signal(pid, libc::SIGTERM)
123}
124
125pub fn process_force_kill(pid: u32) -> Result<(), ProcessInspectError> {
127 signal(pid, libc::SIGKILL)
128}
129
130fn signal(pid: u32, signal: libc::c_int) -> Result<(), ProcessInspectError> {
131 let native_pid = validate_pid(pid)?;
132 let rc = unsafe { libc::kill(native_pid, signal) };
134 if rc == 0 {
135 Ok(())
136 } else {
137 Err(ProcessInspectError::last_os_error(
138 ProcessInspectErrorKind::Host,
139 ))
140 }
141}
142
143fn process_exists(pid: u32) -> bool {
148 let Ok(native_pid) = validate_pid(pid) else {
149 return false;
150 };
151 let rc = unsafe { libc::kill(native_pid, 0) };
153 if rc == 0 {
154 return true;
155 }
156 matches!(io::Error::last_os_error().raw_os_error(), Some(libc::EPERM))
157}
158
159fn validate_pid(pid: u32) -> Result<libc::pid_t, ProcessInspectError> {
160 if pid == 0 || pid > libc::pid_t::MAX as u32 {
161 Err(ProcessInspectError::stated(
162 ProcessInspectErrorKind::InvalidPid,
163 "pid outside the range this host issues",
164 ))
165 } else {
166 Ok(pid as libc::pid_t)
167 }
168}
169
170fn try_pidfd_open(pid: u32) -> Result<Option<OwnedFd>, ProcessInspectError> {
177 let raw = unsafe { libc::syscall(libc::SYS_pidfd_open, pid as libc::pid_t, 0_u32) };
179 if raw >= 0 {
180 return Ok(Some(unsafe { OwnedFd::from_raw_fd(raw as i32) }));
183 }
184
185 match io::Error::last_os_error().raw_os_error() {
186 Some(libc::ESRCH) => Err(not_found()),
187 _ => Ok(None),
188 }
189}
190
191fn pidfd_is_alive(pid_fd: &OwnedFd) -> bool {
193 let mut poll_fd = libc::pollfd {
194 fd: pid_fd.as_raw_fd(),
195 events: libc::POLLIN,
196 revents: 0,
197 };
198 let rc = unsafe { libc::poll(&mut poll_fd, 1, 0) };
201 rc == 0
202}
203
204fn not_found() -> ProcessInspectError {
205 ProcessInspectError::stated(ProcessInspectErrorKind::NotFound, "no such process")
206}
207
208#[cfg(test)]
209mod tests {
210 use super::*;
211
212 #[test]
215 fn pid_zero_is_never_valid() {
216 let error = ProcessLiveness::open(0).expect_err("pid 0");
217 assert_eq!(error.kind, ProcessInspectErrorKind::InvalidPid);
218 assert!(!process_exists(0));
219 }
220
221 #[test]
222 fn pinned_control_rejects_missing_handle_without_signalling_pid() {
223 let unpinned = ProcessLiveness {
224 pid: std::process::id(),
225 pid_fd: None,
226 };
227 assert_eq!(
228 unpinned.signal_pinned(libc::SIGKILL).unwrap_err().kind(),
229 io::ErrorKind::Unsupported
230 );
231 assert_eq!(
232 ProcessLiveness::open_pinned(0).unwrap_err().kind(),
233 io::ErrorKind::InvalidInput
234 );
235 }
236
237 #[test]
238 fn pinned_control_keeps_identity_after_exit() {
239 let mut command = std::process::Command::new("/bin/sh");
240 command.args(["-c", "exec sleep 60"]);
241 let mut child = crate::spawn_sync(
242 &mut command,
243 crate::platform::process::SpawnStdio::default(),
244 crate::platform::process::SyncEnvironment::Inherit,
245 )
246 .unwrap();
247 let pinned = ProcessLiveness::open_pinned(child.id()).unwrap();
248 pinned.signal_pinned(libc::SIGKILL).unwrap();
249 child.wait().unwrap();
250 assert!(!pinned.is_alive());
251 assert_eq!(
252 pinned.signal_pinned(0).unwrap_err().raw_os_error(),
253 Some(libc::ESRCH)
254 );
255 }
256
257 #[test]
259 fn this_process_is_alive_and_locatable() {
260 let me = std::process::id();
261 let handle = ProcessLiveness::open(me).expect("open self");
262 assert_eq!(handle.pid(), me);
263 assert!(handle.is_alive());
264 assert_eq!(
265 process_executable_path(me).expect("exe"),
266 std::env::current_exe().expect("current_exe")
267 );
268 }
269
270 #[test]
274 fn a_dead_process_reports_dead() {
275 let child = std::process::Command::new("/bin/sh")
276 .args(["-c", "exit 0"])
277 .spawn()
278 .expect("spawn");
279 let pid = child.id();
280 let handle = ProcessLiveness::open(pid).expect("open child");
281 let mut child = child;
282 child.wait().expect("reap");
283 assert!(!handle.is_alive(), "a reaped child must report dead");
284 }
285}
286
287pub fn process_same_executable_path(actual: &std::path::Path, expected: &std::path::Path) -> bool {
295 let resolve =
296 |path: &std::path::Path| std::fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf());
297 resolve(actual) == resolve(expected)
298}
299
300#[cfg(test)]
301mod path_tests {
302 use super::*;
303 use std::path::Path;
304
305 #[test]
307 fn case_distinguishes_two_images() {
308 assert!(!process_same_executable_path(
309 Path::new("/tmp/Daemon"),
310 Path::new("/tmp/daemon"),
311 ));
312 }
313
314 #[test]
316 fn a_path_matches_itself() {
317 assert!(process_same_executable_path(
318 Path::new("/tmp/rp-does-not-exist/daemon"),
319 Path::new("/tmp/rp-does-not-exist/daemon"),
320 ));
321 let me = std::env::current_exe().expect("current_exe");
322 assert!(process_same_executable_path(&me, &me));
323 }
324}