1use crate::{
2 io::{interest::Interest, PollEvented},
3 process::{
4 imp::{orphan::Wait, OrphanQueue},
5 kill::Kill,
6 },
7 util::error::RUNTIME_SHUTTING_DOWN_ERROR,
8};
9
10use libc::{syscall, SYS_pidfd_open, ENOSYS, PIDFD_NONBLOCK};
11use mio::{event::Source, unix::SourceFd};
12use std::{
13 fs::File,
14 future::Future,
15 io,
16 marker::Unpin,
17 ops::Deref,
18 os::unix::io::{AsRawFd, FromRawFd, RawFd},
19 pin::Pin,
20 process::ExitStatus,
21 sync::atomic::{AtomicBool, Ordering::Relaxed},
22 task::{Context, Poll},
23};
24
25#[derive(Debug)]
26struct Pidfd {
27 fd: File,
28}
29
30impl Pidfd {
31 fn open(pid: u32) -> Option<Pidfd> {
32 // Store false (0) to reduce executable size
33 static NO_PIDFD_SUPPORT: AtomicBool = AtomicBool::new(false);
34
35 if NO_PIDFD_SUPPORT.load(Relaxed) {
36 return None;
37 }
38
39 // Safety: The following function calls invovkes syscall pidfd_open,
40 // which takes two parameter: pidfd_open(fd: c_int, flag: c_int)
41 let fd = unsafe { syscall(SYS_pidfd_open, pid, PIDFD_NONBLOCK) };
42 if fd == -1 {
43 let errno = io::Error::last_os_error().raw_os_error().unwrap();
44
45 if errno == ENOSYS {
46 NO_PIDFD_SUPPORT.store(true, Relaxed)
47 }
48
49 None
50 } else {
51 // Safety: pidfd_open returns -1 on error or a valid fd with ownership.
52 Some(Pidfd {
53 fd: unsafe { File::from_raw_fd(fd as i32) },
54 })
55 }
56 }
57}
58
59impl AsRawFd for Pidfd {
60 fn as_raw_fd(&self) -> RawFd {
61 self.fd.as_raw_fd()
62 }
63}
64
65impl Source for Pidfd {
66 fn register(
67 &mut self,
68 registry: &mio::Registry,
69 token: mio::Token,
70 interest: mio::Interest,
71 ) -> io::Result<()> {
72 SourceFd(&self.as_raw_fd()).register(registry, token, interests:interest)
73 }
74
75 fn reregister(
76 &mut self,
77 registry: &mio::Registry,
78 token: mio::Token,
79 interest: mio::Interest,
80 ) -> io::Result<()> {
81 SourceFd(&self.as_raw_fd()).reregister(registry, token, interests:interest)
82 }
83
84 fn deregister(&mut self, registry: &mio::Registry) -> io::Result<()> {
85 SourceFd(&self.as_raw_fd()).deregister(registry)
86 }
87}
88
89#[derive(Debug)]
90struct PidfdReaperInner<W>
91where
92 W: Unpin,
93{
94 inner: W,
95 pidfd: PollEvented<Pidfd>,
96}
97
98#[allow(deprecated)]
99fn is_rt_shutdown_err(err: &io::Error) -> bool {
100 if let Some(inner: &(dyn Error + Sync + Send)) = err.get_ref() {
101 // Using `Error::description()` is more efficient than `format!("{inner}")`,
102 // so we use it here even if it is deprecated.
103 err.kind() == io::ErrorKind::Other
104 && inner.source().is_none()
105 && inner.description() == RUNTIME_SHUTTING_DOWN_ERROR
106 } else {
107 false
108 }
109}
110
111impl<W> Future for PidfdReaperInner<W>
112where
113 W: Wait + Unpin,
114{
115 type Output = io::Result<ExitStatus>;
116
117 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
118 let this: &mut PidfdReaperInner = Pin::into_inner(self);
119
120 match ready!(this.pidfd.poll_read_ready(cx)) {
121 Err(err: Error) if is_rt_shutdown_err(&err) => {
122 this.pidfd.reregister(interest:Interest::READABLE)?;
123 ready!(this.pidfd.poll_read_ready(cx))?
124 }
125 res: Result<(), Error> => res?,
126 }
127 Poll::Ready(Ok(this
128 .inner
129 .try_wait()?
130 .expect(msg:"pidfd is ready to read, the process should have exited")))
131 }
132}
133
134#[derive(Debug)]
135pub(crate) struct PidfdReaper<W, Q>
136where
137 W: Wait + Unpin,
138 Q: OrphanQueue<W> + Unpin,
139{
140 inner: Option<PidfdReaperInner<W>>,
141 orphan_queue: Q,
142}
143
144impl<W, Q> Deref for PidfdReaper<W, Q>
145where
146 W: Wait + Unpin,
147 Q: OrphanQueue<W> + Unpin,
148{
149 type Target = W;
150
151 fn deref(&self) -> &Self::Target {
152 &self.inner.as_ref().expect(msg:"inner has gone away").inner
153 }
154}
155
156impl<W, Q> PidfdReaper<W, Q>
157where
158 W: Wait + Unpin,
159 Q: OrphanQueue<W> + Unpin,
160{
161 pub(crate) fn new(inner: W, orphan_queue: Q) -> Result<Self, (Option<io::Error>, W)> {
162 if let Some(pidfd: Pidfd) = Pidfd::open(pid:inner.id()) {
163 match PollEvented::new_with_interest(io:pidfd, interest:Interest::READABLE) {
164 Ok(pidfd: PollEvented) => Ok(Self {
165 inner: Some(PidfdReaperInner { pidfd, inner }),
166 orphan_queue,
167 }),
168 Err(io_error: Error) => Err((Some(io_error), inner)),
169 }
170 } else {
171 Err((None, inner))
172 }
173 }
174
175 pub(crate) fn inner_mut(&mut self) -> &mut W {
176 &mut self.inner.as_mut().expect(msg:"inner has gone away").inner
177 }
178}
179
180impl<W, Q> Future for PidfdReaper<W, Q>
181where
182 W: Wait + Unpin,
183 Q: OrphanQueue<W> + Unpin,
184{
185 type Output = io::Result<ExitStatus>;
186
187 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
188 PinPin<&mut PidfdReaperInner<…>>::new(
189 pointer:Pin::into_inner(self)
190 .inner
191 .as_mut()
192 .expect(msg:"inner has gone away"),
193 )
194 .poll(cx)
195 }
196}
197
198impl<W, Q> Kill for PidfdReaper<W, Q>
199where
200 W: Wait + Unpin + Kill,
201 Q: OrphanQueue<W> + Unpin,
202{
203 fn kill(&mut self) -> io::Result<()> {
204 self.inner_mut().kill()
205 }
206}
207
208impl<W, Q> Drop for PidfdReaper<W, Q>
209where
210 W: Wait + Unpin,
211 Q: OrphanQueue<W> + Unpin,
212{
213 fn drop(&mut self) {
214 let mut orphan: W = self.inner.take().expect(msg:"inner has gone away").inner;
215 if let Ok(Some(_)) = orphan.try_wait() {
216 return;
217 }
218
219 self.orphan_queue.push_orphan(orphan);
220 }
221}
222
223#[cfg(all(test, not(loom), not(miri)))]
224mod test {
225 use super::*;
226 use crate::{
227 process::unix::orphan::test::MockQueue,
228 runtime::{Builder as RuntimeBuilder, Runtime},
229 };
230 use std::process::{Command, Output};
231
232 fn create_runtime() -> Runtime {
233 RuntimeBuilder::new_current_thread()
234 .enable_io()
235 .build()
236 .unwrap()
237 }
238
239 fn run_test(fut: impl Future<Output = ()>) {
240 create_runtime().block_on(fut)
241 }
242
243 fn is_pidfd_available() -> bool {
244 let Output { stdout, status, .. } = Command::new("uname").arg("-r").output().unwrap();
245 assert!(status.success());
246 let stdout = String::from_utf8_lossy(&stdout);
247
248 let mut kernel_version_iter = stdout.split_once('-').unwrap().0.split('.');
249 let major: u32 = kernel_version_iter.next().unwrap().parse().unwrap();
250 let minor: u32 = kernel_version_iter.next().unwrap().parse().unwrap();
251
252 major >= 6 || (major == 5 && minor >= 10)
253 }
254
255 #[test]
256 fn test_pidfd_reaper_poll() {
257 if !is_pidfd_available() {
258 eprintln!("pidfd is not available on this linux kernel, skip this test");
259 return;
260 }
261
262 let queue = MockQueue::new();
263
264 run_test(async {
265 let child = Command::new("true").spawn().unwrap();
266 let pidfd_reaper = PidfdReaper::new(child, &queue).unwrap();
267
268 let exit_status = pidfd_reaper.await.unwrap();
269 assert!(exit_status.success());
270 });
271
272 assert!(queue.all_enqueued.borrow().is_empty());
273 }
274
275 #[test]
276 fn test_pidfd_reaper_kill() {
277 if !is_pidfd_available() {
278 eprintln!("pidfd is not available on this linux kernel, skip this test");
279 return;
280 }
281
282 let queue = MockQueue::new();
283
284 run_test(async {
285 let child = Command::new("sleep").arg("1800").spawn().unwrap();
286 let mut pidfd_reaper = PidfdReaper::new(child, &queue).unwrap();
287
288 pidfd_reaper.kill().unwrap();
289
290 let exit_status = pidfd_reaper.await.unwrap();
291 assert!(!exit_status.success());
292 });
293
294 assert!(queue.all_enqueued.borrow().is_empty());
295 }
296
297 #[test]
298 fn test_pidfd_reaper_drop() {
299 if !is_pidfd_available() {
300 eprintln!("pidfd is not available on this linux kernel, skip this test");
301 return;
302 }
303
304 let queue = MockQueue::new();
305
306 let mut child = Command::new("sleep").arg("1800").spawn().unwrap();
307
308 run_test(async {
309 let _pidfd_reaper = PidfdReaper::new(&mut child, &queue).unwrap();
310 });
311
312 assert_eq!(queue.all_enqueued.borrow().len(), 1);
313
314 child.kill().unwrap();
315 child.wait().unwrap();
316 }
317}
318