Skip to main content

conmonrs/
process.rs

1//! Signalling, waiting for and reaping child processes without hitting
2//! another process which reused their PID.
3
4use crate::{
5    async_fd,
6    mutex::{self, lock},
7};
8use anyhow::{Context, Result, bail};
9use nix::{
10    errno::Errno,
11    sys::{
12        prctl,
13        signal::{Signal, kill},
14        wait::{Id, WaitPidFlag},
15    },
16    unistd::{Pid, getpgid, getpid},
17};
18use std::{
19    os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd},
20    path::Path,
21    sync::{Arc, LazyLock, Mutex, RwLock, RwLockWriteGuard},
22    thread,
23    time::Duration,
24};
25use tokio::{io::Interest, sync::oneshot, task, time};
26use tracing::{debug, error};
27
28/// Read a PID file like the runtimes write it, the number may be followed by
29/// a line ending.
30pub(crate) fn read_pid_file(path: &Path) -> Result<Pid> {
31    let content = std::fs::read_to_string(path)
32        .with_context(|| format!("read PID file {}", path.display()))?;
33    parse_pid(&content).with_context(|| format!("parse PID file {}", path.display()))
34}
35
36/// Parse a PID, which is positive.
37fn parse_pid(pid: &str) -> Result<Pid> {
38    positive_pid(pid.trim().parse()?)
39}
40
41/// The PID of a spawned child by the ID which the standard library reports,
42/// or tokio, which has none anymore once the child got reaped.
43pub(crate) fn child_pid(id: impl Into<Option<u32>>) -> Result<Pid> {
44    let id = id.into().context("child got reaped already")?;
45    positive_pid(i32::try_from(id).with_context(|| format!("invalid PID {id}"))?)
46}
47
48/// A PID, which is positive. Zero and negative numbers refer to process
49/// groups when signalling.
50fn positive_pid(pid: i32) -> Result<Pid> {
51    if pid <= 0 {
52        bail!("invalid PID {pid}")
53    }
54    Ok(Pid::from_raw(pid))
55}
56
57/// Taken for writing while the server reaps one of its children, and for
58/// reading while it checks that one was not reaped yet and signals it. A
59/// child found unreaped under the read lock stays unreaped until the signals
60/// were sent. So neither its PID nor the ID of its process group, which it
61/// keeps referring to as a zombie, can be taken by another process meanwhile.
62/// The server is the only one which reaps its children.
63static REAP_LOCK: RwLock<()> = RwLock::new(());
64
65/// Hold while reaping a child of the server, see [`REAP_LOCK`].
66pub(crate) fn reap_guard() -> RwLockWriteGuard<'static, ()> {
67    mutex::write(&REAP_LOCK)
68}
69
70/// The fields of `/proc/<pid>/stat` which follow the parenthesized command
71/// name, which may contain spaces and parentheses itself.
72#[derive(Debug, PartialEq)]
73struct Stat {
74    state: char,
75    ppid: i32,
76    pgrp: i32,
77}
78
79impl Stat {
80    fn read(pid: Pid) -> Option<Self> {
81        Self::parse(&std::fs::read(format!("/proc/{pid}/stat")).ok()?)
82    }
83
84    /// The command name is not UTF-8 necessarily, any process can set its
85    /// own, only the fields after it are.
86    fn parse(stat: &[u8]) -> Option<Self> {
87        let end = stat.windows(2).rposition(|w| w == b") ")?;
88        let rest = std::str::from_utf8(&stat[end + 2..]).ok()?;
89        let mut fields = rest.split_whitespace();
90        Some(Self {
91            state: fields.next()?.chars().next()?,
92            ppid: fields.next()?.parse().ok()?,
93            pgrp: fields.next()?.parse().ok()?,
94        })
95    }
96}
97
98/// The PIDs of all processes, without the ones of their threads.
99fn pids() -> impl Iterator<Item = Pid> {
100    std::fs::read_dir("/proc")
101        .into_iter()
102        .flatten()
103        .flatten()
104        .filter_map(|entry| parse_pid(entry.file_name().to_str()?).ok())
105}
106
107/// Whether the process is a zombie.
108pub(crate) fn is_zombie(pid: Pid) -> bool {
109    Stat::read(pid).is_some_and(|stat| stat.state == 'Z')
110}
111
112/// The children of this process, found by scanning all processes.
113pub(crate) fn children_by_scan() -> Vec<Pid> {
114    let own = getpid().as_raw();
115    pids()
116        .filter(|pid| Stat::read(*pid).is_some_and(|stat| stat.ppid == own))
117        .collect()
118}
119
120/// Whether the kernel lists the children of a thread, see [`children`].
121static PROC_CHILDREN: LazyLock<bool> =
122    LazyLock::new(|| Path::new("/proc/thread-self/children").exists());
123
124/// Whether a child of this process belongs to the process group. The
125/// children the kernel lists come first, only without a member among them
126/// all processes get scanned: the lists are reliable for stopped children
127/// only. A listed child may have got reaped meanwhile, tokio does not take
128/// the [`REAP_LOCK`], so its parent gets checked again.
129fn group_has_child(pgid: Pid) -> bool {
130    let own = getpid().as_raw();
131    let member = |pids: Vec<Pid>| {
132        pids.into_iter()
133            .filter_map(Stat::read)
134            .any(|stat| stat.ppid == own && stat.pgrp == pgid.as_raw())
135    };
136    // Without the lists, children() scanned already.
137    member(children().0) || *PROC_CHILDREN && member(children_by_scan())
138}
139
140/// Whether the process group of a child gets signalled. Group 1 would end up
141/// as kill(-1), which kills everything conmon-rs is allowed to, and the group
142/// of this server would kill it as well. A container process which did not
143/// get a group of its own shares the one of its runtime, which runs in a
144/// group of its own.
145fn signals_group(pgid: Pid, own_pgid: Option<Pid>) -> bool {
146    pgid.as_raw() > 1 && Some(pgid) != own_pgid
147}
148
149/// Open a pidfd for the process, which also works for one that already
150/// exited but was not reaped yet. The descriptor is close-on-exec.
151#[expect(unsafe_code, reason = "a system call nix does not wrap")]
152pub(crate) fn pidfd_open(pid: Pid) -> nix::Result<OwnedFd> {
153    // SAFETY: pidfd_open takes a PID and flags and returns a new descriptor
154    // or a negative error, it does not touch any memory of ours.
155    let fd = unsafe { libc::syscall(libc::SYS_pidfd_open, pid.as_raw(), 0) };
156    let fd = RawFd::try_from(Errno::result(fd)?).map_err(|_| Errno::EBADF)?;
157    // SAFETY: the descriptor was just returned by the kernel and is not
158    // owned by anything else.
159    Ok(unsafe { OwnedFd::from_raw_fd(fd) })
160}
161
162/// Send a signal through a pidfd, none only checks that the process still
163/// exists.
164#[expect(unsafe_code, reason = "a system call nix does not wrap")]
165fn pidfd_send_signal(pidfd: impl AsFd, signal: Option<Signal>) -> nix::Result<()> {
166    let signal = signal.map_or(0, |signal| signal as libc::c_int);
167    // SAFETY: the descriptor is open for the duration of the call, the null
168    // siginfo makes the kernel fill in the same as kill(2) does.
169    let res = unsafe {
170        libc::syscall(
171            libc::SYS_pidfd_send_signal,
172            pidfd.as_fd().as_raw_fd(),
173            signal,
174            std::ptr::null::<libc::siginfo_t>(),
175            0,
176        )
177    };
178    Errno::result(res).map(drop)
179}
180
181/// A child process of the server, which can be signalled without hitting
182/// another process that reused its PID.
183#[derive(Debug)]
184pub(crate) struct Process {
185    pid: Pid,
186
187    /// Keeps referring to the process after it got reaped, unlike the PID.
188    /// None if the kernel does not support pidfds.
189    pidfd: Option<OwnedFd>,
190}
191
192impl Process {
193    /// Refer to a child which was not reaped yet, so the PID is still its
194    /// own.
195    pub(crate) fn open(pid: Pid) -> Self {
196        let pidfd = match pidfd_open(pid) {
197            Ok(pidfd) => Some(pidfd),
198            Err(e) => {
199                debug!("No pidfd for PID {pid}, checking the PID instead: {e}");
200                None
201            }
202        };
203        Self { pid, pidfd }
204    }
205
206    /// Refer to a child by its PID alone, which gets checked to still belong
207    /// to an unreaped child of the server before every signal.
208    pub(crate) fn child(pid: Pid) -> Self {
209        Self { pid, pidfd: None }
210    }
211
212    /// The PID of the process.
213    pub(crate) fn pid(&self) -> Pid {
214        self.pid
215    }
216
217    /// Signal the process, none only checks that it still exists. Its PID
218    /// gets reused once something took its exit status, the pidfd still
219    /// refers to the exited process then and the signal fails with `ESRCH`
220    /// instead of hitting another process.
221    pub(crate) fn signal(&self, signal: impl Into<Option<Signal>>) -> nix::Result<()> {
222        let _guard = mutex::read(&REAP_LOCK);
223        self.signal_unlocked(signal.into())
224    }
225
226    /// Whether the child still runs. A zombie which was not reaped yet does
227    /// not, although it can still be signalled.
228    #[cfg(test)]
229    pub(crate) fn is_running(&self) -> bool {
230        let _guard = mutex::read(&REAP_LOCK);
231        self.signal_unlocked(None).is_ok() && matches!(peek_exit(Id::Pid(self.pid)), Ok(None))
232    }
233
234    /// Signal the process group of the process and the process itself, once
235    /// it was checked to be unreaped, see [`REAP_LOCK`]. The group only gets
236    /// signalled if [`signals_group`] allows it.
237    ///
238    /// A process which got reaped already may have led a group which still
239    /// has members, like the init of a container without a PID namespace
240    /// whose children hold the pipes. They got reparented to this server,
241    /// the subreaper, so the group gets signalled as long as one of them is
242    /// an unreaped child of the server, which keeps the group ID in use.
243    pub(crate) fn signal_group(&self, signal: Signal) -> nix::Result<()> {
244        let _guard = mutex::read(&REAP_LOCK);
245        let own_pgid = getpgid(None).ok();
246        if let Err(e) = self.signal_unlocked(None) {
247            return self.signal_remaining_group(signal, own_pgid, e);
248        }
249        if let Ok(pgid) = getpgid(Some(self.pid))
250            && signals_group(pgid, own_pgid)
251            && let Err(e) = kill(Pid::from_raw(-pgid.as_raw()), signal)
252        {
253            debug!("Unable to signal process group {pgid}: {e}");
254        }
255        self.signal_unlocked(Some(signal))
256    }
257
258    /// Signal the remaining group of a process which is gone, if it still
259    /// has a member which keeps its ID in use, fail with `e` otherwise. Not
260    /// while another process has the PID: the group of a leader which got
261    /// reaped keeps the ID from getting reused, but once that group is gone
262    /// as well, a process which reused the PID may lead a group of its own
263    /// under it.
264    fn signal_remaining_group(
265        &self,
266        signal: Signal,
267        own_pgid: Option<Pid>,
268        e: Errno,
269    ) -> nix::Result<()> {
270        if signals_group(self.pid, own_pgid)
271            && kill(self.pid, None) == Err(Errno::ESRCH)
272            && group_has_child(self.pid)
273        {
274            debug!("Signalling the remaining group of PID {}", self.pid);
275            return kill(Pid::from_raw(-self.pid.as_raw()), signal);
276        }
277        Err(e)
278    }
279
280    fn signal_unlocked(&self, signal: Option<Signal>) -> nix::Result<()> {
281        if let Some(pidfd) = &self.pidfd {
282            return pidfd_send_signal(pidfd, signal);
283        }
284
285        // Without pidfds (Linux before 5.3) check that the process is still a
286        // child of this server which was not reaped yet, its PID cannot be
287        // reused then. The caller holds the lock which keeps it unreaped.
288        peek_exit(Id::Pid(self.pid))?;
289        kill(self.pid, signal)
290    }
291}
292
293/// The exit code of a child which exited, without waiting or reaping it,
294/// none while it runs. Fails with `ECHILD` if there is no such child of the
295/// server, also once it got reaped.
296fn peek_exit(id: Id<'_>) -> nix::Result<Option<i32>> {
297    wait_exit(id, WaitPidFlag::WNOHANG | WaitPidFlag::WNOWAIT)
298}
299
300/// Whether the process is a child of the server which was not reaped yet, a
301/// zombie included. Only such a PID is safe to register, see
302/// `ChildReaper::create_child`.
303pub(crate) fn is_child(pid: Pid) -> bool {
304    peek_exit(Id::Pid(pid)) != Err(Errno::ECHILD)
305}
306
307/// Whether the processes which the children of the server leave behind get
308/// reparented to it, which the server sets up at its start, see
309/// [`init::set_up_serving`](crate::init::set_up_serving). The tests do not.
310/// One which cannot tell counts as a subreaper.
311pub(crate) fn is_subreaper() -> bool {
312    prctl::get_child_subreaper().unwrap_or(true)
313}
314
315/// Whether a child of any thread of this process exited and was not
316/// reaped yet, without reaping it.
317pub(crate) fn has_zombie() -> bool {
318    !matches!(peek_exit(Id::All), Ok(None) | Err(Errno::ECHILD))
319}
320
321/// The zombie children of this process, and whether the list is
322/// complete, see [`children`].
323pub(crate) fn zombie_children() -> (Vec<Pid>, bool) {
324    let (children, complete) = children();
325    let zombies = children.into_iter().filter(|pid| is_zombie(*pid)).collect();
326    (zombies, complete)
327}
328
329/// The children of all threads of this process. The kernel lists them in
330/// `/proc/self/task/<tid>/children`, which needs `CONFIG_PROC_CHILDREN`,
331/// fall back to scanning all processes without it. The list is not
332/// complete if a thread exited while listing them.
333fn children() -> (Vec<Pid>, bool) {
334    if !*PROC_CHILDREN {
335        return (children_by_scan(), true);
336    }
337    // Nothing gets reaped for children which cannot be listed, which is the
338    // safe side.
339    let Ok(tasks) = std::fs::read_dir("/proc/self/task") else {
340        return (vec![], true);
341    };
342    let mut children = vec![];
343    let mut complete = true;
344    for task in tasks.flatten() {
345        // A thread which exited meanwhile took its list along, its
346        // children got another thread as parent.
347        let list = match std::fs::read_to_string(task.path().join("children")) {
348            Ok(list) => list,
349            Err(e) => {
350                if matches!(e.raw_os_error(), Some(libc::ENOENT | libc::ESRCH)) {
351                    complete = false;
352                }
353                continue;
354            }
355        };
356        children.extend(
357            list.split_whitespace()
358                .filter_map(|pid| pid.parse().ok())
359                .map(Pid::from_raw),
360        );
361    }
362    (children, complete)
363}
364
365/// The exit code of a process whose exit status is not known, because it did
366/// neither exit nor get killed by a signal, or waitpid failed after its pidfd
367/// got readable.
368const EXIT_CODE_UNKNOWN: i32 = -1;
369
370/// The exit code of a process whose exit status the blocking wait, used
371/// without pidfds, could not get, for example because it got reaped already.
372/// It is the same as
373/// [`EXIT_CODE_TIMED_OUT`](crate::child_reaper::EXIT_CODE_TIMED_OUT) for
374/// compatibility, the pidfd wait reports [`EXIT_CODE_UNKNOWN`] instead.
375pub(crate) const EXIT_CODE_WAIT_FAILED: i32 = -3;
376
377/// Wait for the exit of a child like waitid(2) with `WEXITED` and `flags`,
378/// and return its exit code, the one of a signal like a shell reports it.
379/// None if it still runs, with `WNOHANG`. The wait of nix fails with
380/// `EINVAL` for a child killed by a real-time signal, which it does not
381/// know, after the kernel reaped it already.
382pub(crate) fn wait_exit(id: Id<'_>, flags: WaitPidFlag) -> nix::Result<Option<i32>> {
383    Ok(wait_child(id, flags)?.map(|(_, exit)| exit.code()))
384}
385
386/// The name of a signal, or its number if nix does not know it, like the
387/// real-time signals.
388pub(crate) fn signal_name(signal: i32) -> String {
389    Signal::try_from(signal).map_or_else(|_| signal.to_string(), |s| s.as_str().into())
390}
391
392/// How a child exited, see [`wait_child`].
393#[derive(Clone, Copy)]
394enum ChildExit {
395    Exited(i32),
396    Signaled(i32),
397    Unknown,
398}
399
400impl ChildExit {
401    /// The exit code, the one of a signal like a shell reports it.
402    pub(crate) fn code(self) -> i32 {
403        match self {
404            Self::Exited(code) => code,
405            Self::Signaled(sig) => sig + 128,
406            Self::Unknown => EXIT_CODE_UNKNOWN,
407        }
408    }
409
410    /// Log the exit. Only the waits for a single process do, the sweeps
411    /// log their own line.
412    fn log(self) {
413        match self {
414            Self::Exited(code) => debug!("Exited {code}"),
415            Self::Signaled(sig) => debug!("Signaled: {}", signal_name(sig)),
416            Self::Unknown => {}
417        }
418    }
419}
420
421/// The waitid(2) behind [`wait_exit`], which also reports real-time
422/// signals, along with the PID of the child.
423#[expect(unsafe_code, reason = "nix does not convert real-time signals")]
424fn wait_child(id: Id<'_>, flags: WaitPidFlag) -> nix::Result<Option<(Pid, ChildExit)>> {
425    let (idtype, id) = match id {
426        Id::All => (libc::P_ALL, 0),
427        Id::Pid(pid) => (libc::P_PID, pid.as_raw().cast_unsigned()),
428        Id::PGid(pgid) => (libc::P_PGID, pgid.as_raw().cast_unsigned()),
429        Id::PIDFd(fd) => (libc::P_PIDFD, fd.as_raw_fd().cast_unsigned()),
430        // A hidden variant, which nix does not construct on Linux.
431        _ => return Err(Errno::EINVAL),
432    };
433    // SAFETY: an all-zero siginfo_t is valid, it consists of integers. The
434    // kernel leaves it zeroed if no child exited yet with WNOHANG.
435    let mut info: libc::siginfo_t = unsafe { std::mem::zeroed() };
436    let flags = (WaitPidFlag::WEXITED | flags).bits();
437    // SAFETY: the siginfo outlives the call, the kernel only writes to it.
438    Errno::result(unsafe { libc::waitid(idtype, id, &raw mut info, flags) })?;
439    // SAFETY: the kernel filled in the fields of SIGCHLD, or none.
440    let (pid, status) = unsafe { (info.si_pid(), info.si_status()) };
441    if pid == 0 {
442        return Ok(None);
443    }
444    let exit = match info.si_code {
445        libc::CLD_EXITED => ChildExit::Exited(status),
446        libc::CLD_KILLED | libc::CLD_DUMPED => ChildExit::Signaled(status),
447        // Only stopped and continued children report other codes, which
448        // WEXITED excludes.
449        code => {
450            debug!("Unexpected wait code {code} of PID {pid}");
451            ChildExit::Unknown
452        }
453    };
454    Ok(Some((Pid::from_raw(pid), exit)))
455}
456
457/// Wait for a process to exit using pidfd_open for async polling,
458/// with a blocking waitpid fallback for older kernels.
459pub(crate) async fn wait_for_exit_code(pid: Pid) -> i32 {
460    debug!("Waiting for exit code of PID {pid}");
461
462    // Gone already, the blocking wait would only learn the same.
463    let pidfd = match pidfd_open(pid) {
464        Ok(pidfd) => Some(pidfd),
465        Err(Errno::ESRCH) => {
466            debug!("PID {pid} got reaped already");
467            return EXIT_CODE_UNKNOWN;
468        }
469        Err(_) => None,
470    };
471    wait_for_exit_with(pid, pidfd.as_ref().map(AsFd::as_fd)).await
472}
473
474/// Wait for the exit of a registered child like [`wait_for_exit_code`],
475/// through the pidfd it holds already, so that the wait opens none of its
476/// own. Without one, on kernels before 5.3, the wait falls back like for a
477/// PID.
478pub(crate) async fn wait_for_exit(process: &Process) -> i32 {
479    match &process.pidfd {
480        Some(pidfd) => {
481            debug!("Waiting for exit code of PID {}", process.pid);
482            wait_for_exit_with(process.pid, Some(pidfd.as_fd())).await
483        }
484        None => wait_for_exit_code(process.pid).await,
485    }
486}
487
488/// Wait for the exit of `pid` through its `pidfd`, or on a thread without
489/// one, see [`wait_for_exit_code`].
490async fn wait_for_exit_with(pid: Pid, pidfd: Option<BorrowedFd<'_>>) -> i32 {
491    if let Some(pidfd) = pidfd
492        && let Some(code) = try_pidfd_wait(pid, pidfd).await
493    {
494        return code;
495    }
496    debug!("Pidfd not available, falling back to blocking waitpid");
497    wait_for_exit_code_on_thread(pid).await
498}
499
500/// Wait for a process to exit with a blocking wait on a thread of its own,
501/// without pidfds, see [`spawn_waitpid_thread`].
502async fn wait_for_exit_code_on_thread(pid: Pid) -> i32 {
503    let (tx, rx) = oneshot::channel();
504    spawn_waitpid_thread(move || {
505        let _ = tx.send(wait_for_exit_code_blocking(pid));
506    });
507    rx.await.unwrap_or_else(|_| {
508        error!("Waitpid thread exited without a result");
509        EXIT_CODE_WAIT_FAILED
510    })
511}
512
513/// Stack size of the waitpid threads, which only wait and log.
514const WAITPID_STACK_SIZE: usize = 256 * 1024;
515
516/// Run a blocking waitpid on a dedicated thread. It blocks for the lifetime
517/// of the process, so it must not occupy a thread of the bounded blocking
518/// pool, which serves the file I/O of all containers. If no thread can be
519/// spawned, for example because of a pids limit, queue it on the pool
520/// rather than failing the wait.
521fn spawn_waitpid_thread<F: FnOnce() + Send + 'static>(f: F) {
522    // The builder drops the closure on failure, keep it for the fallback.
523    let slot = Arc::new(Mutex::new(Some(f)));
524    fn run<F: FnOnce()>(slot: &Mutex<Option<F>>) {
525        // Not under the lock, the closure blocks.
526        let f = lock(slot).take();
527        if let Some(f) = f {
528            f();
529        }
530    }
531
532    let thread_slot = slot.clone();
533    if let Err(e) = thread::Builder::new()
534        .name("waitpid".into())
535        .stack_size(WAITPID_STACK_SIZE)
536        .spawn(move || run(&thread_slot))
537    {
538        error!("Unable to spawn waitpid thread, using blocking pool: {e:#}");
539        task::spawn_blocking(move || run(&slot));
540    }
541}
542
543/// The bounds of the delay between the waits for an exited process which is
544/// not waitable yet, unless its pidfd wakes the wait up earlier, see
545/// [`try_pidfd_wait`]. The waits go on as long as its tracer does not wait
546/// for it, like for a process stuck in the kernel.
547const PIDFD_RETRY_MIN: Duration = Duration::from_millis(10);
548const PIDFD_RETRY_MAX: Duration = Duration::from_secs(1);
549
550/// Wait for the exit of `pid` through its `pidfd` with async polling. None if
551/// the pidfd cannot be waited for.
552async fn try_pidfd_wait(pid: Pid, pidfd: BorrowedFd<'_>) -> Option<i32> {
553    // The readiness comes from epoll and the wait uses WNOHANG, so the
554    // descriptor itself does not need to be non-blocking.
555    let async_fd = match async_fd::register(pidfd, Interest::READABLE) {
556        Ok(fd) => fd,
557        Err(e) => {
558            error!("Unable to create AsyncFd from pidfd: {e:#}");
559            return None;
560        }
561    };
562
563    let mut ready = match async_fd.readable().await {
564        Ok(ready) => Some(ready),
565        Err(_) => return None,
566    };
567
568    // Through the pidfd, which refers to this process only: once the
569    // orphan sweep took it, its PID may belong to another child already.
570    // Kernels before 5.4 only know the PID, they fail with EINVAL. They get
571    // one wait only, a retry could take the exit of that other child.
572    let mut backoff = PIDFD_RETRY_MIN;
573    let exit = loop {
574        let reaping = reap_guard();
575        let pidfd = Id::PIDFd(async_fd.get_ref().as_fd());
576        match wait_child(pidfd, WaitPidFlag::WNOHANG) {
577            Err(Errno::EINVAL) => break wait_child(Id::Pid(pid), WaitPidFlag::WNOHANG),
578            // The pidfd of a traced process gets readable once it exited,
579            // but its parent can only wait for it after the tracer did.
580            Ok(None) => {}
581            exit => break exit,
582        }
583        drop(reaping);
584        if backoff == PIDFD_RETRY_MIN {
585            debug!("PID {pid} is not waitable yet, retrying until its tracer waited");
586        }
587        // The pidfd stays readable, but the kernel wakes it up again once
588        // the tracer waited for the process or detached from it, when it
589        // notifies the parent. The edge triggered registration reports that
590        // wakeup, unless it came before the readiness got cleared, which
591        // keeps it then. The timer covers a kernel which does not.
592        if let Some(mut ready) = ready.take() {
593            ready.clear_ready();
594        }
595        ready = time::timeout(backoff, async_fd.readable())
596            .await
597            .ok()
598            .and_then(Result::ok);
599        backoff = (backoff * 2).min(PIDFD_RETRY_MAX);
600    };
601    match exit {
602        Ok(Some((_, exit))) => {
603            exit.log();
604            Some(exit.code())
605        }
606        Ok(None) => {
607            debug!("PID {pid} is not waitable yet, not retrying by its PID");
608            Some(EXIT_CODE_UNKNOWN)
609        }
610        // The orphan sweep may have taken it meanwhile.
611        Err(Errno::ECHILD) => {
612            debug!("PID {pid} got reaped already");
613            Some(EXIT_CODE_UNKNOWN)
614        }
615        Err(e) => {
616            error!("Waitid after pidfd readable failed: {e:#}");
617            Some(EXIT_CODE_UNKNOWN)
618        }
619    }
620}
621
622/// Wait for the exit without reaping, which then happens under the
623/// [`reap_guard`].
624fn wait_for_exit_code_blocking(pid: Pid) -> i32 {
625    loop {
626        match wait_exit(Id::Pid(pid), WaitPidFlag::WNOWAIT) {
627            Ok(_) => {}
628            Err(Errno::EINTR) => continue,
629            Err(Errno::ECHILD) => {
630                debug!("PID {pid} got reaped already");
631                return EXIT_CODE_WAIT_FAILED;
632            }
633            Err(err) => {
634                error!("Unable to wait for PID {pid}: {err:#}");
635                return EXIT_CODE_WAIT_FAILED;
636            }
637        }
638        let reaping = reap_guard();
639        let exit = wait_child(Id::Pid(pid), WaitPidFlag::WNOHANG);
640        drop(reaping);
641        match exit {
642            Ok(Some((_, exit))) => {
643                exit.log();
644                return exit.code();
645            }
646            Ok(None) => {}
647            Err(Errno::EINTR) => debug!("Failed to wait for PID {pid} on EINTR, retrying"),
648            Err(err) => {
649                error!("Unable to wait for PID {pid}: {err:#}");
650                return EXIT_CODE_WAIT_FAILED;
651            }
652        }
653    }
654}
655
656/// The entry point of the fuzz target.
657#[cfg(any(test, feature = "fuzzing"))]
658pub(crate) mod fuzzing;
659
660#[cfg(test)]
661mod tests;