Skip to main content

detcore/syscalls/
io.rs

1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 * All rights reserved.
4 *
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the root directory of this source tree.
7 */
8
9//! System calls dealing with IO and networking.
10//!
11//! Of course this overlaps somewhat with "files.rs".
12
13use std::net::Ipv4Addr;
14use std::net::Ipv6Addr;
15use std::os::unix::io::RawFd;
16use std::time::Duration;
17// Only the kernel-run `poll` restart reads the host clock, and only x86_64
18// has `poll`.
19#[cfg(target_arch = "x86_64")]
20use std::time::Instant;
21
22use nix::fcntl::OFlag;
23use reverie::Errno;
24use reverie::Error;
25use reverie::Guest;
26use reverie::Stack;
27use reverie::syscalls;
28use reverie::syscalls::Addr;
29use reverie::syscalls::AddrMut;
30use reverie::syscalls::Displayable;
31use reverie::syscalls::MemoryAccess;
32use reverie::syscalls::Syscall;
33use reverie::syscalls::SyscallArgs;
34use reverie::syscalls::SyscallInfo;
35use reverie::syscalls::Sysno;
36use reverie::syscalls::Timespec;
37use reverie::syscalls::linux_abi::Cmsghdr;
38use reverie::syscalls::linux_abi::Mmsghdr;
39use reverie::syscalls::linux_abi::Msghdr;
40#[cfg(target_arch = "x86_64")]
41use tracing::debug;
42use tracing::trace;
43
44use crate::config::SchedHeuristic;
45use crate::fd::FdType;
46use crate::record_or_replay::RecordOrReplay;
47use crate::resources::LOOPBACK_POLL_YIELD_FYI;
48use crate::resources::Permission;
49use crate::resources::ResourceID;
50use crate::resources::Resources;
51use crate::scheduler::runqueue::FIRST_PRIORITY;
52use crate::syscalls::files::SocketRole;
53use crate::syscalls::helpers::GuestHeldPollProof;
54#[cfg(target_arch = "x86_64")]
55use crate::syscalls::helpers::KernelPollDeadline;
56use crate::syscalls::helpers::KernelSignalWait;
57use crate::syscalls::helpers::NonblockableSyscall;
58use crate::syscalls::helpers::PublishedWaitMask;
59use crate::syscalls::helpers::REAL_TIME_SIGNALS;
60#[cfg(target_arch = "x86_64")]
61use crate::syscalls::helpers::RestartCall;
62use crate::syscalls::helpers::act_on_pending_real_time;
63use crate::syscalls::helpers::guest_signal_state_task;
64use crate::syscalls::helpers::is_accepted_connection;
65#[cfg(target_arch = "x86_64")]
66use crate::syscalls::helpers::keep_restart_block;
67use crate::syscalls::helpers::millis_duration_to_absolute_timeout;
68use crate::syscalls::helpers::observe_under_mask;
69use crate::syscalls::helpers::pending_real_time_among;
70use crate::syscalls::helpers::poll_deadline_for;
71use crate::syscalls::helpers::poll_targets_guest_held;
72use crate::syscalls::helpers::publish_wait_mask;
73use crate::syscalls::helpers::record_retry_event;
74use crate::syscalls::helpers::refuse_held_signal_loss;
75use crate::syscalls::helpers::result_after_restore;
76use crate::syscalls::helpers::retry_blocking_wait_under_mask;
77use crate::syscalls::helpers::retry_nonblocking_syscall_with_timeout;
78use crate::syscalls::helpers::unblocked_by;
79use crate::syscalls::scm_rights::ReceiveGeometry;
80use crate::syscalls::scm_rights::ReceivedFrom;
81use crate::syscalls::scm_rights::SentRights;
82use crate::syscalls::scm_rights::received_descriptors;
83use crate::syscalls::signal::STACK_RED_ZONE;
84use crate::syscalls::signal::kernel_installed_signal_mask;
85use crate::syscalls::signal::read_kernel_sigset;
86use crate::syscalls::threads::KernelSigset;
87use crate::syscalls::threads::read_thread_signal_state;
88use crate::syscalls::threads::read_thread_wait_signal_state;
89use crate::tool_global::*;
90use crate::tool_local::Detcore;
91use crate::types::DetTid;
92use crate::types::LogicalTime;
93
94mod temporary_mask_forms;
95
96// Printing helper
97// TODO: this should be subsumed by better syscall printing.
98#[cfg(target_arch = "x86_64")]
99fn print_poll(call: &syscalls::Poll) {
100    let len = call.nfds();
101    debug!("POLL: on {} fds, timeout {}", len, call.timeout());
102    // TODO: nicer API for reading arrays from the guest:
103    unsafe {
104        for i in 0..len {
105            debug!(
106                "POLL: fd {} = {}",
107                i,
108                call.fds().unwrap().offset(i as isize)
109            );
110        }
111    }
112}
113
114/// Build the scheduler request for a zero-timeout poll.
115///
116/// An empty request only rotates the caller within its current priority band. That is not a
117/// yield when a busy poller has a higher priority than the producer whose readiness it is
118/// probing. `SchedYield` excludes the caller from the next selection, allowing exactly one
119/// other runnable guest to make progress before the nonblocking poll executes. Limit that strong
120/// yield to SaBRe tasks with a loopback peer: libcurl alternates its loopback socket with an
121/// internal wakeup fd, and either zero-timeout probe can otherwise starve the peer. General
122/// build-tool polling retains the existing empty-turn behavior.
123fn zero_timeout_poll_request(dettid: DetTid, yield_to_peer: bool) -> Resources {
124    let mut request = Resources::new(dettid);
125    if yield_to_peer {
126        request.insert(ResourceID::SchedYield, Permission::W);
127        request.fyi(LOOPBACK_POLL_YIELD_FYI);
128    }
129    request
130}
131
132fn connect_result_allows_peer_classification(result: &Result<i64, Error>) -> bool {
133    match result {
134        Ok(_) => true,
135        Err(Error::Errno(errno)) => *errno == Errno::EINPROGRESS,
136        Err(_) => false,
137    }
138}
139
140const KERNEL_SIGSET_SIZE: usize = std::mem::size_of::<u64>();
141pub(super) const PSELECT6_INTERNAL_MAX_NFDS: i32 =
142    (std::mem::size_of::<libc::c_ulong>() * 8) as i32;
143
144#[derive(Clone, Copy)]
145#[repr(C)]
146pub(super) struct Pselect6SigmaskArg {
147    pub(super) sigmask: usize,
148    pub(super) sigsetsize: usize,
149}
150
151/// Where a call's private cells (`PrivateCells`) live while the kernel reads
152/// them (`stage_private_cells`).
153enum StagedCellsStorage<Guard> {
154    /// The guest stack scratch below the red zone, checked out by the guard.
155    Stack(Guard),
156    /// Part of the guest's red zone, whose first `len` bytes `saved` holds
157    /// until `StagedCells::release` writes them back.
158    RedZone {
159        at: usize,
160        len: usize,
161        saved: [u8; MAX_PRIVATE_CELLS_LEN],
162    },
163}
164
165/// What a call hands the kernel in place of the guest's own memory: a
166/// temporary signal mask, which a sibling could rewrite while the call waits
167/// and which Hermit sanitizes, and the cell that goes with it. The mask comes
168/// first.
169///
170/// Every layout with a mask takes 24 bytes, the size of replay's signal
171/// delivery for an interrupted call (`end_replayed_pselect6_under_its_temporary_mask`),
172/// so replay can deliver wherever recording found room for the call (Claude
173/// re-review of https://github.com/rrnewton/hermit/pull/4087).
174#[derive(Clone, Copy)]
175enum PrivateCells {
176    /// A pselect6 temporary mask and the `{ sigmask, sigsetsize }` wrapper
177    /// that names it.
178    Pselect6Mask(KernelSigset),
179    /// A temporary mask for a call that takes the mask without a wrapper
180    /// (`epoll_pwait`, a ppoll the kernel waits in), and 16 unused bytes.
181    Mask(KernelSigset),
182    /// A temporary mask and a zero timeout (a zero-timeout ppoll probe, and
183    /// the replayed signal delivery).
184    MaskAndZeroTimeout(KernelSigset),
185    /// A zero timeout alone (a zero-timeout ppoll probe with no mask).
186    ZeroTimeout,
187}
188
189const MASK_LEN: usize = std::mem::size_of::<KernelSigset>();
190
191/// The most any `PrivateCells` takes: a mask and a 16-byte cell.
192const MAX_PRIVATE_CELLS_LEN: usize = MASK_LEN + std::mem::size_of::<Timespec>();
193
194// The wrapper and the timeout share the cell after the mask.
195const _: () = assert!(std::mem::size_of::<Pselect6SigmaskArg>() == std::mem::size_of::<Timespec>());
196
197impl PrivateCells {
198    fn len(self) -> usize {
199        match self {
200            Self::Pselect6Mask(_) | Self::Mask(_) | Self::MaskAndZeroTimeout(_) => {
201                MAX_PRIVATE_CELLS_LEN
202            }
203            Self::ZeroTimeout => std::mem::size_of::<Timespec>(),
204        }
205    }
206
207    /// The cells' bytes when they start at `at`. Only the first `len` count,
208    /// and a zero timeout is all zeroes.
209    fn bytes(self, at: usize) -> [u8; MAX_PRIVATE_CELLS_LEN] {
210        let mut cells = [0u8; MAX_PRIVATE_CELLS_LEN];
211        match self {
212            Self::Pselect6Mask(mask) => {
213                cells[..MASK_LEN].copy_from_slice(&mask.to_ne_bytes());
214                cells[MASK_LEN..MASK_LEN + 8].copy_from_slice(&at.to_ne_bytes());
215                cells[MASK_LEN + 8..].copy_from_slice(&KERNEL_SIGSET_SIZE.to_ne_bytes());
216            }
217            Self::Mask(mask) | Self::MaskAndZeroTimeout(mask) => {
218                cells[..MASK_LEN].copy_from_slice(&mask.to_ne_bytes());
219            }
220            Self::ZeroTimeout => {}
221        }
222        cells
223    }
224}
225
226/// A call's private cells in guest memory (`stage_private_cells`).
227struct StagedCells<Guard> {
228    /// The first cell: the mask, or the zero timeout of
229    /// `PrivateCells::ZeroTimeout`.
230    at: usize,
231    storage: StagedCellsStorage<Guard>,
232    /// Memory for making an expired timerfd readable while these cells hold
233    /// the scratch stack (`crate::syscalls::timerfd::publish_expired_in`);
234    /// `None` for red-zone cells, which hold no stack.
235    timerfd_scratch: Option<crate::syscalls::timerfd::Scratch>,
236}
237
238impl<Guard> StagedCells<Guard> {
239    /// The mask copy.
240    fn mask(&self) -> usize {
241        self.at
242    }
243
244    /// The cell after the mask: a pselect6 wrapper, or a zero timeout.
245    fn after_mask(&self) -> usize {
246        self.at + MASK_LEN
247    }
248
249    /// Gives the storage back once the call has returned: drops the stack
250    /// guard, or writes the guest's red-zone bytes back. That is a write to
251    /// guest memory, not an injected syscall, so a call that ended with a
252    /// restart code goes back to the kernel untouched.
253    fn release<G, T>(self, guest: &mut G)
254    where
255        G: Guest<Detcore<T>>,
256        T: RecordOrReplay,
257    {
258        if let StagedCellsStorage::RedZone { at, len, saved } = self.storage
259            && let Err(errno) = AddrMut::<u8>::from_raw(at)
260                .ok_or(Errno::EFAULT)
261                .and_then(|bytes| guest.memory().write_exact(bytes, &saved[..len]))
262        {
263            // Not expected: the same bytes were written before the call.
264            tracing::warn!("[detcore] could not restore the red zone after a syscall: {errno}");
265        }
266    }
267}
268
269/// Stages `cells` where the kernel can read them in place of the guest's own
270/// memory.
271///
272/// The cells go in the guest stack scratch below the red zone when that is
273/// writable. A thread may run with no writable memory there, on a stack it
274/// placed near the start of a page, and a call on such a stack is valid
275/// (Codex re-check of https://github.com/rrnewton/hermit/pull/4053). Then the
276/// cells go in the red zone, whose bytes are saved here and written back by
277/// `StagedCells::release` when the call returns. The red zone may hold the
278/// call's own buffers, which Linux reads and writes, so the cells take the
279/// highest 8-aligned window that overlaps none of `live` (Codex re-check #3).
280/// The guest's own copies of the cells (its mask, wrapper or timeout) may be
281/// overwritten: Linux reads none of them, as the call is redirected to the
282/// private cells, and all are restored. When no window is free and writable,
283/// this returns `None`, and the caller refuses the call
284/// (`refuse_unstageable_pselect6_mask`). The guest runs no code in between:
285/// the call holds the thread in the kernel, a signal frame is built below the
286/// red zone, and a call the kernel restarts comes back as a new syscall with
287/// the guest's own arguments, which is staged again. Nothing is mapped, so the
288/// guest's address space is unchanged (Codex re-check of the earlier
289/// per-thread page).
290///
291/// Whether the scratch faults is a function of the guest's own stack, so the
292/// choice is deterministic.
293async fn stage_private_cells<G, T>(
294    guest: &mut G,
295    cells: PrivateCells,
296    live: &[std::ops::Range<usize>],
297) -> Result<Option<StagedCells<<G::Stack as Stack>::StackGuard>>, Errno>
298where
299    G: Guest<Detcore<T>>,
300    T: RecordOrReplay,
301{
302    let len = cells.len();
303    let mut stack = guest.stack().await;
304    let at = match cells {
305        PrivateCells::ZeroTimeout => stack.reserve::<Timespec>().as_raw(),
306        _ => stack.reserve::<[u64; 3]>().as_raw(),
307    };
308    // Only a process with an armed virtual timerfd can need the publication
309    // scratch; any other keeps the whole stack for its cells, as before
310    // timerfds were virtualized.
311    let timerfd_scratch = crate::syscalls::timerfd::holds_armed_virtual_timerfd(guest)
312        .then(|| crate::syscalls::timerfd::reserve_scratch(&mut stack));
313    match stack.commit() {
314        Ok(guard) => {
315            let bytes = AddrMut::<u8>::from_raw(at).ok_or(Errno::EFAULT)?;
316            // The commit wrote zeroes, which the cells' address fills in.
317            guest.memory().write_exact(bytes, &cells.bytes(at)[..len])?;
318            return Ok(Some(StagedCells {
319                at,
320                storage: StagedCellsStorage::Stack(guard),
321                timerfd_scratch,
322            }));
323        }
324        Err(Errno::EFAULT) => {}
325        Err(errno) => return Err(errno),
326    }
327    let rsp = crate::arch::stack_pointer(&guest.regs().await) as usize;
328    let Some(top) = rsp.checked_sub(len).map(|top| top & !7) else {
329        return Ok(None);
330    };
331    let bottom = rsp.saturating_sub(STACK_RED_ZONE);
332    let mut saved = [0u8; MAX_PRIVATE_CELLS_LEN];
333    let mut found = None;
334    for at in (bottom..=top).rev().step_by(8) {
335        let window = at..at + len;
336        if live
337            .iter()
338            .any(|range| range.start < window.end && window.start < range.end)
339        {
340            continue;
341        }
342        let Some(bytes) = AddrMut::<u8>::from_raw(at) else {
343            continue;
344        };
345        if guest.memory().read_exact(bytes, &mut saved[..len]).is_ok() {
346            found = Some((at, bytes));
347            break;
348        }
349    }
350    let Some((at, bytes)) = found else {
351        return Ok(None);
352    };
353    let staged = StagedCells {
354        at,
355        storage: StagedCellsStorage::RedZone { at, len, saved },
356        timerfd_scratch: None,
357    };
358    if guest
359        .memory()
360        .write_exact(bytes, &cells.bytes(at)[..len])
361        .is_err()
362    {
363        // A read-only stack page. A write that failed partway may have changed
364        // some bytes, so put them back.
365        staged.release(guest);
366        return Ok(None);
367    }
368    Ok(Some(staged))
369}
370
371/// The temporary mask a record/replay pselect6 that may wait sleeps under,
372/// read at the call's entry, where Linux copies it (recording waits on a
373/// private copy of it, `handle_pselect6`). The mask is read through the kernel
374/// (`read_kernel_sigset`), so a mask the kernel's copy faults on, such as one
375/// on a `PROT_NONE` page that a tracer read would still return, gives `None`:
376/// the call then keeps the guest's wrapper and the kernel fails it with EFAULT
377/// in Linux's order (Claude review of
378/// https://github.com/rrnewton/hermit/pull/4166). `None` too when the call
379/// has no mask, its wrapper cannot be read as the kernel copies it
380/// (`read_pselect6_argument`), or the size is not the kernel's.
381///
382/// The read is the call's first injection: a signal that stops it restarts
383/// the call (`restart_if_first_probe_not_run`), the only error returned.
384async fn entry_pselect6_temporary_mask<G, T>(
385    guest: &mut G,
386    call: syscalls::Pselect6,
387) -> Result<Option<KernelSigset>, Error>
388where
389    G: Guest<Detcore<T>>,
390    T: RecordOrReplay,
391{
392    let Some(wrapper) = call.sigmask() else {
393        return Ok(None);
394    };
395    let Some(argument) = read_pselect6_argument(guest, wrapper) else {
396        return Ok(None);
397    };
398    if argument.sigsetsize != KERNEL_SIGSET_SIZE {
399        return Ok(None);
400    }
401    let Some(mask_addr) = Addr::<libc::sigset_t>::from_raw(argument.sigmask) else {
402        return Ok(None);
403    };
404    let mask = match read_kernel_sigset(guest, mask_addr).await {
405        Ok(mask) => mask,
406        Err(error @ Error::Errno(Errno::ERESTARTSYS)) => {
407            return crate::syscalls::signal::restart_if_first_probe_not_run(Err(error));
408        }
409        Err(_) => return Ok(None),
410    };
411    super::signal_shadow::note_wait_mask(guest, mask);
412    Ok(Some(mask))
413}
414
415/// Whether a pselect6 names no mask: it passes no `{ sigmask, sigsetsize }`
416/// argument, or one whose mask pointer is NULL, as glibc's `pselect` passes
417/// it. Linux then leaves the thread's mask in place without reading the size
418/// (`set_user_sigmask`). The argument is read whole, once, with the guest's
419/// own access (`read_pselect6_argument`), so one the kernel would fail to copy
420/// counts as naming a mask: the call keeps it, and its EFAULT.
421fn pselect6_names_no_mask<G, T>(guest: &mut G, call: syscalls::Pselect6) -> bool
422where
423    G: Guest<Detcore<T>>,
424    T: RecordOrReplay,
425{
426    let Some(wrapper) = call.sigmask() else {
427        return true;
428    };
429    read_pselect6_argument(guest, wrapper).is_some_and(|argument| argument.sigmask == 0)
430}
431
432/// pselect6's `{ sigmask, sigsetsize }` argument at `wrapper`, read as Linux
433/// copies it (`get_sigset_argpack`): whole, in one read that honours the
434/// guest's page protections, or `None` where the kernel's copy would fault,
435/// or where the tracer cannot read it although the kernel can (a write-only
436/// page on x86). Either way the call keeps the guest's argument. A tracer read
437/// retried word by word reaches a `PROT_NONE` tail on ptrace
438/// (`PTRACE_PEEKDATA`), which the kernel's copy does not.
439fn read_pselect6_argument<G, T>(
440    guest: &mut G,
441    wrapper: Addr<'_, libc::sigset_t>,
442) -> Option<Pselect6SigmaskArg>
443where
444    G: Guest<Detcore<T>>,
445    T: RecordOrReplay,
446{
447    let mut bytes = [0u8; std::mem::size_of::<Pselect6SigmaskArg>()];
448    guest
449        .memory()
450        .read_exact_with_user_access(wrapper.cast::<u8>(), &mut bytes)
451        .ok()?;
452    let word = |at: usize| {
453        usize::from_ne_bytes(
454            bytes[at..at + std::mem::size_of::<usize>()]
455                .try_into()
456                .expect("a word"),
457        )
458    };
459    Some(Pselect6SigmaskArg {
460        sigmask: word(0),
461        sigsetsize: word(std::mem::size_of::<usize>()),
462    })
463}
464
465/// The temporary mask a replayed zero-timeout pselect6 sleeps under, read
466/// once the scheduler grants the call, which keeps its turn until it returns;
467/// `None` when the call has no mask or the wrapper or the mask cannot be read
468/// (`end_replayed_pselect6_under_its_temporary_mask`).
469fn replayed_pselect6_temporary_mask<G, T>(
470    guest: &mut G,
471    call: syscalls::Pselect6,
472) -> Option<KernelSigset>
473where
474    G: Guest<Detcore<T>>,
475    T: RecordOrReplay,
476{
477    let wrapper = call.sigmask()?;
478    let argument: Pselect6SigmaskArg = guest.memory().read_value(wrapper.cast()).ok()?;
479    if argument.sigsetsize != KERNEL_SIGSET_SIZE {
480        return None;
481    }
482    let mask_addr = Addr::<KernelSigset>::from_raw(argument.sigmask)?;
483    let mask = guest.memory().read_value(mask_addr).ok()?;
484    super::signal_shadow::note_wait_mask(guest, mask);
485    Some(mask)
486}
487
488/// Whether replay could deliver a signal that interrupts this record/replay
489/// pselect6 (`end_replayed_pselect6_under_its_temporary_mask`), whose 24 bytes
490/// go in the stack scratch or at the top of the red zone. Recording a
491/// zero-timeout call passes the guest's own wrapper to the kernel, so it
492/// accepts the call on any stack, and a call whose stack pointer is less than
493/// 24 bytes above unwritable memory left replay nowhere to deliver: the kernel
494/// restarted the served call and replay stopped at the next syscall
495/// (https://github.com/rrnewton/hermit/issues/4112). The top 24 bytes of the
496/// red zone are readable exactly when the stack pointer is at least 24 bytes
497/// above unwritable memory, and the stack scratch lies below them, so this
498/// reads them. `true` for a call with no mask to deliver under, and for one
499/// whose wrapper Linux cannot read, which it fails before it waits. A function
500/// of the guest's stack and memory, which replay reproduces.
501async fn replayed_pselect6_delivery_fits<G, T>(guest: &mut G, call: syscalls::Pselect6) -> bool
502where
503    G: Guest<Detcore<T>>,
504    T: RecordOrReplay,
505{
506    let Some(wrapper) = call.sigmask() else {
507        return true;
508    };
509    let argument: Result<Pselect6SigmaskArg, _> = guest.memory().read_value(wrapper.cast());
510    match argument {
511        Ok(argument) if argument.sigmask != 0 => {}
512        _ => return true,
513    }
514    let rsp = crate::arch::stack_pointer(&guest.regs().await) as usize;
515    let mut cells = [0u8; MAX_PRIVATE_CELLS_LEN];
516    rsp.checked_sub(MAX_PRIVATE_CELLS_LEN)
517        .and_then(|top| AddrMut::<u8>::from_raw(top & !7))
518        .is_some_and(|top| guest.memory().read_exact(top, &mut cells).is_ok())
519}
520
521/// Ends a replayed pselect6 that the recording says a signal interrupted
522/// (`ERESTARTNOHAND`), when the signal is one that only the call's temporary
523/// mask unblocks.
524///
525/// Replay serves pselect6 from the recording, so the kernel never installs the
526/// call's temporary mask. Under the thread's own mask, which blocks the signal
527/// the scheduler sent at the recorded point, the kernel finds nothing to
528/// deliver, restarts the call, and the guest's next system call no longer
529/// matches the recording (https://github.com/rrnewton/hermit/issues/3992).
530/// Linux delivers such a signal before it puts the thread's own mask back, and
531/// the handler's frame restores that mask when it returns. Only the kernel can
532/// do both, so replay injects a ppoll with no descriptors, a zero timeout
533/// and a scratch copy of the mask the call read at entry (`temporary_mask`,
534/// from `replayed_pselect6_temporary_mask`), never the guest's buffer, which a
535/// sibling may have rewritten while the call was served (review of
536/// https://github.com/rrnewton/hermit/pull/4052): the kernel installs
537/// the mask, finds the signal pending, returns `ERESTARTNOHAND` and delivers
538/// the signal, as it did when the recorded call returned. A zero timeout never
539/// sleeps, and Linux writes no remaining time back for it, so neither host
540/// timing nor host time reaches the guest.
541///
542/// A zero-timeout masked pselect6 and a zero-timeout masked ppoll end the same
543/// way when a signal their mask unblocks is already pending, and replay serves
544/// them the same way, so both end through this too
545/// (https://github.com/rrnewton/hermit/issues/4054).
546///
547/// Every value this reads (guest memory, and the kernel's pending set, mask
548/// and dispositions in this thread's turn) is a function of the replayed
549/// schedule. When no such signal is pending, the call returns as before.
550async fn end_replayed_pselect6_under_its_temporary_mask<G, T>(
551    guest: &mut G,
552    temporary_mask: KernelSigset,
553) where
554    G: Guest<Detcore<T>>,
555    T: RecordOrReplay,
556{
557    let Ok(state) = read_thread_signal_state(guest.pid(), guest.tid()) else {
558        return;
559    };
560    if state.pending
561        & state.blocked
562        & state.interrupting(kernel_installed_signal_mask(temporary_mask))
563        == 0
564    {
565        return;
566    }
567    // The cells go in the stack scratch, or at the top of the red zone when
568    // the thread runs on a stack it placed near the start of a page, which is
569    // valid. The guest's red-zone bytes go back once the injected call has
570    // returned. The kernel builds a signal frame below the red zone, and the
571    // guest runs no code before the bytes are back, so it never sees them
572    // change. Nothing is mapped, so the replayed address space stays the
573    // recording's. No window is excluded: the call has returned, and Linux
574    // touches only the cells. The 24 bytes fit wherever recording staged its
575    // own 24-byte mask copy (Claude re-review of
576    // https://github.com/rrnewton/hermit/pull/4087).
577    let Ok(Some(staged)) =
578        stage_private_cells(guest, PrivateCells::MaskAndZeroTimeout(temporary_mask), &[]).await
579    else {
580        return;
581    };
582    inject_signal_delivery(guest, staged.mask(), staged.after_mask()).await;
583    staged.release(guest);
584}
585
586/// Injects the zero-timeout ppoll with no descriptors that delivers a
587/// replayed call's signal under its temporary mask (`mask`), as recording ran
588/// the call itself: the kernel installs the mask, finds the signal pending,
589/// returns `ERESTARTNOHAND` and delivers the signal as the injected call
590/// returns, as it did after the recorded call, and the guest then gets the
591/// recorded result. ppoll takes the mask pointer directly, so the delivery
592/// needs no `{ sigmask, sigsetsize }` wrapper, which a pselect6 would.
593async fn inject_signal_delivery<G, T>(guest: &mut G, mask: usize, zero_timeout: usize)
594where
595    G: Guest<Detcore<T>>,
596    T: RecordOrReplay,
597{
598    let _ = guest
599        .inject(
600            syscalls::Ppoll::new()
601                .with_fds(None)
602                .with_nfds(0)
603                .with_timeout(AddrMut::from_raw(zero_timeout))
604                .with_sigmask(Addr::from_raw(mask))
605                .with_sigsetsize(KERNEL_SIGSET_SIZE),
606        )
607        .await;
608}
609
610pub(super) fn pselect6_fd_set_len(nfds: i32) -> Result<usize, Errno> {
611    let nfds = usize::try_from(nfds).map_err(|_| Errno::EINVAL)?;
612    let bits_per_word = std::mem::size_of::<libc::c_ulong>() * 8;
613    Ok(nfds.div_ceil(bits_per_word) * std::mem::size_of::<libc::c_ulong>())
614}
615
616fn pselect6_probe_result(result: Result<i64, Errno>) -> Result<i64, Errno> {
617    match result {
618        // The injected syscall runs outside the guest's original restart frame.
619        // Do not expose this kernel-internal restart instruction at the
620        // rewritten pselect6 call site.
621        Err(Errno::ERESTARTSYS) => Err(Errno::EINTR),
622        result => result,
623    }
624}
625
626/// Runs `probe`, a zero-timeout ppoll, and returns its result, `EINTR`
627/// included. Only `ERESTARTSYS`, which ppoll itself never returns, is retried:
628/// it is the backend's report that a signal stopped the probe before its
629/// `syscall` instruction ran, and the backend holds that signal for the
630/// guest's resume, so the probe runs again, as `inject_with_retry` ran it.
631///
632/// `inject_with_retry` also retried `EINTR`, which hung the call forever on a
633/// backend that runs it itself: where the probe's temporary mask lets in a
634/// pending caught signal, such a backend answers `EINTR` and installs the
635/// mask for that signal's delivery as the guest resumes, and the signal stays
636/// pending until then, so every retry found it again. Linux answers that case
637/// with ERESTARTNOHAND (`do_poll`, fs/select.c), keeps the temporary mask for
638/// the delivery (`poll_select_finish`), and the delivery to a handler makes
639/// it `EINTR` (`handle_signal`, arch/x86/kernel/signal.c), so `EINTR` is the
640/// guest's answer, as for pselect6's probes. A traced guest's probe gets the
641/// kernel's own ERESTARTNOHAND, which was never retried. A backend that runs
642/// the real call inside the guest's process gets `EINTR` once a handler has
643/// run for the signal, which the retry turned into a later probe's 0.
644async fn inject_ppoll_probe<G, T>(guest: &mut G, probe: syscalls::Ppoll) -> Result<i64, Errno>
645where
646    G: Guest<Detcore<T>>,
647    T: RecordOrReplay,
648{
649    loop {
650        let result = guest.inject(probe).await;
651        if !ppoll_probe_runs_again(result) {
652            return result;
653        }
654    }
655}
656
657/// Whether `inject_ppoll_probe` runs a probe again after `result`: only after
658/// the backend's report that it did not run (`ERESTARTSYS`).
659fn ppoll_probe_runs_again(result: Result<i64, Errno>) -> bool {
660    result == Err(Errno::ERESTARTSYS)
661}
662
663/// The pending signals that the temporary mask `mask` lets in, in the signal
664/// state `sets` a backend reports, when every one of them is a signal the
665/// process ignores (`crate::syscalls::ignored_at_generation`: set to SIG_IGN,
666/// or default-ignored with no handler), and so one Linux discards as a ppoll
667/// under `mask` returns (`get_signal`, kernel/signal.c); the empty set when
668/// the mask lets none in. `None` when it lets in one that is not ignored.
669fn ignored_signals_let_in(sets: &reverie::SignalSets, mask: KernelSigset) -> Option<KernelSigset> {
670    let let_in = (sets.pending | sets.shared_pending) & !mask;
671    let ignored = crate::syscalls::ignored_at_generation(sets.ignored, sets.caught);
672    (let_in & !ignored == 0).then_some(let_in)
673}
674
675/// [`ignored_signals_let_in`] for the calling thread, from the signal state
676/// its backend reports (`Guest::signal_sets`); `None` from a backend that
677/// reports none.
678fn ignored_signals_let_in_now<G, T>(guest: &G, mask: KernelSigset) -> Option<KernelSigset>
679where
680    G: Guest<Detcore<T>>,
681    T: RecordOrReplay,
682{
683    guest
684        .signal_sets(guest.tid())
685        .ok()
686        .and_then(|sets| ignored_signals_let_in(&sets, mask))
687}
688
689/// The refusal of a masked pselect6 that a backend running the call itself
690/// failed with ENOSYS for a pending signal its temporary mask lets in, which
691/// it cannot act on inside the call, as for ppoll
692/// (`Detcore::refuse_masked_ppoll_signal`).
693const MASKED_PSELECT6_SIGNAL_REASON: &str = "a pselect6 whose temporary signal mask lets in a \
694     pending signal that would stop the process, or an ignored one pending beside a signal \
695     that ends the call, cannot be served on this backend: it cannot act on that signal inside \
696     the call";
697
698/// The refusal of a masked pselect6 that a backend running the call itself
699/// failed with ENOSYS for its descriptor sets, as KVM's does for more than 64
700/// descriptors or for a selected standard output or error it captures, each
701/// before it looks at a signal (`masked_pselect6_refusal_reason`). Linux
702/// serves both: it clamps the count to its descriptor table
703/// (`core_sys_select`, fs/select.c:641-642 at v6.17) and never fails the
704/// call with ENOSYS.
705const MASKED_PSELECT6_DECLINED_REASON: &str = "a pselect6 with a temporary signal mask whose \
706     descriptor sets this backend does not serve (more than 64 descriptors, or a standard \
707     output or error it captures) cannot be served on this backend";
708
709/// The signals whose default action stops the process (`SIG_KERNEL_STOP_MASK`
710/// and `sig_kernel_stop`, include/linux/signal.h:422-424 and :448 at v6.17):
711/// SIGSTOP, SIGTSTP, SIGTTIN and SIGTTOU.
712const DEFAULT_STOP_SIGNALS: KernelSigset = (1 << (libc::SIGSTOP - 1))
713    | (1 << (libc::SIGTSTP - 1))
714    | (1 << (libc::SIGTTIN - 1))
715    | (1 << (libc::SIGTTOU - 1));
716
717/// Whether the signal state `sets` holds a pending signal that the temporary
718/// mask `mask` lets in (SIGKILL and SIGSTOP always: no mask blocks them) and
719/// that a backend running a masked ppoll or pselect6 itself cannot act on
720/// inside the call, so that it fails the call with ENOSYS: one the process
721/// ignores (the shared rule, `crate::syscalls::ignored_at_generation`), which
722/// Linux discards before it restarts the call, or one whose default action
723/// stops the process with no handler (`DEFAULT_STOP_SIGNALS`). A caught
724/// signal, or one whose default action ends the process, ends the call
725/// instead.
726fn lets_in_a_signal_the_backend_cannot_act_on(
727    sets: &reverie::SignalSets,
728    mask: KernelSigset,
729) -> bool {
730    let unblockable: KernelSigset = (1 << (libc::SIGKILL - 1)) | (1 << (libc::SIGSTOP - 1));
731    let let_in = (sets.pending | sets.shared_pending) & !(mask & !unblockable);
732    let ignored = crate::syscalls::ignored_at_generation(sets.ignored, sets.caught);
733    let stops = DEFAULT_STOP_SIGNALS & !sets.caught & !ignored;
734    let_in & (ignored | stops) != 0
735}
736
737/// Why a masked pselect6 of `nfds` descriptors that the backend failed with
738/// ENOSYS, under the temporary mask `mask`, is refused, from the signal state
739/// `sets` the backend reports for the thread, if it reports any. More
740/// descriptors than one machine word holds (`PSELECT6_INTERNAL_MAX_NFDS`)
741/// KVM's backend declines first, before it reads the sets or looks at a
742/// signal, so the call is refused for its descriptor sets whatever is
743/// pending. Otherwise it is refused for a pending signal the backend cannot
744/// act on inside the call (`lets_in_a_signal_the_backend_cannot_act_on`), or,
745/// with none pending, for the call's descriptor sets. A selected standard
746/// output or error the backend captures it also declines before it looks at
747/// a signal, and Detcore cannot see which descriptors it captures, so such a
748/// call with such a signal pending is refused for the signal. With no state
749/// reported, the signal.
750fn masked_pselect6_refusal_reason(
751    sets: Option<&reverie::SignalSets>,
752    mask: KernelSigset,
753    nfds: i32,
754) -> &'static str {
755    if nfds > PSELECT6_INTERNAL_MAX_NFDS {
756        return MASKED_PSELECT6_DECLINED_REASON;
757    }
758    match sets {
759        Some(sets) if !lets_in_a_signal_the_backend_cannot_act_on(sets, mask) => {
760            MASKED_PSELECT6_DECLINED_REASON
761        }
762        _ => MASKED_PSELECT6_SIGNAL_REASON,
763    }
764}
765
766/// [`masked_pselect6_refusal_reason`] for the calling thread, from the signal
767/// state its backend reports now (`Guest::signal_sets`).
768fn masked_pselect6_refusal_reason_now<G, T>(
769    guest: &G,
770    mask: KernelSigset,
771    nfds: i32,
772) -> &'static str
773where
774    G: Guest<Detcore<T>>,
775    T: RecordOrReplay,
776{
777    masked_pselect6_refusal_reason(guest.signal_sets(guest.tid()).ok().as_ref(), mask, nfds)
778}
779
780pub(super) fn read_pselect6_fd_set<T, G>(
781    guest: &mut G,
782    address: Option<AddrMut<'_, libc::fd_set>>,
783    len: usize,
784) -> Result<Option<Vec<u8>>, Error>
785where
786    T: RecordOrReplay,
787    G: Guest<Detcore<T>>,
788{
789    let Some(address) = address else {
790        return Ok(None);
791    };
792    let mut bytes = vec![0; len];
793    if len != 0 {
794        guest
795            .memory()
796            .read_exact(address.cast(), &mut bytes)
797            .map_err(|_| Errno::EFAULT)?;
798    }
799    Ok(Some(bytes))
800}
801
802pub(super) fn write_pselect6_fd_set<T, G>(
803    guest: &mut G,
804    address: Option<AddrMut<'_, libc::fd_set>>,
805    bytes: &Option<Vec<u8>>,
806) -> Result<(), Error>
807where
808    T: RecordOrReplay,
809    G: Guest<Detcore<T>>,
810{
811    if let (Some(address), Some(bytes)) = (address, bytes)
812        && !bytes.is_empty()
813    {
814        guest
815            .memory()
816            .write_exact(address.cast(), bytes)
817            .map_err(|_| Errno::EFAULT)?;
818    }
819    Ok(())
820}
821
822fn copy_pselect6_fd_set<T, G>(
823    guest: &mut G,
824    source: Option<AddrMut<'_, libc::fd_set>>,
825    destination: Option<AddrMut<'_, libc::fd_set>>,
826    len: usize,
827) -> Result<(), Error>
828where
829    T: RecordOrReplay,
830    G: Guest<Detcore<T>>,
831{
832    if let (Some(source), Some(destination)) = (source, destination)
833        && len != 0
834    {
835        let mut bytes = vec![0; len];
836        guest
837            .memory()
838            .read_exact(source.cast(), &mut bytes)
839            .map_err(|_| Errno::EFAULT)?;
840        guest
841            .memory()
842            .write_exact(destination.cast(), &bytes)
843            .map_err(|_| Errno::EFAULT)?;
844    }
845    Ok(())
846}
847
848pub(super) fn ppoll_timeout_duration(timeout: Timespec) -> Result<Duration, Errno> {
849    let seconds = u64::try_from(timeout.tv_sec).map_err(|_| Errno::EINVAL)?;
850    let nanoseconds = u32::try_from(timeout.tv_nsec).map_err(|_| Errno::EINVAL)?;
851    if nanoseconds >= 1_000_000_000 {
852        return Err(Errno::EINVAL);
853    }
854    Ok(Duration::new(seconds, nanoseconds))
855}
856
857/// The start time and deadline of a wait for `timeout`, or neither without
858/// one. Observing the virtual clock asks the scheduler, which publishes the
859/// thread's signal state and can end its turn, so a call whose first mask probe
860/// may restart it (`restart_if_first_probe_not_run`) calls this after the
861/// probe. `LogicalTime + Duration` saturates, where a nanosecond count cast to
862/// u64 would wrap a 2^64 ns timeout to none at all (Claude review of
863/// https://github.com/rrnewton/hermit/pull/4235).
864async fn ppoll_start<G: Guest<Detcore<T>>, T: RecordOrReplay>(
865    guest: &mut G,
866    timeout: Option<Duration>,
867) -> (Option<LogicalTime>, Option<LogicalTime>) {
868    match timeout {
869        Some(timeout) => {
870            let started_at = thread_observe_time(guest).await;
871            (Some(started_at), Some(started_at + timeout))
872        }
873        None => (None, None),
874    }
875}
876
877/// The temporary mask `call` names, if any, read by a kernel-access probe;
878/// EINVAL for a size the kernel rejects. `first` says the read is the call's
879/// first injection, so a signal that stops it restarts the call
880/// (`restart_if_first_probe_not_run`).
881async fn read_ppoll_mask<G: Guest<Detcore<T>>, T: RecordOrReplay>(
882    guest: &mut G,
883    call: syscalls::Ppoll,
884    first: bool,
885) -> Result<Option<KernelSigset>, Error> {
886    let Some(signal_mask) = call.sigmask() else {
887        return Ok(None);
888    };
889    if call.sigsetsize() != KERNEL_SIGSET_SIZE {
890        return Err(Errno::EINVAL.into());
891    }
892    let read = read_kernel_sigset(guest, signal_mask).await;
893    let signal_mask = if first {
894        crate::syscalls::signal::restart_if_first_probe_not_run(read)?
895    } else {
896        read?
897    };
898    super::signal_shadow::note_wait_mask(guest, signal_mask);
899    Ok(Some(signal_mask))
900}
901
902/// `select`'s timeout as Linux reads it. In Linux v7.1 `kern_select`
903/// (`fs/select.c`) first refuses a negative `tv_sec` or `tv_usec` with
904/// `EINVAL`, then passes `tv_sec + tv_usec / USEC_PER_SEC` seconds and
905/// `(tv_usec % USEC_PER_SEC) * NSEC_PER_USEC` nanoseconds to
906/// `poll_select_set_timeout`, which refuses only what `timespec64_valid`
907/// (`include/linux/time64.h`) refuses: so microseconds of a second or more
908/// fold into the seconds. (v6.12 has no separate sign check, so there a
909/// mixed-sign timeval such as `{2, -1000000}` normalizes to 1 s; v7.1 added
910/// the check because the signed sum could wrap a negative timeout into a
911/// valid one. This follows v7.1, the measured host behaviour.) A sum that
912/// would overflow the signed seconds is `EINVAL` here.
913#[cfg(target_arch = "x86_64")]
914pub(super) fn select_timeout_duration(timeout: libc::timeval) -> Result<Duration, Errno> {
915    if timeout.tv_sec < 0 || timeout.tv_usec < 0 {
916        return Err(Errno::EINVAL);
917    }
918    let seconds = timeout
919        .tv_sec
920        .checked_add(timeout.tv_usec / 1_000_000)
921        .ok_or(Errno::EINVAL)?;
922    let nanoseconds = (timeout.tv_usec % 1_000_000) * 1_000;
923    Ok(Duration::new(seconds as u64, nanoseconds as u32))
924}
925
926fn timespec_from_duration(duration: Duration) -> Timespec {
927    Timespec {
928        tv_sec: duration.as_secs() as libc::time_t,
929        tv_nsec: duration.subsec_nanos() as libc::c_long,
930    }
931}
932
933const SCM_TIMESTAMP_OLD: libc::c_int = 29;
934const SCM_TIMESTAMPNS_OLD: libc::c_int = 35;
935const SCM_TIMESTAMPING_OLD: libc::c_int = 37;
936const SCM_TIMESTAMP_NEW: libc::c_int = 63;
937const SCM_TIMESTAMPNS_NEW: libc::c_int = 64;
938const SCM_TIMESTAMPING_NEW: libc::c_int = 65;
939const MAX_CONTROL_BYTES: usize = 64 * 1024;
940
941#[derive(Clone, Copy)]
942enum SocketTimestampKind {
943    Timeval,
944    Timespec,
945    Timestamping,
946}
947
948#[derive(Clone, Copy)]
949struct SocketTimestampMessage {
950    data_offset: usize,
951    available_data_len: usize,
952    kind: SocketTimestampKind,
953}
954
955fn cmsg_align(length: usize) -> usize {
956    let alignment = std::mem::size_of::<usize>();
957    (length + alignment - 1) & !(alignment - 1)
958}
959
960fn read_control_value<T: Copy>(bytes: &[u8]) -> Option<T> {
961    if bytes.len() < std::mem::size_of::<T>() {
962        return None;
963    }
964    // SAFETY: the length check guarantees a complete T, and read_unaligned
965    // permits the control buffer's byte alignment.
966    Some(unsafe { bytes.as_ptr().cast::<T>().read_unaligned() })
967}
968
969fn write_control_value<T: Copy>(bytes: &mut [u8], value: T) -> bool {
970    if bytes.len() < std::mem::size_of::<T>() {
971        return false;
972    }
973    // SAFETY: the length check guarantees room for T, and write_unaligned
974    // permits the control buffer's byte alignment.
975    unsafe { bytes.as_mut_ptr().cast::<T>().write_unaligned(value) };
976    true
977}
978
979fn write_control_prefix<T: Copy>(bytes: &mut [u8], value: T) -> usize {
980    let value_len = std::mem::size_of::<T>();
981    let write_len = bytes.len().min(value_len);
982    // SAFETY: `value` is alive for this copy and the resulting byte view has
983    // exactly its initialized object representation.
984    let value_bytes =
985        unsafe { std::slice::from_raw_parts((&value as *const T).cast::<u8>(), value_len) };
986    bytes[..write_len].copy_from_slice(&value_bytes[..write_len]);
987    write_len
988}
989
990fn socket_timestamp_messages(control: &[u8]) -> Vec<SocketTimestampMessage> {
991    let header_len = cmsg_align(std::mem::size_of::<Cmsghdr>());
992    let mut messages = Vec::new();
993    let mut offset = 0usize;
994
995    while let Some(header_bytes) = control.get(offset..) {
996        let Some(header) = read_control_value::<Cmsghdr>(header_bytes) else {
997            break;
998        };
999        if header.cmsg_len < header_len {
1000            break;
1001        }
1002        let Some(end) = offset.checked_add(header.cmsg_len) else {
1003            break;
1004        };
1005
1006        if header.cmsg_level == libc::SOL_SOCKET {
1007            let data_offset = offset + header_len;
1008            let declared_data_len = header.cmsg_len - header_len;
1009            let available_data_len = control
1010                .len()
1011                .saturating_sub(data_offset)
1012                .min(declared_data_len);
1013            let kind = match header.cmsg_type {
1014                SCM_TIMESTAMP_OLD | SCM_TIMESTAMP_NEW => Some(SocketTimestampKind::Timeval),
1015                SCM_TIMESTAMPNS_OLD | SCM_TIMESTAMPNS_NEW => Some(SocketTimestampKind::Timespec),
1016                SCM_TIMESTAMPING_OLD | SCM_TIMESTAMPING_NEW => {
1017                    Some(SocketTimestampKind::Timestamping)
1018                }
1019                _ => None,
1020            };
1021            if let Some(kind) = kind {
1022                messages.push(SocketTimestampMessage {
1023                    data_offset,
1024                    available_data_len,
1025                    kind,
1026                });
1027            }
1028        }
1029
1030        if end > control.len() {
1031            break;
1032        }
1033
1034        let step = cmsg_align(header.cmsg_len);
1035        let Some(next) = offset.checked_add(step) else {
1036            break;
1037        };
1038        if next <= offset {
1039            break;
1040        }
1041        offset = next;
1042    }
1043    messages
1044}
1045
1046// AUTONOMOUS-BOT-IMPLEMENTED
1047// TODO-HUMAN-REVIEW(PR-901)
1048fn canonicalize_socket_timestamps(control: &mut [u8], now: LogicalTime) -> usize {
1049    let messages = socket_timestamp_messages(control);
1050    let timespec = libc::timespec {
1051        tv_sec: now.as_secs() as libc::time_t,
1052        tv_nsec: now.subsec_nanos() as libc::c_long,
1053    };
1054    let timeval = libc::timeval {
1055        tv_sec: timespec.tv_sec,
1056        tv_usec: (timespec.tv_nsec / 1_000) as libc::suseconds_t,
1057    };
1058
1059    for message in &messages {
1060        let available_end = message
1061            .data_offset
1062            .saturating_add(message.available_data_len)
1063            .min(control.len());
1064        let data = &mut control[message.data_offset..available_end];
1065        match message.kind {
1066            SocketTimestampKind::Timeval => {
1067                write_control_prefix(data, timeval);
1068            }
1069            SocketTimestampKind::Timespec => {
1070                write_control_prefix(data, timespec);
1071            }
1072            SocketTimestampKind::Timestamping => {
1073                let size = std::mem::size_of::<libc::timespec>();
1074                let zero = libc::timespec {
1075                    tv_sec: 0,
1076                    tv_nsec: 0,
1077                };
1078                for slot in 0..3 {
1079                    let start = slot * size;
1080                    if start >= data.len() {
1081                        break;
1082                    }
1083                    let end = (start + size).min(data.len());
1084                    let slot_bytes = &mut data[start..end];
1085                    if slot_bytes.len() != size {
1086                        slot_bytes.fill(0);
1087                        continue;
1088                    }
1089                    let original = read_control_value::<libc::timespec>(slot_bytes)
1090                        .expect("complete timestamping slot");
1091                    let replacement = if original.tv_sec == 0 && original.tv_nsec == 0 {
1092                        zero
1093                    } else {
1094                        timespec
1095                    };
1096                    let _ = write_control_value(slot_bytes, replacement);
1097                }
1098            }
1099        }
1100    }
1101    messages.len()
1102}
1103
1104fn sanitize_ppoll_signal_mask(mask: u64) -> u64 {
1105    let signal_bit = (reverie::PERF_EVENT_SIGNAL as usize) - 1;
1106    mask & !(1_u64 << signal_bit)
1107}
1108
1109/// The pollfd array a ppoll reads and writes, if it has one.
1110fn ppoll_fds_range(call: syscalls::Ppoll) -> Option<std::ops::Range<usize>> {
1111    call.fds().map(|fds| {
1112        let start = fds.as_raw();
1113        let len = usize::try_from(call.nfds())
1114            .unwrap_or(usize::MAX)
1115            .saturating_mul(std::mem::size_of::<libc::pollfd>());
1116        start..start.saturating_add(len)
1117    })
1118}
1119
1120/// Whether the kernel owns a ppoll's blocking wait: when threads are not
1121/// sequentialized, and for every record/replay call, masked or not, as for a
1122/// masked pselect6 (https://github.com/rrnewton/hermit/issues/4042). A masked
1123/// sequentialized run-mode wait still takes the probe path.
1124fn ppoll_uses_kernel_wait(sequentialize_threads: bool, recordreplay_modes: bool) -> bool {
1125    !sequentialize_threads || recordreplay_modes
1126}
1127
1128// Linux copies the outer wrapper before rejecting this private malformed
1129// timeout. Keep the operation and its closed purpose together: using this
1130// disposition with a valid timeout would bypass runtime mask interposition.
1131async fn validate_pselect6_wrapper<G, T>(
1132    guest: &mut G,
1133    argument: Addr<'_, libc::sigset_t>,
1134) -> Result<(), Error>
1135where
1136    T: reverie::Tool,
1137    G: Guest<T>,
1138{
1139    let mut stack = guest.stack().await;
1140    let validation_timeout = stack.reserve::<Timespec>();
1141    let _guard = stack.commit()?;
1142    guest.memory().write_value(
1143        validation_timeout,
1144        &Timespec {
1145            tv_sec: 0,
1146            tv_nsec: 1_000_000_000,
1147        },
1148    )?;
1149    let validation = syscalls::Pselect6::new()
1150        .with_nfds(0)
1151        .with_readfds(None)
1152        .with_writefds(None)
1153        .with_exceptfds(None)
1154        .with_timeout(Some(validation_timeout))
1155        .with_sigmask(Some(argument));
1156    match guest
1157        .inject_with_purpose(
1158            validation,
1159            reverie::InjectionPurpose::Pselect6WrapperValidation,
1160        )
1161        .await
1162    {
1163        Err(error) => match error.into_errno()? {
1164            Errno::EINVAL => Ok(()),
1165            // This is the call's first injection. A mechanism failure must not
1166            // be converted to a guest errno or a successful validation.
1167            errno => crate::syscalls::signal::restart_if_first_probe_not_run(Err(errno.into())),
1168        },
1169        // Success would mean the backend did not validate the probe.
1170        Ok(_) => Err(Errno::EIO.into()),
1171    }
1172}
1173
1174/// The result Detcore gives the guest for a `poll` it ran in the kernel. A
1175/// signal pending while the poll runs ends it with `ERESTART_RESTARTBLOCK`,
1176/// even with a zero timeout, which cannot wait (`do_poll` checks for a signal
1177/// before it honours the timeout), and with no handler to run the kernel
1178/// restarts it through `restart_syscall`. The poll returns the code Detcore's
1179/// own polls return for it instead (`NonblockableSyscall::kernel_restart_errno`).
1180/// A zero or infinite timeout keeps no deadline (`do_restart_poll` reruns the
1181/// poll as issued), so that code is `ERESTARTNOHAND`, which the kernel restarts
1182/// by running the call again; a positive timeout keeps Linux's own. A handler
1183/// turns either code into `EINTR`. Under a tracer a default-ignored `SIGCHLD`
1184/// is still queued, so a child that exits while its parent polls is such a
1185/// signal, and so is the backend's own timer signal under `--imprecise-timers`.
1186///
1187/// A poll that `keeps_record` (`RestartCall::KernelPoll`) always returns
1188/// `ERESTART_RESTARTBLOCK` here, even a restart whose time is up, and keeps
1189/// its record for `restart_syscall`; `kernel_poll_restart_code` then decides
1190/// the code the guest gets.
1191#[cfg(target_arch = "x86_64")]
1192fn kernel_poll_result(
1193    call: &syscalls::Poll,
1194    result: Result<i64, Error>,
1195    keeps_record: bool,
1196) -> Result<i64, Error> {
1197    if !poll_interrupted(&result) {
1198        result
1199    } else if keeps_record {
1200        Err(Errno::ERESTART_RESTARTBLOCK.into())
1201    } else {
1202        Err(call.kernel_restart_errno().into())
1203    }
1204}
1205
1206/// The code a `poll` with a positive timeout returns to the guest when a
1207/// signal stopped it before it ran (`before_run`,
1208/// `poll_stopped_before_it_ran`), from `kept`, the result
1209/// `kernel_poll_result` gave it. The backend holds such a signal for the
1210/// guest's resume, and passes a held signal on unreported when the guest's
1211/// call returns `ERESTART_RESTARTBLOCK`. Record then took no turn for its
1212/// delivery, while replay, which serves the poll from the recording without
1213/// running it, took one where the signal reached the guest at an ordinary
1214/// delivery stop, and refused at the next logged readmission. Such a poll
1215/// returns `ERESTARTNOHAND` instead, as a wait that holds a signal does
1216/// (`helpers::held_signal_restart_errno`), and still keeps its record. With no
1217/// handler to run the kernel then runs the guest's call again: a resumed
1218/// poll's `restart_syscall`, which finds the record, or the guest's own poll,
1219/// which never ran and starts its timeout again (Linux's restart would keep
1220/// the first end time; under `--no-sequentialize-threads` the end moves by
1221/// the delivery's latency). A handler turns either code into `EINTR`. A
1222/// signal that ends a poll that ran is not held: it reaches the guest at an
1223/// ordinary delivery stop, which the backend reports. Any other result passes
1224/// through.
1225///
1226/// Unlike `held_signal_restart_errno`, this does not know which signal the
1227/// backend holds, so a held `SIGSTOP` also gets `ERESTARTNOHAND`. That is
1228/// harmless: after the stop the kernel runs the guest's call again, as for
1229/// any other held signal above. A resumed poll's `restart_syscall` still finds
1230/// its record; a poll that never ran starts its timeout again, which loses
1231/// nothing under record and replay and, under `--no-sequentialize-threads`,
1232/// drops the end time Detcore read for it as described above. For a stop,
1233/// the guest's poll runs again only once the stop ends, so its end moves by
1234/// the whole length of the stop, not by a delivery's latency.
1235#[cfg(target_arch = "x86_64")]
1236fn kernel_poll_restart_code(kept: Result<i64, Error>, before_run: bool) -> Result<i64, Error> {
1237    match kept {
1238        Err(Error::Errno(Errno::ERESTART_RESTARTBLOCK)) if before_run => {
1239            Err(Errno::ERESTARTNOHAND.into())
1240        }
1241        kept => kept,
1242    }
1243}
1244
1245/// Whether a signal stopped a `poll` before it ran: the backend reports that
1246/// as `ERESTARTSYS` (`Guest::inject`), which Linux's poll never returns.
1247#[cfg(target_arch = "x86_64")]
1248fn poll_stopped_before_it_ran(raw: &Result<i64, Error>) -> bool {
1249    matches!(raw, Err(Error::Errno(Errno::ERESTARTSYS)))
1250}
1251
1252/// Whether a signal ended a `poll` Detcore ran in the kernel: Linux's own
1253/// `ERESTART_RESTARTBLOCK`, or `ERESTARTSYS` when the signal stopped the call
1254/// before it ran (`Guest::inject`). Linux's poll never returns `ERESTARTSYS`
1255/// (`do_poll` reports any signal as `-ERESTARTNOHAND`, which `poll` turns
1256/// into `ERESTART_RESTARTBLOCK`), so both are the same interruption, and
1257/// passing `ERESTARTSYS` on would let `SA_RESTART` rerun a poll that Linux
1258/// ends with `EINTR`, and rerun a `restart_syscall` that keeps no record.
1259#[cfg(target_arch = "x86_64")]
1260fn poll_interrupted(result: &Result<i64, Error>) -> bool {
1261    matches!(
1262        result,
1263        Err(Error::Errno(
1264            Errno::ERESTART_RESTARTBLOCK | Errno::ERESTARTSYS
1265        ))
1266    )
1267}
1268
1269/// Whether a `poll` with a nonzero timeout runs in the kernel: under record and
1270/// replay, whose replay cannot rely on the descriptors, and without
1271/// sequentialized threads. A plain or strict run (`--strict` refuses
1272/// `--no-sequentialize-threads`) emulates it on virtual time
1273/// (`handle_internal_poll`), so the host clock `kernel_poll_deadline` reads
1274/// never reaches such a run.
1275#[cfg(target_arch = "x86_64")]
1276fn polls_in_kernel(sequentialize_threads: bool, recordreplay_modes: bool) -> bool {
1277    !sequentialize_threads || recordreplay_modes
1278}
1279
1280/// The end of a positive timeout for a `poll` Detcore runs in the kernel
1281/// (`RestartCall::KernelPoll`), read when the poll is first issued; `None`
1282/// for a zero or infinite timeout, whose restart reruns the call
1283/// (`kernel_poll_result`). Under record and replay the end holds no instant
1284/// (`KernelPollDeadline`), so those modes read no host clock here.
1285#[cfg(target_arch = "x86_64")]
1286fn kernel_poll_deadline(
1287    call: &syscalls::Poll,
1288    recordreplay_modes: bool,
1289) -> Option<KernelPollDeadline> {
1290    call.restart_keeps_deadline().then(|| {
1291        KernelPollDeadline(
1292            (!recordreplay_modes)
1293                .then(|| Instant::now() + Duration::from_millis(call.timeout() as u64)),
1294        )
1295    })
1296}
1297
1298/// Whether `restart`, a `restart_syscall` at the interrupted poll's address,
1299/// still holds `call`'s pollfd array and length in its argument registers.
1300/// Linux's restart leaves the registers as the poll left them (it sets only
1301/// the syscall number and moves the instruction pointer back,
1302/// `arch_do_signal_or_restart`), so the kernel's restart block and the
1303/// registers describe the same poll.
1304#[cfg(target_arch = "x86_64")]
1305fn restart_names_poll(restart: &Syscall, call: &syscalls::Poll) -> bool {
1306    let (number, args) = restart.into_parts();
1307    number == Sysno::restart_syscall
1308        && args.arg0 == call.fds().map_or(0, |fds| fds.as_raw())
1309        && args.arg1 == call.nfds() as usize
1310}
1311
1312/// The timeout, in whole milliseconds rounded up, that a restarted kernel-run
1313/// `poll` waits from `now` until `end`: 0 once `end` has passed.
1314#[cfg(target_arch = "x86_64")]
1315fn remaining_poll_millis(now: Instant, end: Instant) -> i32 {
1316    let left = end.saturating_duration_since(now).as_nanos();
1317    i32::try_from(left.div_ceil(1_000_000)).unwrap_or(i32::MAX)
1318}
1319
1320impl<T: RecordOrReplay> Detcore<T> {
1321    /// poll syscall (MAYHANG)
1322    // TODO-HUMAN-REVIEW(PR-1023): Review zero-timeout poll scheduling across backends.
1323    #[cfg(target_arch = "x86_64")]
1324    pub async fn handle_poll<G: Guest<Self>>(
1325        &self,
1326        guest: &mut G,
1327
1328        call: syscalls::Poll,
1329    ) -> Result<i64, Error> {
1330        if self.cfg.sequentialize_threads && call.timeout() == 0 {
1331            // This cannot block, but still yield a scheduler turn so a polling thread cannot
1332            // monopolize the guest between preemptions.
1333            let yield_to_peer = self.cfg.backend.loopback_pollers_yield_to_peers
1334                && guest.thread_state().has_loopback_peer();
1335            resource_request(
1336                guest,
1337                zero_timeout_poll_request(guest.thread_state().dettid, yield_to_peer),
1338            )
1339            .await;
1340            let result = if self.cfg.recordreplay_modes {
1341                self.record_or_replay(guest, call).await
1342            } else {
1343                // A virtual timerfd among the fds is readable exactly when its
1344                // deadline has passed (`crate::syscalls::timerfd`).
1345                crate::syscalls::timerfd::publish_expired(guest).await;
1346                guest.inject(call).await
1347            };
1348            kernel_poll_result(&call, result.map_err(Error::from), false)
1349        } else if polls_in_kernel(self.cfg.sequentialize_threads, self.cfg.recordreplay_modes) {
1350            // In replay mode, we cannot assume the existence of FILES during replay.
1351            // Thus we must record the poll and replay it from the trace.
1352            let deadline = kernel_poll_deadline(&call, self.cfg.recordreplay_modes);
1353            Ok(self.handle_external_poll(guest, call, deadline).await?)
1354        } else {
1355            // TODO:
1356            // if is-external-poll { self.handle_external_poll(guest, call) }
1357            self.handle_internal_poll(guest, call).await
1358        }
1359    }
1360
1361    /// Record or replay a raw `select` or `pselect6` the way `poll` is: a zero
1362    /// timeout cannot block and takes an ordinary scheduler turn; any other call
1363    /// may wait in the kernel, so it runs as a blocking external operation. The
1364    /// recorder captures the descriptor sets and remaining time, so replay never
1365    /// asks the kernel about descriptors it did not recreate.
1366    #[cfg(target_arch = "x86_64")]
1367    async fn record_or_replay_select_family<G: Guest<Self>>(
1368        &self,
1369        guest: &mut G,
1370        call: Syscall,
1371        zero_timeout: bool,
1372    ) -> Result<i64, Error> {
1373        if zero_timeout {
1374            resource_request(guest, Resources::new(guest.thread_state().dettid)).await;
1375            Ok(self.record_or_replay(guest, call).await?)
1376        } else {
1377            self.record_or_replay_shielded_wait(guest, call).await
1378        }
1379    }
1380
1381    /// Refuses a large-nfds pselect6 with a temporary mask, or a record/replay
1382    /// one that may wait (`record_or_replay_pselect6_under_a_private_mask`),
1383    /// whose private copy has nowhere to go: the stack has no writable scratch below its red zone,
1384    /// and every red-zone window overlaps one of the call's own buffers
1385    /// (`stage_private_cells`). Forwarding the guest's wrapper instead
1386    /// would let a sibling change the mask the kernel installs after the
1387    /// scheduler has recorded the one it waits for, so the call could time out
1388    /// with a wrong answer (Codex re-check #4 of
1389    /// https://github.com/rrnewton/hermit/pull/4053). A fail-closed run stops
1390    /// with the policy refusal and this message. A run started with
1391    /// `--allow-unsupported-syscalls` runs on: the call keeps the guest's
1392    /// wrapper, whose mask a sibling may still change, so the scheduler records
1393    /// its sleeping mask as unknown (`Resources::blocked_signal_mask_unknown`),
1394    /// not the thread's own, and arms no entry barrier for it (Codex re-check
1395    /// #5).
1396    async fn refuse_unstageable_pselect6_mask<G: Guest<Self>>(
1397        &self,
1398        guest: &mut G,
1399        call: syscalls::Pselect6,
1400    ) -> Result<i64, Error> {
1401        use std::io::Write as _;
1402        let message = "unsupported: pselect6 with a temporary signal mask, on a stack with \
1403            no room below its red zone whose red zone the call's own buffers fill: Hermit \
1404            has nowhere to put the private copy of the mask the kernel must install \
1405            (https://github.com/rrnewton/hermit/pull/4053)";
1406        tracing::error!("[tid {}] {message}", guest.tid());
1407        if self.cfg.panic_on_unsupported_syscalls {
1408            let _ = writeln!(crate::util::RetryingStderr, "{message}");
1409            return self
1410                .refuse_unserviceable_operation(guest, Sysno::pselect6, Errno::EFAULT)
1411                .await;
1412        }
1413        self.record_or_replay_blocking_with_unknown_mask(guest, Syscall::Pselect6(call))
1414            .await
1415    }
1416
1417    /// Refuses a record/replay pselect6 with a temporary mask on a stack less
1418    /// than 24 bytes above unwritable memory, where replay could not deliver a
1419    /// signal that interrupts it (`replayed_pselect6_delivery_fits`). Recording
1420    /// and replay refuse it alike. A fail-closed run stops with the policy
1421    /// refusal and this message, whose result this returns. A run started
1422    /// with `--allow-unsupported-syscalls` records the call as before, and a
1423    /// replay of an interrupted one stops at the next syscall; this returns
1424    /// `None` for it.
1425    async fn refuse_undeliverable_pselect6_mask<G: Guest<Self>>(
1426        &self,
1427        guest: &mut G,
1428    ) -> Option<Result<i64, Error>> {
1429        use std::io::Write as _;
1430        let message = "unsupported: pselect6 with a temporary signal mask on a stack less than \
1431            24 bytes above unwritable memory: replay has nowhere to deliver a signal that \
1432            interrupts it (https://github.com/rrnewton/hermit/issues/4112)";
1433        tracing::error!("[tid {}] {message}", guest.tid());
1434        if !self.cfg.panic_on_unsupported_syscalls {
1435            return None;
1436        }
1437        let _ = writeln!(crate::util::RetryingStderr, "{message}");
1438        Some(
1439            self.refuse_unserviceable_operation(guest, Sysno::pselect6, Errno::EFAULT)
1440                .await,
1441        )
1442    }
1443
1444    /// Records or replays a masked pselect6 that may wait, under a private
1445    /// copy of the mask read at its entry (`mask`), staged clear of the fd
1446    /// sets and timeout the kernel reads and writes (`stage_private_cells`).
1447    /// The outer `Err` is a staging failure; a call with no room for the copy
1448    /// is refused (`refuse_unstageable_pselect6_mask`).
1449    async fn record_or_replay_pselect6_under_a_private_mask<G: Guest<Self>>(
1450        &self,
1451        guest: &mut G,
1452        call: syscalls::Pselect6,
1453        mask: KernelSigset,
1454    ) -> Result<Result<i64, Error>, Error> {
1455        let set_len = pselect6_fd_set_len(call.nfds()).unwrap_or(0);
1456        let live: Vec<std::ops::Range<usize>> = [
1457            call.readfds().map(|set| (set.as_raw(), set_len)),
1458            call.writefds().map(|set| (set.as_raw(), set_len)),
1459            call.exceptfds().map(|set| (set.as_raw(), set_len)),
1460            call.timeout()
1461                .map(|timeout| (timeout.as_raw(), std::mem::size_of::<Timespec>())),
1462        ]
1463        .into_iter()
1464        .flatten()
1465        .map(|(start, len)| start..start.saturating_add(len))
1466        .collect();
1467        let Some(staged) =
1468            stage_private_cells(guest, PrivateCells::Pselect6Mask(mask), &live).await?
1469        else {
1470            return Ok(self.refuse_unstageable_pselect6_mask(guest, call).await);
1471        };
1472        let result = self
1473            .record_or_replay_blocking_with_unknown_mask(
1474                guest,
1475                Syscall::Pselect6(call.with_sigmask(Addr::from_raw(staged.after_mask()))),
1476            )
1477            .await;
1478        staged.release(guest);
1479        Ok(result)
1480    }
1481
1482    /// pselect6 syscall (MAYHANG).
1483    // AUTONOMOUS-BOT-IMPLEMENTED
1484    // TODO-HUMAN-REVIEW(#686): Review scratch fd sets and scheduler polling.
1485    pub async fn handle_pselect6<G: Guest<Self>>(
1486        &self,
1487        guest: &mut G,
1488        call: syscalls::Pselect6,
1489    ) -> Result<i64, Error> {
1490        if self.cfg.recordreplay_modes {
1491            let zero_timeout = call.timeout().is_some_and(|timeout| {
1492                guest
1493                    .memory()
1494                    .read_value(timeout)
1495                    .is_ok_and(|timeout: Timespec| timeout.tv_sec == 0 && timeout.tv_nsec == 0)
1496            });
1497            // A call with a `{ sigmask, sigsetsize }` wrapper may sleep under a
1498            // temporary mask, and the scheduler must not take the thread's
1499            // ordinary mask for it: it would arm the release barrier for a
1500            // signal the call blocks and wait for a wake Linux never delivers
1501            // (https://github.com/rrnewton/hermit/issues/3963). Nor can this turn
1502            // read the mask the kernel will install: the wrapper may sit in
1503            // memory a tracer read cannot reach though the kernel can, and a
1504            // sibling may rewrite the mask before the kernel copies it. So the
1505            // scheduler records no mask for such a call
1506            // (`Resources::blocked_signal_mask_unknown`). Without a wrapper the
1507            // call sleeps under the thread's own mask, which the scheduler reads.
1508            // A call that may wait sleeps under a private copy of its mask,
1509            // read here, at its entry, where Linux copies it. Handing the kernel
1510            // the guest's wrapper let a sibling turn that ran after the grant
1511            // change the mask the kernel copied, at a point host timing chose,
1512            // and replay could not tell (https://github.com/rrnewton/hermit/issues/4095).
1513            // Replay reads the mask at the same point and delivers a recorded
1514            // interruption under it. A zero-timeout call holds its turn until
1515            // it returns, so no sibling runs before the kernel copies the mask,
1516            // and replay reads it once the call is granted (review of
1517            // https://github.com/rrnewton/hermit/pull/4084). A zero timeout still
1518            // installs the mask and ends the call with ERESTARTNOHAND for a
1519            // pending signal it unblocks
1520            // (https://github.com/rrnewton/hermit/issues/4054).
1521            //
1522            // The entry read is the call's first injection, which a signal can
1523            // stop and restart (`restart_if_first_probe_not_run`), so it comes
1524            // before the refusal check: that logs, and the restarted call would
1525            // log again.
1526            // `None` inside when the call names no mask, or one the kernel
1527            // rejects or the tracer cannot read, though the kernel can: such a
1528            // call keeps the guest's own wrapper.
1529            let entry_mask = if call.sigmask().is_some() && !zero_timeout {
1530                Some(entry_pselect6_temporary_mask(guest, call).await?)
1531            } else {
1532                None
1533            };
1534            if !replayed_pselect6_delivery_fits(guest, call).await
1535                && let Some(refused) = self.refuse_undeliverable_pselect6_mask(guest).await
1536            {
1537                return refused;
1538            }
1539            let read_mask = self.cfg.replaying && call.sigmask().is_some();
1540            let mut temporary_mask = None;
1541            let result = if let Some(entry_mask) = entry_mask {
1542                if read_mask {
1543                    temporary_mask = entry_mask;
1544                }
1545                match entry_mask {
1546                    Some(mask) => {
1547                        self.record_or_replay_pselect6_under_a_private_mask(guest, call, mask)
1548                            .await?
1549                    }
1550                    // A wrapper that names no mask leaves the thread's own
1551                    // mask in place, which the scheduler reads and the shield
1552                    // covers. The kernel is handed no wrapper, which Linux
1553                    // takes alike, so a sibling cannot write a mask into the
1554                    // guest's before the kernel reads it.
1555                    None if pselect6_names_no_mask(guest, call) => {
1556                        self.record_or_replay_shielded_wait(
1557                            guest,
1558                            Syscall::Pselect6(call.with_sigmask(None)),
1559                        )
1560                        .await
1561                    }
1562                    None => {
1563                        self.record_or_replay_blocking_with_unknown_mask(
1564                            guest,
1565                            Syscall::Pselect6(call),
1566                        )
1567                        .await
1568                    }
1569                }
1570            } else if zero_timeout {
1571                resource_request(guest, Resources::new(guest.thread_state().dettid)).await;
1572                if read_mask {
1573                    temporary_mask = replayed_pselect6_temporary_mask(guest, call);
1574                }
1575                self.record_or_replay(guest, Syscall::Pselect6(call))
1576                    .await
1577                    .map_err(Into::into)
1578            } else {
1579                self.record_or_replay_shielded_wait(guest, Syscall::Pselect6(call))
1580                    .await
1581            };
1582            if let Some(mask) = temporary_mask
1583                && matches!(result, Err(Error::Errno(Errno::ERESTARTNOHAND)))
1584            {
1585                end_replayed_pselect6_under_its_temporary_mask(guest, mask).await;
1586            }
1587            return result;
1588        }
1589        if !self.cfg.sequentialize_threads {
1590            // A call with a mask waits under it; one that names none is
1591            // shielded as select is, and the kernel is handed no wrapper (as
1592            // under record), since a sibling runs concurrently here.
1593            if !pselect6_names_no_mask(guest, call) {
1594                return Ok(guest.inject(call).await?);
1595            }
1596            let may_wait = !call.timeout().is_some_and(|timeout| {
1597                guest
1598                    .memory()
1599                    .read_value(timeout)
1600                    .is_ok_and(|timeout: Timespec| timeout.tv_sec == 0 && timeout.tv_nsec == 0)
1601            });
1602            return self
1603                .inject_shielded_wait(guest, call.with_sigmask(None), may_wait)
1604                .await;
1605        }
1606        // Under the in-guest delivery gate (A5 step 3c.1d) the forms follow
1607        // design D39 and D50, and no probe carries the guest's temporary mask
1608        // (IR-9): `gated_pselect6`.
1609        if self.cfg.in_guest_signal_delivery {
1610            return Box::pin(self.gated_pselect6(guest, call)).await;
1611        }
1612
1613        if call.nfds() < 0 {
1614            return Ok(guest.inject(call).await?);
1615        }
1616
1617        // Linux copies pselect6's outer { sigmask, sigsetsize } wrapper, then
1618        // validates the timeout, then reads the inner mask (fs/select.c,
1619        // https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/fs/select.c?h=v6.12#n800
1620        // and #n752). Linux checks the wrapper here; its words are read only
1621        // after the timeout, because a tracer read can fail where the kernel's
1622        // copy succeeds: a write-only page is readable on x86, but not to
1623        // process_vm_readv. Read first, a malformed timeout there returned
1624        // EFAULT, not Linux's EINVAL (dh-obsc8).
1625        let sigmask_wrapper = match call.sigmask() {
1626            Some(argument) => {
1627                // A split read can fall back to PTRACE_PEEKDATA for the final
1628                // word, bypassing PROT_NONE or reporting EIO for an unmapped
1629                // page. Have Linux validate both wrapper words first. It copies
1630                // this wrapper before rejecting a malformed timeout, without
1631                // reading the inner mask, changing it, waiting, or writing output.
1632                validate_pselect6_wrapper::<_, Self>(guest, argument).await?;
1633                Some(argument)
1634            }
1635            None => None,
1636        };
1637        let raw_timeout = match call.timeout() {
1638            Some(timeout) => {
1639                let timeout: Timespec = guest.memory().read_value(timeout)?;
1640                Some(timeout)
1641            }
1642            None => None,
1643        };
1644        let timeout = raw_timeout.map(ppoll_timeout_duration).transpose()?;
1645        // Word by word, as `read_kernel_sigset` reads a mask: the probe above
1646        // showed Linux can copy both words, and on ptrace a word-sized read
1647        // is PTRACE_PEEKDATA, which reaches a write-only page that
1648        // process_vm_readv refuses. Backends that read through
1649        // process_vm_readv at every size (DBT, SaBRe, LiteInst and
1650        // in-guest-trap) still fail there with EFAULT, as does the timeout's
1651        // own read on every tracer-read backend
1652        // (https://github.com/rrnewton/hermit/issues/4530).
1653        let sigmask_argument = match sigmask_wrapper {
1654            Some(argument) => {
1655                let sigmask: usize = guest.memory().read_value(argument.cast::<usize>())?;
1656                let second = argument
1657                    .as_raw()
1658                    .checked_add(std::mem::size_of::<usize>())
1659                    .and_then(Addr::<usize>::from_raw)
1660                    .ok_or(Errno::EFAULT)?;
1661                let sigsetsize: usize = guest.memory().read_value(second)?;
1662                Some(Pselect6SigmaskArg {
1663                    sigmask,
1664                    sigsetsize,
1665                })
1666            }
1667            None => None,
1668        };
1669        if let Some(argument) = sigmask_argument
1670            && argument.sigmask != 0
1671        {
1672            // After the wrapper's validation: not the call's first injection.
1673            self.observe_under_temporary_mask(
1674                guest,
1675                Addr::<libc::sigset_t>::from_raw(argument.sigmask),
1676                argument.sigsetsize,
1677                false,
1678            )
1679            .await?;
1680        }
1681        if timeout == Some(Duration::ZERO) {
1682            // A virtual timerfd among the sets is readable exactly when its
1683            // deadline has passed (`crate::syscalls::timerfd`).
1684            crate::syscalls::timerfd::publish_expired(guest).await;
1685            let result = guest.inject(call).await;
1686            // A backend that runs the call itself answers ENOSYS for a pending
1687            // signal the mask lets in that it cannot act on inside the call
1688            // (`settle_masked_pselect6_enosys`).
1689            if result == Err(Errno::ENOSYS)
1690                && let Some(argument) = sigmask_argument
1691                && argument.sigmask != 0
1692                && argument.sigsetsize == KERNEL_SIGSET_SIZE
1693                && let Some(mask_addr) = Addr::<libc::sigset_t>::from_raw(argument.sigmask)
1694            {
1695                let mask = read_kernel_sigset(guest, mask_addr).await?;
1696                return self
1697                    .settle_masked_pselect6_enosys(guest, call, sanitize_ppoll_signal_mask(mask))
1698                    .await;
1699            }
1700            return Ok(result?);
1701        }
1702
1703        // Linux clamps raw fd-set copies to the process fd table's current max_fds.
1704        // Its initial table holds one machine word; larger nfds values can therefore
1705        // require fewer bytes than a userspace calculation predicts. Keep those calls
1706        // under kernel ownership rather than over-reading the guest bitmap.
1707        if call.nfds() > PSELECT6_INTERNAL_MAX_NFDS {
1708            // The kernel sleeps under the inner mask, so record it for the
1709            // scheduler's SIGCHLD target choice (`Resources::blocked_signal_mask`).
1710            // A mask the kernel would reject (wrong size, or a pointer it cannot
1711            // read) fails the real call before it sleeps or installs anything, so
1712            // the thread's own mask is the right record there (`None`).
1713            let snapshot = match sigmask_argument {
1714                Some(argument)
1715                    if argument.sigmask != 0 && argument.sigsetsize == KERNEL_SIGSET_SIZE =>
1716                {
1717                    match Addr::<libc::sigset_t>::from_raw(argument.sigmask) {
1718                        Some(mask_addr) => {
1719                            let mask = read_kernel_sigset(guest, mask_addr).await.ok();
1720                            if let Some(mask) = mask {
1721                                super::signal_shadow::note_wait_mask(guest, mask);
1722                            }
1723                            mask
1724                        }
1725                        None => None,
1726                    }
1727                }
1728                _ => None,
1729            };
1730            let Some(mask) = snapshot else {
1731                if sigmask_argument.is_none_or(|argument| argument.sigmask == 0) {
1732                    // No mask, read in this turn: hand the kernel no wrapper,
1733                    // as record does.
1734                    return self
1735                        .record_or_replay_shielded_wait(
1736                            guest,
1737                            Syscall::Pselect6(call.with_sigmask(None)),
1738                        )
1739                        .await;
1740                }
1741                return self
1742                    .record_or_replay_blocking_with_mask(guest, Syscall::Pselect6(call), None)
1743                    .await;
1744            };
1745            // The call sleeps under a private copy of the mask read here, not the
1746            // guest's wrapper: a sibling may rewrite the guest's buffer before the
1747            // released call reaches the kernel, and the scheduler waits for the
1748            // kernel to install exactly this mask (`background_entry_barriers`,
1749            // Codex review of https://github.com/rrnewton/hermit/pull/4053). The
1750            // wrapper and the mask were validated above, in Linux's order. The
1751            // copy stays readable until the call has rejoined.
1752            // The buffers Linux reads and writes, which the copy must not
1753            // overlap. The fd sets span at most their nominal length; Linux
1754            // may copy less.
1755            let set_len = pselect6_fd_set_len(call.nfds())?;
1756            let live: Vec<std::ops::Range<usize>> = [
1757                call.readfds().map(|set| (set.as_raw(), set_len)),
1758                call.writefds().map(|set| (set.as_raw(), set_len)),
1759                call.exceptfds().map(|set| (set.as_raw(), set_len)),
1760                call.timeout()
1761                    .map(|timeout| (timeout.as_raw(), std::mem::size_of::<Timespec>())),
1762            ]
1763            .into_iter()
1764            .flatten()
1765            .map(|(start, len)| start..start.saturating_add(len))
1766            .collect();
1767            let Some(staged) =
1768                stage_private_cells(guest, PrivateCells::Pselect6Mask(mask), &live).await?
1769            else {
1770                return self.refuse_unstageable_pselect6_mask(guest, call).await;
1771            };
1772            let result = self
1773                .record_or_replay_blocking_with_mask(
1774                    guest,
1775                    Syscall::Pselect6(call.with_sigmask(Addr::from_raw(staged.after_mask()))),
1776                    Some(kernel_installed_signal_mask(mask)),
1777                )
1778                .await;
1779            staged.release(guest);
1780            // Linux's pselect6 never fails with ENOSYS. A backend that runs
1781            // the call itself answers it for more descriptors than it serves,
1782            // which it declines before it looks at any signal: refused by name,
1783            // as the zero-timeout call is (`settle_masked_pselect6_enosys`),
1784            // never handed to the guest.
1785            if let Err(Error::Errno(Errno::ENOSYS)) = result {
1786                let reason = masked_pselect6_refusal_reason_now(guest, mask, call.nfds());
1787                return self
1788                    .refuse_masked_wait_because(guest, Sysno::pselect6, reason)
1789                    .await;
1790            }
1791            return result;
1792        }
1793
1794        // Linux wraps pselect6's temporary mask in { pointer, size }. Glibc supplies
1795        // the wrapper even when the inner pointer is null. A real mask must stay in
1796        // effect for the whole wait so an unblocked signal (make's jobserver unblocks
1797        // SIGCHLD) can interrupt it. Previously that forced the external-blocking path,
1798        // whose completion timing is host-decided and is a source of `make -jN`
1799        // execution-log divergence. With SIGCHLD admission now deterministic (scheduler
1800        // `sigchld_deferred`/`sigchld_ready`), honor the mask on each deterministic poll
1801        // probe instead: a pending unblocked signal is observed at a scheduler-decided
1802        // probe point rather than at host signal-arrival time.
1803        let sigmask = if let Some(argument) = sigmask_argument {
1804            if argument.sigmask != 0 {
1805                if argument.sigsetsize != KERNEL_SIGSET_SIZE {
1806                    return Err(Errno::EINVAL.into());
1807                }
1808                let mask_addr =
1809                    Addr::<libc::sigset_t>::from_raw(argument.sigmask).ok_or(Errno::EFAULT)?;
1810                let mask = read_kernel_sigset(guest, mask_addr).await?;
1811                super::signal_shadow::note_wait_mask(guest, mask);
1812                Some(sanitize_ppoll_signal_mask(mask))
1813            } else {
1814                None
1815            }
1816        } else {
1817            None
1818        };
1819        // The inner mask was snapshotted above. Do not let later guest mutations of the
1820        // outer wrapper change the meaning of a retry probe.
1821        let call = call.with_sigmask(None);
1822
1823        self.handle_internal_pselect6(guest, call, timeout, sigmask)
1824            .await
1825    }
1826
1827    async fn handle_internal_pselect6<G: Guest<Self>>(
1828        &self,
1829        guest: &mut G,
1830        call: syscalls::Pselect6,
1831        timeout: Option<Duration>,
1832        sigmask: Option<u64>,
1833    ) -> Result<i64, Error> {
1834        let len = pselect6_fd_set_len(call.nfds())?;
1835        // Under the in-guest delivery gate (A5 step 3c.1d, D50 reader (3)):
1836        // no probe carries the temporary mask (IR-9); each turn that finds
1837        // nothing ready decides from the thread's `/proc` status under the
1838        // temporary mask (`temporary_mask_check`), or under the guest's own
1839        // mask without one (`gated_wait_ends`); a masked call's first probe
1840        // and its check run in the call's own turn, before any request, so
1841        // that check sees exactly the instances pending at entry (IR-7,
1842        // round-1 ruling 2), and a result that first probe finds is returned
1843        // after one yielding empty request
1844        // (`temporary_mask_forms::yield_after_in_turn_result`, round-2 ruling
1845        // 5); the request publishes the temporary mask; and a set Detcore
1846        // cannot read is EFAULT only where Linux's copy faults
1847        // (`temporary_mask_forms::read_gated_fd_sets`).
1848        let gated = self.cfg.in_guest_signal_delivery;
1849        // A masked call under the gate observes the clock only after its first
1850        // probe and check (round-3 ruling 1,
1851        // `temporary_mask_forms::observe_deadline`): `deferred` holds the
1852        // timeout until then. Gate off, as before.
1853        let in_turn_first = gated && sigmask.is_some();
1854        let deferred = if in_turn_first { timeout } else { None };
1855        let deadline = match timeout {
1856            Some(timeout) if !in_turn_first => Some(thread_observe_time(guest).await + timeout),
1857            _ => None,
1858        };
1859        // Gate off, the three sets are read into locals as before the gate,
1860        // with no allocation (round-2 ruling 1).
1861        let (original_readfds, original_writefds, original_exceptfds) = if gated {
1862            // Boxed: the gate-off future stays its size.
1863            match Box::pin(temporary_mask_forms::read_gated_fd_sets(
1864                guest, "pselect6", call, len,
1865            ))
1866            .await
1867            {
1868                Ok(sets) => sets,
1869                Err(error) => {
1870                    let deadline = match deferred {
1871                        Some(_) => temporary_mask_forms::observe_deadline(guest, deferred)
1872                            .await
1873                            .map(|(_, deadline)| deadline),
1874                        None => deadline,
1875                    };
1876                    self.write_pselect6_remaining(guest, call, deadline).await?;
1877                    return Err(error);
1878                }
1879            }
1880        } else {
1881            let original_readfds = match read_pselect6_fd_set(guest, call.readfds(), len) {
1882                Ok(value) => value,
1883                Err(error) => {
1884                    self.write_pselect6_remaining(guest, call, deadline).await?;
1885                    return Err(error);
1886                }
1887            };
1888            let original_writefds = match read_pselect6_fd_set(guest, call.writefds(), len) {
1889                Ok(value) => value,
1890                Err(error) => {
1891                    self.write_pselect6_remaining(guest, call, deadline).await?;
1892                    return Err(error);
1893                }
1894            };
1895            let original_exceptfds = match read_pselect6_fd_set(guest, call.exceptfds(), len) {
1896                Ok(value) => value,
1897                Err(error) => {
1898                    self.write_pselect6_remaining(guest, call, deadline).await?;
1899                    return Err(error);
1900                }
1901            };
1902            (original_readfds, original_writefds, original_exceptfds)
1903        };
1904
1905        let mut stack = guest.stack().await;
1906        let readfds = call.readfds().map(|_| stack.reserve::<libc::fd_set>());
1907        let writefds = call.writefds().map(|_| stack.reserve::<libc::fd_set>());
1908        let exceptfds = call.exceptfds().map(|_| stack.reserve::<libc::fd_set>());
1909        // pselect6's timeout is a writable in-out kernel timespec, so the probe
1910        // needs a mutable scratch cell (re-zeroed each iteration below to keep
1911        // every probe a non-blocking poll).
1912        let probe_timeout = stack.reserve::<Timespec>();
1913        // Carry the temporary signal mask on every zero-timeout probe so the kernel
1914        // applies it atomically: a pending, mask-unblocked signal makes the probe return
1915        // EINTR at a deterministic scheduler point. The probe's wrapper points at scratch
1916        // memory the guard keeps alive across each injection.
1917        // On a backend whose kernel reports the guest's signal state, a
1918        // `KernelSignalWait` below decides interruption against the temporary
1919        // mask instead (`with_wait_mask`), and the probes carry no mask.
1920        let kernel_signal_wait = self.cfg.backend.supports_blocked_wait_signal_interruption;
1921        // The probes' mask cell and the mask it holds, kept to write back after
1922        // a removal in that cell (`remove_signals_discarded_under_a_temporary_mask_in`).
1923        let probe_mask_cell = sigmask
1924            .filter(|_| !kernel_signal_wait && !gated)
1925            .map(|mask| (stack.push(mask).as_raw(), mask));
1926        let probe_sigmask = probe_mask_cell.map(|(sigset, _)| {
1927            stack
1928                .push(Pselect6SigmaskArg {
1929                    sigmask: sigset,
1930                    sigsetsize: KERNEL_SIGSET_SIZE,
1931                })
1932                .cast()
1933        });
1934        // On a backend whose kernel reports the guest's signal state, only a
1935        // signal that would end the wait natively does; the mask cell lets the
1936        // wait block signals under the one scratch-stack guard
1937        // (https://github.com/rrnewton/hermit/issues/3146). A temporary mask is
1938        // the mask the call sleeps under, as Linux installs it for the whole
1939        // call: a signal it blocks never ends the call, and one it unblocks ends
1940        // it although the guest's own mask blocks it (`with_wait_mask`,
1941        // https://github.com/rrnewton/hermit/issues/3991). A restart resumes from
1942        // the remaining time written back below, so a default stop ends the wait
1943        // as on Linux. Unlike the other waits, it holds no signal until the call
1944        // returns, so a pending caught `SIGCHLD` ends it as on Linux, whoever
1945        // sent it, at a turn that host timing can choose: a known gap that this
1946        // wait had before (`KernelSignalWait::for_select`).
1947        let mut signals = kernel_signal_wait.then(|| {
1948            KernelSignalWait::for_select(guest)
1949                .with_wait_mask(sigmask.map(crate::syscalls::signal::kernel_installed_signal_mask))
1950        });
1951        // Set when a signal the temporary mask unblocks, pending in the kernel,
1952        // ends the call: the kernel must then deliver it under that mask (see
1953        // below).
1954        let mut deliver_under_temporary_mask = false;
1955        let mask_cell = signals.as_ref().map(|_| stack.reserve::<KernelSigset>());
1956        // This frame holds the scratch stack across every probe, so the
1957        // memory that makes an expired timerfd readable is reserved here
1958        // (`crate::syscalls::timerfd::publish_expired_in`).
1959        let timerfd_scratch = crate::syscalls::timerfd::reserve_scratch(&mut stack);
1960        let _guard = stack.commit()?;
1961        // The scheduler reads the effective mask while the wait parks
1962        // (`publish_wait_mask`).
1963        let mut published = PublishedWaitMask::default();
1964        if let Some(signals) = signals.as_ref() {
1965            publish_wait_mask(guest, signals.effective_mask(), &mut published).await;
1966        }
1967        let probe = call
1968            .with_readfds(readfds)
1969            .with_writefds(writefds)
1970            .with_exceptfds(exceptfds)
1971            .with_timeout(Some(probe_timeout))
1972            .with_sigmask(probe_sigmask);
1973
1974        let mut resources = Resources::new(guest.thread_state().dettid);
1975        resources.insert(ResourceID::InternalIOPolling, Permission::W);
1976        resources.fyi("pselect6");
1977        resources.poll_deadline = poll_deadline_for(guest, deadline);
1978        if gated {
1979            resources.blocked_signal_mask = sigmask;
1980        }
1981        // Keep the request metadata accurate, but do not make it eligible for
1982        // the scheduler's ERESTARTSYS wakeup. A cross-task signal must first be
1983        // checked against pselect6's snapshotted temporary mask and disposition.
1984        resources.set_signal_interrupt_errno(Errno::EINTR);
1985
1986        // A masked call's entry and end under the gate (A5 step 3c.1e A,
1987        // `temporary_mask_forms::TemporaryMaskCall`: IR-23 and D37's R2).
1988        // Boxed, and `None` with the gate off, so the gate-off future stays
1989        // its size.
1990        let mut masked: Option<Box<temporary_mask_forms::TemporaryMaskCall>> = None;
1991        // Every exit, including a `?` on a guest-memory write, must reach the mask
1992        // restore below, so the loop runs in its own async block.
1993        let result: Result<i64, Error> = {
1994            // Moved reborrows: a future holding a shared reference to a scratch
1995            // address would not be `Send`.
1996            let guest = &mut *guest;
1997            let signals = &mut signals;
1998            let resources = &mut resources;
1999            let published = &mut published;
2000            let deliver_under_temporary_mask = &mut deliver_under_temporary_mask;
2001            let masked = &mut masked;
2002            async move {
2003                // Under the gate a masked call's first probe and its check run
2004                // in the call's own turn (see `entry` above); an unmasked one,
2005                // which has no entry rule, requests before each probe as
2006                // `select` does (round-2 ruling 4).
2007                let mut first_turn = in_turn_first;
2008                let mut deadline = deadline;
2009                let mut deferred = deferred;
2010                // The at-entry set of the first check, from the entry before
2011                // the first timerfd publication (`TemporaryMaskCall::enter`),
2012                // and the deadline, observed after the first probe and check
2013                // (`observe_deadline`; round-3 ruling 1), or at the entry with
2014                // an armed virtual timerfd (IR-23(c)).
2015                if in_turn_first && let Some(mask) = sigmask {
2016                    let call = Box::pin(temporary_mask_forms::TemporaryMaskCall::enter(
2017                        guest, mask, deferred,
2018                    ))
2019                    .await?;
2020                    if call.observes_deadline_at_entry() {
2021                        deadline = call.entry_deadline().map(|(_, deadline)| deadline);
2022                        deferred = None;
2023                    }
2024                    *masked = Some(Box::new(call));
2025                }
2026                let mut entry = masked.as_deref().map(|call| call.entry());
2027                macro_rules! settle_deadline {
2028                    () => {
2029                        if deferred.is_some() {
2030                            deadline = Box::pin(temporary_mask_forms::observe_deadline(
2031                                guest,
2032                                deferred.take(),
2033                            ))
2034                            .await
2035                            .map(|(_, deadline)| deadline);
2036                        }
2037                    };
2038                }
2039                loop {
2040                    let in_turn = first_turn;
2041                    if !in_turn && let Some(call) = masked.as_deref_mut() {
2042                        call.note_request();
2043                    }
2044                    let signaled = !in_turn
2045                        && matches!(
2046                            resource_request(guest, resources.clone()).await,
2047                            ResumeStatus::Signaled(_)
2048                        );
2049                    first_turn = false;
2050                    // Virtual timerfds first (`crate::syscalls::timerfd`).
2051                    let next_timerfd =
2052                        crate::syscalls::timerfd::publish_expired_in(guest, Some(timerfd_scratch))
2053                            .await;
2054                    resources.poll_deadline =
2055                        crate::syscalls::timerfd::poll_deadline_with(guest, deadline, next_timerfd);
2056                    // A pending real-time timer expiration that this wait would
2057                    // be ended by, under the guest's mask or the call's temporary
2058                    // one (https://github.com/rrnewton/hermit/issues/4089).
2059                    let pending = pending_real_time_among(guest, REAL_TIME_SIGNALS).await;
2060                    if pending != 0 {
2061                        let (observable, discarded) = match (signals.as_ref(), sigmask) {
2062                            (Some(signals), _) => (signals.observable_now()?, 0),
2063                            (None, Some(mask)) => unblocked_by(
2064                                &read_thread_wait_signal_state(guest.pid(), guest.tid())?,
2065                                mask,
2066                            ),
2067                            (None, None) => (0, 0),
2068                        };
2069                        act_on_pending_real_time(guest, pending, observable, discarded).await;
2070                    }
2071                    // Every turn probes before it looks at pending signals, as Linux
2072                    // does: do_select polls every descriptor on each pass and returns
2073                    // the count of ready ones before it tests signal_pending, and at
2074                    // the start max_select_fd returns EBADF for a descriptor that is
2075                    // not open (`core_sys_select`). So a descriptor that becomes ready
2076                    // in a later turn in which a signal is also pending is reported
2077                    // (round-12 Medium on https://github.com/rrnewton/hermit/pull/3361).
2078                    // Each probe is a new select call, so a descriptor closed during
2079                    // the wait gets EBADF, where Linux reports it ready. Without the
2080                    // kernel's signal state, a scheduler `Signaled` answer still ends
2081                    // the call before the probe.
2082                    if signals.is_none() && signaled && !gated {
2083                        self.write_pselect6_remaining(guest, call, deadline).await?;
2084                        return Err(Errno::EINTR.into());
2085                    }
2086                    guest.memory().write_value(
2087                        probe_timeout,
2088                        &Timespec {
2089                            tv_sec: 0,
2090                            tv_nsec: 0,
2091                        },
2092                    )?;
2093                    write_pselect6_fd_set(guest, probe.readfds(), &original_readfds)?;
2094                    write_pselect6_fd_set(guest, probe.writefds(), &original_writefds)?;
2095                    write_pselect6_fd_set(guest, probe.exceptfds(), &original_exceptfds)?;
2096
2097                    let raw_result = match signals.as_mut() {
2098                        // A signal stop that does not end the wait is absorbed (see
2099                        // `KernelSignalWait`). Every probe runs before the turn's check
2100                        // (`inject_first_probe`): a signal that would end the wait and
2101                        // stops the probe is held, the probe runs again, and the signal
2102                        // is delivered as the call returns. Once `block` has blocked
2103                        // every blockable signal, a pending one cannot stop the probe.
2104                        Some(signals) => match signals.inject_first_probe(guest, probe).await {
2105                            Ok(result) => result,
2106                            Err(error) => {
2107                                self.write_pselect6_remaining(guest, call, deadline).await?;
2108                                break Err(error);
2109                            }
2110                        },
2111                        None => guest.inject(probe).await,
2112                    };
2113                    let mut result = pselect6_probe_result(raw_result);
2114                    // A backend that runs the call itself answers ENOSYS for a
2115                    // pending signal the mask lets in that it cannot act on, as
2116                    // it does for ppoll (`settle_masked_ppoll_enosys`): ignored
2117                    // ones alone are removed, in the probe's own cells, as
2118                    // Linux discards them, and the probe runs again with its
2119                    // cells as it read them. Anything else is refused by name.
2120                    if result == Err(Errno::ENOSYS)
2121                        && let Some((mask_cell, mask)) = probe_mask_cell
2122                    {
2123                        if let Some(set) = ignored_signals_let_in_now(guest, mask) {
2124                            crate::syscalls::helpers::remove_signals_discarded_under_a_temporary_mask_in(
2125                                guest,
2126                                set,
2127                                mask_cell,
2128                                probe_timeout,
2129                            )
2130                            .await?;
2131                            guest.memory().write_value(
2132                                AddrMut::<KernelSigset>::from_raw(mask_cell).ok_or(Errno::EFAULT)?,
2133                                &mask,
2134                            )?;
2135                            guest.memory().write_value(
2136                                probe_timeout,
2137                                &Timespec {
2138                                    tv_sec: 0,
2139                                    tv_nsec: 0,
2140                                },
2141                            )?;
2142                            write_pselect6_fd_set(guest, probe.readfds(), &original_readfds)?;
2143                            write_pselect6_fd_set(guest, probe.writefds(), &original_writefds)?;
2144                            write_pselect6_fd_set(guest, probe.exceptfds(), &original_exceptfds)?;
2145                            result = pselect6_probe_result(guest.inject(probe).await);
2146                        }
2147                        if result == Err(Errno::ENOSYS) {
2148                            self.write_pselect6_remaining(guest, call, deadline).await?;
2149                            let reason =
2150                                masked_pselect6_refusal_reason_now(guest, mask, call.nfds());
2151                            break self
2152                                .refuse_masked_wait_because(guest, Sysno::pselect6, reason)
2153                                .await;
2154                        }
2155                    }
2156                    if result != Ok(0) {
2157                        let copy_result = if result.is_ok() {
2158                            self.copy_pselect6_results(guest, probe, call, len)
2159                        } else {
2160                            Ok(())
2161                        };
2162                        settle_deadline!();
2163                        self.write_pselect6_remaining(guest, call, deadline).await?;
2164                        // D37's R2 as the result is decided, before the yield
2165                        // (A5 step 3c.1e A).
2166                        let decided: Result<i64, Error> = match &result {
2167                            Ok(count) => Ok(*count),
2168                            Err(errno) => Err((*errno).into()),
2169                        };
2170                        if let Some(call) = masked.as_deref_mut()
2171                            && let Err(error) = Box::pin(call.finish(guest, &decided)).await
2172                        {
2173                            break Err(error);
2174                        }
2175                        if in_turn {
2176                            // Boxed: the gate-off future stays its size.
2177                            Box::pin(temporary_mask_forms::yield_after_in_turn_result(guest)).await;
2178                        }
2179                        copy_result?;
2180                        break result.map_err(Into::into);
2181                    }
2182                    // The turn's check, after its probe found no descriptor ready and no
2183                    // error: only then does a pending signal end the call. A scheduler
2184                    // `Signaled` answer is only a hint here; the kernel's state decides.
2185                    // pselect6 returns ERESTARTNOHAND: EINTR after a handler, a restart
2186                    // with the remaining timeout after a stop.
2187                    if gated {
2188                        let ends = match sigmask {
2189                            Some(mask) => {
2190                                // Boxed: the gate-off future stays its size.
2191                                let check =
2192                                    Box::pin(crate::syscalls::helpers::temporary_mask_check(
2193                                        guest,
2194                                        mask,
2195                                        entry.take(),
2196                                        false,
2197                                    ))
2198                                    .await;
2199                                if let (Ok(check), Some(call)) = (&check, masked.as_deref_mut()) {
2200                                    call.note_check(*check);
2201                                }
2202                                match check {
2203                                    Ok(crate::syscalls::helpers::TemporaryMaskCheck::Waits) => None,
2204                                    Ok(crate::syscalls::helpers::TemporaryMaskCheck::Fatal) => {
2205                                        Some(Errno::ERESTARTNOHAND)
2206                                    }
2207                                    Ok(_) => Some(Errno::EINTR),
2208                                    Err(error) => break Err(error),
2209                                }
2210                            }
2211                            None => match crate::syscalls::helpers::gated_wait_ends(guest, None) {
2212                                Ok(true) => Some(Errno::ERESTARTNOHAND),
2213                                Ok(false) => None,
2214                                Err(error) => break Err(error),
2215                            },
2216                        };
2217                        settle_deadline!();
2218                        if let Some(errno) = ends {
2219                            self.write_pselect6_remaining(guest, call, deadline).await?;
2220                            break Err(errno.into());
2221                        }
2222                        resources.poll_deadline = crate::syscalls::timerfd::poll_deadline_with(
2223                            guest,
2224                            deadline,
2225                            next_timerfd,
2226                        );
2227                    }
2228                    if let Some(signals) = signals.as_ref() {
2229                        match signals.interrupted_with_state() {
2230                            Ok((false, _)) => {}
2231                            Ok((true, state)) => {
2232                                self.write_pselect6_remaining(guest, call, deadline).await?;
2233                                // A signal the backend holds, or one the guest's own
2234                                // mask leaves unblocked, is delivered as the call
2235                                // returns. One that only the temporary mask unblocks
2236                                // stays pending under the guest's own mask, and must be
2237                                // delivered under the temporary mask.
2238                                *deliver_under_temporary_mask = !signals.holds_signal()
2239                                    && signals.interrupts_only_under_wait_mask(&state);
2240                                break Err(Errno::ERESTARTNOHAND.into());
2241                            }
2242                            Err(error) => break Err(error),
2243                        }
2244                    }
2245                    if let Some(signals) = signals.as_mut()
2246                        && signals.needs_block()
2247                        && let Err(error) = signals.block(guest, mask_cell).await
2248                    {
2249                        self.write_pselect6_remaining(guest, call, deadline).await?;
2250                        break Err(error);
2251                    }
2252                    if let Some(signals) = signals.as_ref() {
2253                        publish_wait_mask(guest, signals.effective_mask(), published).await;
2254                    }
2255
2256                    resources.poll_attempt += 1;
2257                    if let Some(deadline) = deadline
2258                        && thread_observe_time(guest).await >= deadline
2259                    {
2260                        let copy_result = self.copy_pselect6_results(guest, probe, call, len);
2261                        self.write_pselect6_remaining(guest, call, Some(deadline))
2262                            .await?;
2263                        copy_result?;
2264                        break Ok(0);
2265                    }
2266                    trace!(
2267                        "Retry #{} for syscall due to result Ok(0): {}",
2268                        resources.poll_attempt,
2269                        probe.display(&guest.memory())
2270                    );
2271                    record_retry_event(guest, probe).await;
2272                }
2273            }
2274            .await
2275        };
2276        // D37's R2 at any other end of a masked call (A5 step 3c.1e A); a
2277        // no-op once it ran before a yield, and with the gate off.
2278        let result = match masked.as_deref_mut() {
2279            Some(call) => match Box::pin(call.finish(guest, &result)).await {
2280                Ok(()) => result,
2281                Err(error) => Err(error),
2282            },
2283            None => result,
2284        };
2285        let result = match &mut signals {
2286            Some(signals) => {
2287                let restored = signals.restore(guest, mask_cell).await;
2288                publish_wait_mask(guest, None, &mut published).await;
2289                result_after_restore(restored, result)?
2290            }
2291            None => result,
2292        };
2293        // Restoring the guest's mask cannot deliver that signal, which the mask
2294        // blocks, but check again that the backend holds nothing.
2295        if deliver_under_temporary_mask
2296            && !signals.as_ref().is_some_and(KernelSignalWait::holds_signal)
2297            && let Some(mask) = sigmask
2298            && matches!(result, Err(Error::Errno(Errno::ERESTARTNOHAND)))
2299        {
2300            // Linux delivers such a signal before it puts the guest's own mask
2301            // back, which may block it, and the handler's frame restores the
2302            // guest's mask when it returns. Only the kernel can do both, so the
2303            // thread makes one more call, a ppoll with no descriptors, a zero
2304            // timeout and the call's temporary mask, as replay delivers a
2305            // recorded call's signal (`inject_signal_delivery`): the kernel
2306            // installs the mask, finds the signal pending, returns
2307            // ERESTARTNOHAND and delivers it as the call returns, EINTR after a
2308            // handler (https://github.com/rrnewton/hermit/issues/3991).
2309            // - The mask is a private copy of the one this call read at entry
2310            //   (`sigmask`), as Linux copies it once at entry; the guest's
2311            //   buffer, which a sibling may have rewritten since, is not read
2312            //   again (review of https://github.com/rrnewton/hermit/pull/4051).
2313            // - A zero timeout never sleeps, and Linux writes no remaining time
2314            //   back for it; the guest's timeout already holds the remaining
2315            //   time written above.
2316            // - A plain injection, not a tail injection, so the rest of this
2317            //   handler and Detcore's post-syscall work still run.
2318            drop(_guard);
2319            // No room for the cells cannot happen in practice: this call's
2320            // probe cells, at least as large, were committed in the same stack
2321            // scratch above. Should it happen, the signal stays pending under
2322            // the guest's own mask and the call returns EINTR without its
2323            // handler run, as before this delivery existed.
2324            match stage_private_cells(guest, PrivateCells::MaskAndZeroTimeout(mask), &[]).await? {
2325                Some(staged) => {
2326                    inject_signal_delivery(guest, staged.mask(), staged.after_mask()).await;
2327                    staged.release(guest);
2328                }
2329                None => tracing::warn!(
2330                    "[tid {}] no room to deliver a pselect6 signal under its temporary mask; \
2331                     its handler runs only once the guest unblocks it",
2332                    guest.tid()
2333                ),
2334            }
2335        }
2336        refuse_held_signal_loss(guest, Sysno::pselect6, result).await
2337    }
2338
2339    fn copy_pselect6_results<G: Guest<Self>>(
2340        &self,
2341        guest: &mut G,
2342        probe: syscalls::Pselect6,
2343        call: syscalls::Pselect6,
2344        len: usize,
2345    ) -> Result<(), Error> {
2346        copy_pselect6_fd_set(guest, probe.readfds(), call.readfds(), len)?;
2347        copy_pselect6_fd_set(guest, probe.writefds(), call.writefds(), len)?;
2348        copy_pselect6_fd_set(guest, probe.exceptfds(), call.exceptfds(), len)
2349    }
2350
2351    pub(super) async fn write_pselect6_remaining<G: Guest<Self>>(
2352        &self,
2353        guest: &mut G,
2354        call: syscalls::Pselect6,
2355        deadline: Option<LogicalTime>,
2356    ) -> Result<(), Error> {
2357        if let (Some(timeout), Some(deadline)) = (call.timeout(), deadline) {
2358            let now = thread_observe_time(guest).await;
2359            let remaining = deadline.as_nanos().saturating_sub(now.as_nanos());
2360            let remaining = Timespec {
2361                tv_sec: (remaining / 1_000_000_000) as libc::time_t,
2362                tv_nsec: (remaining % 1_000_000_000) as libc::c_long,
2363            };
2364            // pselect6's timeout is a writable in-out kernel timespec; reverie-syscalls
2365            // now types it as `AddrMut<Timespec>`, so the remaining time can be written
2366            // back directly without an unsafe pointer cast.
2367            if let Err(error) = guest.memory().write_value(timeout, &remaining) {
2368                // Linux preserves the pselect6 result when remaining-time copyout faults.
2369                trace!(?error, "ignoring pselect6 timeout writeback failure");
2370            }
2371        }
2372        Ok(())
2373    }
2374
2375    /// select syscall (MAYHANG).
2376    ///
2377    /// `select` is the classic `timeval` sibling of `pselect6` (which is already
2378    /// Determinized). It reuses the pselect6 fd-set scratch machinery, but takes
2379    /// a `struct timeval` timeout (which Linux updates in place with the time not
2380    /// slept) and carries no signal mask.
2381    // AUTONOMOUS-BOT-IMPLEMENTED
2382    // TODO-HUMAN-REVIEW(#800): Review select determinization mirroring pselect6.
2383    #[cfg(target_arch = "x86_64")]
2384    pub async fn handle_select<G: Guest<Self>>(
2385        &self,
2386        guest: &mut G,
2387        call: syscalls::Select,
2388    ) -> Result<i64, Error> {
2389        if self.cfg.recordreplay_modes {
2390            let zero_timeout = call.timeout().is_some_and(|timeout| {
2391                guest
2392                    .memory()
2393                    .read_value(timeout)
2394                    .is_ok_and(|timeout: libc::timeval| timeout.tv_sec == 0 && timeout.tv_usec == 0)
2395            });
2396            return self
2397                .record_or_replay_select_family(guest, Syscall::Select(call), zero_timeout)
2398                .await;
2399        }
2400        if !self.cfg.sequentialize_threads {
2401            // A zero timeout is not shielded, as it is not under record: a
2402            // pending signal ends such a call with ERESTARTNOHAND and Linux
2403            // runs it again in place, which the guest does not see, and every
2404            // probe would pay for two mask changes. A timeout the tracer
2405            // cannot read is shielded.
2406            let may_wait = !call.timeout().is_some_and(|timeout| {
2407                guest
2408                    .memory()
2409                    .read_value(timeout)
2410                    .is_ok_and(|timeout: libc::timeval| timeout.tv_sec == 0 && timeout.tv_usec == 0)
2411            });
2412            return self.inject_shielded_wait(guest, call, may_wait).await;
2413        }
2414
2415        if call.nfds() < 0 {
2416            return Ok(guest.inject(call).await?);
2417        }
2418
2419        let raw_timeout = match call.timeout() {
2420            Some(timeout) => {
2421                let timeout: libc::timeval = guest.memory().read_value(timeout)?;
2422                Some(timeout)
2423            }
2424            None => None,
2425        };
2426        if matches!(raw_timeout, Some(timeout) if timeout.tv_sec == 0 && timeout.tv_usec == 0) {
2427            // A zero timeout is a pure non-blocking poll; the kernel can service it directly,
2428            // once a virtual timerfd among the sets is readable exactly when its deadline
2429            // has passed (`crate::syscalls::timerfd`).
2430            crate::syscalls::timerfd::publish_expired(guest).await;
2431            return Ok(guest.inject(call).await?);
2432        }
2433
2434        // Mirror pselect6: keep large fd tables under kernel ownership rather than
2435        // over-reading the guest bitmap (Linux clamps raw fd-set copies to max_fds).
2436        if call.nfds() > PSELECT6_INTERNAL_MAX_NFDS {
2437            return self
2438                .record_or_replay_shielded_wait(guest, Syscall::Select(call))
2439                .await;
2440        }
2441
2442        let timeout = raw_timeout.map(select_timeout_duration).transpose()?;
2443        self.handle_internal_select(guest, call, timeout).await
2444    }
2445
2446    #[cfg(target_arch = "x86_64")]
2447    async fn handle_internal_select<G: Guest<Self>>(
2448        &self,
2449        guest: &mut G,
2450        call: syscalls::Select,
2451        timeout: Option<Duration>,
2452    ) -> Result<i64, Error> {
2453        let len = pselect6_fd_set_len(call.nfds())?;
2454        let deadline = match timeout {
2455            Some(timeout) => Some(thread_observe_time(guest).await + timeout),
2456            None => None,
2457        };
2458        let original_readfds = match read_pselect6_fd_set(guest, call.readfds(), len) {
2459            Ok(value) => value,
2460            Err(error) => {
2461                self.write_select_remaining(guest, call, deadline).await?;
2462                return Err(error);
2463            }
2464        };
2465        let original_writefds = match read_pselect6_fd_set(guest, call.writefds(), len) {
2466            Ok(value) => value,
2467            Err(error) => {
2468                self.write_select_remaining(guest, call, deadline).await?;
2469                return Err(error);
2470            }
2471        };
2472        let original_exceptfds = match read_pselect6_fd_set(guest, call.exceptfds(), len) {
2473            Ok(value) => value,
2474            Err(error) => {
2475                self.write_select_remaining(guest, call, deadline).await?;
2476                return Err(error);
2477            }
2478        };
2479
2480        let mut stack = guest.stack().await;
2481        let readfds = call.readfds().map(|_| stack.reserve::<libc::fd_set>());
2482        let writefds = call.writefds().map(|_| stack.reserve::<libc::fd_set>());
2483        let exceptfds = call.exceptfds().map(|_| stack.reserve::<libc::fd_set>());
2484        // select modifies its timeout in place, so the probe timeout must be a
2485        // writable scratch cell. It is re-zeroed each iteration to keep every
2486        // probe a non-blocking poll (a NULL timeout would block indefinitely).
2487        let probe_timeout = stack.reserve::<libc::timeval>();
2488        // On a backend whose kernel reports the guest's signal state, only a signal
2489        // that would end the wait natively does; the mask cell lets the wait block
2490        // signals under the one scratch-stack guard
2491        // (https://github.com/rrnewton/hermit/issues/3146). A restart resumes from
2492        // the remaining time written back below, so a default stop ends the wait
2493        // as on Linux. Unlike the other waits, it holds no signal until the call
2494        // returns, so a pending caught `SIGCHLD` ends it as on Linux, whoever
2495        // sent it, at a turn that host timing can choose: a known gap that this
2496        // wait had before (`KernelSignalWait::for_select`).
2497        let mut signals = self
2498            .cfg
2499            .backend
2500            .supports_blocked_wait_signal_interruption
2501            .then(|| KernelSignalWait::for_select(guest));
2502        let mask_cell = signals.as_ref().map(|_| stack.reserve::<KernelSigset>());
2503        // This frame holds the scratch stack across every probe, so the
2504        // memory that makes an expired timerfd readable is reserved here
2505        // (`crate::syscalls::timerfd::publish_expired_in`).
2506        let timerfd_scratch = crate::syscalls::timerfd::reserve_scratch(&mut stack);
2507        let _guard = stack.commit()?;
2508        // The scheduler reads the effective mask while the wait parks
2509        // (`publish_wait_mask`).
2510        let mut published = PublishedWaitMask::default();
2511        if let Some(signals) = signals.as_ref() {
2512            publish_wait_mask(guest, signals.effective_mask(), &mut published).await;
2513        }
2514        let probe = call
2515            .with_readfds(readfds)
2516            .with_writefds(writefds)
2517            .with_exceptfds(exceptfds)
2518            .with_timeout(Some(probe_timeout));
2519
2520        // Under the in-guest delivery gate (A5 step 3c.1d, D50 reader (4)): a
2521        // turn that finds nothing ready decides from the thread's `/proc`
2522        // status under the mask it published, its own (`gated_wait_ends`).
2523        let gated = self.cfg.in_guest_signal_delivery;
2524        let mut resources = Resources::new(guest.thread_state().dettid);
2525        resources.insert(ResourceID::InternalIOPolling, Permission::W);
2526        resources.fyi("select");
2527        resources.poll_deadline = poll_deadline_for(guest, deadline);
2528        // EINTR records select's interruption result. The scheduler only wakes
2529        // ERESTARTSYS requests: blocked and ignored signals still need a target-
2530        // side disposition check before this Signaled path can be used.
2531        resources.set_signal_interrupt_errno(Errno::EINTR);
2532
2533        // Every exit, including a `?` on a guest-memory write, must reach the mask
2534        // restore below, so the loop runs in its own async block.
2535        let result: Result<i64, Error> = {
2536            // Moved reborrows: a future holding a shared reference to a scratch
2537            // address would not be `Send`.
2538            let guest = &mut *guest;
2539            let signals = &mut signals;
2540            let resources = &mut resources;
2541            let published = &mut published;
2542            async move {
2543                loop {
2544                    let signaled = matches!(
2545                        resource_request(guest, resources.clone()).await,
2546                        ResumeStatus::Signaled(_)
2547                    );
2548                    // Virtual timerfds first (`crate::syscalls::timerfd`).
2549                    let next_timerfd =
2550                        crate::syscalls::timerfd::publish_expired_in(guest, Some(timerfd_scratch))
2551                            .await;
2552                    resources.poll_deadline =
2553                        crate::syscalls::timerfd::poll_deadline_with(guest, deadline, next_timerfd);
2554                    // A pending real-time timer expiration that this wait would
2555                    // be ended by (https://github.com/rrnewton/hermit/issues/4089).
2556                    let pending = pending_real_time_among(guest, REAL_TIME_SIGNALS).await;
2557                    if pending != 0
2558                        && let Some(signals) = signals.as_ref()
2559                    {
2560                        let observable = signals.observable_now()?;
2561                        act_on_pending_real_time(guest, pending, observable, 0).await;
2562                    }
2563                    // Every turn probes before it looks at pending signals, as Linux
2564                    // does: do_select polls every descriptor on each pass and returns
2565                    // the count of ready ones before it tests signal_pending, and at
2566                    // the start max_select_fd returns EBADF for a descriptor that is
2567                    // not open (`core_sys_select`). So a descriptor that becomes ready
2568                    // in a later turn in which a signal is also pending is reported
2569                    // (round-12 Medium on https://github.com/rrnewton/hermit/pull/3361).
2570                    // Each probe is a new select call, so a descriptor closed during
2571                    // the wait gets EBADF, where Linux reports it ready. Without the
2572                    // kernel's signal state, a scheduler `Signaled` answer still ends
2573                    // the call before the probe.
2574                    if signals.is_none() && signaled && !gated {
2575                        self.write_select_remaining(guest, call, deadline).await?;
2576                        return Err(Errno::EINTR.into());
2577                    }
2578                    guest.memory().write_value(
2579                        probe_timeout,
2580                        &libc::timeval {
2581                            tv_sec: 0,
2582                            tv_usec: 0,
2583                        },
2584                    )?;
2585                    write_pselect6_fd_set(guest, probe.readfds(), &original_readfds)?;
2586                    write_pselect6_fd_set(guest, probe.writefds(), &original_writefds)?;
2587                    write_pselect6_fd_set(guest, probe.exceptfds(), &original_exceptfds)?;
2588
2589                    let result = match signals.as_mut() {
2590                        // A signal stop that does not end the wait is absorbed (see
2591                        // `KernelSignalWait`). Every probe runs before the turn's check
2592                        // (`inject_first_probe`): a signal that would end the wait and
2593                        // stops the probe is held, the probe runs again, and the signal
2594                        // is delivered as the call returns. Once `block` has blocked
2595                        // every blockable signal, a pending one cannot stop the probe.
2596                        Some(signals) => match signals.inject_first_probe(guest, probe).await {
2597                            Ok(result) => result,
2598                            Err(error) => {
2599                                self.write_select_remaining(guest, call, deadline).await?;
2600                                break Err(error);
2601                            }
2602                        },
2603                        None => guest.inject(probe).await,
2604                    };
2605                    if result != Ok(0) {
2606                        let copy_result = if result.is_ok() {
2607                            self.copy_select_results(guest, probe, call, len)
2608                        } else {
2609                            Ok(())
2610                        };
2611                        self.write_select_remaining(guest, call, deadline).await?;
2612                        copy_result?;
2613                        break result.map_err(Into::into);
2614                    }
2615                    // The turn's check, after its probe found no descriptor ready and no
2616                    // error: only then does a pending signal end the call. A scheduler
2617                    // `Signaled` answer is only a hint here; the kernel's state decides.
2618                    // select returns ERESTARTNOHAND: EINTR after a handler, a restart
2619                    // with the remaining timeout after a stop.
2620                    if gated {
2621                        match crate::syscalls::helpers::gated_wait_ends(guest, None) {
2622                            Ok(true) => {
2623                                self.write_select_remaining(guest, call, deadline).await?;
2624                                break Err(Errno::ERESTARTNOHAND.into());
2625                            }
2626                            Ok(false) => {}
2627                            Err(error) => break Err(error),
2628                        }
2629                    }
2630                    if let Some(signals) = signals.as_ref() {
2631                        match signals.interrupted_with_state() {
2632                            Ok((false, _)) => {}
2633                            Ok((true, _)) => {
2634                                self.write_select_remaining(guest, call, deadline).await?;
2635                                break Err(Errno::ERESTARTNOHAND.into());
2636                            }
2637                            Err(error) => break Err(error),
2638                        }
2639                    }
2640                    if let Some(signals) = signals.as_mut()
2641                        && signals.needs_block()
2642                        && let Err(error) = signals.block(guest, mask_cell).await
2643                    {
2644                        self.write_select_remaining(guest, call, deadline).await?;
2645                        break Err(error);
2646                    }
2647                    if let Some(signals) = signals.as_ref() {
2648                        publish_wait_mask(guest, signals.effective_mask(), published).await;
2649                    }
2650
2651                    resources.poll_attempt += 1;
2652                    if let Some(deadline) = deadline
2653                        && thread_observe_time(guest).await >= deadline
2654                    {
2655                        let copy_result = self.copy_select_results(guest, probe, call, len);
2656                        self.write_select_remaining(guest, call, Some(deadline))
2657                            .await?;
2658                        copy_result?;
2659                        break Ok(0);
2660                    }
2661                    trace!(
2662                        "Retry #{} for syscall due to result Ok(0): {}",
2663                        resources.poll_attempt,
2664                        probe.display(&guest.memory())
2665                    );
2666                    record_retry_event(guest, probe).await;
2667                }
2668            }
2669            .await
2670        };
2671        let result = match &mut signals {
2672            Some(signals) => {
2673                let restored = signals.restore(guest, mask_cell).await;
2674                publish_wait_mask(guest, None, &mut published).await;
2675                result_after_restore(restored, result)?
2676            }
2677            None => result,
2678        };
2679        refuse_held_signal_loss(guest, Sysno::select, result).await
2680    }
2681
2682    #[cfg(target_arch = "x86_64")]
2683    fn copy_select_results<G: Guest<Self>>(
2684        &self,
2685        guest: &mut G,
2686        probe: syscalls::Select,
2687        call: syscalls::Select,
2688        len: usize,
2689    ) -> Result<(), Error> {
2690        copy_pselect6_fd_set(guest, probe.readfds(), call.readfds(), len)?;
2691        copy_pselect6_fd_set(guest, probe.writefds(), call.writefds(), len)?;
2692        copy_pselect6_fd_set(guest, probe.exceptfds(), call.exceptfds(), len)
2693    }
2694
2695    #[cfg(target_arch = "x86_64")]
2696    pub(super) async fn write_select_remaining<G: Guest<Self>>(
2697        &self,
2698        guest: &mut G,
2699        call: syscalls::Select,
2700        deadline: Option<LogicalTime>,
2701    ) -> Result<(), Error> {
2702        if let (Some(timeout), Some(deadline)) = (call.timeout(), deadline) {
2703            let now = thread_observe_time(guest).await;
2704            let remaining = deadline.as_nanos().saturating_sub(now.as_nanos());
2705            let remaining = libc::timeval {
2706                tv_sec: (remaining / 1_000_000_000) as libc::time_t,
2707                tv_usec: ((remaining % 1_000_000_000) / 1_000) as libc::suseconds_t,
2708            };
2709            // select's timeout is a writable in-out kernel timeval reporting the
2710            // time not slept; derive it from deterministic virtual time.
2711            if let Err(error) = guest.memory().write_value(timeout, &remaining) {
2712                // Linux preserves the select result when remaining-time copyout faults.
2713                trace!(?error, "ignoring select timeout writeback failure");
2714            }
2715        }
2716        Ok(())
2717    }
2718
2719    /// ppoll syscall (MAYHANG)
2720    // TODO-HUMAN-REVIEW(PR-273)
2721    pub async fn handle_ppoll<G: Guest<Self>>(
2722        &self,
2723        guest: &mut G,
2724        call: syscalls::Ppoll,
2725    ) -> Result<i64, Error> {
2726        // A masked ppoll under the in-guest delivery gate (A5 step 3c.1d, D50
2727        // reader (5)): `gated_masked_ppoll`, which reads the timeout itself.
2728        if self.cfg.in_guest_signal_delivery
2729            && call.sigmask().is_some()
2730            && !ppoll_uses_kernel_wait(self.cfg.sequentialize_threads, self.cfg.recordreplay_modes)
2731        {
2732            return Box::pin(self.gated_masked_ppoll(guest, call)).await;
2733        }
2734        let timeout_address = call.timeout();
2735        let timeout = match timeout_address {
2736            Some(timeout) => Some(ppoll_timeout_duration(guest.memory().read_value(timeout)?)?),
2737            None => None,
2738        };
2739
2740        let result: Result<i64, Error> = if timeout == Some(Duration::ZERO) {
2741            // In run mode the mask's read below is the call's first injection
2742            // unless a virtual timerfd's expiration is published first, which
2743            // can inject only for a process holding an armed one. Under record
2744            // and replay a scheduler request comes first, which changes
2745            // scheduler state (it publishes the thread's signal state and can
2746            // grant another thread a turn), so a restart would repeat it: the
2747            // read keeps the not-run report there
2748            // (https://github.com/rrnewton/hermit/issues/4526).
2749            let first = !self.cfg.recordreplay_modes
2750                && !crate::syscalls::timerfd::holds_armed_virtual_timerfd(guest);
2751            if self.cfg.recordreplay_modes {
2752                resource_request(guest, Resources::new(guest.thread_state().dettid)).await;
2753            } else {
2754                // A virtual timerfd among the fds is readable exactly when its
2755                // deadline has passed (`crate::syscalls::timerfd`); before the
2756                // probe's copies take the scratch stack.
2757                crate::syscalls::timerfd::publish_expired(guest).await;
2758            }
2759            let signal_mask = read_ppoll_mask(guest, call, first).await?;
2760            let (probe, staged) = match self.stage_ppoll_probe(guest, call, signal_mask).await? {
2761                Some((probe, staged)) => (probe, Some(staged)),
2762                None => {
2763                    if let Some(refused) = self.refuse_unstageable_ppoll_copy(guest).await {
2764                        return refused;
2765                    }
2766                    // Runs on with the guest's own zero timeout and mask.
2767                    (call, None)
2768                }
2769            };
2770            // The mask the probe installs, read at entry, for replay to deliver
2771            // under (https://github.com/rrnewton/hermit/issues/4054).
2772            let temporary_mask = if self.cfg.replaying {
2773                match call.sigmask() {
2774                    Some(signal_mask) => read_kernel_sigset(guest, signal_mask)
2775                        .await
2776                        .ok()
2777                        .map(sanitize_ppoll_signal_mask),
2778                    None => None,
2779                }
2780            } else {
2781                None
2782            };
2783            if let Some(mask) = temporary_mask {
2784                super::signal_shadow::note_wait_mask(guest, mask);
2785            }
2786            // No `?` on the call's own result: an interruption must reach the
2787            // replay delivery below.
2788            let result: Result<i64, Error> = if self.cfg.recordreplay_modes {
2789                self.record_or_replay(guest, probe)
2790                    .await
2791                    .map_err(Into::into)
2792            } else {
2793                inject_ppoll_probe(guest, probe).await.map_err(Into::into)
2794            };
2795            if let Some(staged) = staged {
2796                staged.release(guest);
2797            }
2798            // A backend that runs the call itself answers ENOSYS for a pending
2799            // signal the mask lets in that it cannot act on inside the call
2800            // (`settle_masked_ppoll_enosys`); after the copies are given back,
2801            // whose stack the settling may use.
2802            let result = match (result, signal_mask) {
2803                (Err(Error::Errno(Errno::ENOSYS)), Some(signal_mask))
2804                    if !self.cfg.recordreplay_modes =>
2805                {
2806                    self.settle_masked_ppoll_enosys(guest, call, signal_mask, false)
2807                        .await
2808                }
2809                (result, _) => result,
2810            };
2811            // Replay serves the probe from the recording, so the kernel never
2812            // installs its mask; deliver a recorded interruption under it, as
2813            // for pselect6.
2814            if let Some(mask) = temporary_mask
2815                && matches!(result, Err(Error::Errno(Errno::ERESTARTNOHAND)))
2816            {
2817                end_replayed_pselect6_under_its_temporary_mask(guest, mask).await;
2818            }
2819            // Linux does not write back an initially zero timeout. Besides matching the
2820            // kernel, omitting this write matters when the timeout aliases the pollfd array:
2821            // the injected probe may have just stored revents in those same bytes.
2822            result
2823        } else if ppoll_uses_kernel_wait(
2824            self.cfg.sequentialize_threads,
2825            self.cfg.recordreplay_modes,
2826        ) {
2827            // The kernel owns the blocking wait (`ppoll_uses_kernel_wait`). Use
2828            // a private copy only for the signal mask so raw ppoll can still
2829            // update the guest timeout.
2830            let mut staged = None;
2831            // The mask the real call sleeps under, recorded for the scheduler's
2832            // SIGCHLD target choice (`Resources::blocked_signal_mask`).
2833            let mut blocked_signal_mask = None;
2834            // The mask read at entry, for replay to deliver under.
2835            let mut temporary_mask = None;
2836            let call = if let Some(signal_mask) = call.sigmask() {
2837                if call.sigsetsize() != KERNEL_SIGSET_SIZE {
2838                    return Err(Errno::EINVAL.into());
2839                }
2840                let signal_mask = crate::syscalls::signal::restart_if_first_probe_not_run(
2841                    read_kernel_sigset(guest, signal_mask).await,
2842                )?;
2843                super::signal_shadow::note_wait_mask(guest, signal_mask);
2844                let signal_mask = sanitize_ppoll_signal_mask(signal_mask);
2845                blocked_signal_mask = Some(kernel_installed_signal_mask(signal_mask));
2846                temporary_mask = Some(signal_mask);
2847                // Linux writes revents into the pollfd array and the time left
2848                // into the timeout, so the copy overlaps neither.
2849                let live: Vec<std::ops::Range<usize>> = ppoll_fds_range(call)
2850                    .into_iter()
2851                    .chain(call.timeout().map(|timeout| {
2852                        let start = timeout.as_raw();
2853                        start..start.saturating_add(std::mem::size_of::<Timespec>())
2854                    }))
2855                    .collect();
2856                match stage_private_cells(guest, PrivateCells::Mask(signal_mask), &live).await? {
2857                    Some(cells) => {
2858                        let mask = cells.mask();
2859                        staged = Some(cells);
2860                        call.with_sigmask(Addr::from_raw(mask))
2861                    }
2862                    None => {
2863                        if let Some(refused) = self.refuse_unstageable_ppoll_copy(guest).await {
2864                            return refused;
2865                        }
2866                        // Runs on with the guest's own mask. A sibling may
2867                        // change it while the call waits, which a masked
2868                        // record/replay wait already allows for: its sleeping
2869                        // mask is recorded as unknown.
2870                        call
2871                    }
2872                }
2873            } else {
2874                call
2875            };
2876            // A masked record/replay wait sleeps under a temporary mask the
2877            // scheduler records as unknown, as a masked pselect6's does
2878            // (`Resources::blocked_signal_mask_unknown`), although Hermit knows
2879            // it exactly. A known mask would let the scheduler wait for the
2880            // kernel to install it, and replay serves the call from the
2881            // recording without the kernel ever doing so; recording and replay
2882            // must schedule the call alike. The result has no `?`: an
2883            // interruption must reach the replay delivery below.
2884            let result: Result<i64, Error> = if temporary_mask.is_none() {
2885                // No mask: the call waits under the thread's own.
2886                self.record_or_replay_shielded_wait(guest, Syscall::Ppoll(call))
2887                    .await
2888            } else if self.cfg.recordreplay_modes {
2889                self.record_or_replay_blocking_with_unknown_mask(guest, Syscall::Ppoll(call))
2890                    .await
2891            } else {
2892                self.record_or_replay_blocking_with_mask(
2893                    guest,
2894                    Syscall::Ppoll(call),
2895                    blocked_signal_mask,
2896                )
2897                .await
2898            };
2899            if let Some(staged) = staged {
2900                staged.release(guest);
2901            }
2902            // Linux's ppoll never fails with ENOSYS. A backend that runs the
2903            // call itself answers it for a masked wait it cannot keep under
2904            // the mask, and for a pending signal the mask lets in that it
2905            // cannot act on inside the call: refused by name
2906            // (`refuse_masked_ppoll_enosys`), never handed to the guest.
2907            if let (Err(Error::Errno(Errno::ENOSYS)), Some(mask)) = (&result, temporary_mask) {
2908                return self.refuse_masked_ppoll_enosys(guest, mask).await;
2909            }
2910            // Replay serves the call from the recording, so the kernel never
2911            // installs its mask; deliver a recorded interruption under it
2912            // (https://github.com/rrnewton/hermit/issues/3992).
2913            if self.cfg.replaying
2914                && let Some(mask) = temporary_mask
2915                && matches!(result, Err(Error::Errno(Errno::ERESTARTNOHAND)))
2916            {
2917                end_replayed_pselect6_under_its_temporary_mask(guest, mask).await;
2918            }
2919            result
2920        } else {
2921            self.handle_internal_ppoll(guest, call, timeout).await
2922        };
2923
2924        result
2925    }
2926
2927    /// The zero-timeout probe for `call`: it reads a private zero timeout,
2928    /// and a private, sanitized copy of the call's mask, staged by
2929    /// `stage_private_cells` clear of the pollfd array that Linux writes. So
2930    /// neither a sibling's write nor the guest's own mask can make the probe
2931    /// sleep or block Hermit's timer signal. `None` when the copies have
2932    /// nowhere to go (`refuse_unstageable_ppoll_copy`). `signal_mask` is the
2933    /// mask `read_ppoll_mask` read, if the call names one.
2934    async fn stage_ppoll_probe<G: Guest<Self>>(
2935        &self,
2936        guest: &mut G,
2937        call: syscalls::Ppoll,
2938        signal_mask: Option<KernelSigset>,
2939    ) -> Result<
2940        Option<(
2941            syscalls::Ppoll,
2942            StagedCells<<G::Stack as Stack>::StackGuard>,
2943        )>,
2944        Error,
2945    > {
2946        let signal_mask = match signal_mask {
2947            Some(signal_mask) => {
2948                // The probe's temporary mask lets the kernel take a pending
2949                // real-time timer expiration that every thread blocked when it
2950                // expired (https://github.com/rrnewton/hermit/issues/4089).
2951                observe_under_mask(guest, signal_mask).await?;
2952                Some(sanitize_ppoll_signal_mask(signal_mask))
2953            }
2954            None => None,
2955        };
2956        let cells = match signal_mask {
2957            Some(signal_mask) => PrivateCells::MaskAndZeroTimeout(signal_mask),
2958            None => PrivateCells::ZeroTimeout,
2959        };
2960        let live: Vec<std::ops::Range<usize>> = ppoll_fds_range(call).into_iter().collect();
2961        let Some(staged) = stage_private_cells(guest, cells, &live).await? else {
2962            return Ok(None);
2963        };
2964        let probe = match signal_mask {
2965            Some(_) => call
2966                .with_timeout(AddrMut::from_raw(staged.after_mask()))
2967                .with_sigmask(Addr::from_raw(staged.mask())),
2968            None => call.with_timeout(AddrMut::from_raw(staged.at)),
2969        };
2970        Ok(Some((probe, staged)))
2971    }
2972
2973    /// Refuses a ppoll whose private copies have nowhere to go
2974    /// (`stage_private_cells`): a zero-timeout probe's zero timeout and mask,
2975    /// or the mask of a call the kernel waits in. The stack has no writable
2976    /// scratch below its red zone, and every red-zone window overlaps the
2977    /// pollfd array or, for a kernel wait, the timeout
2978    /// (https://github.com/rrnewton/hermit/issues/4113). A fail-closed run
2979    /// stops with the policy refusal and this message, whose result this
2980    /// returns. A run started with `--allow-unsupported-syscalls` runs on with
2981    /// the guest's own timeout and mask, and this returns `None`.
2982    async fn refuse_unstageable_ppoll_copy<G: Guest<Self>>(
2983        &self,
2984        guest: &mut G,
2985    ) -> Option<Result<i64, Error>> {
2986        use std::io::Write as _;
2987        let message = "unsupported: ppoll on a stack with no room below its red zone whose \
2988            red zone the call's pollfd array and timeout fill: Hermit has nowhere to put its \
2989            private copy of the timeout and the signal mask \
2990            (https://github.com/rrnewton/hermit/issues/4113)";
2991        tracing::error!("[tid {}] {message}", guest.tid());
2992        if !self.cfg.panic_on_unsupported_syscalls {
2993            return None;
2994        }
2995        let _ = writeln!(crate::util::RetryingStderr, "{message}");
2996        Some(
2997            self.refuse_unserviceable_operation(guest, Sysno::ppoll, Errno::EFAULT)
2998                .await,
2999        )
3000    }
3001
3002    /// Handle a guest-internal `ppoll` using zero-time kernel probes.
3003    async fn handle_internal_ppoll<G: Guest<Self>>(
3004        &self,
3005        guest: &mut G,
3006        call: syscalls::Ppoll,
3007        timeout: Option<Duration>,
3008    ) -> Result<i64, Error> {
3009        debug_assert_ne!(timeout, Some(Duration::ZERO));
3010        let timeout_address = call.timeout();
3011
3012        // Every zero-timeout probe carries a private copy of the call's temporary
3013        // mask, so the kernel applies it atomically, as pselect6's probes do
3014        // (`handle_internal_pselect6`). A pending signal that the mask lets in
3015        // ends a probe with ERESTARTNOHAND at that probe's scheduler point, and
3016        // the kernel delivers it under the temporary mask and only then puts the
3017        // thread's own mask back, as Linux does for a ppoll that a signal
3018        // interrupts. That probe is the last injection before the guest
3019        // resumes, so nothing disturbs the pending restore
3020        // (https://github.com/rrnewton/reverie/issues/1003). A ppoll that would
3021        // block used to return ENOSYS here
3022        // (https://github.com/rrnewton/hermit/issues/4042).
3023        if call.sigmask().is_some() {
3024            // Only memory was read before: the mask's read is the call's
3025            // first injection. The virtual clock is observed right after it
3026            // (`ppoll_start`), before the observation under the mask, which
3027            // can ask the scheduler too: Linux takes the end time before it
3028            // installs the mask.
3029            let signal_mask = read_ppoll_mask(guest, call, true).await?;
3030            let (started_at, deadline) = ppoll_start(guest, timeout).await;
3031            let Some((probe, staged)) = self.stage_ppoll_probe(guest, call, signal_mask).await?
3032            else {
3033                // The guest's own mask could block Hermit's timer signal while
3034                // the call is parked, so a run that allows unsupported
3035                // operations gets the same answer as a mask it cannot honor.
3036                return match self.refuse_unstageable_ppoll_copy(guest).await {
3037                    Some(refused) => refused,
3038                    None => Err(Errno::ENOSYS.into()),
3039                };
3040            };
3041            // The copies are given back before the time left is written: their
3042            // red-zone window may cover the guest's timeout, which Linux never
3043            // sees.
3044            let result = if self.cfg.backend.supports_blocked_wait_signal_interruption {
3045                let result = self
3046                    .wait_on_masked_ppoll_probe(
3047                        guest,
3048                        probe,
3049                        staged.mask(),
3050                        staged.timerfd_scratch,
3051                        deadline,
3052                    )
3053                    .await;
3054                staged.release(guest);
3055                result
3056            } else {
3057                // Without the kernel's signal state, Hermit cannot tell whether
3058                // the backend holds a signal that should end the wait, so a
3059                // ppoll that would block fails closed, refused by name
3060                // (`refuse_masked_ppoll_wait`). An expired timerfd on virtual
3061                // time is made readable first, in the staged cells' scratch
3062                // (`crate::syscalls::timerfd`). A backend that runs the call
3063                // itself answers EINTR, with the mask installed for the
3064                // delivery, when a pending caught signal ends it
3065                // (`inject_ppoll_probe`), and ENOSYS for one it cannot act on
3066                // (`settle_masked_ppoll_enosys`, after the copies are given
3067                // back, whose stack the settling may use).
3068                crate::syscalls::timerfd::publish_expired_in(guest, staged.timerfd_scratch).await;
3069                let result = inject_ppoll_probe(guest, probe).await;
3070                staged.release(guest);
3071                match (result, signal_mask) {
3072                    (Err(Errno::ENOSYS), Some(signal_mask)) => {
3073                        self.settle_masked_ppoll_enosys(guest, call, signal_mask, true)
3074                            .await
3075                    }
3076                    (result, _) if probe.syscall_would_have_blocked(result) => {
3077                        self.refuse_masked_ppoll_wait(guest).await
3078                    }
3079                    (result, _) => result.map_err(Into::into),
3080                }
3081            };
3082            if let (Some(timeout_address), Some(timeout), Some(started_at)) =
3083                (timeout_address, timeout, started_at)
3084            {
3085                self.write_ppoll_remaining(guest, timeout_address, timeout, started_at)
3086                    .await?;
3087            }
3088            return result;
3089        }
3090
3091        let (started_at, deadline) = ppoll_start(guest, timeout).await;
3092        let mut rsrc = Resources::new(guest.thread_state().dettid);
3093        rsrc.insert(ResourceID::InternalIOPolling, Permission::W);
3094        rsrc.fyi("ppoll");
3095        rsrc.set_signal_interrupt_errno(Errno::EINTR);
3096        rsrc.signal_state_task = guest_signal_state_task(guest);
3097        rsrc.relies_on_guest_held_marks = poll_targets_guest_held(
3098            guest,
3099            call.fds().map(|fds| fds.as_raw()),
3100            call.nfds() as usize,
3101        );
3102        rsrc.polls_guest_state_only = rsrc.relies_on_guest_held_marks;
3103        let result = retry_nonblocking_syscall_with_timeout(guest, call, rsrc, deadline).await;
3104        if let (Some(timeout_address), Some(timeout), Some(started_at)) =
3105            (timeout_address, timeout, started_at)
3106        {
3107            self.write_ppoll_remaining(guest, timeout_address, timeout, started_at)
3108                .await?;
3109        }
3110        result
3111    }
3112
3113    /// The answer to a ppoll with a temporary signal mask that would wait, on
3114    /// a backend that cannot interrupt a blocked wait for a signal
3115    /// (`BackendCapabilities::supports_blocked_wait_signal_interruption` is
3116    /// false): Hermit cannot keep the mask in place across the wait, so the
3117    /// call is refused by name through the unsupported-operation policy, on
3118    /// stderr when the run ends (`report_refusal_reason`); with unsupported
3119    /// operations allowed the guest gets ENOSYS, as before. Linux's ppoll
3120    /// never fails with ENOSYS.
3121    async fn refuse_masked_ppoll_wait<G: Guest<Self>>(&self, guest: &mut G) -> Result<i64, Error> {
3122        const REASON: &str = "a ppoll with a temporary signal mask that would wait cannot be \
3123             served on this backend: it cannot keep the mask in place across the wait";
3124        self.refuse_masked_wait_because(guest, Sysno::ppoll, REASON)
3125            .await
3126    }
3127
3128    /// The answer to a ppoll whose temporary signal mask lets in a pending
3129    /// signal that the backend, which runs the call itself, cannot act on
3130    /// inside the call: one that would stop the process, or an ignored one
3131    /// pending beside one that ends the call (`settle_masked_ppoll_enosys`).
3132    /// Refused by name as `refuse_masked_ppoll_wait` refuses a wait.
3133    async fn refuse_masked_ppoll_signal<G: Guest<Self>>(
3134        &self,
3135        guest: &mut G,
3136    ) -> Result<i64, Error> {
3137        const REASON: &str = "a ppoll whose temporary signal mask lets in a pending signal that \
3138             would stop the process, or an ignored one pending beside a signal that ends the \
3139             call, cannot be served on this backend: it cannot act on that signal inside the call";
3140        self.refuse_masked_wait_because(guest, Sysno::ppoll, REASON)
3141            .await
3142    }
3143
3144    /// A masked wait, `sysno`, refused by name for `reason`, through the
3145    /// unsupported-operation policy.
3146    async fn refuse_masked_wait_because<G: Guest<Self>>(
3147        &self,
3148        guest: &mut G,
3149        sysno: Sysno,
3150        reason: &'static str,
3151    ) -> Result<i64, Error> {
3152        crate::syscalls::helpers::report_refusal_reason(
3153            self.cfg.panic_on_unsupported_syscalls,
3154            &format!(
3155                "[dtid {}] {sysno} refused: {reason}",
3156                guest.thread_state().dettid
3157            ),
3158        );
3159        if !self.cfg.panic_on_unsupported_syscalls {
3160            crate::tool_global::report_unsupported_syscall(guest, sysno).await;
3161        }
3162        self.refuse_unserviceable_operation_because(guest, sysno, Errno::ENOSYS, reason)
3163            .await
3164    }
3165
3166    /// The answer to a masked ppoll that the kernel beneath waits in
3167    /// (`ppoll_uses_kernel_wait`) and the backend failed with ENOSYS, which
3168    /// Linux's ppoll never returns: a backend that runs the call itself
3169    /// declines a wait it cannot keep under the mask, and a pending signal
3170    /// the mask lets in that it cannot act on inside the call. Ignored ones
3171    /// alone, which Linux discards as the call returns before it restarts
3172    /// the call to wait again, are removed as it removes them
3173    /// (`discard_ignored_signals_let_in`), and the call is refused as a wait;
3174    /// anything else is refused as such a signal.
3175    async fn refuse_masked_ppoll_enosys<G: Guest<Self>>(
3176        &self,
3177        guest: &mut G,
3178        mask: KernelSigset,
3179    ) -> Result<i64, Error> {
3180        if self.discard_ignored_signals_let_in(guest, mask).await? {
3181            self.refuse_masked_ppoll_wait(guest).await
3182        } else {
3183            self.refuse_masked_ppoll_signal(guest).await
3184        }
3185    }
3186
3187    /// The answer to a zero-timeout probe of a masked ppoll, `call` with
3188    /// the temporary mask `signal_mask` as `read_ppoll_mask` read it, that the
3189    /// backend failed with ENOSYS, which Linux's ppoll never returns. A
3190    /// backend that runs the call itself answers it when a pending signal the
3191    /// mask lets in is one it cannot act on inside the call, where Linux
3192    /// returns ERESTARTNOHAND (`do_poll`, fs/select.c): it cannot discard an
3193    /// ignored signal, restart the call, or stop the process.
3194    ///
3195    /// When every such signal is one the process ignores (the shared rule,
3196    /// `crate::syscalls::ignored_at_generation`), Linux discards each as the
3197    /// call returns (`get_signal`, kernel/signal.c) and, having delivered
3198    /// nothing, restarts the call, which polls again
3199    /// (`arch_do_signal_or_restart`, arch/x86/kernel/signal.c). So does this:
3200    /// they are removed from the thread's queues
3201    /// (`discard_ignored_signals_let_in`), and a new probe runs, whose result
3202    /// is the call's. Where the call would wait (`waits`), a probe that finds
3203    /// nothing ready is refused as a wait (`refuse_masked_ppoll_wait`), as
3204    /// one without those signals is. Any other such signal, a stop or an
3205    /// ignored one beside a signal that ends the call, is refused by name
3206    /// (`refuse_masked_ppoll_signal`): which ignored signals Linux discards
3207    /// then depends on the handler's own mask, read nowhere here.
3208    async fn settle_masked_ppoll_enosys<G: Guest<Self>>(
3209        &self,
3210        guest: &mut G,
3211        call: syscalls::Ppoll,
3212        signal_mask: KernelSigset,
3213        waits: bool,
3214    ) -> Result<i64, Error> {
3215        if !self
3216            .discard_ignored_signals_let_in(guest, sanitize_ppoll_signal_mask(signal_mask))
3217            .await?
3218        {
3219            return self.refuse_masked_ppoll_signal(guest).await;
3220        }
3221        let again = match self
3222            .stage_ppoll_probe(guest, call, Some(signal_mask))
3223            .await?
3224        {
3225            Some((probe, staged)) => {
3226                let again = inject_ppoll_probe(guest, probe).await;
3227                staged.release(guest);
3228                again
3229            }
3230            None => {
3231                return match self.refuse_unstageable_ppoll_copy(guest).await {
3232                    Some(refused) => refused,
3233                    None => Err(Errno::ENOSYS.into()),
3234                };
3235            }
3236        };
3237        match again {
3238            Err(Errno::ENOSYS) => self.refuse_masked_ppoll_signal(guest).await,
3239            Ok(0) if waits => self.refuse_masked_ppoll_wait(guest).await,
3240            result => result.map_err(Into::into),
3241        }
3242    }
3243
3244    /// The answer to a zero-timeout masked pselect6, `call` with the
3245    /// temporary mask `mask`, that the backend failed with ENOSYS, as for
3246    /// ppoll (`settle_masked_ppoll_enosys`): when every pending signal the mask
3247    /// lets in is one the process ignores, they are removed, as Linux discards
3248    /// them before it restarts the call, and the call runs again, whose result
3249    /// is the answer. Anything else is refused by name: for a pending signal
3250    /// the backend cannot act on inside the call, or, with none, for the
3251    /// descriptor sets it declined (`masked_pselect6_refusal_reason`).
3252    async fn settle_masked_pselect6_enosys<G: Guest<Self>>(
3253        &self,
3254        guest: &mut G,
3255        call: syscalls::Pselect6,
3256        mask: KernelSigset,
3257    ) -> Result<i64, Error> {
3258        if self.discard_ignored_signals_let_in(guest, mask).await? {
3259            match guest.inject(call).await {
3260                Err(Errno::ENOSYS) => {}
3261                result => return Ok(result?),
3262            }
3263        }
3264        let reason = masked_pselect6_refusal_reason_now(guest, mask, call.nfds());
3265        self.refuse_masked_wait_because(guest, Sysno::pselect6, reason)
3266            .await
3267    }
3268
3269    /// Removes the pending signals the temporary mask `mask` lets in, when
3270    /// the backend reports this thread's signal state (`Guest::signal_sets`)
3271    /// and every one of them is a signal the process ignores
3272    /// (`ignored_signals_let_in`): Linux discards those as the call returns.
3273    /// True when it removed them, or there were none; false, removing
3274    /// nothing, when one is not ignored or the backend reports no state.
3275    async fn discard_ignored_signals_let_in<G: Guest<Self>>(
3276        &self,
3277        guest: &mut G,
3278        mask: KernelSigset,
3279    ) -> Result<bool, Error> {
3280        let Some(ignored) = ignored_signals_let_in_now(guest, mask) else {
3281            return Ok(false);
3282        };
3283        crate::syscalls::helpers::remove_signals_discarded_under_a_temporary_mask(guest, ignored)
3284            .await?;
3285        Ok(true)
3286    }
3287
3288    /// The wait of a ppoll with a temporary mask under sequentialized threads:
3289    /// `probe`, a zero-timeout copy of the call that reads the mask staged at
3290    /// `mask` (`prepare_ppoll_probe`), runs once per scheduler turn until it
3291    /// reports a ready descriptor or an error, until a signal that the mask
3292    /// lets in ends the wait, or until `deadline` passes, which returns 0.
3293    ///
3294    /// The probe carries the mask, so the kernel ends a probe with
3295    /// ERESTARTNOHAND for a pending signal the mask lets in, and delivers it
3296    /// under the mask as the call returns. A signal that the guest's own mask
3297    /// lets in can instead stop a probe before the kernel installs the mask;
3298    /// the backend then holds it, and `KernelSignalWait` decides against the
3299    /// call's mask whether it ends the wait (`with_wait_mask`). Without that,
3300    /// the wait ran on to its deadline, or forever, with the signal held. The
3301    /// backend delivers a held signal under the guest's own mask
3302    /// (https://github.com/rrnewton/reverie/issues/1011). A signal that only
3303    /// the mask lets in can be pending at a turn's check without having ended
3304    /// that turn's probe only if it arrived from outside the guest between
3305    /// the two; the next probe, which carries the mask, ends the call for it
3306    /// and the kernel delivers it under the mask. The wait never blocks
3307    /// signals (`KernelSignalWait::block`): a probe that a signal ended would
3308    /// then leave the all-blocked mask to be restored.
3309    ///
3310    /// Every probe runs before the turn's check, as Linux's do_poll polls every
3311    /// descriptor before it tests for a pending signal. The wait holds no
3312    /// signal until the call returns (`KernelSignalWait::for_select`): the
3313    /// kernel ends a probe for any pending signal the mask lets in, `SIGCHLD`
3314    /// included, so a `SIGCHLD` the kernel posts for a child ends it at a turn
3315    /// that host timing chooses, the known gap that select and pselect6 waits
3316    /// share (https://github.com/rrnewton/hermit/issues/3146).
3317    async fn wait_on_masked_ppoll_probe<G: Guest<Self>>(
3318        &self,
3319        guest: &mut G,
3320        probe: syscalls::Ppoll,
3321        mask: usize,
3322        timerfd_scratch: Option<crate::syscalls::timerfd::Scratch>,
3323        deadline: Option<LogicalTime>,
3324    ) -> Result<i64, Error> {
3325        let temporary_mask: KernelSigset = guest
3326            .memory()
3327            .read_value(Addr::<KernelSigset>::from_raw(mask).ok_or(Errno::EFAULT)?)?;
3328        let mut signals = KernelSignalWait::for_select(guest)
3329            .with_wait_mask(Some(crate::syscalls::signal::kernel_installed_signal_mask(
3330                temporary_mask,
3331            )))
3332            .with_deadline(deadline.is_some());
3333        let mut rsrc = Resources::new(guest.thread_state().dettid);
3334        rsrc.insert(ResourceID::InternalIOPolling, Permission::W);
3335        rsrc.fyi("ppoll");
3336        rsrc.poll_deadline = poll_deadline_for(guest, deadline);
3337        rsrc.relies_on_guest_held_marks = poll_targets_guest_held(
3338            guest,
3339            probe.fds().map(|fds| fds.as_raw()),
3340            probe.nfds() as usize,
3341        );
3342        rsrc.polls_guest_state_only = rsrc.relies_on_guest_held_marks;
3343        // As for pselect6: a scheduler `Signaled` answer wakes the wait, and
3344        // the probe and the kernel's state decide whether a signal ends it.
3345        rsrc.set_signal_interrupt_errno(Errno::EINTR);
3346        rsrc.signal_state_task = guest_signal_state_task(guest);
3347        // Between probes the kernel holds the guest's own mask, while Linux's
3348        // `blocked` is the temporary one for the whole call.
3349        let mut guest_held = GuestHeldPollProof::capture(guest, probe.into(), &mut rsrc);
3350        let mut published = PublishedWaitMask::default();
3351        publish_wait_mask(guest, signals.effective_mask(), &mut published).await;
3352        let result = loop {
3353            crate::syscalls::helpers::refresh_guest_poll_state(guest, probe.into(), &mut rsrc);
3354            // A guest-held proof this call already lost stays lost.
3355            guest_held.revalidate(guest, &mut rsrc);
3356            resource_request(guest, rsrc.clone()).await;
3357            guest_held.revalidate(guest, &mut rsrc);
3358            // Virtual timerfds first (`crate::syscalls::timerfd`), in the
3359            // staged cells' scratch while they hold the stack.
3360            let next_timerfd =
3361                crate::syscalls::timerfd::publish_expired_in(guest, timerfd_scratch).await;
3362            rsrc.poll_deadline =
3363                crate::syscalls::timerfd::poll_deadline_with(guest, deadline, next_timerfd);
3364            // A pending real-time timer expiration that the temporary mask lets
3365            // the kernel deliver or discard
3366            // (https://github.com/rrnewton/hermit/issues/4089).
3367            if let Err(error) = observe_under_mask(guest, temporary_mask).await {
3368                break Err(error);
3369            }
3370            let injected = match signals.inject_first_probe(guest, probe).await {
3371                Ok(result) => result,
3372                Err(error) => break Err(error),
3373            };
3374            if !probe.syscall_would_have_blocked(injected) {
3375                break injected.map_err(Into::into);
3376            }
3377            match signals.interrupted_with_state() {
3378                Ok((false, _)) => {}
3379                // The next probe ends the call for it (see above).
3380                Ok((true, state))
3381                    if !signals.holds_signal()
3382                        && signals.interrupts_only_under_wait_mask(&state) =>
3383                {
3384                    continue;
3385                }
3386                Ok((true, _)) => break Err(Errno::ERESTARTNOHAND.into()),
3387                Err(error) => break Err(error),
3388            }
3389            rsrc.poll_attempt += 1;
3390            if let Some(deadline) = deadline
3391                && thread_observe_time(guest).await >= deadline
3392            {
3393                break Ok(0);
3394            }
3395            record_retry_event(guest, probe).await;
3396        };
3397        publish_wait_mask(guest, None, &mut published).await;
3398        refuse_held_signal_loss(guest, Sysno::ppoll, result).await
3399    }
3400
3401    #[cfg(test)]
3402    pub(crate) async fn masked_ppoll_probe_for_test<G: Guest<Self>>(
3403        &self,
3404        guest: &mut G,
3405        probe: syscalls::Ppoll,
3406        mask: usize,
3407    ) -> Result<i64, Error> {
3408        self.wait_on_masked_ppoll_probe(guest, probe, mask, None, None)
3409            .await
3410    }
3411
3412    pub(super) async fn write_ppoll_remaining<G: Guest<Self>>(
3413        &self,
3414        guest: &mut G,
3415        timeout_address: AddrMut<'_, Timespec>,
3416        timeout: Duration,
3417        started_at: LogicalTime,
3418    ) -> Result<(), Error> {
3419        let now = thread_observe_time(guest).await;
3420        let elapsed = Duration::from_nanos(now.as_nanos().saturating_sub(started_at.as_nanos()));
3421        let remaining = timeout.saturating_sub(elapsed);
3422        if let Err(error) = guest
3423            .memory()
3424            .write_value(timeout_address, &timespec_from_duration(remaining))
3425        {
3426            // Linux preserves the ppoll result when remaining-time copyout faults.
3427            trace!(?error, "ignoring ppoll timeout writeback failure");
3428        }
3429        Ok(())
3430    }
3431
3432    /// Handle a guest-internal poll call that can be fully determinized.
3433    // TODO-HUMAN-REVIEW(PR-1052): Review scheduler fairness for zero-timeout poll.
3434    #[cfg(target_arch = "x86_64")]
3435    pub async fn handle_internal_poll<G: Guest<Self>>(
3436        &self,
3437        guest: &mut G,
3438        call: syscalls::Poll,
3439    ) -> Result<i64, Error> {
3440        let timeout_millis = call.timeout();
3441        if timeout_millis == 0 {
3442            // A nonblocking poll can still be the synchronization point in a
3443            // userspace polling loop. Yield once before probing so backends
3444            // without PMU preemption cannot let that loop starve its producer.
3445            let yield_to_peer = self.cfg.backend.loopback_pollers_yield_to_peers
3446                && guest.thread_state().has_loopback_peer();
3447            resource_request(
3448                guest,
3449                zero_timeout_poll_request(guest.thread_state().dettid, yield_to_peer),
3450            )
3451            .await;
3452            crate::syscalls::timerfd::publish_expired(guest).await;
3453            Ok(guest.inject(call).await?) // Already non-blocking.
3454        } else {
3455            let maybe_timeout_ns = millis_duration_to_absolute_timeout(guest, timeout_millis).await;
3456            self.handle_internal_poll_until(guest, call, maybe_timeout_ns)
3457                .await
3458        }
3459    }
3460
3461    /// The blocking half of `handle_internal_poll`, waiting until `deadline`. A
3462    /// `restart_syscall` resumes an interrupted `poll` here with the deadline the
3463    /// wait kept, as Linux's `do_restart_poll` does
3464    /// (`Detcore::handle_restart_syscall`).
3465    #[cfg(target_arch = "x86_64")]
3466    pub(crate) async fn handle_internal_poll_until<G: Guest<Self>>(
3467        &self,
3468        guest: &mut G,
3469        call: syscalls::Poll,
3470        deadline: Option<LogicalTime>,
3471    ) -> Result<i64, Error> {
3472        let mut rsrc = Resources::new(guest.thread_state().dettid);
3473        rsrc.insert(ResourceID::InternalIOPolling, Permission::W);
3474        rsrc.fyi("poll");
3475        rsrc.set_signal_interrupt_errno(Errno::EINTR);
3476        rsrc.signal_state_task = guest_signal_state_task(guest);
3477        rsrc.relies_on_guest_held_marks = poll_targets_guest_held(
3478            guest,
3479            call.fds().map(|fds| fds.as_raw()),
3480            call.nfds() as usize,
3481        );
3482        rsrc.polls_guest_state_only = rsrc.relies_on_guest_held_marks;
3483        let result = retry_nonblocking_syscall_with_timeout(guest, call, rsrc, deadline).await;
3484        keep_restart_block(guest, &result, RestartCall::Poll(call), deadline).await;
3485        result
3486    }
3487
3488    /// Handle a poll syscall that deponds on external, nondeterminstic IO.
3489    ///
3490    /// `deadline` is the end of a positive timeout (`kernel_poll_deadline`).
3491    #[cfg(target_arch = "x86_64")]
3492    pub(crate) async fn handle_external_poll<G: Guest<Self>>(
3493        &self,
3494        guest: &mut G,
3495        call: syscalls::Poll,
3496        deadline: Option<KernelPollDeadline>,
3497    ) -> Result<i64, Error> {
3498        let len = call.nfds();
3499        let time_delta = Duration::from_millis(call.timeout() as u64);
3500
3501        if len == 0 && time_delta.is_zero() {
3502            let request = Self::sleep_request(guest, time_delta).await;
3503            resource_request(guest, request).await;
3504            Ok(0)
3505        } else if call.timeout() == 0 {
3506            // Not shielded, as no zero-timeout probe is: a signal pending at
3507            // its entry still ends it with a restart code.
3508            print_poll(&call);
3509            self.run_kernel_poll(guest, call, deadline).await
3510        } else {
3511            print_poll(&call);
3512            let result = self
3513                .record_or_replay_shielded_wait(guest, Syscall::Poll(call))
3514                .await;
3515            let result = kernel_poll_result(&call, result, deadline.is_some());
3516            // The shielded wait replaced `run_kernel_poll` on this path, but
3517            // a signal that ends the poll still leaves the guest's own
3518            // `restart_syscall` to resume it, so keep the same record
3519            // `run_kernel_poll_as` keeps.
3520            if let Some(deadline) = deadline {
3521                keep_restart_block(
3522                    guest,
3523                    &result,
3524                    RestartCall::KernelPoll(call, deadline),
3525                    None,
3526                )
3527                .await;
3528            }
3529            result
3530        }
3531    }
3532
3533    /// Runs `call` in the kernel through the recorder. A signal that ends a
3534    /// poll with a positive timeout there leaves `ERESTART_RESTARTBLOCK`, and
3535    /// with no handler Linux restarts it through `restart_syscall` until the
3536    /// end time it kept (`do_restart_poll`), so such a poll keeps `deadline`
3537    /// for that restart (`RestartCall::KernelPoll`). A poll without a deadline
3538    /// restarts by running again (`kernel_poll_result`), and one that a
3539    /// signal stopped before it ran returns `ERESTARTNOHAND`
3540    /// (`kernel_poll_restart_code`).
3541    #[cfg(target_arch = "x86_64")]
3542    async fn run_kernel_poll<G: Guest<Self>>(
3543        &self,
3544        guest: &mut G,
3545        call: syscalls::Poll,
3546        deadline: Option<KernelPollDeadline>,
3547    ) -> Result<i64, Error> {
3548        self.run_kernel_poll_as(guest, call, deadline, Syscall::Poll(call))
3549            .await
3550    }
3551
3552    /// `run_kernel_poll`, with `run` the call that runs for `call` (or that
3553    /// replay serves): the poll itself, or the guest's own `restart_syscall`
3554    /// that restarts it (`resume_kernel_poll`).
3555    #[cfg(target_arch = "x86_64")]
3556    async fn run_kernel_poll_as<G: Guest<Self>>(
3557        &self,
3558        guest: &mut G,
3559        call: syscalls::Poll,
3560        deadline: Option<KernelPollDeadline>,
3561        run: Syscall,
3562    ) -> Result<i64, Error> {
3563        debug_assert!(
3564            polls_in_kernel(self.cfg.sequentialize_threads, self.cfg.recordreplay_modes),
3565            "a plain or strict run emulates poll; it never runs one in the kernel"
3566        );
3567        let raw = self
3568            .record_or_replay_blocking_as(guest, Syscall::Poll(call), run)
3569            .await;
3570        let before_run = poll_stopped_before_it_ran(&raw);
3571        let result = kernel_poll_result(&call, raw, deadline.is_some());
3572        if let Some(deadline) = deadline {
3573            keep_restart_block(
3574                guest,
3575                &result,
3576                RestartCall::KernelPoll(call, deadline),
3577                None,
3578            )
3579            .await;
3580        }
3581        kernel_poll_restart_code(result, before_run)
3582    }
3583
3584    /// The kernel's restart of a `poll` with a positive timeout that a signal
3585    /// ended in the kernel (`RestartCall::KernelPoll`). `restart` is the
3586    /// guest's own `restart_syscall`. It runs in place: the poll ran in the
3587    /// kernel, so the thread's restart block describes it, and Linux's
3588    /// `do_restart_poll` waits until the end time the poll kept and writes its
3589    /// `revents`. Nothing runs in its stead, so no signal is taken off the queue
3590    /// before the restart runs, as the single step of a poll injected for it
3591    /// did: the kernel delivers pending signals in its own order when the
3592    /// restart returns. Record keeps the restart's outputs as the poll's, and
3593    /// replay serves them (`hermit_record_format::event::InterruptedPoll`).
3594    ///
3595    /// The restart names the poll in its argument registers, which Linux
3596    /// leaves as the poll left them (`restart_names_poll`). If it does not,
3597    /// the poll runs again with the time left until `deadline`, as before:
3598    /// rounded up to whole milliseconds, so the poll never ends before its
3599    /// deadline, and 0 once the deadline has passed. Under record and replay
3600    /// that poll keeps the timeout it was issued with (`KernelPollDeadline`).
3601    /// Either way it runs the recorded kernel call, never the zero-length
3602    /// sleep `handle_external_poll` makes of a poll of no descriptors.
3603    #[cfg(target_arch = "x86_64")]
3604    pub(crate) async fn resume_kernel_poll<G: Guest<Self>>(
3605        &self,
3606        guest: &mut G,
3607        call: syscalls::Poll,
3608        deadline: KernelPollDeadline,
3609        restart: Syscall,
3610    ) -> Result<i64, Error> {
3611        if restart_names_poll(&restart, &call) {
3612            return self
3613                .run_kernel_poll_as(guest, call, Some(deadline), restart)
3614                .await;
3615        }
3616        let call = match deadline.0 {
3617            Some(end) => call.with_timeout(remaining_poll_millis(Instant::now(), end)),
3618            None => call,
3619        };
3620        self.run_kernel_poll(guest, call, Some(deadline)).await
3621    }
3622
3623    /// epoll_create1 syscall
3624    pub async fn handle_epoll_create1<G: Guest<Self>>(
3625        &self,
3626        guest: &mut G,
3627        call: syscalls::EpollCreate1,
3628    ) -> Result<i64, Error> {
3629        let dettid = guest.thread_state().dettid;
3630        resource_request(guest, Resources::new(dettid)).await; // empty request
3631        let fd = self.record_or_replay(guest, call).await? as RawFd;
3632        // Register the epoll fd in the DetFd table like every other
3633        // fd-creating syscall (openat, eventfd2, pipe2, socket, ...). Without
3634        // this, later operations that consult the table via `with_detfd` /
3635        // `dup_fd` (F_GETFL, F_SETFD, F_DUPFD[_CLOEXEC], dup, ...) would fail
3636        // with EBADF even though the underlying kernel fd is valid. This broke,
3637        // for example, running rustup proxies (cargo/rustc) under hermit, whose
3638        // tokio runtime dups its epoll fd at startup.
3639        //
3640        // EPOLL_CLOEXEC shares the same bit value as O_CLOEXEC, so we can carry
3641        // the cloexec flag straight across.
3642        self.add_fd(
3643            guest,
3644            fd,
3645            OFlag::from_bits_truncate(call.flags().bits()),
3646            FdType::Epoll,
3647        )
3648        .await?;
3649        Ok(fd as i64)
3650    }
3651
3652    /// Apply an ADD, MOD, or DEL mutation to an epoll interest list.
3653    ///
3654    /// Determinism: strict execution serializes this mutation, so its result depends only on the
3655    /// operation, event payload, and deterministically reconstructed epoll/file-descriptor state.
3656    /// Record/replay records each answer and replays it; replay reapplies only the recorded
3657    /// successes, so its epoll sets hold what the recording's held
3658    /// (<https://github.com/rrnewton/hermit/issues/31>).
3659    pub async fn handle_epoll_ctl<G: Guest<Self>>(
3660        &self,
3661        guest: &mut G,
3662        call: syscalls::EpollCtl,
3663    ) -> Result<i64, Error> {
3664        let dettid = guest.thread_state().dettid;
3665        resource_request(guest, Resources::new(dettid)).await; // empty request
3666        Ok(self.record_or_replay(guest, call).await?)
3667    }
3668
3669    /// epoll_pwait syscall (MAYHANG)
3670    pub async fn handle_epoll_pwait<G: Guest<Self>>(
3671        &self,
3672        guest: &mut G,
3673        call: syscalls::EpollPwait,
3674    ) -> Result<i64, Error> {
3675        // This used to unconditionally inject the raw
3676        // call and wait for it to return. With an infinite timeout under
3677        // `--sequentialize-threads` that DEADLOCKS the whole guest: the calling
3678        // task holds the scheduler turn while blocked in the kernel, and the
3679        // only task that could ever satisfy the wait is sitting in the run queue
3680        // waiting for a turn that never comes. Observed as `cmake` configure
3681        // hanging forever at zero CPU with a grandchild frozen mid-`openat`;
3682        // hermit's own scheduler log ends at `COMMIT turn N, dettid <parent>`
3683        // injecting `epoll_pwait(..., -1, NULL, 8)` with `queue len 2`.
3684        //
3685        // WHO ACTUALLY REACHES THIS, measured rather than assumed. It is NOT
3686        // glibc's `epoll_wait(2)` on this architecture: glibc calls
3687        // `SYS_epoll_pwait` only where `__NR_epoll_wait` does not exist (arm64
3688        // and friends). x86_64 has it, and `strace` on glibc 2.34/x86_64 shows
3689        // a plain `epoll_wait` syscall, which `handle_epoll_wait` has always
3690        // handled correctly. The callers that land here are programs issuing
3691        // `epoll_pwait` DIRECTLY -- libuv does, which is how the original
3692        // `cmake` hang was found. With a NULL sigmask the two calls are
3693        // semantically identical, so route them together.
3694        //
3695        // A NON-NULL sigmask swaps the signal mask atomically for the duration
3696        // of the wait. Under record and replay a masked call that may wait is
3697        // a kernel wait outside the turn (`record_or_replay_masked_epoll_pwait`).
3698        // A sequentialized run on a backend whose kernel reports the guest's
3699        // signal state polls it under that mask
3700        // (`handle_internal_masked_epoll_pwait`). Otherwise, and for a zero
3701        // timeout, it keeps its turn: on another backend, or without
3702        // sequentialized threads, such a call can still block the scheduler
3703        // (https://github.com/rrnewton/hermit/issues/4041), and because it
3704        // never sleeps outside the runnable set the scheduler needs no record
3705        // of its temporary mask (`Resources::blocked_signal_mask`).
3706        if call.sigmask().is_some() && self.cfg.recordreplay_modes && call.timeout() != 0 {
3707            return self.record_or_replay_masked_epoll_pwait(guest, call).await;
3708        }
3709        // Under the in-guest delivery gate (A5 step 3c.1d, D39) a masked call
3710        // polls with no mask on its probes and decides under its temporary
3711        // mask, with D37's EINTR exception; a zero timeout makes one maskless
3712        // probe (`gated_masked_epoll_pwait`; IR-9).
3713        if call.sigmask().is_some()
3714            && self.cfg.sequentialize_threads
3715            && self.cfg.in_guest_signal_delivery
3716        {
3717            return Box::pin(self.gated_masked_epoll_pwait(guest, call)).await;
3718        }
3719        if call.sigmask().is_some()
3720            && call.timeout() != 0
3721            && self.cfg.sequentialize_threads
3722            && self.cfg.backend.supports_blocked_wait_signal_interruption
3723        {
3724            return self.handle_internal_masked_epoll_pwait(guest, call).await;
3725        }
3726        if call.sigmask().is_some() {
3727            self.observe_under_temporary_mask(guest, call.sigmask(), call.sigsetsize(), true)
3728                .await?;
3729            let dettid = guest.thread_state().dettid;
3730            resource_request(guest, Resources::new(dettid)).await; // empty request
3731            // The raw call runs in this turn. A zero timeout only probes, once a
3732            // virtual timerfd is readable exactly when its deadline has passed;
3733            // any other waits in the host (`crate::syscalls::timerfd`).
3734            if call.timeout() == 0 {
3735                crate::syscalls::timerfd::publish_expired(guest).await;
3736            } else {
3737                crate::syscalls::timerfd::refuse_host_wait(guest, "epoll_pwait with a signal mask");
3738            }
3739            return Ok(self.record_or_replay(guest, call).await?);
3740        }
3741        if self.cfg.recordreplay_modes && call.timeout() == 0 {
3742            // Cannot block, but still yield a scheduler turn so a polling thread
3743            // cannot monopolize the guest between preemptions.
3744            resource_request(guest, Resources::new(guest.thread_state().dettid)).await;
3745            Ok(self.record_or_replay(guest, call).await?)
3746        } else if !self.cfg.sequentialize_threads || self.cfg.recordreplay_modes {
3747            self.record_or_replay_shielded_wait(guest, Syscall::EpollPwait(call))
3748                .await
3749        } else {
3750            self.handle_internal_epoll_pwait(guest, call).await
3751        }
3752    }
3753
3754    /// A masked `epoll_pwait` that may wait, under sequentialized threads on a
3755    /// backend whose kernel reports the guest's signal state. It used to run
3756    /// the real call in the thread's turn, so nothing else ran while it waited
3757    /// and it returned 0 at once or at its timeout
3758    /// (https://github.com/rrnewton/hermit/issues/4041). It now polls, as an
3759    /// unmasked `epoll_pwait` does, with probes that carry no mask, and decides
3760    /// interruption against the temporary mask read at entry
3761    /// (`retry_blocking_wait_under_mask`): a signal the mask blocks never ends
3762    /// the call and is delivered after it, and one the mask lets in ends it
3763    /// with EINTR although the guest's own mask may block it. A zero-timeout
3764    /// `epoll_pwait` never looks at pending signals, so no probe could be ended
3765    /// by one in any case. A signal that only the temporary mask lets in stays
3766    /// pending under the guest's own mask once it is put back, and is delivered
3767    /// under the temporary mask (`inject_signal_delivery`), as for a masked
3768    /// pselect6. Linux checks the mask's size, then reads it, before it looks
3769    /// at the epoll descriptor.
3770    async fn handle_internal_masked_epoll_pwait<G: Guest<Self>>(
3771        &self,
3772        guest: &mut G,
3773        call: syscalls::EpollPwait,
3774    ) -> Result<i64, Error> {
3775        if call.sigsetsize() != KERNEL_SIGSET_SIZE {
3776            return Err(Errno::EINVAL.into());
3777        }
3778        let mask_address = call.sigmask().ok_or(Errno::EFAULT)?;
3779        let mask = crate::syscalls::signal::restart_if_first_probe_not_run(
3780            read_kernel_sigset(guest, mask_address.cast()).await,
3781        )?;
3782        super::signal_shadow::note_wait_mask(guest, mask);
3783        // A pending real-time timer expiration that the mask lets the kernel
3784        // deliver or discard (https://github.com/rrnewton/hermit/issues/4089).
3785        observe_under_mask(guest, mask).await?;
3786        let mask = sanitize_ppoll_signal_mask(mask);
3787        let deadline = millis_duration_to_absolute_timeout(guest, call.timeout()).await;
3788        self.wait_on_masked_epoll(
3789            guest,
3790            call.with_sigmask(None),
3791            mask,
3792            deadline,
3793            "epoll_pwait",
3794        )
3795        .await
3796    }
3797
3798    /// The wait of a masked epoll_pwait or epoll_pwait2 under sequentialized
3799    /// threads, once the call's temporary `mask` (sanitized) has been read at
3800    /// entry: `probe`, an unmasked epoll_pwait, polls until an event, an
3801    /// error, a signal that `mask` lets in (EINTR), or `deadline`, which
3802    /// returns 0 (`handle_internal_masked_epoll_pwait`).
3803    async fn wait_on_masked_epoll<G: Guest<Self>>(
3804        &self,
3805        guest: &mut G,
3806        probe: syscalls::EpollPwait,
3807        mask: KernelSigset,
3808        deadline: Option<LogicalTime>,
3809        fyi: &'static str,
3810    ) -> Result<i64, Error> {
3811        let mut rsrc = Resources::new(guest.thread_state().dettid);
3812        rsrc.insert(ResourceID::InternalIOPolling, Permission::W);
3813        rsrc.fyi(fyi);
3814        let (result, deliver) = retry_blocking_wait_under_mask(
3815            guest,
3816            probe,
3817            rsrc,
3818            // Both calls return 0 at their timeout.
3819            deadline.map(|deadline| (deadline, Ok(0))),
3820            Some(crate::syscalls::signal::kernel_installed_signal_mask(mask)),
3821        )
3822        .await;
3823        if deliver && matches!(result, Err(Error::Errno(Errno::EINTR))) {
3824            // The probes staged nothing, so they say nothing about room for
3825            // these 24 bytes. `stage_private_cells`, which tests a window by
3826            // reading it, finds none only on a stack whose pointer is less
3827            // than 24 bytes above unreadable memory and whose red zone is
3828            // unreadable too
3829            // (https://github.com/rrnewton/hermit/issues/4113). Then the signal
3830            // stays pending under the guest's own mask, and the call returns
3831            // EINTR without its handler run and logs this warning.
3832            match stage_private_cells(guest, PrivateCells::MaskAndZeroTimeout(mask), &[]).await? {
3833                Some(staged) => {
3834                    inject_signal_delivery(guest, staged.mask(), staged.after_mask()).await;
3835                    staged.release(guest);
3836                }
3837                None => tracing::warn!(
3838                    "[tid {}] no room to deliver an {fyi} signal under its temporary mask; its \
3839                     handler runs only once the guest unblocks it",
3840                    guest.tid()
3841                ),
3842            }
3843        }
3844        result
3845    }
3846
3847    /// A masked `epoll_pwait` that may wait, under record and replay. As for a
3848    /// masked pselect6 or ppoll there, the kernel owns the wait outside the
3849    /// scheduler turn, so the timer's signal can end it as on Linux; it kept
3850    /// its turn before, so nothing else ran and the call returned at its
3851    /// timeout (https://github.com/rrnewton/hermit/issues/4041). The call sleeps
3852    /// under a private copy of the mask read at entry, which a sibling cannot
3853    /// rewrite, and the scheduler records its sleeping mask as unknown
3854    /// (`Resources::blocked_signal_mask_unknown`). Replay serves the call from
3855    /// the recording, so for a recorded EINTR it delivers the signal under that
3856    /// mask (`end_replayed_pselect6_under_its_temporary_mask`).
3857    async fn record_or_replay_masked_epoll_pwait<G: Guest<Self>>(
3858        &self,
3859        guest: &mut G,
3860        call: syscalls::EpollPwait,
3861    ) -> Result<i64, Error> {
3862        if call.sigsetsize() != KERNEL_SIGSET_SIZE {
3863            return Err(Errno::EINVAL.into());
3864        }
3865        let mask_address = call.sigmask().ok_or(Errno::EFAULT)?;
3866        let mask = crate::syscalls::signal::restart_if_first_probe_not_run(
3867            read_kernel_sigset(guest, mask_address.cast()).await,
3868        )?;
3869        super::signal_shadow::note_wait_mask(guest, mask);
3870        // The copy may go in the red zone on a stack with no scratch below it,
3871        // clear of the events buffer the kernel writes (`stage_private_cells`;
3872        // re-review of https://github.com/rrnewton/hermit/pull/4087).
3873        let live: Vec<std::ops::Range<usize>> = call
3874            .events()
3875            .map(|events| {
3876                let start = events.as_raw();
3877                let len = usize::try_from(call.maxevents().max(0)).unwrap_or(0)
3878                    * std::mem::size_of::<libc::epoll_event>();
3879                start..start.saturating_add(len)
3880            })
3881            .into_iter()
3882            .collect();
3883        let Some(staged) = stage_private_cells(guest, PrivateCells::Mask(mask), &live).await?
3884        else {
3885            return self.refuse_unstageable_epoll_pwait_mask(guest, call).await;
3886        };
3887        let result = self
3888            .record_or_replay_blocking_with_unknown_mask(
3889                guest,
3890                Syscall::EpollPwait(call.with_sigmask(Addr::from_raw(staged.mask()))),
3891            )
3892            .await;
3893        staged.release(guest);
3894        if self.cfg.replaying && matches!(result, Err(Error::Errno(Errno::EINTR))) {
3895            end_replayed_pselect6_under_its_temporary_mask(guest, mask).await;
3896        }
3897        result
3898    }
3899
3900    /// Refuses a masked record/replay `epoll_pwait` whose private mask copy has
3901    /// nowhere to go: no stack scratch below the red zone, and every red-zone
3902    /// window overlaps the events buffer (`refuse_unstageable_pselect6_mask`
3903    /// for pselect6). A run that allows unsupported operations forwards the
3904    /// guest's own mask.
3905    async fn refuse_unstageable_epoll_pwait_mask<G: Guest<Self>>(
3906        &self,
3907        guest: &mut G,
3908        call: syscalls::EpollPwait,
3909    ) -> Result<i64, Error> {
3910        use std::io::Write as _;
3911        let message = "unsupported: epoll_pwait with a temporary signal mask, on a stack \
3912            with no room below its red zone whose red zone the call's events buffer fills: \
3913            Hermit has nowhere to put the private copy of the mask \
3914            (https://github.com/rrnewton/hermit/pull/4087)";
3915        tracing::error!("[tid {}] {message}", guest.tid());
3916        if self.cfg.panic_on_unsupported_syscalls {
3917            let _ = writeln!(crate::util::RetryingStderr, "{message}");
3918            return self
3919                .refuse_unserviceable_operation(guest, Sysno::epoll_pwait, Errno::EFAULT)
3920                .await;
3921        }
3922        self.record_or_replay_blocking_with_unknown_mask(guest, Syscall::EpollPwait(call))
3923            .await
3924    }
3925
3926    /// Handle a guest-internal `epoll_pwait` (NULL sigmask) that can be fully
3927    /// determinized. Mirrors `handle_internal_epoll_wait`.
3928    pub async fn handle_internal_epoll_pwait<G: Guest<Self>>(
3929        &self,
3930        guest: &mut G,
3931        call: syscalls::EpollPwait,
3932    ) -> Result<i64, Error> {
3933        let timeout_millis = call.timeout();
3934        if timeout_millis == 0 {
3935            // Cannot block, but must still yield a scheduler turn: a
3936            // zero-timeout polling loop that never requests a resource can
3937            // monopolize the guest between preemptions and starve the producer
3938            // it is polling for. `handle_poll` takes a turn for every
3939            // sequential mode, and the record/replay arm of `handle_epoll_pwait`
3940            // does the same; before this PR routed NULL-sigmask `epoll_pwait`
3941            // here, the old handler always made an empty request. Omitting it
3942            // only on the plain-strict path would be a scheduling regression,
3943            // not a refactor.
3944            if self.cfg.sequentialize_threads {
3945                let yield_to_peer = self.cfg.backend.loopback_pollers_yield_to_peers
3946                    && guest.thread_state().has_loopback_peer();
3947                resource_request(
3948                    guest,
3949                    zero_timeout_poll_request(guest.thread_state().dettid, yield_to_peer),
3950                )
3951                .await;
3952            }
3953            crate::syscalls::timerfd::publish_expired(guest).await;
3954            Ok(guest.inject(call).await?) // Already non-blocking.
3955        } else {
3956            let maybe_timeout_ns = millis_duration_to_absolute_timeout(guest, timeout_millis).await;
3957            let mut rsrc = Resources::new(guest.thread_state().dettid);
3958            rsrc.insert(ResourceID::InternalIOPolling, Permission::W);
3959            rsrc.fyi("epoll_pwait");
3960            retry_nonblocking_syscall_with_timeout(guest, call, rsrc, maybe_timeout_ns).await
3961        }
3962    }
3963
3964    /// epoll_pwait2 syscall (MAYHANG).
3965    ///
3966    /// epoll_pwait2 is epoll_pwait with a `struct timespec *` timeout instead of
3967    /// an int-milliseconds timeout; recent glibc implements epoll_wait/
3968    /// epoll_pwait via epoll_pwait2 when the kernel supports it. The pinned
3969    /// Reverie revision has no typed variant, so it arrives as a raw
3970    /// `Syscall::Other` and is dispatched here by Sysno. Detcore treats it
3971    /// like epoll_pwait: under record and replay a call that may wait waits
3972    /// outside the turn; under sequentialized threads it polls
3973    /// (`handle_internal_epoll_pwait2`); otherwise, and for a zero timeout, a
3974    /// scheduler yield point is followed by the raw call.
3975    // AUTONOMOUS-BOT-IMPLEMENTED
3976    // TODO-HUMAN-REVIEW(#773)
3977    pub async fn handle_epoll_pwait2<G: Guest<Self>>(
3978        &self,
3979        guest: &mut G,
3980        call: Syscall,
3981    ) -> Result<i64, Error> {
3982        // epoll_pwait2(epfd, events, maxevents, timeout, sigmask, sigsetsize)
3983        let (_, args) = call.into_parts();
3984        // Under the in-guest delivery gate (A5 step 3c.1d, D39), as a masked
3985        // epoll_pwait (`gated_masked_epoll_pwait2`), whose D41 check comes
3986        // before anything else, the observation and the timeout's read below
3987        // included (round-2 ruling 3); its mask's read is then the call's
3988        // first injection.
3989        if !self.cfg.recordreplay_modes
3990            && self.cfg.sequentialize_threads
3991            && args.arg4 != 0
3992            && self.cfg.in_guest_signal_delivery
3993        {
3994            return Box::pin(self.gated_masked_epoll_pwait2(guest, args, true)).await;
3995        }
3996        // The call's first injection, if it makes one; when it does not, the
3997        // mask's read below is the first.
3998        let observed = self
3999            .observe_under_temporary_mask(
4000                guest,
4001                Addr::<libc::sigset_t>::from_raw(args.arg4),
4002                args.arg5,
4003                true,
4004            )
4005            .await?;
4006        // A NULL timeout waits forever; a zero timespec cannot wait and keeps
4007        // the turn.
4008        let zero_timeout = args.arg3 != 0
4009            && Addr::<Timespec>::from_raw(args.arg3)
4010                .and_then(|timeout| guest.memory().read_value(timeout).ok())
4011                .is_some_and(|timeout: Timespec| timeout.tv_sec == 0 && timeout.tv_nsec == 0);
4012        // Under record and replay, a call that may wait does so outside the
4013        // scheduler turn, as epoll_pwait's does: unmasked through the logged
4014        // background path, masked as a kernel wait under a private copy of its
4015        // mask (`record_or_replay_masked_epoll_pwait2`).
4016        if self.cfg.recordreplay_modes {
4017            if !zero_timeout {
4018                return if args.arg4 != 0 {
4019                    self.record_or_replay_masked_epoll_pwait2(guest, args, !observed)
4020                        .await
4021                } else {
4022                    self.record_or_replay_shielded_wait(guest, call).await
4023                };
4024            }
4025        } else if self.cfg.sequentialize_threads
4026            && !zero_timeout
4027            && (args.arg4 == 0 || self.cfg.backend.supports_blocked_wait_signal_interruption)
4028        {
4029            // With threads sequentialized, a call that may wait polls, as
4030            // epoll_pwait does, rather than waiting in the kernel inside the
4031            // turn, where no other guest thread could run
4032            // (https://github.com/rrnewton/hermit/issues/4228). A masked call
4033            // on a backend whose kernel does not report the guest's signal
4034            // state keeps the turn, as a masked epoll_pwait does there.
4035            return self
4036                .handle_internal_epoll_pwait2(guest, args, !observed)
4037                .await;
4038        }
4039        let dettid = guest.thread_state().dettid;
4040        resource_request(guest, Resources::new(dettid)).await; // empty request
4041        // The raw call runs in this turn. A zero timeout only probes, once a
4042        // virtual timerfd is readable exactly when its deadline has passed;
4043        // any other wait here is in the host (`crate::syscalls::timerfd`).
4044        if zero_timeout {
4045            crate::syscalls::timerfd::publish_expired(guest).await;
4046        } else {
4047            crate::syscalls::timerfd::refuse_host_wait(guest, "epoll_pwait2 with a signal mask");
4048        }
4049        // Only a call that may wait under the thread's own mask needs the
4050        // shield; a masked one waits under its own temporary mask.
4051        let shield = if zero_timeout || args.arg4 != 0 {
4052            crate::syscalls::helpers::IgnoredSignalShield::none()
4053        } else {
4054            crate::syscalls::helpers::IgnoredSignalShield::raise(guest).await?
4055        };
4056        let result = self
4057            .record_or_replay(guest, call)
4058            .await
4059            .map_err(Error::from);
4060        shield.lower(guest).await?;
4061        result
4062    }
4063
4064    /// `record_or_replay_blocking` for a wait in the kernel under the thread's
4065    /// own mask, with the signals the guest ignores blocked for the wait
4066    /// (`IgnoredSignalShield`): an `epoll_wait`, or an `epoll_pwait` or
4067    /// `epoll_pwait2` without a mask, or a `poll`, `select`, or `ppoll` or
4068    /// `pselect6` naming no mask. Linux discards those signals at generation,
4069    /// so they never end the wait, but ptrace queues them.
4070    async fn record_or_replay_shielded_wait<G: Guest<Self>>(
4071        &self,
4072        guest: &mut G,
4073        call: Syscall,
4074    ) -> Result<i64, Error> {
4075        let shield = crate::syscalls::helpers::IgnoredSignalShield::raise(guest).await?;
4076        let result = self.record_or_replay_blocking(guest, call).await;
4077        shield.lower(guest).await?;
4078        result
4079    }
4080
4081    /// `guest.inject(call)` for a `select`, or a `pselect6` handed no mask,
4082    /// that waits in the kernel without sequentialized threads, with the
4083    /// signals the guest ignores blocked for the wait when it `may_wait`
4084    /// (`IgnoredSignalShield`).
4085    async fn inject_shielded_wait<G: Guest<Self>, S: SyscallInfo>(
4086        &self,
4087        guest: &mut G,
4088        call: S,
4089        may_wait: bool,
4090    ) -> Result<i64, Error> {
4091        let shield = if may_wait {
4092            crate::syscalls::helpers::IgnoredSignalShield::raise(guest).await?
4093        } else {
4094            crate::syscalls::helpers::IgnoredSignalShield::none()
4095        };
4096        let result = guest.inject(call).await.map_err(Error::from);
4097        shield.lower(guest).await?;
4098        result
4099    }
4100
4101    /// An epoll_pwait2 that may wait, under sequentialized threads: it polls
4102    /// with zero-timeout probes, as epoll_pwait does. A probe is an
4103    /// epoll_pwait with no mask, whose answer for a zero timeout is the same
4104    /// as epoll_pwait2's. Linux copies and checks the timespec before the
4105    /// mask (`record_or_replay_masked_epoll_pwait2`), then the mask's size
4106    /// and the mask, before it looks at the epoll descriptor. An unmasked
4107    /// call waits as an unmasked epoll_pwait does
4108    /// (`handle_internal_epoll_pwait`), a masked one as a masked epoll_pwait
4109    /// does (`wait_on_masked_epoll`). It used to make the real call in the
4110    /// thread's turn, so it returned 0 at its timeout, or never, when only
4111    /// another guest thread could make an event ready
4112    /// (https://github.com/rrnewton/hermit/issues/4228).
4113    /// `first` says the mask's read is the call's first injection
4114    /// (`restart_if_first_probe_not_run`).
4115    async fn handle_internal_epoll_pwait2<G: Guest<Self>>(
4116        &self,
4117        guest: &mut G,
4118        args: SyscallArgs,
4119        first: bool,
4120    ) -> Result<i64, Error> {
4121        let timeout = match Addr::<Timespec>::from_raw(args.arg3) {
4122            Some(timeout) => {
4123                let timeout: Timespec = guest.memory().read_value(timeout)?;
4124                Some(ppoll_timeout_duration(timeout)?)
4125            }
4126            None => None,
4127        };
4128        let probe = syscalls::EpollPwait::new()
4129            .with_epfd(args.arg0 as i32)
4130            .with_events(AddrMut::from_raw(args.arg1))
4131            .with_maxevents(args.arg2 as i32)
4132            .with_timeout(0)
4133            .with_sigmask(None)
4134            .with_sigsetsize(args.arg5);
4135        if args.arg4 == 0 {
4136            let (_, deadline) = ppoll_start(guest, timeout).await;
4137            let mut rsrc = Resources::new(guest.thread_state().dettid);
4138            rsrc.insert(ResourceID::InternalIOPolling, Permission::W);
4139            rsrc.fyi("epoll_pwait2");
4140            return retry_nonblocking_syscall_with_timeout(guest, probe, rsrc, deadline).await;
4141        }
4142        if args.arg5 != KERNEL_SIGSET_SIZE {
4143            return Err(Errno::EINVAL.into());
4144        }
4145        let mask_address = Addr::<libc::sigset_t>::from_raw(args.arg4).ok_or(Errno::EFAULT)?;
4146        let read = read_kernel_sigset(guest, mask_address).await;
4147        let mask = if first {
4148            crate::syscalls::signal::restart_if_first_probe_not_run(read)?
4149        } else {
4150            read?
4151        };
4152        super::signal_shadow::note_wait_mask(guest, mask);
4153        let (_, deadline) = ppoll_start(guest, timeout).await;
4154        // A pending real-time timer expiration that the mask lets the kernel
4155        // deliver or discard (https://github.com/rrnewton/hermit/issues/4089).
4156        observe_under_mask(guest, mask).await?;
4157        let mask = sanitize_ppoll_signal_mask(mask);
4158        self.wait_on_masked_epoll(guest, probe, mask, deadline, "epoll_pwait2")
4159            .await
4160    }
4161
4162    /// Before a call that sleeps or probes under the temporary mask at `mask`
4163    /// (`sigsetsize` bytes), refuse the run by name if the mask lets the kernel
4164    /// take a pending real-time timer expiration that every thread blocked when
4165    /// it expired, and discard the ones it lets the kernel discard
4166    /// (https://github.com/rrnewton/hermit/issues/4089). A mask the kernel would
4167    /// reject fails the call itself, which then observes nothing.
4168    ///
4169    /// Returns whether it made an injection, which it does only while a
4170    /// real-time expiration is kept pending. With `first`, its mask read is
4171    /// the call's first injection, so a signal that stops it restarts the call
4172    /// (`restart_if_first_probe_not_run`); any other failed read is left to
4173    /// the call itself, as before.
4174    async fn observe_under_temporary_mask<G: Guest<Self>>(
4175        &self,
4176        guest: &mut G,
4177        mask: Option<Addr<'_, libc::sigset_t>>,
4178        sigsetsize: usize,
4179        first: bool,
4180    ) -> Result<bool, Error> {
4181        let Some(mask) = mask else {
4182            return Ok(false);
4183        };
4184        if sigsetsize != KERNEL_SIGSET_SIZE
4185            || !crate::scheduler::real_time_expiration_kept_pending()
4186        {
4187            return Ok(false);
4188        }
4189        match read_kernel_sigset(guest, mask).await {
4190            Ok(mask) => observe_under_mask(guest, mask).await?,
4191            Err(error @ Error::Errno(Errno::ERESTARTSYS)) if first => {
4192                return crate::syscalls::signal::restart_if_first_probe_not_run(Err(error));
4193            }
4194            Err(_) => {}
4195        }
4196        Ok(true)
4197    }
4198
4199    /// `record_or_replay_masked_epoll_pwait` for `epoll_pwait2`, whose mask
4200    /// pointer and size are its fifth and sixth arguments.
4201    /// `first` says the mask's read is the call's first injection
4202    /// (`restart_if_first_probe_not_run`).
4203    async fn record_or_replay_masked_epoll_pwait2<G: Guest<Self>>(
4204        &self,
4205        guest: &mut G,
4206        args: SyscallArgs,
4207        first: bool,
4208    ) -> Result<i64, Error> {
4209        // Linux copies and checks the timespec before the mask, so an
4210        // unreadable timeout is EFAULT and an invalid one EINVAL whatever the
4211        // mask and its size (Claude review of
4212        // https://github.com/rrnewton/hermit/pull/4128).
4213        if let Some(timeout) = Addr::<Timespec>::from_raw(args.arg3) {
4214            let timeout: Timespec = guest.memory().read_value(timeout)?;
4215            ppoll_timeout_duration(timeout)?;
4216        }
4217        if args.arg5 != KERNEL_SIGSET_SIZE {
4218            return Err(Errno::EINVAL.into());
4219        }
4220        let mask_address = Addr::<libc::sigset_t>::from_raw(args.arg4).ok_or(Errno::EFAULT)?;
4221        let read = read_kernel_sigset(guest, mask_address).await;
4222        let mask = if first {
4223            crate::syscalls::signal::restart_if_first_probe_not_run(read)?
4224        } else {
4225            read?
4226        };
4227        super::signal_shadow::note_wait_mask(guest, mask);
4228        // The copy may go in the red zone on a stack with no scratch below it,
4229        // clear of the events buffer the kernel writes and of the timeout it
4230        // reads, as for epoll_pwait (`stage_private_cells`).
4231        let events_len = usize::try_from((args.arg2 as i32).max(0)).unwrap_or(0)
4232            * std::mem::size_of::<libc::epoll_event>();
4233        let live: Vec<std::ops::Range<usize>> = [
4234            (args.arg1, events_len),
4235            (args.arg3, std::mem::size_of::<Timespec>()),
4236        ]
4237        .into_iter()
4238        .filter(|&(start, _)| start != 0)
4239        .map(|(start, len)| start..start.saturating_add(len))
4240        .collect();
4241        let Some(staged) = stage_private_cells(guest, PrivateCells::Mask(mask), &live).await?
4242        else {
4243            return self.refuse_unstageable_epoll_pwait2_mask(guest, args).await;
4244        };
4245        let mut private = args;
4246        private.arg4 = staged.mask();
4247        let result = self
4248            .record_or_replay_blocking_with_unknown_mask(
4249                guest,
4250                Syscall::Other(Sysno::epoll_pwait2, private),
4251            )
4252            .await;
4253        staged.release(guest);
4254        if self.cfg.replaying && matches!(result, Err(Error::Errno(Errno::EINTR))) {
4255            end_replayed_pselect6_under_its_temporary_mask(guest, mask).await;
4256        }
4257        result
4258    }
4259
4260    /// `refuse_unstageable_epoll_pwait_mask` for `epoll_pwait2`, whose red
4261    /// zone the events buffer and the timeout fill.
4262    async fn refuse_unstageable_epoll_pwait2_mask<G: Guest<Self>>(
4263        &self,
4264        guest: &mut G,
4265        args: SyscallArgs,
4266    ) -> Result<i64, Error> {
4267        use std::io::Write as _;
4268        let message = "unsupported: epoll_pwait2 with a temporary signal mask, on a stack \
4269            with no room below its red zone whose red zone the call's events buffer and \
4270            timeout fill: Hermit has nowhere to put the private copy of the mask \
4271            (https://github.com/rrnewton/hermit/issues/4088)";
4272        tracing::error!("[tid {}] {message}", guest.tid());
4273        if self.cfg.panic_on_unsupported_syscalls {
4274            let _ = writeln!(crate::util::RetryingStderr, "{message}");
4275            return self
4276                .refuse_unserviceable_operation(guest, Sysno::epoll_pwait2, Errno::EFAULT)
4277                .await;
4278        }
4279        self.record_or_replay_blocking_with_unknown_mask(
4280            guest,
4281            Syscall::Other(Sysno::epoll_pwait2, args),
4282        )
4283        .await
4284    }
4285
4286    /// epoll_wait syscall (MAYHANG)
4287    #[cfg(target_arch = "x86_64")]
4288    pub async fn handle_epoll_wait<G: Guest<Self>>(
4289        &self,
4290        guest: &mut G,
4291        call: syscalls::EpollWait,
4292    ) -> Result<i64, Error> {
4293        if self.cfg.recordreplay_modes && call.timeout() == 0 {
4294            // This cannot block, but still yield a scheduler turn so a polling thread cannot
4295            // monopolize the guest between preemptions.
4296            resource_request(guest, Resources::new(guest.thread_state().dettid)).await;
4297            Ok(self.record_or_replay(guest, call).await?)
4298        } else if !self.cfg.sequentialize_threads || self.cfg.recordreplay_modes {
4299            self.record_or_replay_shielded_wait(guest, Syscall::EpollWait(call))
4300                .await
4301        } else {
4302            self.handle_internal_epoll_wait(guest, call).await
4303        }
4304    }
4305
4306    /// Handle a guest-internal `epoll_wait` call that can be fully determinized.
4307    #[cfg(target_arch = "x86_64")]
4308    pub async fn handle_internal_epoll_wait<G: Guest<Self>>(
4309        &self,
4310        guest: &mut G,
4311        call: syscalls::EpollWait,
4312    ) -> Result<i64, Error> {
4313        let timeout_millis = call.timeout();
4314        if timeout_millis == 0 {
4315            crate::syscalls::timerfd::publish_expired(guest).await;
4316            Ok(guest.inject(call).await?) // Already non-blocking.
4317        } else {
4318            let maybe_timeout_ns = millis_duration_to_absolute_timeout(guest, timeout_millis).await;
4319            let mut rsrc = Resources::new(guest.thread_state().dettid);
4320            rsrc.insert(ResourceID::InternalIOPolling, Permission::W);
4321            rsrc.fyi("epoll_wait");
4322            retry_nonblocking_syscall_with_timeout(guest, call, rsrc, maybe_timeout_ns).await
4323        }
4324    }
4325
4326    /// Connect system call (MAYHANG)
4327    /// Note that connect waits until a TCP handshake but does not wait for accept() on the other end.
4328    /// Nevertheless, it can block for a long time while waiting for connection, unless the socket
4329    /// is already nonblocking.
4330    pub async fn handle_connect<G: Guest<Self>>(
4331        &self,
4332        guest: &mut G,
4333        call: syscalls::Connect,
4334    ) -> Result<i64, Error> {
4335        let fd = call.fd();
4336        let uservaddr = call.uservaddr();
4337        let addrlen = call.addrlen();
4338
4339        if guest.config().sched_heuristic == SchedHeuristic::ConnectBind {
4340            trace!("Scheduling heuristic: reprioritizing connect");
4341            let resource = ResourceID::PriorityChangePoint(
4342                FIRST_PRIORITY,
4343                guest.thread_state().thread_logical_time.as_nanos(),
4344                guest.thread_state().committed_clock_value,
4345                Vec::new(),
4346            );
4347            let req = guest.thread_state().mk_request(resource, Permission::W);
4348            resource_request(guest, req).await;
4349        }
4350
4351        // Whether the connection completes in this turn.
4352        let in_turn = self.completes_in_turn(guest, call);
4353        guest.thread_state_mut().nonblocking_result_normalized = false;
4354        let result = self.execute_nonblockable_fd_syscall(guest, call).await;
4355        // A retried attempt's `EISCONN`, read as success, saw only that the
4356        // open file was connected, by an earlier attempt or another thread's
4357        // connect, not to which socket, nor that it read the address now
4358        // there (Codex review of https://github.com/rrnewton/hermit/pull/4862).
4359        let completed_by_another_attempt =
4360            std::mem::take(&mut guest.thread_state_mut().nonblocking_result_normalized);
4361        // What the address names, as the attempt that connected read it. A
4362        // connect that completes in its turn made that attempt in this turn,
4363        // after the last request it filed, so no other thread has rewritten
4364        // the address since; an earlier read could name another socket than
4365        // the one it reached, if another thread rewrote the address between
4366        // attempts (Codex review of
4367        // https://github.com/rrnewton/hermit/pull/4862). A connect outside its
4368        // turn may have reached any name (`Detcore::note_unix_connect`).
4369        let connected_to = uservaddr.map_or(
4370            crate::syscalls::scm_rights::UnixAddress::Unnamed,
4371            |address| {
4372                crate::syscalls::scm_rights::unix_address(
4373                    &guest.memory(),
4374                    address.as_raw(),
4375                    usize::try_from(addrlen).unwrap_or(0),
4376                )
4377            },
4378        );
4379        if let Ok(0) = &result {
4380            // Pair it with nothing, and no name with anything.
4381            let in_turn = in_turn && !completed_by_another_attempt;
4382            self.note_unix_connect(guest, fd, &connected_to, in_turn);
4383        }
4384        if self.cfg.backend.loopback_pollers_yield_to_peers
4385            && connect_result_allows_peer_classification(&result)
4386        {
4387            // This metadata is a loopback-poll scheduling hint, not part of connect's semantics.
4388            // Let the kernel establish the authoritative result first, then classify the peer
4389            // best-effort so an invalid guest pointer or an untracked fd can never replace the
4390            // kernel's errno.
4391            let loopback_peer = (|| -> Result<Option<bool>, Error> {
4392                let Some(address) = uservaddr else {
4393                    return Ok(None);
4394                };
4395                let addrlen = usize::try_from(addrlen).unwrap_or(0);
4396                if addrlen < std::mem::size_of::<u16>() {
4397                    return Ok(None);
4398                }
4399                let family: u16 = guest.memory().read_value(address.cast())?;
4400                if family == libc::AF_INET as u16 {
4401                    if addrlen < std::mem::size_of::<libc::sockaddr_in>() {
4402                        return Ok(None);
4403                    }
4404                    let address: libc::sockaddr_in = guest.memory().read_value(address.cast())?;
4405                    Ok(Some(
4406                        Ipv4Addr::from(address.sin_addr.s_addr.to_ne_bytes()).is_loopback(),
4407                    ))
4408                } else if family == libc::AF_INET6 as u16 {
4409                    if addrlen < std::mem::size_of::<libc::sockaddr_in6>() {
4410                        return Ok(None);
4411                    }
4412                    let address: libc::sockaddr_in6 = guest.memory().read_value(address.cast())?;
4413                    Ok(Some(
4414                        Ipv6Addr::from(address.sin6_addr.s6_addr).is_loopback(),
4415                    ))
4416                } else {
4417                    // This includes a successful AF_UNSPEC disconnect and successful connects
4418                    // to non-IP families, neither of which has a loopback IP peer.
4419                    Ok(Some(false))
4420                }
4421            })()
4422            .ok()
4423            .flatten();
4424            if let Some(loopback_peer) = loopback_peer {
4425                let _ = guest
4426                    .thread_state()
4427                    .with_detfd(fd, |detfd| detfd.set_loopback_peer(loopback_peer));
4428            }
4429        }
4430        if matches!(result, Ok(0)) {
4431            self.classify_socket_peer(guest, fd, SocketRole::Connected)
4432                .await;
4433        }
4434        // A connect ends what a guest-held socket was connected to: a datagram
4435        // socketpair end reconnected to another process's socket, or
4436        // disconnected, is no longer guest-held. A stream connection to a
4437        // listener this process made is (`classify_guest_held_peer`).
4438        if result.is_ok() {
4439            let _ = guest.thread_state().with_detfd(fd, |detfd| {
4440                if detfd.is_guest_held() {
4441                    detfd.clear_guest_held();
4442                }
4443            });
4444            let connecting = guest
4445                .thread_state()
4446                .with_detfd(fd, |detfd| detfd.open_file_handle())
4447                .ok();
4448            let listener = self.listener_named(guest, &connected_to);
4449            let guest_created = crate::syscalls::scm_rights::both_ends_guest_made(
4450                in_turn && !completed_by_another_attempt,
4451                connecting.as_ref(),
4452                listener.as_ref(),
4453            );
4454            self.classify_guest_held_peer(guest, fd, guest_created)
4455                .await;
4456        }
4457
4458        result
4459    }
4460
4461    /// Mark guest-held (`DetFd::is_guest_held`) the pipes and sockets among
4462    /// `received`, descriptors a receive on `channel` just recorded from
4463    /// `SCM_RIGHTS`, whose object only guests hold. That needs `channel` to be
4464    /// guest-held, so the sender was a guest. Then a received descriptor is
4465    /// guest-held when this table already names its object, or the other end
4466    /// of the same pipe, through a guest-held description (the same host
4467    /// device and inode). A socket's peer credentials do not mark it here:
4468    /// they name its peer, not every holder of its open file.
4469    ///
4470    /// A received descriptor that joins the open file its sender's table
4471    /// modeled (`crate::syscalls::scm_rights`) shares that open file's mark,
4472    /// set or not, as a dup would. The mark it arrives with is sound: a join
4473    /// takes only what a guest's `sendmsg` or `sendmmsg` queued on the
4474    /// receiving socket's known peer (`queue_sent_rights`), so the sender is a
4475    /// guest; either the channel was guest-held, so the receiver is a guest
4476    /// too and every holder of the open file is one, or the send revoked every
4477    /// mark first (`note_descriptors_passed_out`, which both send paths run
4478    /// before they send). This function never sees a joined descriptor:
4479    /// `record_received_descriptors` returns only the descriptors it recorded
4480    /// as descriptions of their own (`Receipt::New`), so a join keeps its open
4481    /// file's mark as it is. The rule here holds for the open file all the
4482    /// same: the object or its other end is already guest-held in this table.
4483    /// A descriptor nothing queued names, such as one from a process outside
4484    /// the guest, is a description of its own and starts unmarked; this
4485    /// function marks it only on a guest-held channel, as a known object.
4486    /// Marks
4487    /// only ever claim that no process outside the guest holds the object;
4488    /// once a descriptor may have left the guest, the scheduler stops trusting
4489    /// every mark (`Scheduler::polls_only_guest_state`).
4490    async fn classify_received_descriptors<G: Guest<Self>>(
4491        &self,
4492        guest: &mut G,
4493        channel: RawFd,
4494        received: &[RawFd],
4495    ) {
4496        if received.is_empty() {
4497            return;
4498        }
4499        let from_a_guest = guest
4500            .thread_state()
4501            .with_detfd(channel, |detfd| detfd.is_guest_held())
4502            .unwrap_or(false);
4503        if !from_a_guest {
4504            return;
4505        }
4506        for &fd in received {
4507            let Ok((ty, stat)) = guest
4508                .thread_state()
4509                .with_detfd(fd, |detfd| (detfd.ty(), detfd.stat()))
4510            else {
4511                continue;
4512            };
4513            if !matches!(ty, FdType::Pipe | FdType::Socket) {
4514                continue;
4515            }
4516            let known = stat.is_some_and(|stat| {
4517                guest
4518                    .thread_state()
4519                    .names_guest_held_object(fd, stat.dev, stat.inode)
4520            });
4521            // A received socket's peer credentials name its peer, not every
4522            // holder of its open file: a host process may still hold the same
4523            // open file, having handed it in over a channel that is not
4524            // guest-held, from which a guest forwarded it here. So a socket
4525            // that joined nothing is marked only as a known object, never
4526            // from its credentials (Codex review of
4527            // https://github.com/rrnewton/hermit/pull/4862).
4528            if known {
4529                let _ = guest
4530                    .thread_state()
4531                    .with_detfd(fd, |detfd| detfd.set_guest_held());
4532            }
4533        }
4534    }
4535
4536    /// Mark socket `fd`, a connection `connect` or `accept` just made,
4537    /// guest-held (`DetFd::is_guest_held`) when guests alone hold both of its
4538    /// ends and its peer is the caller's own process.
4539    ///
4540    /// `guest_created` is the provenance, which the caller checks: the
4541    /// connecting socket and the listener are both sockets a guest made in
4542    /// this run and never passed out of the guest (`DetFd::is_guest_created`),
4543    /// the connecting socket being `fd` itself for a `connect` and, for an
4544    /// `accept`, the socket whose connection it took
4545    /// (`Detcore::note_unix_accept`). Only guests then hold the connecting
4546    /// socket, and only guests can accept from the listener, so only guests
4547    /// hold either end. Peer credentials alone prove neither: they name the
4548    /// process that connected or listened, not every holder of the open file,
4549    /// and a host process may have kept an alias of a socket it handed in
4550    /// (Codex review of https://github.com/rrnewton/hermit/pull/4862). On
4551    /// top of the provenance, `SO_PEERCRED` must name the caller's own
4552    /// process:
4553    ///
4554    /// - An accepted socket (`accept`, `accept4`): Linux takes its credentials
4555    ///   from the task that connected, when it connected.
4556    /// - A connected stream or seqpacket socket (`connect`): Linux copies the
4557    ///   credentials of whoever called `listen`.
4558    ///
4559    /// A received socket is never classified here
4560    /// (`classify_received_descriptors`): its credentials name its peer, but
4561    /// a host process may hold the received open file itself.
4562    ///
4563    /// A datagram socket's credentials are not its peer's, so it is never
4564    /// marked here. Nor is a listening socket: `listen` sets a listener's
4565    /// credentials to its caller's, so one this process listened on and then
4566    /// received names this process, yet any process that can name its
4567    /// address can connect and make it ready. Accept and connect never return
4568    /// a listener, and receipts are not classified here, so the
4569    /// `SO_ACCEPTCONN` test, which must read 0 and leaves a socket whose
4570    /// value cannot be read unmarked, is a guard against a future caller. A peer in
4571    /// another process, guest or not, and a socket with
4572    /// no peer credentials (any non-`AF_UNIX` socket, whose peer pid is 0) are
4573    /// left as they were. Best-effort and read-only: an injection that fails
4574    /// leaves the socket unmarked, and nothing the guest sees changes. Only
4575    /// plain runs read the mark for the scheduler's new idle-poller detection,
4576    /// so only they pay for it. Record/replay retains its earlier internal-FD
4577    /// polling and FIFO-open decisions without consulting these marks.
4578    pub(crate) async fn classify_guest_held_peer<G: Guest<Self>>(
4579        &self,
4580        guest: &mut G,
4581        fd: RawFd,
4582        guest_created: bool,
4583    ) {
4584        if !self.cfg.sequentialize_threads || self.cfg.recordreplay_modes || !guest_created {
4585            return;
4586        }
4587        // A datagram socket's credentials are its creator's, or its
4588        // socketpair's, whatever it is connected to now: only a connection's
4589        // name its peer.
4590        let Some(kind) = socket_option::<libc::c_int, _, _>(guest, fd, libc::SO_TYPE).await else {
4591            return;
4592        };
4593        if kind != libc::SOCK_STREAM && kind != libc::SOCK_SEQPACKET {
4594            return;
4595        }
4596        // A listener's credentials are its own (`unix_listen` sets them to
4597        // the caller's), and an outside client's connect makes it ready.
4598        if socket_option::<libc::c_int, _, _>(guest, fd, libc::SO_ACCEPTCONN).await != Some(0) {
4599            return;
4600        }
4601        let Some(credentials) =
4602            socket_option::<libc::ucred, _, _>(guest, fd, libc::SO_PEERCRED).await
4603        else {
4604            return;
4605        };
4606        let peer = credentials.pid;
4607        if peer <= 0 {
4608            return;
4609        }
4610        // The caller's own process id in the same PID namespace that
4611        // `SO_PEERCRED` translated the peer's into.
4612        let Ok(own) = guest.inject(syscalls::Getpid::new()).await else {
4613            return;
4614        };
4615        if i64::from(peer) == own {
4616            let _ = guest
4617                .thread_state()
4618                .with_detfd(fd, |detfd| detfd.set_guest_held());
4619        }
4620    }
4621
4622    /// Handles sendto, sendmsg, and sendmmsg syscalls (MAYHANG).
4623    pub async fn handle_sendrecv<
4624        G: Guest<Self>,
4625        C: SyscallInfo + NonblockableSyscall + Into<Syscall>,
4626    >(
4627        &self,
4628        guest: &mut G,
4629        call: C,
4630    ) -> Result<i64, Error> {
4631        self.execute_nonblockable_fd_syscall(guest, call).await
4632    }
4633
4634    /// Sends one message and invalidates process-wide flock knowledge after success.
4635    ///
4636    /// The guest can mutate shared message and control memory while this helper
4637    /// deschedules. Parsing before the syscall would not prove which descriptors the
4638    /// kernel later transferred, so a successful unbound send conservatively makes
4639    /// every cached open-file-description lock mode unknown.
4640    pub async fn handle_sendmsg<G: Guest<Self>>(
4641        &self,
4642        guest: &mut G,
4643        call: syscalls::Sendmsg,
4644    ) -> Result<i64, Error> {
4645        if self.accept_in_turn_enabled() {
4646            let headers: Vec<usize> = call
4647                .msg()
4648                .map(|header| header.as_raw())
4649                .into_iter()
4650                .collect();
4651            self.mark_exported_listeners(guest, &headers);
4652        }
4653        let headers: Vec<usize> = call
4654            .msg()
4655            .map(|header| header.as_raw())
4656            .into_iter()
4657            .collect();
4658        self.refuse_sending_virtual_timerfds(guest, &headers);
4659        // The placeholder descriptions it sends, counted in flight, and what
4660        // each attempt checks again, or its refusal by name
4661        // (`Detcore::inspect_placeholder_send`).
4662        let (copies, outside_turn) = match self
4663            .inspect_send_placeholders(guest, Sysno::sendmsg, call.fd(), &headers, call)
4664            .await
4665        {
4666            Ok(inspected) => inspected,
4667            Err(refusal) => return refusal,
4668        };
4669        // A per-process file's record sequence, which every holder of the
4670        // open file shares, moves to Detcore's global state before the
4671        // descriptor can reach another process.
4672        Box::pin(self.share_sent_record_sequences(guest, &headers)).await;
4673        let in_turn = self.completes_in_turn(guest, call);
4674        if outside_turn || !sent_descriptors(guest, &headers).0.is_empty() {
4675            Self::note_descriptors_passed_out(guest, call.fd(), &headers, outside_turn);
4676        }
4677        self.note_sockets_passed_out(guest, &headers);
4678        let result = self.execute_nonblockable_fd_syscall(guest, call).await;
4679        guest.thread_state_mut().send_inspection = None;
4680        if outside_turn
4681            && Self::may_send_placeholders(guest)
4682            && let Some(refusal) =
4683                Box::pin(self.placeholder_send_after(guest, Sysno::sendmsg, call.fd())).await
4684        {
4685            return refusal;
4686        }
4687        if result.is_err() && !copies.is_empty() {
4688            let _ = crate::tool_global::placeholder_copies(
4689                guest,
4690                crate::procfs::locks::PlaceholderCopies::Unsent(copies),
4691            )
4692            .await;
4693        }
4694        if crate::syscalls::scm_rights::found_its_peer_gone(&result) {
4695            let addressed = sends_to_a_name(guest, &headers);
4696            self.note_peer_gone(guest, call.fd(), addressed);
4697        }
4698        let result = result?;
4699        guest.thread_state().forget_flock_modes();
4700        if let Some(stream) = self.rights_peer_is_stream(guest, call.fd()) {
4701            if in_turn {
4702                // One message, of `result` bytes.
4703                let sent = sent_rights(guest, &headers, |_| usize::try_from(result).unwrap_or(0));
4704                let out_of_band = call.flags() & libc::MSG_OOB != 0;
4705                self.queue_sent_rights(guest, call.fd(), stream, sent, out_of_band)
4706                    .await?;
4707            } else {
4708                self.lose_peer_lockstep(guest, call.fd());
4709            }
4710        }
4711        Ok(result)
4712    }
4713
4714    /// The placeholder step of a send `call` (`sysno`, on `socket`), whose
4715    /// messages' headers are at the guest addresses `headers`, where its
4716    /// `SCM_RIGHTS` descriptors can matter to the `/proc` directories Detcore
4717    /// serves (`Detcore::may_send_placeholders`): the inspection
4718    /// (`Detcore::inspect_placeholder_send`), kept for the attempts the call
4719    /// makes (`ThreadState::send_inspection`), with the descriptions it
4720    /// counted in flight, or the refusal by name; and, for every send,
4721    /// whether the call runs outside this thread's turn in a run that
4722    /// sequentializes threads (a blocking call), after which
4723    /// `Detcore::placeholder_send_after` checks what joined the table. The
4724    /// inspection is boxed: only a send that may pass a placeholder carries
4725    /// it.
4726    async fn inspect_send_placeholders<G, C>(
4727        &self,
4728        guest: &mut G,
4729        sysno: Sysno,
4730        socket: RawFd,
4731        headers: &[usize],
4732        call: C,
4733    ) -> Result<(Vec<crate::types::OpenFileId>, bool), Result<i64, Error>>
4734    where
4735        G: Guest<Self>,
4736        C: NonblockableSyscall + Into<Syscall> + Copy,
4737    {
4738        // Without sequentialized threads no thread has a turn: every check
4739        // Detcore makes may race the guest's other threads, that mode's named
4740        // residual, which a refusal here would not narrow.
4741        let outside_turn =
4742            self.cfg.sequentialize_threads && self.nonblockable_call_runs_outside_turn(guest, call);
4743        if !Self::may_send_placeholders(guest) {
4744            return Ok((Vec::new(), outside_turn));
4745        }
4746        let (copies, inspection) =
4747            Box::pin(self.inspect_placeholder_send(guest, sysno, socket, headers, outside_turn))
4748                .await?;
4749        guest.thread_state_mut().send_inspection = Some(inspection);
4750        Ok((copies, outside_turn))
4751    }
4752
4753    /// Note, for the scheduler, that a send on `sockfd` may pass
4754    /// descriptors over `SCM_RIGHTS` to a process outside
4755    /// the guest (`ThreadState::passed_descriptors_out_unreported`). A passed
4756    /// descriptor may be, or reach, an object some guest holds a guest-held
4757    /// description of, in any process's table, which a process outside
4758    /// Detcore's view could then make ready; so from then on the scheduler
4759    /// trusts no guest-held mark (`Scheduler::polls_only_guest_state`), and
4760    /// that covers polls already waiting. Control data read at entry cannot
4761    /// prove that no descriptor will be sent: a sibling may rewrite it before
4762    /// a later attempt. Linux imports each attempt's current msghdr and control
4763    /// data (`copy_msghdr_from_user`, `____sys_sendmsg`, Linux v6.12
4764    /// <https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/net/socket.c?h=v6.12#n2529>).
4765    /// A guest-held channel sends only to its guest peer, whatever `msg_name`
4766    /// says, but that exception requires an in-turn attempt: outside the turn
4767    /// even the destination descriptor may change before Linux imports it.
4768    /// Each in-turn retry checks the channel again immediately before sending.
4769    /// Revoke before the call regardless of its eventual result: sendmmsg can
4770    /// send a message before failing to write its length back to guest memory
4771    /// (Linux v6.12 `__sys_sendmmsg`,
4772    /// <https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/net/socket.c?h=v6.12#n2747>).
4773    pub(crate) fn note_descriptors_passed_out<G: Guest<Self>>(
4774        guest: &mut G,
4775        sockfd: RawFd,
4776        headers: &[usize],
4777        outside_turn: bool,
4778    ) {
4779        let reaches_a_guest = guest
4780            .thread_state()
4781            .with_detfd(sockfd, |detfd| detfd.is_guest_held())
4782            .unwrap_or(false);
4783        if reaches_a_guest && !outside_turn {
4784            return;
4785        }
4786        // No control data at entry proves nothing about what another thread
4787        // can install before a later attempt, and outside the turn even the
4788        // destination may change: the send counts as passing descriptors.
4789        // The descriptors the entry headers name also lose their
4790        // guest-created mark, as before.
4791        guest.thread_state_mut().passed_descriptors_out_unreported = true;
4792        let (sent, unread) = sent_descriptors(guest, headers);
4793        crate::reserved_input::before_descriptor_export(guest, &sent, unread)
4794            .unwrap_or_else(|error| panic!("reserved-input export publication failed: {error}"));
4795        if !sent.is_empty() || unread {
4796            clear_guest_created(guest, &sent);
4797        }
4798    }
4799
4800    /// Notes, for a `sendmsg` or `sendmmsg` `call` about to run outside the
4801    /// caller's turn in a plain sequentialized run, that it may pass
4802    /// descriptors out of the guest
4803    /// (`ThreadState::passed_descriptors_out_unreported`), whatever its
4804    /// descriptor and messages name now. The kernel imports them at a
4805    /// host-chosen moment, after other guest threads ran: one may have
4806    /// replaced the descriptor with an `AF_UNIX` socket that is not
4807    /// guest-held and rewritten a message to pass a guest-held one. Called
4808    /// where the call leaves the turn (`record_or_replay_blocking_resource_then`),
4809    /// so the request that leaves it carries the report, whatever the handler
4810    /// decided earlier and however many requests came between (Codex review
4811    /// of https://github.com/rrnewton/hermit/pull/4862).
4812    pub(crate) fn passes_descriptors_out_outside_the_turn<G: Guest<Self>>(
4813        guest: &mut G,
4814        call: Syscall,
4815    ) {
4816        let config = guest.config();
4817        if config.sequentialize_threads
4818            && !config.recordreplay_modes
4819            && matches!(call, Syscall::Sendmsg(_) | Syscall::Sendmmsg(_))
4820        {
4821            // The native ancillary import and descriptor lookup can happen
4822            // after another task changed them. The old poll-reliance report
4823            // is not a persistent-reference certificate.
4824            crate::reserved_input::before_descriptor_export(guest, &[], true).unwrap_or_else(
4825                |error| panic!("reserved-input export publication failed: {error}"),
4826            );
4827            guest.thread_state_mut().passed_descriptors_out_unreported = true;
4828        }
4829    }
4830
4831    /// Whether `call`, a `sendmsg` or `sendmmsg` about to make an attempt in
4832    /// the caller's turn, would pass descriptors out of the guest that this
4833    /// thread has not reported yet (`note_descriptors_passed_out`), read from
4834    /// its messages as they are now: a sibling may have rewritten them since
4835    /// the handler read them, or while an earlier attempt waited. If so, the
4836    /// pass-out is noted (`ThreadState::passed_descriptors_out_unreported`),
4837    /// and the caller reports it in a request of its own before the attempt
4838    /// runs. Only plain sequentialized runs read the marks, so only they
4839    /// check; once this thread has reported a pass-out the scheduler trusts no
4840    /// mark again, so it checks no more (Codex review of
4841    /// https://github.com/rrnewton/hermit/pull/4862).
4842    pub(crate) fn passes_descriptors_out_now<G: Guest<Self>>(guest: &mut G, call: Syscall) -> bool {
4843        let config = guest.config();
4844        if !config.sequentialize_threads || config.recordreplay_modes {
4845            return false;
4846        }
4847        let (sockfd, headers): (RawFd, Vec<usize>) = match call {
4848            Syscall::Sendmsg(call) => (
4849                call.fd(),
4850                call.msg()
4851                    .map(|header| header.as_raw())
4852                    .into_iter()
4853                    .collect(),
4854            ),
4855            Syscall::Sendmmsg(call) => (
4856                call.sockfd(),
4857                call.msgvec().map_or_else(Vec::new, |vector| {
4858                    (0..call.vlen().min(libc::UIO_MAXIOV as u32) as usize)
4859                        .map(|index| vector.as_raw() + index * std::mem::size_of::<Mmsghdr>())
4860                        .collect()
4861                }),
4862            ),
4863            _ => return false,
4864        };
4865        let reaches_a_guest = guest
4866            .thread_state()
4867            .with_detfd(sockfd, |detfd| detfd.is_guest_held())
4868            .unwrap_or(false);
4869        if reaches_a_guest {
4870            return false;
4871        }
4872        let (sent, unread) = sent_descriptors(guest, &headers);
4873        crate::reserved_input::before_descriptor_export(guest, &sent, unread)
4874            .unwrap_or_else(|error| panic!("reserved-input export publication failed: {error}"));
4875        if guest.thread_state().passed_descriptors_out_reported {
4876            return false;
4877        }
4878        if sent.is_empty() && !unread {
4879            return false;
4880        }
4881        guest.thread_state_mut().passed_descriptors_out_unreported = true;
4882        clear_guest_created(guest, &sent);
4883        true
4884    }
4885
4886    /// Mark every accept-in-turn listener (`DetFd::is_accept_in_turn_eligible`)
4887    /// these messages name in `SCM_RIGHTS` as exported, before they are sent
4888    /// and whatever the send returns: Linux may have queued the descriptor
4889    /// even when the call reports an error, and both phases must decide alike.
4890    /// `headers` are the guest addresses of the `msghdr`s: one for `sendmsg`,
4891    /// one per entry Linux could send for `sendmmsg`.
4892    ///
4893    /// An exported listener may be held by a process outside Detcore's view,
4894    /// so its accepts never take the temporary `O_NONBLOCK` again and keep the
4895    /// backgrounded path. An accept already waiting on it refuses at its next
4896    /// attempt (`ACCEPT_EXPORT_REFUSAL`). Both phases read the same guest
4897    /// memory here. When a header or control buffer cannot be read in full
4898    /// (Linux reads a write-only page, which Detcore cannot, and takes up to
4899    /// `optmem_max` bytes of control data, more than `MAX_CONTROL_BYTES`),
4900    /// every eligible listener in the table counts as exported. A thread that
4901    /// rewrites the control buffer while this one is descheduled can send a
4902    /// listener this did not see.
4903    // TODO-HUMAN-REVIEW(PR-3908)
4904    fn mark_exported_listeners<G: Guest<Self>>(&self, guest: &mut G, headers: &[usize]) {
4905        let (sent, unread) = sent_descriptors(guest, headers);
4906        if unread {
4907            // Linux can read memory Detcore cannot (a write-only page) and
4908            // accepts more control data than Detcore reads, so a message it
4909            // could not read may carry any listener.
4910            guest
4911                .thread_state()
4912                .mark_accept_in_turn_listeners_exported();
4913            return;
4914        }
4915        for fd in sent {
4916            let _ = guest.thread_state().with_detfd(fd, |detfd| {
4917                if detfd.is_accept_in_turn_eligible() {
4918                    detfd.set_exported();
4919                }
4920            });
4921        }
4922    }
4923
4924    /// Refuse the run, by name, before messages at `headers` pass a timerfd
4925    /// over `SCM_RIGHTS` while timerfds run on Detcore's virtual clock, armed
4926    /// or not (one armed after it is sent would be modelled only by the
4927    /// sender), or an epoll descriptor while this process holds an armed one
4928    /// (its set may include it). The receiving process keeps no model of the
4929    /// timer, so its probes would never make it readable and it would wait
4930    /// where Linux wakes (`crate::syscalls::timerfd`). A message whose control
4931    /// data Detcore cannot read refuses too while this process holds an armed
4932    /// timerfd.
4933    fn refuse_sending_virtual_timerfds<G: Guest<Self>>(&self, guest: &mut G, headers: &[usize]) {
4934        if !crate::syscalls::timerfd::enabled(guest) {
4935            return;
4936        }
4937        let holds_armed = crate::syscalls::timerfd::has_virtual_timerfds(guest);
4938        let (sent, unread) = sent_descriptors(guest, headers);
4939        if unread && holds_armed {
4940            crate::syscalls::timerfd::refuse(format_args!(
4941                "a message whose SCM_RIGHTS control data Detcore cannot read may pass a timerfd to another process"
4942            ));
4943        }
4944        for fd in sent {
4945            match guest.thread_state().with_detfd(fd, |detfd| detfd.ty()) {
4946                Ok(FdType::Timerfd) => crate::syscalls::timerfd::refuse(format_args!(
4947                    "a message passes timerfd {fd} over SCM_RIGHTS, and its receiver keeps no model of the timer"
4948                )),
4949                Ok(FdType::Epoll) if holds_armed => crate::syscalls::timerfd::refuse(format_args!(
4950                    "a message passes epoll descriptor {fd} over SCM_RIGHTS while this process holds a timerfd its set may include"
4951                )),
4952                _ => {}
4953            }
4954        }
4955    }
4956}
4957
4958/// What the messages at `headers`, which a send completed in the caller's
4959/// turn reports sending, passed over `SCM_RIGHTS`: for each that carried
4960/// descriptors, those descriptors and the bytes `sent_bytes` says it sent
4961/// (`Detcore::queue_sent_rights`). After such a send no other guest thread
4962/// has run, so its header and control data are the ones the send read. A
4963/// message whose header or control data cannot be read in full marks the
4964/// whole as `unread`.
4965/// Withdraws `DetFd::set_guest_created` from each of `sent`, descriptors a
4966/// send passes out of the guest.
4967fn clear_guest_created<T, G>(guest: &G, sent: &[RawFd])
4968where
4969    T: RecordOrReplay,
4970    G: Guest<Detcore<T>>,
4971{
4972    for &fd in sent {
4973        let _ = guest
4974            .thread_state()
4975            .with_detfd(fd, |detfd| detfd.clear_guest_created());
4976    }
4977}
4978
4979fn sent_rights<T, G>(
4980    guest: &G,
4981    headers: &[usize],
4982    sent_bytes: impl Fn(usize) -> usize,
4983) -> SentRights
4984where
4985    T: RecordOrReplay,
4986    G: Guest<Detcore<T>>,
4987{
4988    let mut rights = SentRights::default();
4989    for &header in headers {
4990        match sent_descriptors(guest, &[header]) {
4991            (_, true) => rights.unread = true,
4992            (sent, false) => {
4993                // Whether the message named its destination: a header whose
4994                // name length cannot be read counts as one that did.
4995                let addressed = Addr::<Msghdr>::from_raw(header)
4996                    .and_then(|header| guest.memory().read_value::<_, Msghdr>(header).ok())
4997                    .is_none_or(|header| header.msg_namelen != 0);
4998                rights.messages.push((sent, sent_bytes(header), addressed));
4999            }
5000        }
5001    }
5002    rights
5003}
5004
5005/// Whether the first message at `headers` names its destination (a nonzero
5006/// `msg_namelen`); one whose header cannot be read counts as one that did
5007/// (`Detcore::note_peer_gone`).
5008fn sends_to_a_name<T, G>(guest: &G, headers: &[usize]) -> bool
5009where
5010    T: RecordOrReplay,
5011    G: Guest<Detcore<T>>,
5012{
5013    headers.first().is_none_or(|&header| {
5014        Addr::<Msghdr>::from_raw(header)
5015            .and_then(|header| guest.memory().read_value::<_, Msghdr>(header).ok())
5016            .is_none_or(|header| header.msg_namelen != 0)
5017    })
5018}
5019
5020/// The descriptors the messages at `headers` name in `SCM_RIGHTS`, and
5021/// whether a header or control buffer could not be read in full, in which case
5022/// the list may be incomplete (`mark_exported_listeners`).
5023pub(crate) fn sent_descriptors<T, G>(guest: &G, headers: &[usize]) -> (Vec<RawFd>, bool)
5024where
5025    T: RecordOrReplay,
5026    G: Guest<Detcore<T>>,
5027{
5028    let mut sent: Vec<RawFd> = Vec::new();
5029    let mut unread = false;
5030    for header in headers {
5031        // A null header or control buffer fails the call with EFAULT
5032        // before Linux reads any descriptor.
5033        let Some(header) = Addr::<Msghdr>::from_raw(*header) else {
5034            continue;
5035        };
5036        let Ok(header) = guest.memory().read_value::<_, Msghdr>(header) else {
5037            unread = true;
5038            continue;
5039        };
5040        let Some(control_address) = Addr::<u8>::from_raw(header.msg_control as usize) else {
5041            continue;
5042        };
5043        if header.msg_controllen > MAX_CONTROL_BYTES {
5044            unread = true;
5045            continue;
5046        }
5047        let mut control = vec![0; header.msg_controllen];
5048        if guest
5049            .memory()
5050            .read_exact(control_address, &mut control)
5051            .is_err()
5052        {
5053            unread = true;
5054            continue;
5055        }
5056        sent.extend(received_descriptors(&control));
5057    }
5058    (sent, unread)
5059}
5060
5061impl<T: RecordOrReplay> Detcore<T> {
5062    /// Sends a message batch and invalidates process-wide flock knowledge when the
5063    /// kernel reports at least one message sent. This intentionally includes
5064    /// descriptors named only by an unsent tail message: the mutable guest array is
5065    /// not stable across a possible deschedule, so narrower attribution is unsafe.
5066    pub async fn handle_sendmmsg<G: Guest<Self>>(
5067        &self,
5068        guest: &mut G,
5069        call: syscalls::Sendmmsg,
5070    ) -> Result<i64, Error> {
5071        if self.cfg.recordreplay_modes && is_accepted_connection(guest, call.sockfd()) {
5072            return self.refuse_log_served_mmsg(guest, Sysno::sendmmsg).await;
5073        }
5074        if self.accept_in_turn_enabled() {
5075            // Every entry Linux could send, sent or not: see `mark_exported_listeners`.
5076            // Reverie types the vector as `msghdr`s, but its entries are
5077            // `mmsghdr`s, each a `msghdr` followed by `msg_len`, so step by
5078            // the larger size.
5079            let headers: Vec<usize> = call.msgvec().map_or_else(Vec::new, |vector| {
5080                (0..call.vlen().min(libc::UIO_MAXIOV as u32) as usize)
5081                    .map(|index| vector.as_raw() + index * std::mem::size_of::<Mmsghdr>())
5082                    .collect()
5083            });
5084            self.mark_exported_listeners(guest, &headers);
5085        }
5086        // As for `sendmsg`: every entry Linux could send.
5087        let headers: Vec<usize> = call.msgvec().map_or_else(Vec::new, |vector| {
5088            (0..call.vlen().min(libc::UIO_MAXIOV as u32) as usize)
5089                .map(|index| vector.as_raw() + index * std::mem::size_of::<Mmsghdr>())
5090                .collect()
5091        });
5092        self.refuse_sending_virtual_timerfds(guest, &headers);
5093        // A placeholder sent in a batch is refused by name: which of its
5094        // messages the batch sent is not known (`descriptor_directory`). Each
5095        // attempt checks again that the batch passes none.
5096        let outside_turn = match self
5097            .inspect_send_placeholders(guest, Sysno::sendmmsg, call.sockfd(), &headers, call)
5098            .await
5099        {
5100            Ok((_, outside_turn)) => outside_turn,
5101            Err(refusal) => return refusal,
5102        };
5103        let in_turn = self.completes_in_turn(guest, call);
5104        if outside_turn || !sent_descriptors(guest, &headers).0.is_empty() {
5105            Self::note_descriptors_passed_out(guest, call.sockfd(), &headers, outside_turn);
5106        }
5107        self.note_sockets_passed_out(guest, &headers);
5108        let result = self.execute_nonblockable_fd_syscall(guest, call).await;
5109        guest.thread_state_mut().send_inspection = None;
5110        if outside_turn
5111            && Self::may_send_placeholders(guest)
5112            && let Some(refusal) =
5113                Box::pin(self.placeholder_send_after(guest, Sysno::sendmmsg, call.sockfd())).await
5114        {
5115            return refusal;
5116        }
5117        // A batch fails only where its first message did.
5118        if crate::syscalls::scm_rights::found_its_peer_gone(&result) {
5119            let addressed = sends_to_a_name(guest, &headers[..headers.len().min(1)]);
5120            self.note_peer_gone(guest, call.sockfd(), addressed);
5121        }
5122        let result = result?;
5123        if result > 0 {
5124            guest.thread_state().forget_flock_modes();
5125        }
5126        if let Some(stream) = self.rights_peer_is_stream(guest, call.sockfd()) {
5127            if in_turn {
5128                // The first `result` entries were sent, each of the bytes
5129                // Linux wrote back to its `msg_len`.
5130                let sent_headers =
5131                    &headers[..usize::try_from(result).unwrap_or(0).min(headers.len())];
5132                let sent = sent_rights(guest, sent_headers, |header| {
5133                    Addr::<u32>::from_raw(header + std::mem::offset_of!(Mmsghdr, msg_len))
5134                        .and_then(|length| guest.memory().read_value(length).ok())
5135                        .map_or(0, |length: u32| length as usize)
5136                });
5137                let out_of_band = call.flags() & libc::MSG_OOB != 0;
5138                self.queue_sent_rights(guest, call.sockfd(), stream, sent, out_of_band)
5139                    .await?;
5140            } else {
5141                self.lose_peer_lockstep(guest, call.sockfd());
5142            }
5143        }
5144        Ok(result)
5145    }
5146
5147    /// Record and replay keep an accepted connection only in the log, and
5148    /// replay stands in an eventfd for it. Neither recorder nor replayer
5149    /// records the batched `sendmmsg` and `recvmmsg`, so replay would run them
5150    /// live against that eventfd and return something other than what the
5151    /// recording saw. Refuse them in both modes instead. On other sockets
5152    /// they still run live
5153    /// (https://github.com/rrnewton/hermit/issues/3883).
5154    // TODO-HUMAN-REVIEW(PR-3871): Refuse unrecorded batched I/O on accepted connections.
5155    async fn refuse_log_served_mmsg<G: Guest<Self>>(
5156        &self,
5157        guest: &mut G,
5158        sysno: Sysno,
5159    ) -> Result<i64, Error> {
5160        self.refuse_unserviceable_operation(guest, sysno, Errno::EOPNOTSUPP)
5161            .await
5162    }
5163
5164    // TODO-HUMAN-REVIEW(PR-912): Review receive-time capture across socket aliases.
5165    async fn observe_socket_receive<G: Guest<Self>>(
5166        &self,
5167        guest: &mut G,
5168        fd: i32,
5169    ) -> Result<LogicalTime, Error> {
5170        let timestamp = thread_observe_time(guest).await;
5171        guest.thread_state().with_detfd(fd, |detfd| {
5172            detfd.set_socket_receive_timestamp(timestamp);
5173        })?;
5174        Ok(timestamp)
5175    }
5176
5177    /// Receive one message, then record the descriptors it received over
5178    /// `SCM_RIGHTS` (`Detcore::record_received_descriptors`) and read their
5179    /// locks (`Detcore::refresh_description_locks`). Both read only the
5180    /// control bytes Linux reports writing, at the control buffer the
5181    /// message named before the call (`ReceiveGeometry`).
5182    pub async fn handle_recvmsg<G: Guest<Self>>(
5183        &self,
5184        guest: &mut G,
5185        call: syscalls::Recvmsg,
5186    ) -> Result<i64, Error> {
5187        let discovered = self.discovers_received_descriptors(guest);
5188        // Where the tool discovers received descriptors, what a receive
5189        // delivered is needed for the locks of every descriptor delivered
5190        // (with virtual time), and to mark the inotify instances among them
5191        // (`Detcore::marks_discovered_inotify_receipts`).
5192        let wanted = !discovered
5193            || self.cfg.virtualize_time
5194            || self.marks_discovered_inotify_receipts(guest);
5195        let in_turn = self.completes_in_turn(guest, call);
5196        Self::begin_receive_geometry(guest, wanted, &Syscall::Recvmsg(call));
5197        let result = self.receive_one_message(guest, call).await;
5198        let geometry = guest.thread_state_mut().receive_geometry.take().flatten();
5199        let from = ReceivedFrom {
5200            socket: call.sockfd(),
5201            peek: call.flags() & libc::MSG_PEEK != 0,
5202            messages: 1,
5203        };
5204        // A datagram or seqpacket receive takes its message before it copies
5205        // the bytes, and frees it if that copy fails.
5206        if let Err(Error::Errno(errno)) = &result {
5207            self.note_receive_error(guest, from, *errno, &Syscall::Recvmsg(call), in_turn);
5208        }
5209        let result = result?;
5210        let delivered = geometry
5211            .map(|geometry| geometry.delivered(&guest.memory(), result))
5212            .unwrap_or_default();
5213        let cloexec =
5214            Syscall::Recvmsg(call).into_parts().1.arg2 as i32 & libc::MSG_CMSG_CLOEXEC != 0;
5215        let recorded = self
5216            .record_received_descriptors(
5217                guest,
5218                &delivered,
5219                super::scm_rights::Channel::ScmRights,
5220                in_turn,
5221                cloexec,
5222                from,
5223            )
5224            .await?;
5225        self.classify_received_descriptors(guest, call.sockfd(), &recorded)
5226            .await;
5227        // Where the tool discovers received descriptors, recvmsg's refresh
5228        // covers every descriptor delivered, as before; elsewhere only those
5229        // this receipt recorded.
5230        let refreshed = if discovered {
5231            delivered.fds.as_slice()
5232        } else {
5233            recorded.as_slice()
5234        };
5235        self.refresh_received_locks(guest, refreshed, super::files::LockCall::Recvmsg)
5236            .await?;
5237        Ok(result)
5238    }
5239
5240    /// Keep the input geometry of `call` (a `recvmsg` or `recvmmsg`) in the
5241    /// thread state when `wanted`: read here, at entry, and again before each
5242    /// attempt of the internal polling loop, so that the geometry the caller
5243    /// takes when the call returns is that of the attempt that completed
5244    /// (`ThreadState::receive_geometry`).
5245    fn begin_receive_geometry<G: Guest<Self>>(guest: &mut G, wanted: bool, call: &Syscall) {
5246        guest.thread_state_mut().receive_geometry =
5247            wanted.then(|| ReceiveGeometry::before(&guest.memory(), call));
5248    }
5249
5250    /// A descriptor received over `SCM_RIGHTS` that Detcore models joins
5251    /// this process's table in `procfs::locks::LockLedger` with whatever OFD
5252    /// or flock locks its description holds, and is marked as received, so
5253    /// that a lock change through it invalidates the identity the sender's
5254    /// tables know the description by. One Detcore keeps no model of (not
5255    /// recorded, `Detcore::record_received_descriptors`) is left out, so its
5256    /// locks are hidden once no modelled table names the description (a
5257    /// named residual).
5258    ///
5259    /// Not under record and replay: they render no procfs files
5260    /// (`Detcore::renders_procfs`), so the ledger feeds nothing the guest
5261    /// reads, and the refresh reads each descriptor's fdinfo live, which in
5262    /// replay is a stand-in eventfd's wherever the recording held a file this
5263    /// process held nowhere else.
5264    pub(crate) async fn refresh_received_locks<G: Guest<Self>>(
5265        &self,
5266        guest: &mut G,
5267        fds: &[RawFd],
5268        call: super::files::LockCall,
5269    ) -> Result<(), Error> {
5270        if self.cfg.virtualize_time && !self.cfg.recordreplay_modes {
5271            for &fd in fds {
5272                self.refresh_description_locks(guest, fd, call, None)
5273                    .await?;
5274            }
5275        }
5276        Ok(())
5277    }
5278
5279    // AUTONOMOUS-BOT-IMPLEMENTED
5280    // TODO-HUMAN-REVIEW(PR-901)
5281    /// Receive one message and replace host socket timestamps with logical time.
5282    async fn receive_one_message<G: Guest<Self>>(
5283        &self,
5284        guest: &mut G,
5285        call: syscalls::Recvmsg,
5286    ) -> Result<i64, Error> {
5287        // NETLINK_SOCK_DIAG replies carry host-assigned socket identities in
5288        // their msg_iov payload (not msg_control). Canonicalize the supported
5289        // fields before the binary reply reaches the guest.
5290        // The predicate is shared with the read/readv/recvfrom/recvmmsg paths so
5291        // the five receive syscalls cannot drift apart again.
5292        if self.sock_diag_reply_fd(guest, call.sockfd()) {
5293            return self.handle_sock_diag_recvmsg(guest, call).await;
5294        }
5295
5296        if !self.cfg.virtualize_time {
5297            return self.execute_nonblockable_fd_syscall(guest, call).await;
5298        }
5299
5300        let Some(message_address) = call.msg() else {
5301            return self
5302                .handle_socket_receive(guest, call, call.sockfd(), true)
5303                .await;
5304        };
5305        // Snapshot every input field before the receive. Linux permits the
5306        // control buffer to overlap this header, so rereading it afterward can
5307        // turn a successful consuming receive into an artificial EFAULT.
5308        let message: Msghdr = guest.memory().read_value(message_address)?;
5309        if message.msg_control.is_null() || message.msg_controllen == 0 {
5310            return self
5311                .handle_socket_receive(guest, call, call.sockfd(), true)
5312                .await;
5313        }
5314        let control_len = message.msg_controllen.min(MAX_CONTROL_BYTES);
5315        let control_address: AddrMut<'_, u8> =
5316            AddrMut::from_raw(message.msg_control as usize).ok_or(Errno::EFAULT)?;
5317        let mut control = vec![0; control_len];
5318        // Validate the output region before consuming a datagram.
5319        guest.memory().read_exact(control_address, &mut control)?;
5320
5321        let result = self.execute_nonblockable_fd_syscall(guest, call).await?;
5322        let now = self.observe_socket_receive(guest, call.sockfd()).await?;
5323        let mut control = vec![0; control_len];
5324        guest.memory().read_exact(control_address, &mut control)?;
5325        if socket_timestamp_messages(&control).is_empty() {
5326            return Ok(result);
5327        }
5328
5329        canonicalize_socket_timestamps(&mut control, now);
5330        guest.memory().write_exact(control_address, &control)?;
5331        Ok(result)
5332    }
5333
5334    // AUTONOMOUS-BOT-IMPLEMENTED
5335    // TODO-HUMAN-REVIEW(PR-1064)
5336    /// Whether replies received on `fd` must have their supported socket
5337    /// identities canonicalized.
5338    ///
5339    /// This is the single predicate every receive path consults. It exists as
5340    /// one function because the flag used to be tested inline in `recvmsg`
5341    /// alone, and four other receive syscalls reached the same dump without it
5342    /// (see [`Self::sanitize_sock_diag_segments`]).
5343    pub(crate) fn sock_diag_reply_fd<G: Guest<Self>>(&self, guest: &mut G, fd: RawFd) -> bool {
5344        self.cfg.virtualize_metadata
5345            && guest
5346                .thread_state()
5347                .with_detfd(fd, |detfd| detfd.is_sock_diag() || detfd.is_netlink_route())
5348                .unwrap_or(false)
5349    }
5350
5351    /// Whether this descriptor is specifically a `NETLINK_ROUTE` socket, which
5352    /// needs the link-counter sanitizer rather than the sock-diag one.
5353    fn netlink_route_reply_fd<G: Guest<Self>>(&self, guest: &mut G, fd: RawFd) -> bool {
5354        guest
5355            .thread_state()
5356            .with_detfd(fd, |detfd| detfd.is_netlink_route())
5357            .unwrap_or(false)
5358    }
5359
5360    /// Read an `iovec` array out of guest memory as plain `(address, capacity)`
5361    /// scalars, so no non-`Send` raw pointer is held across an await.
5362    fn read_iov_segments<G: Guest<Self>>(
5363        guest: &mut G,
5364        iov: usize,
5365        iovlen: usize,
5366    ) -> Result<Vec<(usize, usize)>, Error> {
5367        if iov == 0 || iovlen == 0 {
5368            return Ok(Vec::new());
5369        }
5370        let count = iovlen.min(libc::UIO_MAXIOV as usize);
5371        let address: AddrMut<'_, libc::iovec> = AddrMut::from_raw(iov).ok_or(Errno::EFAULT)?;
5372        // SAFETY: `iovec` is a plain C record; an all-zero value is a valid
5373        // staging value immediately overwritten by `read_values`.
5374        let mut iovecs: Vec<libc::iovec> =
5375            (0..count).map(|_| unsafe { std::mem::zeroed() }).collect();
5376        guest.memory().read_values(address.into(), &mut iovecs)?;
5377        Ok(iovecs
5378            .iter()
5379            .map(|iov| (iov.iov_base as usize, iov.iov_len))
5380            .collect())
5381    }
5382
5383    /// Canonicalize host-assigned identities in a `NETLINK_SOCK_DIAG` reply that
5384    /// the kernel has already written into guest memory.
5385    ///
5386    /// `segments` describes the destination buffers as `(address, capacity)` in
5387    /// the order the kernel filled them; `received` is the syscall's return
5388    /// value. The reply is gathered contiguously (it may be scattered across
5389    /// several buffers), sanitized via `crate::sock_diag` (fail-open,
5390    /// zero-only, never resizes), and written back preserving the original
5391    /// boundaries. Bounded by both each buffer's capacity and `received`, which
5392    /// under `MSG_TRUNC` can exceed the total capacity.
5393    ///
5394    /// Synchronous on purpose: every caller has already completed its receive,
5395    /// so nothing here awaits and no guest address outlives the borrow.
5396    fn sanitize_sock_diag_segments<G: Guest<Self>>(
5397        &self,
5398        guest: &mut G,
5399        fd: RawFd,
5400        segments: &[(usize, usize)],
5401        received: usize,
5402    ) -> Result<(), Error> {
5403        if received == 0 || segments.is_empty() {
5404            return Ok(());
5405        }
5406        let mut filled: Vec<(AddrMut<'_, u8>, usize)> = Vec::new();
5407        let mut buffer: Vec<u8> = Vec::with_capacity(received);
5408        let mut remaining = received;
5409        for &(base, capacity) in segments {
5410            if remaining == 0 {
5411                break;
5412            }
5413            if base == 0 || capacity == 0 {
5414                continue;
5415            }
5416            let take = capacity.min(remaining);
5417            let address: AddrMut<'_, u8> = AddrMut::from_raw(base).ok_or(Errno::EFAULT)?;
5418            let mut segment = vec![0u8; take];
5419            guest.memory().read_exact(address, &mut segment)?;
5420            buffer.extend_from_slice(&segment);
5421            filled.push((address, take));
5422            remaining -= take;
5423        }
5424
5425        // NETLINK_ROUTE and NETLINK_SOCK_DIAG replies need different
5426        // sanitizers: one zeroes live interface counters, the other determinizes
5427        // supported socket identities. The descriptor decides which, so a guest
5428        // holding both kinds of socket gets each handled correctly.
5429        let modified = if self.netlink_route_reply_fd(guest, fd) {
5430            crate::netlink_route::sanitize_route_link_stats(&mut buffer)
5431        } else {
5432            crate::sock_diag::sanitize_sock_diag_identities(&mut buffer)
5433        };
5434        if !modified {
5435            return Ok(());
5436        }
5437
5438        let mut offset = 0;
5439        for (address, len) in filled {
5440            guest
5441                .memory()
5442                .write_exact(address, &buffer[offset..offset + len])?;
5443            offset += len;
5444        }
5445        Ok(())
5446    }
5447
5448    /// `recvfrom` on a socket-diag descriptor: one destination buffer.
5449    ///
5450    /// `recv(2)` has no syscall of its own on x86_64 — glibc lowers it to
5451    /// `recvfrom` with a null address — so this covers `recv` as well, which is
5452    /// what Python's `socket.recv()` reaches.
5453    pub async fn handle_sock_diag_recvfrom<G: Guest<Self>>(
5454        &self,
5455        guest: &mut G,
5456        call: syscalls::Recvfrom,
5457    ) -> Result<i64, Error> {
5458        let fd = call.fd();
5459        let base = call.buf().map(|address| address.as_raw()).unwrap_or(0);
5460        let len = call.len();
5461        let result = self.handle_socket_receive(guest, call, fd, true).await?;
5462        let received = usize::try_from(result).unwrap_or(0);
5463        self.sanitize_sock_diag_segments(guest, fd, &[(base, len)], received)?;
5464        Ok(result)
5465    }
5466
5467    /// `read` on a socket-diag descriptor: one destination buffer.
5468    pub async fn handle_sock_diag_read<G: Guest<Self>>(
5469        &self,
5470        guest: &mut G,
5471        call: syscalls::Read,
5472    ) -> Result<i64, Error> {
5473        let fd = call.fd();
5474        let base = call.buf().map(|address| address.as_raw()).unwrap_or(0);
5475        let len = call.len();
5476        let result = self.handle_read(guest, call).await?;
5477        let received = usize::try_from(result).unwrap_or(0);
5478        self.sanitize_sock_diag_segments(guest, fd, &[(base, len)], received)?;
5479        Ok(result)
5480    }
5481
5482    /// Receive a `NETLINK_SOCK_DIAG` dump and zero the host-assigned socket
5483    /// inode numbers in the reply so `ss`-style enumeration is deterministic.
5484    ///
5485    /// The dump lands in `msg_iov` (netlink diag sockets carry no ancillary
5486    /// data), possibly scattered across several iovecs.
5487    async fn handle_sock_diag_recvmsg<G: Guest<Self>>(
5488        &self,
5489        guest: &mut G,
5490        call: syscalls::Recvmsg,
5491    ) -> Result<i64, Error> {
5492        let fd = call.sockfd();
5493        let Some(message_address) = call.msg() else {
5494            return self.handle_socket_receive(guest, call, fd, true).await;
5495        };
5496        // Snapshot the header's iovec pointer/count before the receive (they are
5497        // stable across it; the kernel fills the pointed-to buffers, not the
5498        // array). Scoped so the `msghdr`'s raw pointers are dropped before the
5499        // await below: holding one would make this future non-`Send`.
5500        let segments = {
5501            let message: Msghdr = guest.memory().read_value(message_address)?;
5502            Self::read_iov_segments(guest, message.msg_iov as usize, message.msg_iovlen)?
5503        };
5504
5505        let result = self.handle_socket_receive(guest, call, fd, true).await?;
5506        let received = usize::try_from(result).unwrap_or(0);
5507        self.sanitize_sock_diag_segments(guest, fd, &segments, received)?;
5508        Ok(result)
5509    }
5510
5511    /// `readv` on a socket-diag descriptor: an `iovec` array, no message header.
5512    pub async fn handle_sock_diag_readv<G: Guest<Self>>(
5513        &self,
5514        guest: &mut G,
5515        call: syscalls::Readv,
5516    ) -> Result<i64, Error> {
5517        let fd = call.fd();
5518        let iov = call.iov().map(|address| address.as_raw()).unwrap_or(0);
5519        let segments = Self::read_iov_segments(guest, iov, call.len())?;
5520        let result = self.handle_readv(guest, call).await?;
5521        let received = usize::try_from(result).unwrap_or(0);
5522        self.sanitize_sock_diag_segments(guest, fd, &segments, received)?;
5523        Ok(result)
5524    }
5525
5526    /// `recvmmsg` on a socket-diag descriptor.
5527    ///
5528    /// Each delivered `mmsghdr` is a separate datagram with its own byte count
5529    /// in `msg_len`, so each is gathered and sanitized independently; treating
5530    /// the batch as one buffer would let one message's length run into the
5531    /// next message's memory.
5532    pub async fn handle_sock_diag_recvmmsg<G: Guest<Self>>(
5533        &self,
5534        guest: &mut G,
5535        call: syscalls::Recvmmsg,
5536    ) -> Result<i64, Error> {
5537        let fd = call.fd();
5538        let Some(messages_address) = call.mmsg() else {
5539            return self.handle_recvmmsg(guest, call).await;
5540        };
5541        let vlen = call.vlen();
5542        if vlen == 0 || vlen > libc::UIO_MAXIOV as u32 {
5543            return self.handle_recvmmsg(guest, call).await;
5544        }
5545        let base = messages_address.as_raw();
5546
5547        // Snapshot each message's iovec geometry BEFORE the receive: the kernel
5548        // fills the pointed-to buffers and writes msg_len, but does not move the
5549        // iovec arrays themselves. Scoped, and reduced to plain scalars, so the
5550        // `mmsghdr` raw pointers are dropped before the await below: holding one
5551        // would make this future non-`Send`.
5552        let geometry: Vec<Vec<(usize, usize)>> = {
5553            // SAFETY: `mmsghdr` is a plain C record; an all-zero value is a
5554            // valid staging value immediately overwritten by `read_values`.
5555            let mut headers: Vec<Mmsghdr> = (0..vlen as usize)
5556                .map(|_| unsafe { std::mem::zeroed() })
5557                .collect();
5558            guest
5559                .memory()
5560                .read_values(messages_address.into(), &mut headers)?;
5561            let mut geometry = Vec::with_capacity(headers.len());
5562            for header in &headers {
5563                geometry.push(Self::read_iov_segments(
5564                    guest,
5565                    header.msg_hdr.msg_iov as usize,
5566                    header.msg_hdr.msg_iovlen,
5567                )?);
5568            }
5569            geometry
5570        };
5571
5572        let result = self.handle_recvmmsg(guest, call).await?;
5573        let delivered = usize::try_from(result).unwrap_or(0).min(geometry.len());
5574        if delivered == 0 {
5575            return Ok(result);
5576        }
5577
5578        // Re-read the array for the per-message byte counts the kernel just
5579        // wrote. Also scoped: nothing awaits past this point, but keeping the
5580        // raw pointers contained keeps the rule visible.
5581        let counts: Vec<usize> = {
5582            let address: AddrMut<'_, Mmsghdr> = AddrMut::from_raw(base).ok_or(Errno::EFAULT)?;
5583            // SAFETY: as above.
5584            let mut headers: Vec<Mmsghdr> = (0..vlen as usize)
5585                .map(|_| unsafe { std::mem::zeroed() })
5586                .collect();
5587            guest.memory().read_values(address.into(), &mut headers)?;
5588            headers.iter().map(|h| h.msg_len as usize).collect()
5589        };
5590        for (index, segments) in geometry.iter().enumerate().take(delivered) {
5591            self.sanitize_sock_diag_segments(guest, fd, segments, counts[index])?;
5592        }
5593        Ok(result)
5594    }
5595
5596    // TODO-HUMAN-REVIEW(PR-912): Review receive-time capture across socket aliases.
5597    /// Handle a socket receive and retain one timestamp for every alias of its open file.
5598    pub async fn handle_socket_receive<
5599        G: Guest<Self>,
5600        C: SyscallInfo + NonblockableSyscall + Into<Syscall>,
5601    >(
5602        &self,
5603        guest: &mut G,
5604        call: C,
5605        fd: i32,
5606        zero_delivers_packet: bool,
5607    ) -> Result<i64, Error> {
5608        let result = self.execute_nonblockable_fd_syscall(guest, call).await?;
5609        if self.cfg.virtualize_time && (result > 0 || (result == 0 && zero_delivers_packet)) {
5610            self.observe_socket_receive(guest, fd).await?;
5611        }
5612        Ok(result)
5613    }
5614
5615    /// Receive a message batch, then record the descriptors it received over
5616    /// `SCM_RIGHTS` (`Detcore::record_received_descriptors`) and read their
5617    /// locks, as for `recvmsg`.
5618    pub async fn handle_recvmmsg<G: Guest<Self>>(
5619        &self,
5620        guest: &mut G,
5621        call: syscalls::Recvmmsg,
5622    ) -> Result<i64, Error> {
5623        // Where the tool discovers received descriptors, what a receive
5624        // delivered is needed only to mark the inotify instances among them
5625        // (`Detcore::marks_discovered_inotify_receipts`).
5626        let wanted = !self.discovers_received_descriptors(guest)
5627            || self.marks_discovered_inotify_receipts(guest);
5628        let in_turn = self.completes_in_turn(guest, call);
5629        Self::begin_receive_geometry(guest, wanted, &Syscall::Recvmmsg(call));
5630        let result = self.receive_message_batch(guest, call).await;
5631        let geometry = guest.thread_state_mut().receive_geometry.take().flatten();
5632        let mut from = ReceivedFrom {
5633            socket: call.fd(),
5634            peek: call.flags() & libc::MSG_PEEK as u32 != 0,
5635            messages: 1,
5636        };
5637        // As for `recvmsg`: an error before any message is the first
5638        // message's.
5639        if let Err(Error::Errno(errno)) = &result {
5640            self.note_receive_error(guest, from, *errno, &Syscall::Recvmmsg(call), in_turn);
5641        }
5642        let result = result?;
5643        from.messages = usize::try_from(result).unwrap_or(0);
5644        let delivered = geometry
5645            .map(|geometry| geometry.delivered(&guest.memory(), result))
5646            .unwrap_or_default();
5647        let cloexec =
5648            Syscall::Recvmmsg(call).into_parts().1.arg3 as i32 & libc::MSG_CMSG_CLOEXEC != 0;
5649        let recorded = self
5650            .record_received_descriptors(
5651                guest,
5652                &delivered,
5653                super::scm_rights::Channel::ScmRights,
5654                in_turn,
5655                cloexec,
5656                from,
5657            )
5658            .await?;
5659        self.classify_received_descriptors(guest, call.fd(), &recorded)
5660            .await;
5661        self.note_short_batch(guest, from, call.vlen() as usize);
5662        // Only the descriptors this receipt recorded.
5663        self.refresh_received_locks(guest, &recorded, super::files::LockCall::Recvmmsg)
5664            .await?;
5665        Ok(result)
5666    }
5667
5668    // AUTONOMOUS-BOT-IMPLEMENTED
5669    // TODO-HUMAN-REVIEW(PR-901)
5670    /// Receive a message batch and replace every host socket timestamp with logical time.
5671    async fn receive_message_batch<G: Guest<Self>>(
5672        &self,
5673        guest: &mut G,
5674        call: syscalls::Recvmmsg,
5675    ) -> Result<i64, Error> {
5676        if self.cfg.recordreplay_modes && is_accepted_connection(guest, call.fd()) {
5677            return self.refuse_log_served_mmsg(guest, Sysno::recvmmsg).await;
5678        }
5679        if !self.cfg.virtualize_time || call.vlen() > libc::UIO_MAXIOV as u32 {
5680            return self.execute_nonblockable_fd_syscall(guest, call).await;
5681        }
5682
5683        let Some(messages_address) = call.mmsg() else {
5684            return self.execute_nonblockable_fd_syscall(guest, call).await;
5685        };
5686        let controls = {
5687            // SAFETY: `mmsghdr` is a plain C record and an all-zero value is a valid
5688            // initialized staging value that is immediately overwritten by `read_values`.
5689            let mut messages: Vec<Mmsghdr> = (0..call.vlen())
5690                .map(|_| unsafe { std::mem::zeroed() })
5691                .collect();
5692            guest
5693                .memory()
5694                .read_values(messages_address.into(), &mut messages)?;
5695
5696            let mut controls = Vec::with_capacity(messages.len());
5697            for message in &messages {
5698                let header = &message.msg_hdr;
5699                if header.msg_control.is_null() || header.msg_controllen == 0 {
5700                    controls.push(None);
5701                    continue;
5702                }
5703                controls.push(Some((
5704                    header.msg_control as usize,
5705                    header.msg_controllen.min(MAX_CONTROL_BYTES),
5706                )));
5707            }
5708            controls
5709        };
5710
5711        let result = self.execute_nonblockable_fd_syscall(guest, call).await?;
5712        let delivered = usize::try_from(result).unwrap_or(0).min(controls.len());
5713        if delivered == 0 {
5714            return Ok(result);
5715        }
5716        let now = self.observe_socket_receive(guest, call.fd()).await?;
5717        let mut timestamped = Vec::new();
5718        for (address, length) in controls.into_iter().take(delivered).flatten() {
5719            let address: AddrMut<'_, u8> = AddrMut::from_raw(address).ok_or(Errno::EFAULT)?;
5720            let mut bytes = vec![0; length];
5721            guest.memory().read_exact(address, &mut bytes)?;
5722            if !socket_timestamp_messages(&bytes).is_empty() {
5723                timestamped.push((address, bytes));
5724            }
5725        }
5726        if timestamped.is_empty() {
5727            return Ok(result);
5728        }
5729
5730        for (address, mut bytes) in timestamped {
5731            canonicalize_socket_timestamps(&mut bytes, now);
5732            guest.memory().write_exact(address, &bytes)?;
5733        }
5734        Ok(result)
5735    }
5736}
5737
5738/// Read socket option `optname` (`SOL_SOCKET`) of guest socket `fd` through an
5739/// injected `getsockopt`, read-only. `None` when the injection fails or Linux
5740/// writes less than a whole `T`.
5741async fn socket_option<T, D, G>(guest: &mut G, fd: RawFd, optname: libc::c_int) -> Option<T>
5742where
5743    T: Copy,
5744    D: reverie::Tool,
5745    G: Guest<D>,
5746{
5747    let mut stack = guest.stack().await;
5748    let value: AddrMut<T> = stack.reserve();
5749    let length = stack.push(std::mem::size_of::<T>() as libc::socklen_t);
5750    let _guard = stack.commit().ok()?;
5751    let length = AddrMut::<libc::socklen_t>::from_raw(length.as_raw())?;
5752    let call = syscalls::Getsockopt::new()
5753        .with_fd(fd)
5754        .with_level(libc::SOL_SOCKET)
5755        .with_optname(optname)
5756        .with_optval(Some(value.cast()))
5757        .with_optlen(Some(length));
5758    guest.inject(call).await.ok()?;
5759    let written = guest.memory().read_value(length).ok()?;
5760    if (written as usize) < std::mem::size_of::<T>() {
5761        return None;
5762    }
5763    guest.memory().read_value(value).ok()
5764}
5765
5766#[cfg(test)]
5767mod tests {
5768    use super::*;
5769
5770    #[cfg(target_arch = "x86_64")]
5771    #[test]
5772    fn only_record_replay_and_unsequentialized_runs_poll_in_the_kernel() {
5773        // A plain or strict run sequentializes threads without record or replay.
5774        assert!(!polls_in_kernel(true, false));
5775        assert!(polls_in_kernel(true, true));
5776        assert!(polls_in_kernel(false, false));
5777    }
5778
5779    #[cfg(target_arch = "x86_64")]
5780    #[test]
5781    fn a_restarted_kernel_poll_waits_only_until_its_deadline() {
5782        // Only a positive timeout has an end to keep, and record and replay
5783        // read no clock.
5784        let poll = |timeout| syscalls::Poll::new().with_timeout(timeout);
5785        assert_eq!(kernel_poll_deadline(&poll(0), false), None);
5786        assert_eq!(kernel_poll_deadline(&poll(-1), false), None);
5787        assert_eq!(
5788            kernel_poll_deadline(&poll(100), true),
5789            Some(KernelPollDeadline(None))
5790        );
5791        let before = Instant::now();
5792        let end = kernel_poll_deadline(&poll(100), false)
5793            .and_then(|deadline| deadline.0)
5794            .unwrap();
5795        assert!(end >= before + Duration::from_millis(100));
5796        assert!(end <= Instant::now() + Duration::from_millis(100));
5797        // The restart waits for the time left, rounded up to whole milliseconds.
5798        let now = Instant::now();
5799        let end = now + Duration::from_millis(100);
5800        assert_eq!(remaining_poll_millis(now, end), 100);
5801        assert_eq!(
5802            remaining_poll_millis(now + Duration::from_nanos(1), end),
5803            100
5804        );
5805        assert_eq!(
5806            remaining_poll_millis(now + Duration::from_millis(99), end),
5807            1
5808        );
5809        // At or past the end it only reports what is ready.
5810        assert_eq!(remaining_poll_millis(end, end), 0);
5811        assert_eq!(remaining_poll_millis(end + Duration::from_secs(1), end), 0);
5812        // An end beyond the range of poll's timeout waits the longest it can.
5813        assert_eq!(
5814            remaining_poll_millis(now, now + Duration::from_secs(10_000_000)),
5815            i32::MAX
5816        );
5817    }
5818
5819    #[cfg(target_arch = "x86_64")]
5820    #[test]
5821    fn an_external_send_revokes_marks_before_mutable_control_data_is_read() {
5822        use std::os::fd::AsRawFd;
5823
5824        let mut guest = crate::syscalls::helpers::kernel_signal_wait_failures::fd_test_guest();
5825        let (socket, _peer) = std::os::unix::net::UnixStream::pair().unwrap();
5826        let fd = socket.as_raw_fd();
5827        guest
5828            .thread_state_mut()
5829            .add_fd(fd, nix::fcntl::OFlag::O_RDWR, FdType::Socket, None)
5830            .unwrap();
5831        // No control data at entry proves nothing about what another thread
5832        // can install while this send is descheduled before a later attempt.
5833        Detcore::note_descriptors_passed_out(&mut guest, fd, &[], false);
5834        assert!(guest.thread_state().passed_descriptors_out_unreported);
5835
5836        // A stable guest-held channel cannot export even changed control data.
5837        guest.thread_state_mut().passed_descriptors_out_unreported = false;
5838        guest
5839            .thread_state()
5840            .with_detfd(fd, |detfd| detfd.set_guest_held())
5841            .unwrap();
5842        Detcore::note_descriptors_passed_out(&mut guest, fd, &[], false);
5843        assert!(!guest.thread_state().passed_descriptors_out_unreported);
5844
5845        // Outside the turn, even a channel's descriptor identity can change
5846        // before the kernel imports it. Revoke before handing the call off.
5847        Detcore::note_descriptors_passed_out(&mut guest, fd, &[], true);
5848        assert!(guest.thread_state().passed_descriptors_out_unreported);
5849    }
5850
5851    #[test]
5852    fn a_kernel_poll_without_a_deadline_restarts_by_running_again() {
5853        // Linux's code, and a signal that stopped the call before it ran.
5854        let interrupted = [Errno::ERESTART_RESTARTBLOCK, Errno::ERESTARTSYS];
5855        for errno in interrupted {
5856            for timeout in [0, -1] {
5857                let call = syscalls::Poll::new().with_timeout(timeout);
5858                assert!(matches!(
5859                    kernel_poll_result(&call, Err(Error::Errno(errno)), false),
5860                    Err(Error::Errno(Errno::ERESTARTNOHAND))
5861                ));
5862            }
5863            // A positive timeout keeps its deadline in the kernel's restart block.
5864            let timed = syscalls::Poll::new().with_timeout(5);
5865            assert!(matches!(
5866                kernel_poll_result(&timed, Err(Error::Errno(errno)), false),
5867                Err(Error::Errno(Errno::ERESTART_RESTARTBLOCK))
5868            ));
5869        }
5870        // Every other result is the call's own.
5871        let zero = syscalls::Poll::new().with_timeout(0);
5872        assert!(matches!(kernel_poll_result(&zero, Ok(2), false), Ok(2)));
5873        assert!(matches!(
5874            kernel_poll_result(&zero, Err(Error::Errno(Errno::EINTR)), false),
5875            Err(Error::Errno(Errno::EINTR))
5876        ));
5877        assert!(matches!(
5878            kernel_poll_result(&zero, Err(Error::Errno(Errno::ERESTARTNOINTR)), false),
5879            Err(Error::Errno(Errno::ERESTARTNOINTR))
5880        ));
5881    }
5882
5883    #[cfg(target_arch = "x86_64")]
5884    #[test]
5885    fn a_kernel_poll_that_keeps_a_record_restarts_through_restart_syscall() {
5886        // Linux's code and a signal that stopped the call before it ran both
5887        // restart a kept record, even once its time is up (timeout 0).
5888        for errno in [Errno::ERESTART_RESTARTBLOCK, Errno::ERESTARTSYS] {
5889            for timeout in [100, 0] {
5890                let call = syscalls::Poll::new().with_timeout(timeout);
5891                assert!(matches!(
5892                    kernel_poll_result(&call, Err(Error::Errno(errno)), true),
5893                    Err(Error::Errno(Errno::ERESTART_RESTARTBLOCK))
5894                ));
5895            }
5896        }
5897        let call = syscalls::Poll::new().with_timeout(100);
5898        assert!(matches!(kernel_poll_result(&call, Ok(1), true), Ok(1)));
5899        assert!(matches!(
5900            kernel_poll_result(&call, Err(Error::Errno(Errno::EINTR)), true),
5901            Err(Error::Errno(Errno::EINTR))
5902        ));
5903        // A poll that a signal stopped before it ran gives the guest
5904        // ERESTARTNOHAND, so the backend reports the signal it holds; one that
5905        // ran keeps Linux's code.
5906        let restart = || Err(Error::Errno(Errno::ERESTART_RESTARTBLOCK));
5907        assert!(poll_stopped_before_it_ran(&Err(Error::Errno(
5908            Errno::ERESTARTSYS
5909        ))));
5910        for other in [Ok(0), restart(), Err(Error::Errno(Errno::EINTR))] {
5911            assert!(!poll_stopped_before_it_ran(&other), "{other:?}");
5912        }
5913        assert!(matches!(
5914            kernel_poll_restart_code(restart(), true),
5915            Err(Error::Errno(Errno::ERESTARTNOHAND))
5916        ));
5917        assert!(matches!(
5918            kernel_poll_restart_code(restart(), false),
5919            Err(Error::Errno(Errno::ERESTART_RESTARTBLOCK))
5920        ));
5921        for before_run in [true, false] {
5922            assert!(matches!(kernel_poll_restart_code(Ok(0), before_run), Ok(0)));
5923            assert!(matches!(
5924                kernel_poll_restart_code(Err(Error::Errno(Errno::EINTR)), before_run),
5925                Err(Error::Errno(Errno::EINTR))
5926            ));
5927        }
5928    }
5929
5930    #[test]
5931    fn zero_timeout_socket_poll_requests_a_strong_one_turn_yield() {
5932        let request = zero_timeout_poll_request(DetTid::from_raw(17), true);
5933
5934        assert_eq!(request.resources.len(), 1);
5935        assert_eq!(
5936            request.resources.get(&ResourceID::SchedYield),
5937            Some(&Permission::W)
5938        );
5939        assert_eq!(request.fyi, LOOPBACK_POLL_YIELD_FYI);
5940    }
5941
5942    #[test]
5943    fn zero_timeout_non_socket_poll_keeps_the_existing_empty_turn() {
5944        let request = zero_timeout_poll_request(DetTid::from_raw(17), false);
5945
5946        assert!(request.resources.is_empty());
5947        assert!(request.fyi.is_empty());
5948    }
5949
5950    #[test]
5951    fn connect_peer_classification_never_overrides_kernel_errors() {
5952        assert!(connect_result_allows_peer_classification(&Ok(0)));
5953        assert!(connect_result_allows_peer_classification(&Err(
5954            Error::Errno(Errno::EINPROGRESS)
5955        )));
5956        assert!(!connect_result_allows_peer_classification(&Err(
5957            Error::Errno(Errno::EALREADY)
5958        )));
5959        assert!(!connect_result_allows_peer_classification(&Err(
5960            Error::Errno(Errno::EBADF)
5961        )));
5962        assert!(!connect_result_allows_peer_classification(&Err(
5963            Error::Errno(Errno::EFAULT)
5964        )));
5965    }
5966
5967    #[test]
5968    fn ppoll_timeout_uses_timespec_units() {
5969        assert_eq!(
5970            ppoll_timeout_duration(Timespec {
5971                tv_sec: 2,
5972                tv_nsec: 345_678_901,
5973            }),
5974            Ok(Duration::new(2, 345_678_901))
5975        );
5976        assert_eq!(
5977            timespec_from_duration(Duration::new(2, 345_678_901)),
5978            Timespec {
5979                tv_sec: 2,
5980                tv_nsec: 345_678_901,
5981            }
5982        );
5983    }
5984
5985    #[test]
5986    fn pselect6_fd_set_lengths_follow_the_raw_linux_abi() {
5987        assert_eq!(PSELECT6_INTERNAL_MAX_NFDS, 64);
5988        assert_eq!(pselect6_fd_set_len(-1), Err(Errno::EINVAL));
5989        assert_eq!(pselect6_fd_set_len(0), Ok(0));
5990        assert_eq!(pselect6_fd_set_len(1), Ok(8));
5991        assert_eq!(pselect6_fd_set_len(65), Ok(16));
5992        assert_eq!(pselect6_fd_set_len(libc::FD_SETSIZE as i32), Ok(128));
5993    }
5994
5995    /// A ppoll probe runs again only after the backend's report that it did
5996    /// not run. EINTR, which a backend that runs the call itself answers with
5997    /// the temporary mask installed for a pending caught signal's delivery,
5998    /// is the guest's answer: retrying it found the signal again forever.
5999    #[test]
6000    fn ppoll_probes_run_again_only_after_a_not_run_report() {
6001        assert!(ppoll_probe_runs_again(Err(Errno::ERESTARTSYS)));
6002        for result in [
6003            Err(Errno::EINTR),
6004            Err(Errno::ERESTARTNOHAND),
6005            Err(Errno::ENOSYS),
6006            Err(Errno::EFAULT),
6007            Ok(0),
6008            Ok(1),
6009        ] {
6010            assert!(!ppoll_probe_runs_again(result), "{result:?}");
6011        }
6012    }
6013
6014    /// The pending signals a temporary mask lets in, when every one of them
6015    /// is ignored by the shared rule: set to SIG_IGN, or ignored by default
6016    /// with no handler, on either queue; none when the mask lets in a caught
6017    /// or default-fatal one, or a default-ignored one with a handler.
6018    #[test]
6019    fn a_temporary_mask_lets_in_only_ignored_signals_or_not() {
6020        let bit = |signal: i32| 1_u64 << (signal - 1);
6021        let sets = |pending: u64, shared: u64, ignored: u64, caught: u64| {
6022            reverie::SignalSets::new(pending, shared, bit(libc::SIGWINCH), ignored, caught)
6023        };
6024        let winch = bit(libc::SIGWINCH);
6025        let chld = bit(libc::SIGCHLD);
6026        let usr1 = bit(libc::SIGUSR1);
6027        let hup = bit(libc::SIGHUP);
6028        let none = 0;
6029        for (sets, mask, expected) in [
6030            (sets(winch, 0, 0, 0), none, Some(winch)),
6031            (sets(0, chld, 0, 0), none, Some(chld)),
6032            (sets(winch, chld, 0, 0), none, Some(winch | chld)),
6033            (sets(hup, 0, hup, 0), none, Some(hup)),
6034            (sets(winch | usr1, 0, 0, usr1), usr1, Some(winch)),
6035            (sets(winch, 0, 0, 0), winch, Some(0)),
6036            (sets(0, 0, 0, 0), none, Some(0)),
6037            (sets(winch | usr1, 0, 0, usr1), none, None),
6038            (sets(usr1, 0, 0, 0), none, None),
6039            (sets(winch, 0, 0, winch), none, None),
6040            (sets(bit(libc::SIGTSTP), 0, 0, 0), none, None),
6041        ] {
6042            assert_eq!(
6043                ignored_signals_let_in(&sets, mask),
6044                expected,
6045                "{sets:?} {mask:#x}"
6046            );
6047        }
6048    }
6049
6050    /// A masked pselect6 that the backend failed with ENOSYS is refused for a
6051    /// pending signal only when the mask lets in one the backend cannot act on
6052    /// inside the call: an ignored one, or one whose default action stops the
6053    /// process (SIGSTOP whatever the mask). With nothing pending, a blocked
6054    /// signal, or only a caught or default-fatal one let in, which would end
6055    /// the call instead, the backend declined the call's descriptor sets, as
6056    /// KVM's does for nfds 100. Before, every such call was refused for a
6057    /// pending signal. A backend that reports no signal state keeps that
6058    /// reason.
6059    #[test]
6060    fn a_masked_pselect6_refusal_names_a_signal_only_when_one_is_pending() {
6061        let bit = |signal: i32| 1_u64 << (signal - 1);
6062        let sets = |pending: u64, shared: u64, ignored: u64, caught: u64| {
6063            reverie::SignalSets::new(pending, shared, 0, ignored, caught)
6064        };
6065        let winch = bit(libc::SIGWINCH);
6066        let usr1 = bit(libc::SIGUSR1);
6067        let tstp = bit(libc::SIGTSTP);
6068        let stop = bit(libc::SIGSTOP);
6069        let term = bit(libc::SIGTERM);
6070        let none = 0;
6071        for (sets, mask, expected) in [
6072            (
6073                Some(sets(0, 0, 0, 0)),
6074                none,
6075                MASKED_PSELECT6_DECLINED_REASON,
6076            ),
6077            (
6078                Some(sets(usr1, 0, 0, usr1)),
6079                none,
6080                MASKED_PSELECT6_DECLINED_REASON,
6081            ),
6082            (
6083                Some(sets(0, term, 0, 0)),
6084                none,
6085                MASKED_PSELECT6_DECLINED_REASON,
6086            ),
6087            (
6088                Some(sets(winch, 0, 0, 0)),
6089                winch,
6090                MASKED_PSELECT6_DECLINED_REASON,
6091            ),
6092            (
6093                Some(sets(tstp, 0, 0, tstp)),
6094                none,
6095                MASKED_PSELECT6_DECLINED_REASON,
6096            ),
6097            (
6098                Some(sets(tstp, 0, tstp, 0)),
6099                none,
6100                MASKED_PSELECT6_SIGNAL_REASON,
6101            ),
6102            (
6103                Some(sets(winch, 0, 0, 0)),
6104                none,
6105                MASKED_PSELECT6_SIGNAL_REASON,
6106            ),
6107            (
6108                Some(sets(0, winch, 0, 0)),
6109                none,
6110                MASKED_PSELECT6_SIGNAL_REASON,
6111            ),
6112            (
6113                Some(sets(usr1 | winch, 0, 0, usr1)),
6114                none,
6115                MASKED_PSELECT6_SIGNAL_REASON,
6116            ),
6117            (
6118                Some(sets(tstp, 0, 0, 0)),
6119                none,
6120                MASKED_PSELECT6_SIGNAL_REASON,
6121            ),
6122            (
6123                Some(sets(stop, 0, 0, 0)),
6124                stop,
6125                MASKED_PSELECT6_SIGNAL_REASON,
6126            ),
6127            (None, none, MASKED_PSELECT6_SIGNAL_REASON),
6128        ] {
6129            for nfds in [0, 1, PSELECT6_INTERNAL_MAX_NFDS] {
6130                assert_eq!(
6131                    masked_pselect6_refusal_reason(sets.as_ref(), mask, nfds),
6132                    expected,
6133                    "{sets:?} {mask:#x} {nfds}"
6134                );
6135            }
6136        }
6137    }
6138
6139    /// A masked pselect6 of more descriptors than one machine word holds is
6140    /// refused for its descriptor sets whatever is pending, a caught and an
6141    /// ignored signal together included: KVM's backend declines that count
6142    /// before it looks at any signal. Before, nfds 100 with such signals
6143    /// pending was refused for a signal.
6144    #[test]
6145    fn a_masked_pselect6_of_more_than_64_descriptors_is_refused_for_its_sets() {
6146        let bit = |signal: i32| 1_u64 << (signal - 1);
6147        let winch = bit(libc::SIGWINCH);
6148        let usr1 = bit(libc::SIGUSR1);
6149        let tstp = bit(libc::SIGTSTP);
6150        for sets in [
6151            Some(reverie::SignalSets::new(usr1 | winch, 0, 0, 0, usr1)),
6152            Some(reverie::SignalSets::new(winch, 0, 0, winch, 0)),
6153            Some(reverie::SignalSets::new(tstp, 0, 0, 0, 0)),
6154            Some(reverie::SignalSets::new(0, 0, 0, 0, 0)),
6155            None,
6156        ] {
6157            for nfds in [PSELECT6_INTERNAL_MAX_NFDS + 1, 100, 1024, i32::MAX] {
6158                assert_eq!(
6159                    masked_pselect6_refusal_reason(sets.as_ref(), 0, nfds),
6160                    MASKED_PSELECT6_DECLINED_REASON,
6161                    "{sets:?} {nfds}"
6162                );
6163            }
6164        }
6165    }
6166
6167    #[test]
6168    fn pselect6_probe_result_maps_only_erestartsys_to_eintr() {
6169        assert_eq!(
6170            pselect6_probe_result(Err(Errno::ERESTARTSYS)),
6171            Err(Errno::EINTR)
6172        );
6173
6174        assert_eq!(pselect6_probe_result(Ok(0)), Ok(0));
6175        assert_eq!(pselect6_probe_result(Ok(1)), Ok(1));
6176        assert_eq!(pselect6_probe_result(Err(Errno::EBADF)), Err(Errno::EBADF));
6177        assert_eq!(
6178            pselect6_probe_result(Err(Errno::EINVAL)),
6179            Err(Errno::EINVAL)
6180        );
6181        assert_eq!(pselect6_probe_result(Err(Errno::EINTR)), Err(Errno::EINTR));
6182    }
6183
6184    #[test]
6185    fn select_timeout_folds_whole_seconds_out_of_microseconds() {
6186        let timeval = |tv_sec, tv_usec| libc::timeval { tv_sec, tv_usec };
6187        assert_eq!(
6188            select_timeout_duration(timeval(0, 1_500_000)),
6189            Ok(Duration::new(1, 500_000_000))
6190        );
6191        assert_eq!(
6192            select_timeout_duration(timeval(2, 3_000_001)),
6193            Ok(Duration::new(5, 1_000))
6194        );
6195        assert_eq!(
6196            select_timeout_duration(timeval(0, 999_999)),
6197            Ok(Duration::new(0, 999_999_000))
6198        );
6199        assert_eq!(select_timeout_duration(timeval(0, -1)), Err(Errno::EINVAL));
6200        assert_eq!(select_timeout_duration(timeval(-1, 0)), Err(Errno::EINVAL));
6201        // v7.1 refuses a negative field before normalizing, so neither mixed
6202        // sign is read as the 1 s it would sum to.
6203        assert_eq!(
6204            select_timeout_duration(timeval(2, -1_000_000)),
6205            Err(Errno::EINVAL)
6206        );
6207        assert_eq!(
6208            select_timeout_duration(timeval(-1, 2_000_000)),
6209            Err(Errno::EINVAL)
6210        );
6211        assert_eq!(select_timeout_duration(timeval(1, -1)), Err(Errno::EINVAL));
6212        assert_eq!(
6213            select_timeout_duration(timeval(libc::time_t::MAX, 1_000_000)),
6214            Err(Errno::EINVAL)
6215        );
6216    }
6217
6218    #[test]
6219    fn ppoll_signal_mask_keeps_reverie_preemption_unblocked() {
6220        let preemption_bit = 1_u64 << ((reverie::PERF_EVENT_SIGNAL as usize) - 1);
6221        assert_eq!(sanitize_ppoll_signal_mask(u64::MAX), !preemption_bit);
6222    }
6223
6224    #[test]
6225    fn ppoll_record_replay_waits_are_kernel_waits_and_run_mode_masked_waits_probe() {
6226        // Record/replay: the kernel owns the wait (issue 4042).
6227        assert!(ppoll_uses_kernel_wait(true, true));
6228        // A sequentialized run keeps the probe path.
6229        assert!(!ppoll_uses_kernel_wait(true, false));
6230        // Without sequentialization the kernel always owns it.
6231        assert!(ppoll_uses_kernel_wait(false, false));
6232        assert!(ppoll_uses_kernel_wait(false, true));
6233    }
6234
6235    #[test]
6236    fn ppoll_timeout_rejects_invalid_timespecs() {
6237        assert_eq!(
6238            ppoll_timeout_duration(Timespec {
6239                tv_sec: -1,
6240                tv_nsec: 0,
6241            }),
6242            Err(Errno::EINVAL)
6243        );
6244        assert_eq!(
6245            ppoll_timeout_duration(Timespec {
6246                tv_sec: 0,
6247                tv_nsec: 1_000_000_000,
6248            }),
6249            Err(Errno::EINVAL)
6250        );
6251    }
6252
6253    #[test]
6254    fn socket_timestamp_control_messages_use_logical_time() {
6255        let header_len = cmsg_align(std::mem::size_of::<Cmsghdr>());
6256        let timeval_len = std::mem::size_of::<libc::timeval>();
6257        let timespec_len = std::mem::size_of::<libc::timespec>();
6258        let first_len = header_len + timeval_len;
6259        let second_offset = cmsg_align(first_len);
6260        let second_len = header_len + timespec_len;
6261        let third_offset = second_offset + cmsg_align(second_len);
6262        let third_len = header_len + std::mem::size_of::<i32>();
6263        let mut control = vec![0; third_offset + cmsg_align(third_len)];
6264
6265        assert!(write_control_value(
6266            &mut control,
6267            Cmsghdr {
6268                cmsg_len: first_len,
6269                cmsg_level: libc::SOL_SOCKET,
6270                cmsg_type: SCM_TIMESTAMP_OLD,
6271            }
6272        ));
6273        assert!(write_control_value(
6274            &mut control[header_len..],
6275            libc::timeval {
6276                tv_sec: 99,
6277                tv_usec: 88,
6278            }
6279        ));
6280        assert!(write_control_value(
6281            &mut control[second_offset..],
6282            Cmsghdr {
6283                cmsg_len: second_len,
6284                cmsg_level: libc::SOL_SOCKET,
6285                cmsg_type: SCM_TIMESTAMPNS_OLD,
6286            }
6287        ));
6288        assert!(write_control_value(
6289            &mut control[second_offset + header_len..],
6290            libc::timespec {
6291                tv_sec: 77,
6292                tv_nsec: 66,
6293            }
6294        ));
6295        assert!(write_control_value(
6296            &mut control[third_offset..],
6297            Cmsghdr {
6298                cmsg_len: third_len,
6299                cmsg_level: libc::SOL_SOCKET,
6300                cmsg_type: libc::SCM_RIGHTS,
6301            }
6302        ));
6303        assert!(write_control_value(
6304            &mut control[third_offset + header_len..],
6305            42_i32
6306        ));
6307        let unrelated_message = control[third_offset..].to_vec();
6308
6309        assert_eq!(
6310            canonicalize_socket_timestamps(&mut control, LogicalTime::from_nanos(2_345_678_901)),
6311            2
6312        );
6313        let timeval = read_control_value::<libc::timeval>(&control[header_len..]).unwrap();
6314        assert_eq!(timeval.tv_sec, 2);
6315        assert_eq!(timeval.tv_usec, 345_678);
6316        let timespec =
6317            read_control_value::<libc::timespec>(&control[second_offset + header_len..]).unwrap();
6318        assert_eq!(timespec.tv_sec, 2);
6319        assert_eq!(timespec.tv_nsec, 345_678_901);
6320        assert_eq!(control[third_offset..], unrelated_message);
6321    }
6322
6323    #[test]
6324    fn truncated_timestamp_payload_prefix_is_rewritten() {
6325        let header_len = cmsg_align(std::mem::size_of::<Cmsghdr>());
6326        let full_len = header_len + std::mem::size_of::<libc::timeval>();
6327        let mut control = vec![0xaa; header_len + std::mem::size_of::<i32>()];
6328        assert!(write_control_value(
6329            &mut control,
6330            Cmsghdr {
6331                cmsg_len: full_len,
6332                cmsg_level: libc::SOL_SOCKET,
6333                cmsg_type: SCM_TIMESTAMP_OLD,
6334            }
6335        ));
6336
6337        assert_eq!(
6338            canonicalize_socket_timestamps(&mut control, LogicalTime::from_nanos(2_345_678_901)),
6339            1
6340        );
6341        assert_eq!(
6342            read_control_value::<i32>(&control[header_len..]),
6343            Some(2),
6344            "the visible timeval prefix must not retain host seconds"
6345        );
6346    }
6347
6348    #[test]
6349    fn timestamping_preserves_populated_source_slots() {
6350        let header_len = cmsg_align(std::mem::size_of::<Cmsghdr>());
6351        let timespec_len = std::mem::size_of::<libc::timespec>();
6352        let message_len = header_len + 3 * timespec_len;
6353        let mut control = vec![0; cmsg_align(message_len)];
6354        assert!(write_control_value(
6355            &mut control,
6356            Cmsghdr {
6357                cmsg_len: message_len,
6358                cmsg_level: libc::SOL_SOCKET,
6359                cmsg_type: SCM_TIMESTAMPING_OLD,
6360            }
6361        ));
6362        let zero = libc::timespec {
6363            tv_sec: 0,
6364            tv_nsec: 0,
6365        };
6366        let populated = libc::timespec {
6367            tv_sec: 99,
6368            tv_nsec: 88,
6369        };
6370        assert!(write_control_value(&mut control[header_len..], zero));
6371        assert!(write_control_value(
6372            &mut control[header_len + timespec_len..],
6373            populated
6374        ));
6375        assert!(write_control_value(
6376            &mut control[header_len + 2 * timespec_len..],
6377            populated
6378        ));
6379
6380        assert_eq!(
6381            canonicalize_socket_timestamps(&mut control, LogicalTime::from_nanos(2_345_678_901)),
6382            1
6383        );
6384        let first = read_control_value::<libc::timespec>(&control[header_len..]).unwrap();
6385        let second =
6386            read_control_value::<libc::timespec>(&control[header_len + timespec_len..]).unwrap();
6387        let third = read_control_value::<libc::timespec>(&control[header_len + 2 * timespec_len..])
6388            .unwrap();
6389        assert_eq!((first.tv_sec, first.tv_nsec), (0, 0));
6390        assert_eq!((second.tv_sec, second.tv_nsec), (2, 345_678_901));
6391        assert_eq!((third.tv_sec, third.tv_nsec), (2, 345_678_901));
6392    }
6393}