libdd_common/unix_utils/
process.rs1use libc::{_exit, nfds_t, poll, pollfd, EXIT_FAILURE, POLLHUP};
5use nix::sys::wait::{waitpid, WaitPidFlag, WaitStatus};
6use nix::unistd::Pid;
7use std::os::fd::RawFd;
8
9use crate::timeout::TimeoutManager;
10
11#[derive(Debug, Eq, PartialEq, thiserror::Error)]
12pub enum ReapError {
13 #[error("Timeout waiting for child process to exit")]
14 Timeout,
15 #[error("Error waiting for child process to exit: {0}")]
16 WaitError(#[from] nix::Error),
17}
18
19#[derive(Debug, Eq, PartialEq, thiserror::Error)]
20pub enum PollError {
21 #[error("Poll failed with errno: {0}")]
22 PollError(i32),
23 #[error("Poll returned unexpected result: revents = {0}")]
24 UnexpectedResult(i16),
25}
26
27pub fn reap_child_non_blocking(
36 pid: Pid,
37 timeout_manager: &TimeoutManager,
38) -> Result<bool, ReapError> {
39 loop {
40 match waitpid(pid, Some(WaitPidFlag::WNOHANG)) {
41 Ok(WaitStatus::StillAlive) => {
42 if timeout_manager.elapsed() > timeout_manager.timeout() {
43 return Err(ReapError::Timeout);
44 }
45 }
47 Ok(_status) => return Ok(true),
48 Err(nix::Error::ECHILD) => {
49 return Ok(false);
53 }
54 Err(e) => return Err(ReapError::WaitError(e)),
55 }
56 }
57}
58
59pub fn terminate() -> ! {
61 unsafe { _exit(EXIT_FAILURE) }
63}
64
65pub fn wait_for_pollhup(
67 target_fd: RawFd,
68 timeout_manager: &TimeoutManager,
69) -> Result<bool, PollError> {
70 let mut poll_fds = [pollfd {
71 fd: target_fd,
72 events: POLLHUP,
73 revents: 0,
74 }];
75
76 loop {
77 let timeout_ms = timeout_manager.remaining().as_millis() as i32;
78 let poll_result =
79 unsafe { poll(poll_fds.as_mut_ptr(), poll_fds.len() as nfds_t, timeout_ms) };
80 match poll_result {
81 -1 => {
82 match nix::Error::last_raw() {
83 libc::EAGAIN | libc::EINTR => {
84 continue;
86 }
87 errno => return Err(PollError::PollError(errno)),
88 }
89 }
90 0 => return Ok(false), _ => {
92 let revents = poll_fds[0].revents;
93 if revents & POLLHUP != 0 {
94 return Ok(true); } else {
96 return Err(PollError::UnexpectedResult(revents));
97 }
98 }
99 }
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106 use std::time::Duration;
107
108 #[cfg_attr(miri, ignore)] #[test]
110 fn test_reap_child_non_blocking_timeout() {
111 let timeout = Duration::from_millis(10);
112 let manager = TimeoutManager::new(timeout);
113
114 let result = reap_child_non_blocking(Pid::from_raw(99999), &manager);
116 assert!(matches!(result, Ok(false)));
117 }
118
119 #[cfg_attr(miri, ignore)] #[test]
121 fn test_reap_child_non_blocking_exited_child() {
122 let timeout = Duration::from_secs(1);
125 let manager = TimeoutManager::new(timeout);
126
127 let result = reap_child_non_blocking(Pid::from_raw(99999), &manager);
128 assert!(matches!(result, Ok(false)));
129 }
130
131 #[cfg_attr(miri, ignore)] #[test]
133 fn test_reap_child_non_blocking_nonexistent_pid() {
134 let timeout = Duration::from_secs(1);
135 let manager = TimeoutManager::new(timeout);
136
137 let result = reap_child_non_blocking(Pid::from_raw(99999), &manager);
138 assert!(matches!(result, Ok(false)));
139 }
140
141 #[cfg_attr(miri, ignore)] #[test]
143 fn test_wait_for_pollhup_timeout() {
144 let timeout = Duration::from_millis(10);
145 let manager = TimeoutManager::new(timeout);
146
147 let result = wait_for_pollhup(-1, &manager);
149 assert!(matches!(result, Ok(false)));
150 }
151
152 #[cfg_attr(miri, ignore)] #[test]
154 fn test_wait_for_pollhup_invalid_fd() {
155 let timeout = Duration::from_secs(1);
156 let manager = TimeoutManager::new(timeout);
157
158 let invalid_fd = 999_999;
160 let result = wait_for_pollhup(invalid_fd, &manager);
161
162 match result {
164 Err(PollError::PollError(errno)) => {
165 assert!(errno > 0);
167 }
168 Err(PollError::UnexpectedResult(revents)) => {
169 println!("wait_for_pollhup({invalid_fd}, ..) returned UnexpectedResult({revents}) as allowed on this platform");
170 }
171 _ => panic!("Expected error for invalid FD, got: {result:?}"),
172 }
173 }
174}