Skip to main content

detcore/syscalls/
helpers.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
9use std::num::NonZeroUsize;
10use std::time::Duration;
11
12use async_trait::async_trait;
13use reverie::Errno;
14use reverie::Error;
15use reverie::Guest;
16use reverie::MaskChangeRefused;
17use reverie::Stack;
18use reverie::syscalls;
19use reverie::syscalls::Addr;
20use reverie::syscalls::AddrMut;
21use reverie::syscalls::Displayable;
22use reverie::syscalls::FcntlCmd;
23use reverie::syscalls::MapFlags;
24use reverie::syscalls::MemoryAccess;
25use reverie::syscalls::ProtFlags;
26use reverie::syscalls::Syscall;
27use reverie::syscalls::SyscallInfo;
28use reverie::syscalls::Sysno;
29use reverie::syscalls::Timespec;
30use reverie::syscalls::WaitPidFlag;
31
32use crate::fd::FdType;
33use crate::record_or_replay::RecordOrReplay;
34use crate::resources::ExternalOpId;
35use crate::resources::Permission;
36use crate::resources::ResourceID;
37use crate::resources::Resources;
38use crate::syscalls::entry_import::Displaced;
39use crate::syscalls::entry_import::EntryImport;
40use crate::syscalls::threads::BlockedWaitSignalError;
41use crate::syscalls::threads::KERNEL_SIGSET_SIZE;
42use crate::syscalls::threads::KernelSigaction;
43use crate::syscalls::threads::KernelSignalState;
44use crate::syscalls::threads::KernelSigset;
45use crate::syscalls::threads::SignalQueue;
46use crate::syscalls::threads::WaitSignalDisposition;
47use crate::syscalls::threads::block_signals_for_disposition;
48use crate::syscalls::threads::blocked_signal_mask;
49use crate::syscalls::threads::kernel_sigset_bit;
50use crate::syscalls::threads::read_thread_wait_signal_state;
51use crate::syscalls::threads::read_wait_signal_state;
52use crate::syscalls::threads::restore_signals_after_disposition;
53use crate::syscalls::threads::wait_signal_disposition;
54use crate::tool_global::ResumeStatus;
55use crate::tool_global::host_timed_signals;
56use crate::tool_global::resource_request;
57use crate::tool_global::thread_observe_time;
58use crate::tool_global::trace_schedevent;
59use crate::tool_local::Detcore;
60use crate::tool_local::finish_partial_record_or_replay_write;
61use crate::types::DetTid;
62use crate::types::LogicalTime;
63use crate::types::OpenFileId;
64use crate::types::SchedEvent;
65use crate::types::SyscallPhase;
66
67impl<T: RecordOrReplay> Detcore<T> {
68    // AUTONOMOUS-BOT-IMPLEMENTED
69    // TODO-HUMAN-REVIEW(#2373)
70    /// Apply the established unsupported-syscall refusal policy to a *supported*
71    /// syscall whose particular operation Detcore cannot serve deterministically.
72    ///
73    /// This exists so such a refusal cannot invent its own policy. Three separate
74    /// config knobs govern how a fail-closed run dies -- `shutdown_on_unsupported_syscall`
75    /// (hard `exit(1)` through `unrecoverable_shutdown`), `exit_on_unsupported_syscall`
76    /// (a typed `UnsupportedSyscallError` the backend terminates on without
77    /// unwinding), and the `panic!` fallback -- and `Detcore::handle_unsupported_syscall`
78    /// consults all three in that order. The normal `hermit run` CLI happens to set
79    /// `shutdown_on_unsupported_syscall = panic_on_unsupported_syscalls`, so reading
80    /// only the latter looks equivalent, but that coupling is a CLI default, not an
81    /// invariant: an embedder that sets just `exit_on_unsupported_syscall` would get a
82    /// process-wide `exit(1)` from a bespoke call site where the standard path returns
83    /// a catchable error.
84    ///
85    /// When the run is *not* fail-closed, the caller's `fallback` errno is returned,
86    /// because the operation itself is legal and the guest is entitled to a normal
87    /// failure code (`handle_unsupported_syscall` passes the call through instead,
88    /// which is not available here -- passing through is precisely the thing the
89    /// caller has determined it cannot do).
90    pub(crate) async fn refuse_unserviceable_operation<G: Guest<Self>>(
91        &self,
92        guest: &mut G,
93        sysno: reverie::syscalls::Sysno,
94        fallback: Errno,
95    ) -> Result<i64, Error> {
96        refuse_unserviceable_operation_with(
97            guest,
98            self.cfg.panic_on_unsupported_syscalls,
99            sysno,
100            fallback,
101            None,
102        )
103        .await
104    }
105
106    /// [`Self::refuse_unserviceable_operation`], with the backend's own
107    /// description of why it cannot perform the operation, which a
108    /// fail-closed refusal reports as its reason.
109    pub(crate) async fn refuse_unserviceable_operation_because<G: Guest<Self>>(
110        &self,
111        guest: &mut G,
112        sysno: reverie::syscalls::Sysno,
113        fallback: Errno,
114        reason: &str,
115    ) -> Result<i64, Error> {
116        refuse_unserviceable_operation_with(
117            guest,
118            self.cfg.panic_on_unsupported_syscalls,
119            sysno,
120            fallback,
121            Some(reason),
122        )
123        .await
124    }
125
126    /// A backend that does not implement a guest's call declines it with an
127    /// errno and the typed refusal `operation` (`reverie::UnsupportedRefusal`),
128    /// as for `flock` and for a signal it cannot deliver. Report that refusal
129    /// by name ([`report_refusal_reason`]), and route it through the
130    /// unsupported-operation policy
131    /// ([`Self::refuse_unserviceable_operation_because`]), instead of handing
132    /// the guest the backend's errno as if Linux had answered the call: a
133    /// fail-closed run ends as a policy refusal, and a run that allows
134    /// unsupported operations gives the guest the refusal's errno. Callers
135    /// pass the handler's result right after the injection that may have been
136    /// refused, since the next injection replaces the backend's record. A
137    /// result that is not the refusal's errno, and a backend that records no
138    /// refusal, pass through unchanged; a refusal of another operation passes
139    /// the result through with a warning. No backend is named here: the policy
140    /// is keyed on the backend's refusal alone.
141    pub(crate) async fn report_backend_refusal<G: Guest<Self>>(
142        &self,
143        guest: &mut G,
144        sysno: Sysno,
145        operation: reverie::UnsupportedOperation,
146        result: Result<i64, Error>,
147    ) -> Result<i64, Error> {
148        let errno = match &result {
149            Err(Error::Errno(errno)) => *errno,
150            _ => return result,
151        };
152        let Some(refusal) = guest.take_unsupported_refusal() else {
153            return result;
154        };
155        let dettid = guest.thread_state().dettid;
156        if refusal.operation != operation || refusal.errno != errno {
157            tracing::warn!(
158                "[dtid {dettid}] {sysno}: the backend's refusal ({:?}, {}) is not the one this \
159                 call can be refused with ({operation:?}, {errno}): {}",
160                refusal.operation,
161                refusal.errno,
162                refusal.diagnostic
163            );
164            // Judged as any other last refusal of an event is, as it would
165            // have been had this call not taken it.
166            judge_backend_refusal::<T, G>(guest, Some(refusal), sysno);
167            return result;
168        }
169        report_refusal_reason(
170            self.cfg.panic_on_unsupported_syscalls,
171            &format!(
172                "[dtid {dettid}] {sysno} refused by the backend: {}",
173                refusal.diagnostic
174            ),
175        );
176        if !self.cfg.panic_on_unsupported_syscalls {
177            crate::tool_global::report_unsupported_syscall(guest, sysno).await;
178        }
179        self.refuse_unserviceable_operation_because(guest, sysno, refusal.errno, refusal.diagnostic)
180            .await
181    }
182
183    /// Record or replay a BLOCKING syscall without stalling the current thread (and thus
184    /// deadlocking).  This uses a protocol of an extra resource request before/after the
185    /// syscall to inform the scheduler that the thread is leaving/rejoining the runnable
186    /// threads pool.
187    ///
188    /// This is only valid to use (1) in hermit record/replay modes, or (2)
189    /// when we're in "hermit run", but we're NOT sequentializing threads, because in
190    /// that case it's ok to use the blocking versions of system calls.
191    pub async fn record_or_replay_blocking<G: Guest<Self>>(
192        &self,
193        guest: &mut G,
194        call: Syscall,
195    ) -> Result<i64, Error> {
196        self.record_or_replay_blocking_with_mask(guest, call, None)
197            .await
198    }
199
200    /// `record_or_replay_blocking`, with `run` the call that runs for `call`,
201    /// or that replay serves: the scheduler, the checks and the continuation
202    /// mask all see `call`. A kernel-run poll's restart is the one such pair:
203    /// the guest's own `restart_syscall` runs for the poll
204    /// (`Detcore::resume_kernel_poll`). Only x86_64 has `poll`.
205    #[cfg(target_arch = "x86_64")]
206    pub(crate) async fn record_or_replay_blocking_as<G: Guest<Self>>(
207        &self,
208        guest: &mut G,
209        call: Syscall,
210        run: Syscall,
211    ) -> Result<i64, Error> {
212        let dettid = guest.thread_state().dettid;
213        let op_id = ExternalOpId::new(dettid, guest.thread_state().stats.syscall_count);
214        self.record_or_replay_blocking_resource_then(
215            guest,
216            (call, run),
217            ResourceID::BlockingExternalIO(op_id),
218            None,
219            false,
220            |_| {},
221        )
222        .await
223    }
224
225    /// `record_or_replay_blocking` for a call that sleeps under its own
226    /// temporary signal mask (`ppoll`, `pselect6`, or an `rt_sigsuspend` that
227    /// finds a signal already pending): `blocked_signal_mask` is the mask the
228    /// kernel installs for the call, read in this thread's turn
229    /// (`Resources::blocked_signal_mask`). `None` means the call sleeps under
230    /// the thread's own mask.
231    pub async fn record_or_replay_blocking_with_mask<G: Guest<Self>>(
232        &self,
233        guest: &mut G,
234        call: Syscall,
235        blocked_signal_mask: Option<u64>,
236    ) -> Result<i64, Error> {
237        let dettid = guest.thread_state().dettid;
238        let op_id = ExternalOpId::new(dettid, guest.thread_state().stats.syscall_count);
239        self.record_or_replay_blocking_resource(
240            guest,
241            call,
242            ResourceID::BlockingExternalIO(op_id),
243            blocked_signal_mask,
244            false,
245        )
246        .await
247    }
248
249    /// `record_or_replay_blocking` for a call that sleeps under a temporary
250    /// mask its turn cannot vouch for (`Resources::blocked_signal_mask_unknown`):
251    /// the scheduler records no sleeping mask for it.
252    pub async fn record_or_replay_blocking_with_unknown_mask<G: Guest<Self>>(
253        &self,
254        guest: &mut G,
255        call: Syscall,
256    ) -> Result<i64, Error> {
257        self.record_or_replay_blocking_with_unknown_mask_then(guest, call, |_| {})
258            .await
259    }
260
261    /// `record_or_replay_blocking_with_unknown_mask`, running `after_grant` once
262    /// the scheduler has granted the call and before it runs: after any
263    /// sibling that ran while the request waited. In replay nothing else runs
264    /// until the served call finishes; in recording a sibling turn may still
265    /// run before the call enters the kernel.
266    pub async fn record_or_replay_blocking_with_unknown_mask_then<G: Guest<Self>>(
267        &self,
268        guest: &mut G,
269        call: Syscall,
270        after_grant: impl FnOnce(&mut G),
271    ) -> Result<i64, Error> {
272        let dettid = guest.thread_state().dettid;
273        let op_id = ExternalOpId::new(dettid, guest.thread_state().stats.syscall_count);
274        self.record_or_replay_blocking_resource_then(
275            guest,
276            (call, call),
277            ResourceID::BlockingExternalIO(op_id),
278            None,
279            true,
280            after_grant,
281        )
282        .await
283    }
284
285    /// Execute the real `rt_sigsuspend` outside the runnable set while preserving
286    /// its signal-only completion condition for the scheduler. `temporary_mask`
287    /// is the mask the call sleeps under, as `Resources::blocked_signal_mask`
288    /// describes.
289    pub async fn record_or_replay_rt_sigsuspend<G: Guest<Self>>(
290        &self,
291        guest: &mut G,
292        call: syscalls::RtSigsuspend,
293        temporary_mask: u64,
294    ) -> Result<i64, Error> {
295        let dettid = guest.thread_state().dettid;
296        let op_id = ExternalOpId::new(dettid, guest.thread_state().stats.syscall_count);
297        let result = self
298            .record_or_replay_blocking_resource(
299                guest,
300                call.into(),
301                ResourceID::BlockingRtSigsuspend(op_id),
302                Some(temporary_mask),
303                false,
304            )
305            .await;
306        // Linux's rt_sigsuspend ends only with ERESTARTNOHAND, which a handler
307        // turns into EINTR and never restarts. The host call reports
308        // ERESTARTSYS instead when the signal reached the thread before the
309        // call ran, which depends on host timing; under SA_RESTART the guest
310        // then slept again where Linux returns
311        // (https://github.com/rrnewton/hermit/issues/4320). This fixes the
312        // result and the restart only: in that case the call never ran, so
313        // the kernel never installed its temporary mask, and the handler runs
314        // under the thread's pre-call mask, still a host-timed difference when
315        // the two masks differ (https://github.com/rrnewton/hermit/issues/4338).
316        // Whether the kernel ran the call, before that normalization hides it
317        // (`signal_shadow::kernel_ran_the_wait`). A replayed result is not
318        // taken as evidence, so under replay the wait never counts as run.
319        if !self.cfg.replaying {
320            let ran = !matches!(result, Err(Error::Errno(Errno::ERESTARTSYS)));
321            super::signal_shadow::note_wait_ran(guest, ran);
322        }
323        match result {
324            Err(Error::Errno(Errno::ERESTARTSYS)) => Err(Errno::ERESTARTNOHAND.into()),
325            other => other,
326        }
327    }
328
329    async fn record_or_replay_blocking_resource<G: Guest<Self>>(
330        &self,
331        guest: &mut G,
332        call: Syscall,
333        blocking_resource: ResourceID,
334        blocked_signal_mask: Option<u64>,
335        blocked_signal_mask_unknown: bool,
336    ) -> Result<i64, Error> {
337        self.record_or_replay_blocking_resource_then(
338            guest,
339            (call, call),
340            blocking_resource,
341            blocked_signal_mask,
342            blocked_signal_mask_unknown,
343            |_| {},
344        )
345        .await
346    }
347
348    /// `record_or_replay_blocking_resource`, running `after_grant` between the
349    /// grant and the call, and running `run` for `call`
350    /// (`record_or_replay_blocking_as`).
351    async fn record_or_replay_blocking_resource_then<G: Guest<Self>>(
352        &self,
353        guest: &mut G,
354        (call, run): (Syscall, Syscall),
355        blocking_resource: ResourceID,
356        blocked_signal_mask: Option<u64>,
357        blocked_signal_mask_unknown: bool,
358        after_grant: impl FnOnce(&mut G),
359    ) -> Result<i64, Error> {
360        // D41 (A5 step 3c.1c): the first statement of every background
361        // external call and `rt_sigsuspend`, the two kernel-run families
362        // filed here, before anything below refuses, files or runs it.
363        if let Some(refused) = kernel_run_entry_refusal(guest)? {
364            return Err(refused);
365        }
366        // A host wait on descriptors that may include a timerfd on virtual
367        // time would never see it readable (`crate::syscalls::timerfd`).
368        crate::syscalls::timerfd::refuse_host_wait_for(guest, call);
369        let dettid = guest.thread_state().dettid;
370        let op_id = match &blocking_resource {
371            ResourceID::BlockingExternalIO(op_id) | ResourceID::BlockingRtSigsuspend(op_id) => {
372                *op_id
373            }
374            _ => unreachable!("blocking syscall helper requires a blocking resource"),
375        };
376        // Replay serves some of these calls from the recording, so they cannot block.
377        // They still run in the background, as they did during recording, so that the
378        // schedule matches the recording's. The flag makes the scheduler commit no
379        // other turn until they finish. Otherwise a stand-in descriptor that replay
380        // creates for an accepted connection could take its slot before or after
381        // another thread's open or close, and which of these calls had finished, and
382        // so the order in which they rejoin, would depend on host timing
383        // (https://github.com/rrnewton/hermit/pull/3871).
384        let replay_served_from_log = self.cfg.replaying
385            && (replay_serves_blocking_call_from_log(call)
386                || replay_serves_socket_call_from_log(guest, call));
387        // Internal-vs-external fd classification happens at the call sites that hold the
388        // typed, nonblockize-able syscall (see execute_nonblockable_fd_syscall):
389        // container-internal pipes are routed to the InternalIOPolling nonblockize-retry
390        // path and must NOT reach this external-blocking protocol. BlockingExternalIO
391        // deschedules the thread to run in the background and rejoin nondeterministically,
392        // which is unsafe for a pipe whose reader and writer are interdependent -- doing
393        // so is the root cause of the record/replay pipe deadlock. The remaining callers
394        // (external poll, wait4) are external by construction (their fd is not a single
395        // extractable internal pipe). Guard the invariant in debug builds while the
396        // deterministic scheduler is active. With thread sequentialization disabled,
397        // resource requests are no-ops and internal pipes intentionally use a blocking
398        // host syscall, as documented by this method.
399        debug_assert!(
400            !self.cfg.sequentialize_threads || !syscall_targets_internal_fd(guest, call),
401            "record_or_replay_blocking (BlockingExternalIO) reached for an internal fd \
402             (pipe or socketpair endpoint) on syscall {}; internal fds must use the \
403             InternalIOPolling path",
404            call.name()
405        );
406        // Classify the actual substituted native buffers before releasing
407        // the turn. EntryImport snapshots alone do not make indirect kernel
408        // inputs immutable. The lease survives native return and decoder
409        // copyout until the matching continuation really admits this task.
410        let input_access = crate::reserved_input::BackgroundAccess::acquire(guest, run)?;
411        crate::reserved_input::before_native_call(guest, run)?;
412        {
413            let mut rsrcs = Resources::new(dettid);
414            // With sequentialization enabled, only truly EXTERNAL endpoints reach here.
415            // Without it, resource_request is a no-op and internal fds may block directly.
416            rsrcs.reads_signalfd = matches!(blocking_resource, ResourceID::BlockingExternalIO(_))
417                && call_reads_signalfd(guest, call);
418            if matches!(blocking_resource, ResourceID::BlockingExternalIO(_)) {
419                rsrcs.read_fd =
420                    read_family_fd(call).map(|fd| (guest.thread_state().files_id(), fd));
421            }
422            rsrcs.insert(blocking_resource, Permission::RW);
423            rsrcs.fyi(call.name());
424            rsrcs.blocked_signal_mask = blocked_signal_mask;
425            rsrcs.blocked_signal_mask_unknown = blocked_signal_mask_unknown;
426            rsrcs.replay_served_from_log = replay_served_from_log;
427            // A send leaving the turn reports a possible pass-out with this
428            // request (`Detcore::passes_descriptors_out_outside_the_turn`).
429            Detcore::<T>::passes_descriptors_out_outside_the_turn(guest, call);
430            resource_request(guest, rsrcs).await;
431        }
432        after_grant(guest);
433        // Race probes (docs/RACE_PROBES.md) may delay the call or its rejoin;
434        // both are host timing this protocol already treats as nondeterministic.
435        crate::race_probe::delay(crate::race_probe::BG_PRE_CALL).await;
436        tracing::trace!(
437            "Guest proceeding to execute potentially blocking call {}...",
438            call.name()
439        );
440        let res = {
441            // This explicit child owner was created before grant release and
442            // owns these exact background leases. It replaces the enclosing
443            // callback's observation binding only for this native operation.
444            let mut native_guest = reverie::NativeOperationGuest::<_, Self>::optional(
445                guest,
446                input_access
447                    .as_ref()
448                    .map(|access| access.native_operation()),
449            )?;
450            self.record_or_replay_preserving_tool_errors(&mut native_guest, run)
451                .await
452        };
453        let native_returned = res.is_ok() || matches!(&res, Err(Error::Errno(_)));
454        if native_returned && let Some(access) = &input_access {
455            access.returned()?;
456        }
457        crate::race_probe::delay(crate::race_probe::BG_POST_CALL).await;
458        // N.B. BlockingExternalIO is a "oneshot" resource, so no need to release
459        // explicitly here:
460        {
461            let mut rsrcs = Resources::new(dettid);
462            rsrcs.insert(ResourceID::BlockedExternalContinue(op_id), Permission::RW);
463            rsrcs.fyi(call.name());
464            // The mask the thread is stopped under here, as the scheduler may
465            // decide from it before this turn (`BlockedPool::continuation_masks`).
466            match continuation_mask(call, &res) {
467                ContinuationMask::Temporary => {
468                    rsrcs.blocked_signal_mask = blocked_signal_mask;
469                    rsrcs.blocked_signal_mask_unknown = blocked_signal_mask_unknown;
470                }
471                ContinuationMask::Unknown => rsrcs.blocked_signal_mask_unknown = true,
472                ContinuationMask::Restored => {}
473            }
474            resource_request(guest, rsrcs).await;
475        }
476        if native_returned && let Some(access) = input_access {
477            // Synchronous retirement, before any subsequent yield or RPC.
478            access.readmitted()?;
479        }
480        res
481    }
482
483    /// Whether [`Self::execute_nonblockable_fd_syscall`] runs `call` as a
484    /// blocking call outside this thread's turn
485    /// ([`Self::record_or_replay_blocking`]), where other threads may run
486    /// before the kernel reads the call's arguments: without sequentialized
487    /// threads, for an external descriptor under record and replay, for a
488    /// descriptor that is blocking in the kernel as well, and where the
489    /// descriptor cannot be classified. Decided as that function decides it.
490    pub(crate) fn nonblockable_call_runs_outside_turn<G, C>(&self, guest: &mut G, call: C) -> bool
491    where
492        G: Guest<Self>,
493        C: NonblockableSyscall + Into<Syscall> + Copy,
494    {
495        let wrapped: Syscall = call.into();
496        match ioaction_based_on_fd_status(guest, call) {
497            Err(_) => true,
498            Ok(action) => {
499                !self.cfg.sequentialize_threads
500                    || (self.cfg.recordreplay_modes && !syscall_targets_internal_fd(guest, wrapped))
501                    || action == IOAction::Blocking
502            }
503        }
504    }
505
506    /// Executes a nonblockable syscall according to the following strategy:
507    /// - Record mode: Execute possibly blocking syscall
508    /// - Run mode: Transform the syscall to nonblocking if required before executing
509    ///
510    /// These are fd-oriented syscalls in the sense that whether they block or not depends
511    /// on whether NONBLOCK was set on the corresponding file descriptor.
512    pub async fn execute_nonblockable_fd_syscall<
513        G: Guest<Self>,
514        C: SyscallInfo + NonblockableSyscall + Into<Syscall>,
515    >(
516        &self,
517        guest: &mut G,
518        call: C,
519    ) -> Result<i64, Error> {
520        let wrapped: Syscall = call.into();
521
522        let action = match ioaction_based_on_fd_status(guest, call) {
523            Ok(action) => action,
524            Err(errno) => {
525                // Descriptor metadata is advisory for choosing an execution strategy. If the
526                // descriptor is invalid or cannot be classified, execute through the
527                // scheduler-safe blocking path and let the kernel provide the syscall errno.
528                // Returning the metadata error (or panicking on it) can change Linux error
529                // precedence, for example connect(-1, invalid_sockaddr, ...).
530                tracing::trace!(
531                    "NonblockableSyscall: fd classification failed with {}; executing kernel-authoritatively: {}",
532                    errno,
533                    call.name()
534                );
535                return self.record_or_replay_blocking(guest, wrapped).await;
536            }
537        };
538
539        // Is this operation on a container-INTERNAL fd (a pipe, a socketpair
540        // endpoint, or a socket whose peer is a guest)? Internal fds are made
541        // physically nonblocking even in record/replay (see handle_pipe2,
542        // handle_socketpair and classify_socket_peer), so they can take the
543        // deterministic InternalIOPolling nonblockize-and-retry path. They must NOT be
544        // forced onto the BlockingExternalIO path in R/R: a reader and its paired
545        // writer are not independent, so descheduling the reader as "external
546        // blocking IO" deadlocks the sequentialized scheduler (the documented R/R pipe
547        // hang), and a one-shot read on the physically nonblocking fd returns EAGAIN.
548        // Truly external endpoints (host fds, network sockets, sockets whose peer
549        // is a host process) still use BlockingExternalIO.
550        let internal_fd = syscall_targets_internal_fd(guest, wrapped);
551
552        if !self.cfg.sequentialize_threads
553            || (self.cfg.recordreplay_modes && !internal_fd)
554            || action == IOAction::Blocking
555        {
556            tracing::trace!(
557                "NonblockableSyscall: executing in blocking mode after all: {}",
558                call.name()
559            );
560            // We let these have nondeterminstic timing in record mode:
561            Ok(self.record_or_replay_blocking(guest, wrapped).await?)
562            // If in the future we want to record EXTERNAL network traffic only, we have a
563            // challenge to overcome.  We don't know if we need to record until after the
564            // accept completes, so we need an API for *post-facto* recording.
565        } else if action == IOAction::NonblockizeRetry {
566            tracing::trace!(
567                "NonblockableSyscall: converting to nonblocking syscall (internal polling): {}",
568                call.name()
569            );
570            let mut rsrc = Resources::new(guest.thread_state().dettid);
571            rsrc.insert(ResourceID::InternalIOPolling, Permission::W);
572            rsrc.fyi(call.name());
573            // Binary record/replay keeps the internal-FD authority captured at
574            // entry, as its existing FIFO-open policy requires. Plain runs
575            // instead need revocable proof of closed guest membership.
576            if self.cfg.recordreplay_modes {
577                rsrc.polls_guest_state_only = internal_fd;
578                refresh_guest_poll_state(guest, wrapped, &mut rsrc);
579            } else {
580                (rsrc.polls_guest_state_only, rsrc.relies_on_guest_held_marks) =
581                    single_fd_poll_readiness(guest, wrapped, internal_fd);
582            }
583            // A named FIFO may also be held by a process outside the guest.
584            rsrc.polls_a_named_fifo =
585                internal_fd && get_fd(wrapped).is_some_and(|fd| is_named_fifo(guest, fd));
586            // A pipe read: a recorded private `SIGCHLD` ends it as on Linux.
587            rsrc.private_sigchld_decides = reads_a_pipe(guest, wrapped);
588            // A read waits for data, a write for room (`--pipe-wake-latency`).
589            rsrc.polls_channel = crate::pipe_wake::poll_of(&wrapped).and_then(|(fd, wants)| {
590                self.pipe_channel_end(guest, fd)
591                    .map(|end| Box::new((end.channel, wants)))
592            });
593            // In record/replay mode, route an internal-fd (pipe or socketpair) read/write through the
594            // record/replay subtool so its data is captured on record and reproduced on
595            // replay (see retry_nonblocking_syscall). In plain `hermit run` there is no
596            // recorder, so execute directly (subtool = None).
597            let subtool = (self.cfg.recordreplay_modes && internal_fd).then_some(self);
598            Ok(retry_nonblocking_syscall(guest, call, rsrc, subtool).await?)
599        } else {
600            assert!(action == IOAction::PassThru);
601            tracing::trace!(
602                "NonblockableSyscall: just passing it through: {}",
603                call.name()
604            );
605            // Otherwise, the socket was already nonblocking, so we can safely execute it just once.
606            self.record_or_replay_preserving_tool_errors(guest, wrapped)
607                .await
608        }
609    }
610
611    // AUTONOMOUS-BOT-IMPLEMENTED
612    // TODO-HUMAN-REVIEW(#547)
613    /// Complete a logically blocking pipe writev after Hermit has made the pipe physically
614    /// nonblocking. A positive short write is an implementation artifact here: without
615    /// O_NONBLOCK, Linux blocks until the full vector is written unless a signal or error
616    /// interrupts it. Atomic vectors retain a private iovec snapshot for every retry; larger
617    /// vectors advance a positive short-write remainder through scalar writes.
618    pub async fn execute_blocking_pipe_writev<G: Guest<Self>>(
619        &self,
620        guest: &mut G,
621        call: syscalls::Writev,
622        expected_open_file: OpenFileId,
623    ) -> Result<i64, Error> {
624        const MAX_IOVECS: usize = 1024;
625        // Linux limits a single vectored transfer to INT_MAX rounded down to a page.
626        let max_rw_count = crate::page::max_rw_count();
627        // Linux guarantees pipe writes through this size are atomic.
628        const PIPE_BUF: usize = 4096;
629        // Every backend provides at least 512 bytes of tool scratch. Linux's
630        // own fast-iovec path is smaller; this covers common vectors.
631        const STACK_IOVECS: usize = 32;
632
633        let Some(iov_addr) = call.iov() else {
634            return self.execute_nonblockable_fd_syscall(guest, call).await;
635        };
636        if call.len() == 0 || call.len() > MAX_IOVECS {
637            return self.execute_nonblockable_fd_syscall(guest, call).await;
638        }
639
640        let iovecs: Vec<(usize, usize)> = {
641            let mut raw_iovecs = vec![
642                libc::iovec {
643                    iov_base: std::ptr::null_mut(),
644                    iov_len: 0,
645                };
646                call.len()
647            ];
648            guest.memory().read_values(iov_addr, &mut raw_iovecs)?;
649            raw_iovecs
650                .into_iter()
651                .map(|iovec| (iovec.iov_base as usize, iovec.iov_len))
652                .collect()
653        };
654        // The array as the call began, in the form the guest's own `writev`
655        // copies it: that attempt may come after another thread has run
656        // (`EntryImport`), unlike the later ones, which write from `iovecs`.
657        // Boxed, so the call's future stays small (`hot_path_futures_stay_small`).
658        let entry_import =
659            EntryImport::capture(&guest.memory(), &Syscall::Writev(call)).map(Box::new);
660        let requested = iovecs.iter().try_fold(0usize, |total, (_, length)| {
661            total.checked_add(*length).ok_or(Errno::EINVAL)
662        })?;
663        if requested > isize::MAX as usize {
664            return Err(Errno::EINVAL.into());
665        }
666        let target = requested.min(max_rw_count);
667        if target == 0 {
668            return self.execute_nonblockable_fd_syscall(guest, call).await;
669        }
670
671        let atomic_pipe_write = target <= PIPE_BUF;
672
673        tracing::trace!(
674            "NonblockableSyscall: converting to nonblocking syscall (internal polling): writev"
675        );
676        let guest_held = syscall_targets_guest_held_fd(guest, call.into());
677        let mut resources = pipe_writev_resources(guest.thread_state().dettid, call, guest_held);
678        resources.polls_a_named_fifo = is_named_fifo(guest, call.fd());
679        resources.polls_guest_state_only = self.cfg.recordreplay_modes
680            || resources.polls_guest_state_only
681            || resources.polls_a_named_fifo;
682        resources.polls_channel = self
683            .pipe_channel_end(guest, call.fd())
684            .map(|end| Box::new((end.channel, crate::pipe_wake::Wants::Space)));
685        let subtool = self.cfg.recordreplay_modes.then_some(self);
686        let mut current = Syscall::Writev(call);
687        let mut written_total = 0usize;
688
689        // Keep ordinary signals pending while the scheduler and the target-side
690        // disposition check decide whether a wakeup interrupts this write. The
691        // ptrace-owned backends need the explicit mask while inspecting the
692        // target's current disposition.
693        let blocked_mask = blocked_signal_mask();
694        let mut stack = guest.stack().await;
695        let atomic_scratch_iov = if atomic_pipe_write && iovecs.len() <= STACK_IOVECS {
696            let mut raw_iovecs = [libc::iovec {
697                iov_base: std::ptr::null_mut(),
698                iov_len: 0,
699            }; STACK_IOVECS];
700            for (raw, (base, length)) in raw_iovecs.iter_mut().zip(&iovecs) {
701                raw.iov_base = *base as *mut libc::c_void;
702                raw.iov_len = *length;
703            }
704            let scratch_iov: Addr<libc::iovec> = stack.push(raw_iovecs).cast();
705            Some(scratch_iov.as_raw())
706        } else {
707            None
708        };
709        let blocked_mask_addr = stack.push(blocked_mask);
710        let old_mask_addr = stack.reserve::<KernelSigset>();
711        let turn_mask_cell = stack.reserve::<KernelSigset>();
712        let action_addr = stack.reserve::<KernelSigaction>();
713        let _mask_guard = stack.commit()?;
714        // A parked pipe write under the in-guest delivery gate (A5 step 3c.1c,
715        // D50 reader 1) installs no private mask: once an attempt would wait
716        // for room, the design's deliverable set under the thread's published
717        // mask decides (`gated_wait_ends`).
718        let gated = guest.config().in_guest_signal_delivery;
719        let checks_at_turn =
720            !gated && decides_interruption_at_turn(guest, guest_signal_state_task(guest));
721        // Where the turn decides (`read_turn_signals`), the state is read, and
722        // the first attempt made, in the slice of the call's own entry, as the
723        // scalar writer's first attempt is, under the guest's own mask: that
724        // attempt is the guest's own `writev`, which the backend runs as plain
725        // ptrace runs it, with every pending signal left queued in the kernel,
726        // and a copy pending at entry ends the call after it (rules 5 and 6).
727        // No other thread runs between Detcore's snapshot of the iovec array,
728        // taken as it handles the call, and that attempt, so the kernel imports
729        // the array Detcore copied, which every later attempt writes from. A
730        // rewrite that lands before the snapshot (while the call waits at a
731        // checkpoint before its handler runs) is one Linux could also see,
732        // before the call imports the array.
733        // Requesting a turn first let another thread rewrite the array before
734        // the import, and an atomic write to a full pipe then wrote its bytes
735        // (writev_determinism's atomic full-pipe case, which reverted the
736        // first landing of R2b-1). Only a call that goes on polling installs the
737        // private mask, through `KernelSignalWait::block`, in the same turn
738        // (`install_turn_mask`); no copy that ends the call is pending then,
739        // and one Linux discarded at send is held until the call returns.
740        // Installing it first let two such copies stop the installation, and
741        // the backend's single held slot kept only the second (the Claude
742        // implementation review of R2b-1, F2;
743        // https://github.com/rrnewton/reverie/issues/1012).
744        let mut first_turn = if checks_at_turn {
745            let turn = read_turn_signals(guest, None, 0, NamedRealTime::ByDisposition).await?;
746            Some((ResumeStatus::Normal, turn))
747        } else {
748            None
749        };
750        let mut turn_wait = checks_at_turn
751            .then(|| KernelSignalWait::new(guest, 0, false, call.signal_interrupt_errno()));
752        let mut turn_mask: Option<KernelSigset> = None;
753        let guest_signal_mask = if checks_at_turn || gated {
754            0
755        } else {
756            block_signals_for_disposition(guest, blocked_mask_addr, old_mask_addr).await?
757        };
758
759        let result: Result<i64, Error> = loop {
760            let written_before = written_total;
761            // A cross-task signal can replace the initial scheduler request,
762            // before the first physical attempt, as well as a later retry.
763            let (status, turn) = match first_turn.take() {
764                Some((status, turn)) => (status, Some(turn)),
765                None => {
766                    if let Some(wait) = turn_wait.as_mut()
767                        && turn_mask.is_none()
768                    {
769                        match install_turn_mask(guest, wait, turn_mask_cell).await {
770                            Ok(mask) => turn_mask = Some(mask),
771                            Err(Error::Errno(errno)) => {
772                                break if written_total > 0 {
773                                    Ok(written_total as i64)
774                                } else {
775                                    Err(errno.into())
776                                };
777                            }
778                            Err(error) => break Err(error),
779                        }
780                    }
781                    refresh_guest_poll_state(guest, call.into(), &mut resources);
782                    let status = resource_request(guest, resources.clone()).await;
783                    let turn = if checks_at_turn {
784                        // A named signal counts if the guest's own mask leaves
785                        // it unblocked; the private mask keeps every such
786                        // signal pending until this read.
787                        match read_turn_signals(
788                            guest,
789                            turn_mask,
790                            committed_signals(&status),
791                            NamedRealTime::ByDisposition,
792                        )
793                        .await
794                        {
795                            Ok(turn) => Some(turn),
796                            Err(error) => break Err(error),
797                        }
798                    } else {
799                        None
800                    };
801                    (status, turn)
802                }
803            };
804            if turn.is_none() && !gated {
805                let disposition = match wait_signal_disposition(
806                    guest,
807                    status.clone(),
808                    &guest_signal_mask,
809                    action_addr,
810                    true,
811                )
812                .await
813                {
814                    Ok(disposition) => disposition,
815                    Err(error) => break Err(error),
816                };
817                if matches!(status, ResumeStatus::Signaled(_))
818                    && let Some(result) =
819                        interrupted_write_result(&call, written_total, disposition)
820                {
821                    break result;
822                }
823            }
824
825            if resources.poll_attempt > 0
826                && !guest
827                    .thread_state()
828                    .with_detfd(call.fd(), |detfd| {
829                        detfd.open_file_id() == expected_open_file
830                    })
831                    .unwrap_or(false)
832            {
833                break if written_total > 0 {
834                    Ok(written_total as i64)
835                } else {
836                    self.refuse_unserviceable_operation(guest, Sysno::writev, Errno::EOPNOTSUPP)
837                        .await
838                };
839            }
840
841            // The first attempt of a call that decides at its turn runs under
842            // the guest's own mask, where a pending signal would stop a
843            // private injection and the backend's single held slot keeps only
844            // the last of two. So it is always the guest's own `writev`, which
845            // the backend runs with every pending signal left queued in the
846            // kernel (the Claude re-check of R2b-1, N2).
847            let own_first_attempt = turn_wait.is_some() && turn_mask.is_none();
848            let result = if atomic_pipe_write && !own_first_attempt {
849                self.execute_atomic_pipe_writev_attempt(guest, call, &iovecs, atomic_scratch_iov)
850                    .await
851            } else {
852                // The guest's own `writev` copies the guest's array again; a
853                // segment `write` names Hermit's copy.
854                let displaced = matches!(current, Syscall::Writev(_))
855                    .then(|| entry_import.as_ref())
856                    .flatten()
857                    .map(|entry| entry.reinstate(&mut guest.memory()));
858                let result = match subtool {
859                    Some(detcore) => {
860                        detcore
861                            .record_or_replay_preserving_tool_errors(guest, current)
862                            .await
863                    }
864                    None => guest.inject_with_retry(current).await.map_err(Error::from),
865                };
866                if let Err(error) =
867                    put_back_entry_import(guest, &entry_import, displaced, result.is_ok())
868                {
869                    break Err(error);
870                }
871                result
872            };
873            match result {
874                Ok(written) if written > 0 => {
875                    let written = match usize::try_from(written) {
876                        Ok(written) => written,
877                        Err(_) => break Err(Errno::EIO.into()),
878                    };
879                    written_total = match written_total.checked_add(written) {
880                        Some(written_total) => written_total,
881                        None => break Err(Errno::EIO.into()),
882                    };
883                    if written_total >= target {
884                        break Ok(written_total as i64);
885                    }
886                    if atomic_pipe_write {
887                        break Ok(written_total as i64);
888                    }
889                    // A short attempt filled the pipe, so the write would wait:
890                    // a wake ends it with the bytes written, decided after the
891                    // attempt, as Linux tests for room first (R2b-1 rule 6).
892                    if turn.is_some_and(|turn| turn.ends(true)) {
893                        break Ok(written_total as i64);
894                    }
895                    if gated {
896                        match gated_wait_ends(guest, None) {
897                            Ok(true) => break Ok(written_total as i64),
898                            Ok(false) => {}
899                            Err(error) => break Err(error),
900                        }
901                    }
902                    current = match remaining_writev_segment(
903                        call.fd(),
904                        &iovecs,
905                        written_total,
906                        target - written_total,
907                    ) {
908                        Ok(Some(write)) => Syscall::Write(write),
909                        Ok(None) => break Ok(written_total as i64),
910                        Err(_) => break Ok(written_total as i64),
911                    };
912                }
913                Ok(0) => break Ok(written_total as i64),
914                Err(Error::Errno(Errno::EAGAIN))
915                    if turn.is_some_and(|turn| turn.ends(written_total > 0)) =>
916                {
917                    break if written_total > 0 {
918                        Ok(written_total as i64)
919                    } else {
920                        Err(call.signal_interrupt_errno().into())
921                    };
922                }
923                Err(Error::Errno(Errno::EAGAIN)) => {
924                    if gated {
925                        match gated_wait_ends(guest, None) {
926                            Ok(true) => {
927                                break if written_total > 0 {
928                                    Ok(written_total as i64)
929                                } else {
930                                    Err(call.signal_interrupt_errno().into())
931                                };
932                            }
933                            Ok(false) => {}
934                            Err(error) => break Err(error),
935                        }
936                    }
937                    if !atomic_pipe_write && matches!(current, Syscall::Writev(_)) {
938                        current = match remaining_writev_segment(call.fd(), &iovecs, 0, target) {
939                            Ok(Some(write)) => Syscall::Write(write),
940                            Ok(None) => break Ok(0),
941                            Err(error) => break Err(error.into()),
942                        };
943                    }
944                }
945                Err(error) => {
946                    break finish_partial_record_or_replay_write(written_total as i64, error);
947                }
948                Ok(_) => break Err(Errno::EIO.into()),
949            }
950
951            resources.previous_attempt_made_progress = written_total > written_before;
952            // Bytes this attempt wrote ready the readers before this thread
953            // polls for room (`--pipe-wake-latency`).
954            if resources.previous_attempt_made_progress {
955                self.note_fd_readied(guest, call.fd(), crate::pipe_wake::Wants::Data)
956                    .await;
957            }
958            resources.poll_attempt += 1;
959            tracing::trace!(
960                "Retry #{} for {}blocking pipe writev after EAGAIN: {}",
961                resources.poll_attempt,
962                if atomic_pipe_write { "atomic " } else { "" },
963                call.display(&guest.memory())
964            );
965            record_retry_event(guest, call).await;
966        };
967        keep_entry_view(guest, entry_import);
968
969        match turn_wait {
970            Some(mut wait) => {
971                let restored = wait.restore(guest, Some(turn_mask_cell)).await;
972                if turn_mask.is_some() {
973                    crate::tool_global::set_saved_guest_sigmask(guest, None).await;
974                }
975                match result_after_restore(restored, result) {
976                    Ok(result) => refuse_held_signal_loss(guest, Sysno::writev, result).await,
977                    Err(error) => Err(error),
978                }
979            }
980            None if gated => result,
981            None => {
982                restore_signals_after_disposition(guest, old_mask_addr).await?;
983                result
984            }
985        }
986    }
987
988    // AUTONOMOUS-BOT-IMPLEMENTED
989    // TODO-HUMAN-REVIEW(#2176): Review scalar blocking-pipe write completion and
990    // the fail-closed descriptor-replacement boundary.
991    /// Complete a logically blocking scalar pipe write after Hermit has made the pipe
992    /// physically nonblocking.
993    ///
994    /// Linux may return a positive short write for a request larger than `PIPE_BUF`, then
995    /// continue blocking for the remainder. Hermit's physical `O_NONBLOCK` is an internal
996    /// scheduler mechanism, so exposing that first short write changes guest behavior. Retry
997    /// the unconsumed suffix until the logical write completes, a signal arrives, or a real
998    /// error occurs. A signal or error after progress returns the partial byte count, matching
999    /// Linux.
1000    ///
1001    /// A concurrent close/dup2 can replace the numeric fd while this helper is yielded. Linux
1002    /// keeps the original open-file description alive inside a blocking syscall, but Reverie
1003    /// does not yet expose a backend-neutral retained-fd handle. Detect replacement before a
1004    /// retry and fail closed rather than writing the suffix into an unrelated object.
1005    pub async fn execute_blocking_pipe_write<G: Guest<Self>>(
1006        &self,
1007        guest: &mut G,
1008        call: syscalls::Write,
1009        expected_open_file: OpenFileId,
1010    ) -> Result<i64, Error> {
1011        let max_rw_count = crate::page::max_rw_count();
1012
1013        let target = call.len().min(max_rw_count);
1014        if target == 0 {
1015            return self.execute_nonblockable_fd_syscall(guest, call).await;
1016        }
1017
1018        tracing::trace!(
1019            "NonblockableSyscall: converting to nonblocking syscall (internal polling): write"
1020        );
1021        let mut resources = Resources::new(guest.thread_state().dettid);
1022        resources.insert(ResourceID::InternalIOPolling, Permission::W);
1023        resources.fyi(call.name());
1024        // Interruptible by a cross-task signal the kernel would deliver, as a
1025        // polled read is (`retry_nonblocking_syscall_helper`): the scheduler
1026        // answers the next request `Signaled`, and the write returns the
1027        // bytes it wrote, or ERESTARTSYS before any
1028        // (<https://github.com/rrnewton/hermit/issues/4187>).
1029        resources.set_signal_interrupt_errno(call.signal_interrupt_errno());
1030        resources.signal_state_task = guest_signal_state_task(guest);
1031        // A pipe write: a recorded private `SIGCHLD` ends it as on Linux
1032        // (`Resources::private_sigchld_decides`).
1033        resources.private_sigchld_decides = true;
1034        resources.polls_a_named_fifo = is_named_fifo(guest, call.fd());
1035        resources.relies_on_guest_held_marks = syscall_targets_guest_held_fd(guest, call.into());
1036        resources.polls_guest_state_only = self.cfg.recordreplay_modes
1037            || resources.relies_on_guest_held_marks
1038            || resources.polls_a_named_fifo;
1039        resources.polls_channel = self
1040            .pipe_channel_end(guest, call.fd())
1041            .map(|end| Box::new((end.channel, crate::pipe_wake::Wants::Space)));
1042        // Without a host task whose kernel state the scheduler can filter by
1043        // (KVM), the scheduler keeps every signal and the write checks after
1044        // the wake, as `execute_blocking_pipe_writev` does, whether one would
1045        // be delivered: a blocked or ignored signal must not end a write that
1046        // has made progress, which would return a short count where Linux
1047        // keeps waiting.
1048        let disposition_scratch = if resources.signal_state_task.is_none() {
1049            resources.decides_signal_interruption_itself = true;
1050            let mut stack = guest.stack().await;
1051            let mask_addr = stack.reserve::<KernelSigset>();
1052            let action_addr = stack.reserve::<KernelSigaction>();
1053            let guard = stack.commit()?;
1054            Some((mask_addr, action_addr, guard))
1055        } else {
1056            None
1057        };
1058        let subtool = self.cfg.recordreplay_modes.then_some(self);
1059        let mut current = call;
1060        let mut written_total = 0usize;
1061        // Under the in-guest delivery gate (A5 step 3c.1c, D50 reader 1), as
1062        // for a pipe `writev`: no private mask, and the design's deliverable
1063        // set decides once an attempt would wait for room.
1064        let gated = guest.config().in_guest_signal_delivery;
1065        let checks_at_turn =
1066            !gated && decides_interruption_at_turn(guest, resources.signal_state_task);
1067        // The write owns the guest's mask while it blocks every blockable
1068        // signal, as the generic poll does (`retry_nonblocking_syscall_helper`):
1069        // an attempt after the first is a private injection of the
1070        // unwritten suffix, which a pending signal could stop and move into
1071        // the backend's single held slot, where a second one replaces it
1072        // (https://github.com/rrnewton/hermit/issues/4273, stage 2).
1073        let mut turn_wait = checks_at_turn
1074            .then(|| KernelSignalWait::new(guest, 0, false, call.signal_interrupt_errno()));
1075        let mut published = PublishedWaitMask::default();
1076
1077        let result: Result<i64, Error> = loop {
1078            let written_before = written_total;
1079            // A signal ends the write only if it would still wait for room:
1080            // the attempt below runs first, as Linux's `pipe_write` checks
1081            // for a pending signal only when it must wait. After the signal,
1082            // the bytes written so far are returned, or the restart errno if
1083            // there are none.
1084            let status = if resources.poll_attempt > 0 {
1085                refresh_guest_poll_state(guest, call.into(), &mut resources);
1086                Some(resource_request(guest, resources.clone()).await)
1087            } else {
1088                None
1089            };
1090            // Read before every attempt, the turnless first one included
1091            // (`read_turn_signals`): a copy pending at the write's own entry
1092            // is decided in this slice, before the writer can yield, so a later
1093            // `SIG_IGN` and a new handler cannot take it
1094            // (https://github.com/rrnewton/hermit/issues/4273, R2b-1 rule 4).
1095            // Once the write blocks, the turn decides under the guest's own
1096            // mask, which the write owns (Codex review of the R2b-2 design
1097            // rev3, P2-1).
1098            let turn = if checks_at_turn {
1099                let committed = status.as_ref().map_or(0, committed_signals);
1100                // The first attempt is the guest's own write, which the
1101                // backend runs with every pending signal left queued, so only
1102                // a later attempt needs the block.
1103                match turn_signals_with_block(
1104                    guest,
1105                    &mut turn_wait,
1106                    if resources.poll_attempt > 0 {
1107                        ProbeMaskPolicy::PendingSignals
1108                    } else {
1109                        ProbeMaskPolicy::Guest
1110                    },
1111                    committed,
1112                    resources.private_sigchld_decides,
1113                    &mut published,
1114                )
1115                .await
1116                {
1117                    Ok(turn) => Some(turn),
1118                    Err(error) => break Err(error),
1119                }
1120            } else {
1121                None
1122            };
1123            let signaled = if turn.is_some() || gated {
1124                false
1125            } else {
1126                match (&status, &disposition_scratch) {
1127                    (Some(ResumeStatus::Signaled(_)), None) => true,
1128                    (
1129                        Some(status @ ResumeStatus::Signaled(_)),
1130                        Some((mask_addr, action_addr, _)),
1131                    ) => {
1132                        match own_disposition_interrupts(
1133                            guest,
1134                            status.clone(),
1135                            *mask_addr,
1136                            *action_addr,
1137                        )
1138                        .await
1139                        {
1140                            Ok(interrupts) => interrupts,
1141                            Err(error) => break Err(error),
1142                        }
1143                    }
1144                    _ => false,
1145                }
1146            };
1147
1148            if resources.poll_attempt > 0
1149                && !guest
1150                    .thread_state()
1151                    .with_detfd(call.fd(), |detfd| {
1152                        detfd.open_file_id() == expected_open_file
1153                    })
1154                    .unwrap_or(false)
1155            {
1156                break if written_total > 0 {
1157                    Ok(written_total as i64)
1158                } else {
1159                    self.refuse_unserviceable_operation(guest, Sysno::write, Errno::EOPNOTSUPP)
1160                        .await
1161                };
1162            }
1163
1164            let result = match subtool {
1165                Some(detcore) => {
1166                    detcore
1167                        .record_or_replay_preserving_tool_errors(guest, current)
1168                        .await
1169                }
1170                None => guest.inject_with_retry(current).await.map_err(Error::from),
1171            };
1172            // As in `retry_nonblocking_syscall_helper`, an attempt that the
1173            // pending signal stopped under record and replay is not made again.
1174            match result {
1175                Ok(written) if written > 0 => {
1176                    let Ok(written) = usize::try_from(written) else {
1177                        break Err(Errno::EIO.into());
1178                    };
1179                    let Some(remaining) = target.checked_sub(written_total) else {
1180                        break Err(Errno::EIO.into());
1181                    };
1182                    if written > remaining {
1183                        break Err(Errno::EIO.into());
1184                    }
1185                    let Some(total) = written_total.checked_add(written) else {
1186                        break Err(Errno::EIO.into());
1187                    };
1188                    written_total = total;
1189                    // A short attempt filled the pipe, so the write would wait:
1190                    // a wake ends it with the bytes written (`TurnSignals::ends`).
1191                    if written_total == target || turn.map_or(signaled, |turn| turn.ends(true)) {
1192                        break Ok(written_total as i64);
1193                    }
1194                    if gated {
1195                        match gated_wait_ends(guest, None) {
1196                            Ok(true) => break Ok(written_total as i64),
1197                            Ok(false) => {}
1198                            Err(error) => break Err(error),
1199                        }
1200                    }
1201                    let Some(buffer) = call.buf() else {
1202                        break Err(Errno::EFAULT.into());
1203                    };
1204                    let Some(next_buffer) = buffer
1205                        .as_raw()
1206                        .checked_add(written_total)
1207                        .and_then(Addr::<u8>::from_raw)
1208                    else {
1209                        break finish_partial_record_or_replay_write(
1210                            written_total as i64,
1211                            Errno::EFAULT.into(),
1212                        );
1213                    };
1214                    current = call
1215                        .with_buf(Some(next_buffer))
1216                        .with_len(target - written_total);
1217                }
1218                Ok(0) => break Ok(written_total as i64),
1219                Err(Error::Errno(Errno::EAGAIN))
1220                    if turn.map_or(signaled, |turn| turn.ends(written_total > 0)) =>
1221                {
1222                    break if written_total > 0 {
1223                        Ok(written_total as i64)
1224                    } else {
1225                        Err(call.signal_interrupt_errno().into())
1226                    };
1227                }
1228                Err(Error::Errno(Errno::EAGAIN)) => {
1229                    if gated {
1230                        match gated_wait_ends(guest, None) {
1231                            Ok(true) => {
1232                                break if written_total > 0 {
1233                                    Ok(written_total as i64)
1234                                } else {
1235                                    Err(call.signal_interrupt_errno().into())
1236                                };
1237                            }
1238                            Ok(false) => {}
1239                            Err(error) => break Err(error),
1240                        }
1241                    }
1242                }
1243                Err(error) => {
1244                    break finish_partial_record_or_replay_write(written_total as i64, error);
1245                }
1246                Ok(_) => break Err(Errno::EIO.into()),
1247            }
1248
1249            resources.previous_attempt_made_progress = written_total > written_before;
1250            // Bytes this attempt wrote ready the readers before this thread
1251            // polls for room (`--pipe-wake-latency`).
1252            if resources.previous_attempt_made_progress {
1253                self.note_fd_readied(guest, call.fd(), crate::pipe_wake::Wants::Data)
1254                    .await;
1255            }
1256            resources.poll_attempt += 1;
1257            tracing::trace!(
1258                "Retry #{} for blocking pipe write: {}",
1259                resources.poll_attempt,
1260                call.display(&guest.memory())
1261            );
1262            record_retry_event(guest, call).await;
1263        };
1264
1265        // The single exit, as in `retry_nonblocking_syscall_helper`: the
1266        // guest's mask goes back, through `Guest::set_blocked_signals` with no
1267        // scratch memory, before the result is returned.
1268        match turn_wait {
1269            Some(mut wait) if wait.effective_mask().is_some() => {
1270                let restored = wait.restore(guest, None).await;
1271                publish_wait_mask(guest, None, &mut published).await;
1272                match result_after_restore(restored, result) {
1273                    Ok(result) => refuse_held_signal_loss(guest, Sysno::write, result).await,
1274                    Err(error) => Err(error),
1275                }
1276            }
1277            _ => result,
1278        }
1279    }
1280
1281    async fn execute_atomic_pipe_writev_attempt<G: Guest<Self>>(
1282        &self,
1283        guest: &mut G,
1284        call: syscalls::Writev,
1285        iovecs: &[(usize, usize)],
1286        stack_iov: Option<usize>,
1287    ) -> Result<i64, Error> {
1288        if let Some(stack_iov) = stack_iov {
1289            let scratch_call = call.with_iov(Addr::from_raw(stack_iov));
1290            return if self.cfg.recordreplay_modes {
1291                self.record_or_replay_preserving_tool_errors(guest, scratch_call)
1292                    .await
1293            } else {
1294                guest
1295                    .inject_with_retry(scratch_call)
1296                    .await
1297                    .map_err(Error::from)
1298            };
1299        }
1300
1301        let mapping_len = iovecs
1302            .len()
1303            .checked_mul(std::mem::size_of::<libc::iovec>())
1304            .expect("validated iovec count cannot overflow scratch length");
1305        let mapped = guest
1306            .inject_with_retry(Syscall::Mmap(
1307                syscalls::Mmap::new()
1308                    .with_addr(None)
1309                    .with_len(mapping_len)
1310                    .with_prot(ProtFlags::PROT_READ | ProtFlags::PROT_WRITE)
1311                    .with_flags(MapFlags::MAP_PRIVATE | MapFlags::MAP_ANONYMOUS)
1312                    .with_fd(-1)
1313                    .with_offset(0),
1314            ))
1315            .await
1316            .unwrap_or_else(|error| panic!("failed to map atomic writev scratch: {error}"));
1317        let mapped = usize::try_from(mapped)
1318            .unwrap_or_else(|_| panic!("atomic writev scratch mmap returned {mapped}"));
1319        let scratch_iov = Addr::<libc::iovec>::from_raw(mapped)
1320            .unwrap_or_else(|| panic!("atomic writev scratch mmap returned a null address"));
1321        let mapping_addr: Addr<libc::c_void> = scratch_iov.cast();
1322        let write_result = {
1323            let raw_iovecs: Vec<libc::iovec> = iovecs
1324                .iter()
1325                .map(|(base, length)| libc::iovec {
1326                    iov_base: *base as *mut libc::c_void,
1327                    iov_len: *length,
1328                })
1329                .collect();
1330            // SAFETY: the injected anonymous mapping is exclusively owned scratch space.
1331            guest
1332                .memory()
1333                .write_values(unsafe { scratch_iov.into_mut() }, &raw_iovecs)
1334        };
1335        if let Err(write_error) = write_result {
1336            guest
1337                .inject_with_retry(Syscall::Munmap(
1338                    syscalls::Munmap::new()
1339                        .with_addr(Some(mapping_addr))
1340                        .with_len(mapping_len),
1341                ))
1342                .await
1343                .unwrap_or_else(|cleanup_error| {
1344                    panic!(
1345                        "failed to populate atomic writev scratch ({write_error}); cleanup failed ({cleanup_error})"
1346                    )
1347                });
1348            panic!("failed to populate atomic writev scratch: {write_error}");
1349        }
1350
1351        let scratch_call = call.with_iov(Some(scratch_iov));
1352        let result = if self.cfg.recordreplay_modes {
1353            self.record_or_replay_preserving_tool_errors(guest, scratch_call)
1354                .await
1355        } else {
1356            guest
1357                .inject_with_retry(scratch_call)
1358                .await
1359                .map_err(Error::from)
1360        };
1361        guest
1362            .inject_with_retry(Syscall::Munmap(
1363                syscalls::Munmap::new()
1364                    .with_addr(Some(mapping_addr))
1365                    .with_len(mapping_len),
1366            ))
1367            .await
1368            .unwrap_or_else(|error| panic!("failed to unmap atomic writev scratch: {error}"));
1369        result
1370    }
1371
1372    /// Override physically_nonblocking to true for the file descriptor, if appropriate.
1373    pub fn maybe_set_nonblocking_fd<G: Guest<Self>>(&self, guest: &G, fd: i32) {
1374        if self.cfg.sequentialize_threads && !self.cfg.debug_externalize_sockets {
1375            guest
1376                .thread_state()
1377                .with_detfd(fd, |detfd| {
1378                    detfd.set_physically_nonblocking();
1379                })
1380                .unwrap();
1381        }
1382    }
1383}
1384
1385fn remaining_writev_segment(
1386    fd: i32,
1387    iovecs: &[(usize, usize)],
1388    mut consumed: usize,
1389    remaining_limit: usize,
1390) -> Result<Option<syscalls::Write>, Errno> {
1391    for (base, length) in iovecs {
1392        if consumed >= *length {
1393            consumed -= *length;
1394            continue;
1395        }
1396        let base = base.checked_add(consumed).ok_or(Errno::EFAULT)?;
1397        let buffer = Addr::<u8>::from_raw(base).ok_or(Errno::EFAULT)?;
1398        return Ok(Some(
1399            syscalls::Write::new()
1400                .with_fd(fd)
1401                .with_buf(Some(buffer))
1402                .with_len((*length - consumed).min(remaining_limit)),
1403        ));
1404    }
1405    Ok(None)
1406}
1407
1408/// A blocking syscall that involves a fail descriptor may be handled in these three ways:
1409#[derive(PartialEq, Eq, Debug)]
1410pub enum IOAction {
1411    /// It may physically block and we can't change that.  Treat it as ExternalBlockingIO.
1412    Blocking,
1413    /// We can nonblockize and retry the call.
1414    NonblockizeRetry,
1415    /// The call is nonblocking already, and safe to execute.
1416    PassThru,
1417}
1418
1419/// Returns the strategy for an FD-based call that may block when executed.
1420///
1421/// Failure means descriptor metadata could not classify the call; the caller must preserve the
1422/// kernel's authority over the syscall result rather than exposing this advisory lookup error.
1423pub fn ioaction_based_on_fd_status<
1424    G: Guest<Detcore<T>>,
1425    T: RecordOrReplay,
1426    C: NonblockableSyscall + Into<Syscall>,
1427>(
1428    guest: &mut G,
1429    call: C,
1430) -> Result<IOAction, Errno> {
1431    let wrapped: Syscall = call.into();
1432    let fd = get_fd(wrapped).unwrap_or_else(|| panic!("Failed to get fd for {}", call.name()));
1433    let (phys, virt, error_queue_never_waits) = guest.thread_state().with_detfd(fd, |detfd| {
1434        (
1435            detfd.physically_nonblocking(),
1436            detfd.is_nonblocking(),
1437            detfd.error_queue_never_waits(),
1438        )
1439    })?;
1440    tracing::trace!(
1441        "Checking FD {} for nonblocking: physical {} / virtual {}",
1442        fd,
1443        phys,
1444        virt
1445    );
1446    let action = ioaction_for_fd_status(fd, virt, phys, call.flags_forbid_waiting());
1447    // A receive with MSG_ERRQUEUE on a socket whose protocol reads that
1448    // queue without waiting also runs once, as Linux does.
1449    if action == IOAction::NonblockizeRetry && error_queue_never_waits && call.reads_error_queue() {
1450        Ok(IOAction::PassThru)
1451    } else {
1452        Ok(action)
1453    }
1454}
1455
1456/// [`ioaction_based_on_fd_status`] for the descriptor `fd` alone, before any
1457/// flag of the call itself, for a caller whose call does not implement
1458/// [`NonblockableSyscall`].
1459pub(crate) fn ioaction_based_on_fd<G: Guest<Detcore<T>>, T: RecordOrReplay>(
1460    guest: &mut G,
1461    fd: i32,
1462) -> Result<IOAction, Errno> {
1463    let (phys, virt) = guest.thread_state().with_detfd(fd, |detfd| {
1464        (detfd.physically_nonblocking(), detfd.is_nonblocking())
1465    })?;
1466    Ok(ioaction_for_fd_status(fd, virt, phys, false))
1467}
1468
1469/// The decision of [`ioaction_based_on_fd_status`] from the descriptor's
1470/// virtual and physical nonblocking state and the call's own no-wait flags,
1471/// split out so the decision itself is unit-testable without a guest.
1472fn ioaction_for_fd_status(fd: i32, virt: bool, phys: bool, flags_forbid_waiting: bool) -> IOAction {
1473    if virt && !phys {
1474        // TF: simulate nonblocking on top of physically blocking? How?
1475        panic!(
1476            "Invariant violation, fd {fd}: we cannot simulate nonblocking behavior when set to blocking mode in the kernel."
1477        );
1478    } else if !virt && !phys {
1479        // FF: logically blocking, physically blocking, this could only work with BlockingExternalIO.
1480        IOAction::Blocking
1481    } else if virt && phys {
1482        // TT: both nonblocking, so firing once is sufficient
1483        IOAction::PassThru
1484    } else if flags_forbid_waiting {
1485        // FT, but the call itself does not wait (MSG_DONTWAIT), so firing once
1486        // is what Linux does.
1487        IOAction::PassThru
1488    } else {
1489        // FT: Need to simulate blocking on top of nonblocking.
1490        IOAction::NonblockizeRetry
1491    }
1492}
1493
1494/// Does this single-fd syscall operate on a container-INTERNAL file descriptor?
1495///
1496/// This is an execution-routing classification, not proof that only guests can
1497/// change readiness. It recognizes pipes and `socketpair(2)` endpoints. Internal
1498/// fds are made physically
1499/// nonblocking (see `handle_pipe2` and `handle_socketpair`) so a potentially-blocking
1500/// op on them can use the deterministic `InternalIOPolling` nonblockize-and-retry
1501/// strategy instead of `BlockingExternalIO`. Treating an internal fd as external
1502/// blocking IO deadlocks the sequentialized scheduler in record/replay, because a
1503/// reader and its paired writer are not independent.
1504///
1505/// Socketpair origin is known at creation time, and remains useful for routing
1506/// even after an endpoint is exported or a datagram endpoint changes its peer.
1507/// Under record and replay a connected socket whose peer is a guest, which
1508/// `Detcore::classify_socket_peer` finds from the peer's credentials after
1509/// `connect`, `accept` or an `SCM_RIGHTS` receive, is internal too. Every other
1510/// socket, including every loopback IP socket, conservatively remains external.
1511///
1512/// Readiness needs a separate decision: a socketpair endpoint can leave the guest
1513/// through `SCM_RIGHTS`, and a bound, disconnected datagram endpoint can receive
1514/// from an outside sender. See `single_fd_poll_readiness`.
1515///
1516/// A descriptor a guest RECEIVES over `SCM_RIGHTS` is recorded
1517/// (`Detcore::record_received_descriptors`) or discovered. One that names an open
1518/// file the table models joins it as an alias and keeps that file's marks. Any
1519/// other is recorded from the kernel's facts: a received pipe is a pipe here, and
1520/// under record and replay a received socket is internal when its peer is a guest
1521/// (`Detcore::classify_socket_peer`). A received socketpair end from another process
1522/// outside record and replay is not recognized as internal: its open file
1523/// description is still physically nonblocking, and the kernel's `O_NONBLOCK` is all
1524/// the receiver's record knows, so a blocking read through it can return EAGAIN
1525/// where Linux would wait.
1526///
1527/// Syscalls whose fd is not directly extractable (e.g. poll/ppoll, which carry a
1528/// pointer to an fd array) return false and keep their existing handling.
1529pub fn syscall_targets_internal_fd<G: Guest<Detcore<T>>, T: RecordOrReplay>(
1530    guest: &mut G,
1531    call: Syscall,
1532) -> bool {
1533    // An epoll wait's descriptor is the epoll set it waits on, never a pipe it
1534    // reads; a guest that passes a pipe gets EINVAL from the kernel.
1535    match call {
1536        Syscall::EpollPwait(_) => return false,
1537        #[cfg(target_arch = "x86_64")]
1538        Syscall::EpollWait(_) => return false,
1539        _ => {}
1540    }
1541    match get_fd(call) {
1542        Some(fd) => guest
1543            .thread_state()
1544            .with_detfd(fd, |detfd| {
1545                // This is read-end provenance, not a blanket external-fd
1546                // classification: writes and socketpairs retain the guard.
1547                if matches!(call, Syscall::Read(_) | Syscall::Readv(_))
1548                    && detfd.inherited_external_read().is_some()
1549                {
1550                    return false;
1551                }
1552                matches!(detfd.ty(), FdType::Pipe) || detfd.is_internal_socket()
1553            })
1554            .unwrap_or(false),
1555        None => false,
1556    }
1557}
1558
1559/// Whether `call`'s one descriptor is guest-held (`DetFd::is_guest_held`):
1560/// only guests hold its other end, so only a guest can make the call ready.
1561/// It feeds only `Resources::polls_guest_state_only`, never the record/replay
1562/// routing `syscall_targets_internal_fd` decides. An epoll wait's descriptor
1563/// is its set, which this does not track.
1564fn syscall_targets_guest_held_fd<G: Guest<Detcore<T>>, T: RecordOrReplay>(
1565    guest: &mut G,
1566    call: Syscall,
1567) -> bool {
1568    if matches!(call, Syscall::EpollWait(_) | Syscall::EpollPwait(_)) {
1569        return false;
1570    }
1571    get_fd(call).is_some_and(|fd| {
1572        guest
1573            .thread_state()
1574            .with_detfd(fd, |detfd| detfd.is_guest_held())
1575            .unwrap_or(false)
1576    })
1577}
1578
1579/// The generic single-fd poll's guest-only claim and whether it rests on
1580/// revocable guest-held marks. Socketpair origin selects live internal routing,
1581/// but only its current guest-held mark can establish guest-only readiness.
1582/// Mark-dependent requests remain subject to the scheduler's export revocation,
1583/// including requests that were already published when the export was reported.
1584/// Other internal descriptors retain their existing readiness classification.
1585/// In particular, this does not repair guest-only claims for pipes exported
1586/// through SCM_RIGHTS or received from outside the guest.
1587fn single_fd_poll_readiness<G: Guest<Detcore<T>>, T: RecordOrReplay>(
1588    guest: &mut G,
1589    call: Syscall,
1590    internal_fd: bool,
1591) -> (bool, bool) {
1592    let socketpair = get_fd(call).is_some_and(|fd| {
1593        guest
1594            .thread_state()
1595            .with_detfd(fd, |detfd| detfd.is_socketpair_endpoint())
1596            .unwrap_or(false)
1597    });
1598    let relies_on_guest_held_marks =
1599        (!internal_fd || socketpair) && syscall_targets_guest_held_fd(guest, call);
1600    let polls_guest_state_only = (internal_fd && !socketpair) || relies_on_guest_held_marks;
1601    (polls_guest_state_only, relies_on_guest_held_marks)
1602}
1603
1604/// The most entries a `poll` or `ppoll` may have for
1605/// `poll_targets_guest_held` to read them.
1606const GUEST_HELD_POLL_MAX_FDS: usize = 1024;
1607
1608/// Whether only guests can make a `poll` or `ppoll` of the `nfds` entries at
1609/// `fds` ready: every entry Linux polls (a non-negative descriptor) names a
1610/// guest-held descriptor (`DetFd::is_guest_held`) in the caller's table.
1611/// Linux ignores an entry with a negative descriptor. An empty array, one
1612/// larger than `GUEST_HELD_POLL_MAX_FDS` or one Detcore cannot read counts as
1613/// not guest-held, which keeps the call's classification as it was. It feeds
1614/// only `Resources::polls_guest_state_only`. Record/replay retains its prior
1615/// classification and does not use these new guest-held marks.
1616pub(crate) fn poll_targets_guest_held<G: Guest<Detcore<T>>, T: RecordOrReplay>(
1617    guest: &mut G,
1618    fds: Option<usize>,
1619    nfds: usize,
1620) -> bool {
1621    if guest.config().recordreplay_modes || nfds == 0 || nfds > GUEST_HELD_POLL_MAX_FDS {
1622        return false;
1623    }
1624    let Some(address) = fds.and_then(Addr::<u8>::from_raw) else {
1625        return false;
1626    };
1627    let mut bytes = vec![0u8; nfds * std::mem::size_of::<libc::pollfd>()];
1628    if guest.memory().read_exact(address, &mut bytes).is_err() {
1629        return false;
1630    }
1631    let polled = pollfd_descriptors(&bytes);
1632    !polled.is_empty()
1633        && polled.into_iter().all(|fd| {
1634            guest
1635                .thread_state()
1636                .with_detfd(fd, |detfd| detfd.is_guest_held())
1637                .unwrap_or(false)
1638        })
1639}
1640
1641/// Refresh only descriptor-based idle-poll authority, independently of the
1642/// syscall's record/replay routing. In plain runs, pipes and socketpairs also
1643/// rely on the guest-held mark: SCM export revokes that authority for queued
1644/// requests. Record/replay retains the internal-FD classification captured by
1645/// its handler at entry, including across descriptor replacement during a
1646/// retry, for its existing FIFO-open decisions. It does not enable the new
1647/// idle-poller jumps.
1648/// Non-FD primitives retain the proof supplied by their own handlers.
1649pub(crate) fn refresh_guest_poll_state<G: Guest<Detcore<T>>, T: RecordOrReplay>(
1650    guest: &mut G,
1651    call: Syscall,
1652    resources: &mut Resources,
1653) {
1654    if guest.config().recordreplay_modes {
1655        resources.relies_on_guest_held_marks = false;
1656        return;
1657    }
1658    let held = match call {
1659        #[cfg(target_arch = "x86_64")]
1660        Syscall::Poll(call) => poll_targets_guest_held(
1661            guest,
1662            call.fds().map(|fds| fds.as_raw()),
1663            call.nfds() as usize,
1664        ),
1665        Syscall::Ppoll(call) => poll_targets_guest_held(
1666            guest,
1667            call.fds().map(|fds| fds.as_raw()),
1668            call.nfds() as usize,
1669        ),
1670        _ if get_fd(call).is_some() => syscall_targets_guest_held_fd(guest, call),
1671        _ => return,
1672    };
1673    resources.relies_on_guest_held_marks = held;
1674    resources.polls_guest_state_only = held;
1675}
1676
1677/// The descriptors Linux polls in a `pollfd` array's bytes: each entry's
1678/// `fd` when it is not negative.
1679fn pollfd_descriptors(bytes: &[u8]) -> Vec<i32> {
1680    let (entries, _) = bytes.as_chunks::<{ std::mem::size_of::<libc::pollfd>() }>();
1681    entries
1682        .iter()
1683        .map(|entry| i32::from_ne_bytes([entry[0], entry[1], entry[2], entry[3]]))
1684        .filter(|fd| *fd >= 0)
1685        .collect()
1686}
1687
1688/// The descriptors an in-flight call's guest-held proof actually describes.
1689/// A scheduled sibling can replace a descriptor or a pollfd operand while a
1690/// request waits. `Scheduler::latest_progress` and `note_committed_turn` make
1691/// the poller retry after such a turn before it can prove terminal deadlock;
1692/// each granted retry must therefore validate this capture before probing.
1693/// Once lost, the proof stays lost for this call, even if the old slot returns.
1694///
1695/// This does not retain the original Linux open file description across
1696/// private probes. Linux's `do_readv` retains its `fd_pos` reference through
1697/// `vfs_readv` (fs/read_write.c, v7.1.3), whereas each injected call looks up
1698/// the slot again. Likewise `do_sys_poll` copies its pollfd array per syscall
1699/// (fs/select.c). This guard only prevents a stale terminal-deadlock proof.
1700pub(crate) struct GuestHeldPollProof {
1701    call: Syscall,
1702    targets: Option<GuestHeldPollTargets>,
1703    /// Whether the call claimed a proof at capture: a claimed proof, once
1704    /// lost, stays lost for this call.
1705    claimed: bool,
1706}
1707
1708#[derive(PartialEq, Eq)]
1709enum GuestHeldPollTargets {
1710    Single(i32, OpenFileId),
1711    Poll {
1712        address: usize,
1713        entries: Vec<(i32, Option<OpenFileId>)>,
1714    },
1715}
1716
1717impl GuestHeldPollProof {
1718    pub(crate) fn capture<T, G>(guest: &mut G, call: Syscall, resources: &mut Resources) -> Self
1719    where
1720        T: RecordOrReplay,
1721        G: Guest<Detcore<T>>,
1722    {
1723        let targets = resources
1724            .relies_on_guest_held_marks
1725            .then(|| Self::read_targets(guest, call))
1726            .flatten();
1727        // No proof needed (for example a named FIFO) differs from a failed
1728        // capture of a proof the caller claimed. Unknown must clear the latter
1729        // before its very first request is published.
1730        if resources.relies_on_guest_held_marks && targets.is_none() {
1731            Self::clear(resources);
1732        }
1733        Self {
1734            claimed: resources.relies_on_guest_held_marks,
1735            call,
1736            targets,
1737        }
1738    }
1739
1740    pub(crate) fn revalidate<T, G>(&mut self, guest: &mut G, resources: &mut Resources)
1741    where
1742        T: RecordOrReplay,
1743        G: Guest<Detcore<T>>,
1744    {
1745        if self.targets.is_some() && Self::read_targets(guest, self.call) != self.targets {
1746            self.targets = None;
1747            Self::clear(resources);
1748        } else if self.targets.is_none() && (self.claimed || resources.relies_on_guest_held_marks) {
1749            // A proof once lost (or never captured for a call that claimed
1750            // one) cannot be reacquired by a later refresh of the same
1751            // call's classification: restored operands do not restore it.
1752            // Nor can a refresh manufacture mark-dependent authority for
1753            // a call that never captured an identity. Non-mark authority,
1754            // such as binary record/replay's internal-FD routing, is separate.
1755            Self::clear(resources);
1756        }
1757    }
1758
1759    fn clear(resources: &mut Resources) {
1760        resources.polls_guest_state_only = false;
1761        resources.relies_on_guest_held_marks = false;
1762    }
1763
1764    fn identity<T, G>(guest: &mut G, fd: i32) -> Option<OpenFileId>
1765    where
1766        T: RecordOrReplay,
1767        G: Guest<Detcore<T>>,
1768    {
1769        guest
1770            .thread_state()
1771            .with_detfd(fd, |entry| {
1772                entry.is_guest_held().then(|| entry.open_file_id())
1773            })
1774            .ok()
1775            .flatten()
1776    }
1777
1778    fn read_targets<T, G>(guest: &mut G, call: Syscall) -> Option<GuestHeldPollTargets>
1779    where
1780        T: RecordOrReplay,
1781        G: Guest<Detcore<T>>,
1782    {
1783        let (address, nfds) = match call {
1784            Syscall::Poll(call) => (call.fds()?.as_raw(), call.nfds() as usize),
1785            Syscall::Ppoll(call) => (call.fds()?.as_raw(), call.nfds() as usize),
1786            _ => {
1787                let fd = get_fd(call)?;
1788                return Some(GuestHeldPollTargets::Single(fd, Self::identity(guest, fd)?));
1789            }
1790        };
1791        if nfds == 0 || nfds > GUEST_HELD_POLL_MAX_FDS {
1792            return None;
1793        }
1794        let mut bytes = vec![0; nfds * std::mem::size_of::<libc::pollfd>()];
1795        guest
1796            .memory()
1797            .read_exact(Addr::from_raw(address)?, &mut bytes)
1798            .ok()?;
1799        let mut entries = Vec::with_capacity(nfds);
1800        for entry in bytes
1801            .as_chunks::<{ std::mem::size_of::<libc::pollfd>() }>()
1802            .0
1803        {
1804            let fd = i32::from_ne_bytes([entry[0], entry[1], entry[2], entry[3]]);
1805            entries.push((
1806                fd,
1807                if fd < 0 {
1808                    None
1809                } else {
1810                    Some(Self::identity(guest, fd)?)
1811                },
1812            ));
1813        }
1814        // Keep negative operands too: changing one into a live descriptor
1815        // changes the target set. Ignore revents, which the kernel writes,
1816        // and events: interest bits do not change who can ready these OFDs.
1817        entries
1818            .iter()
1819            .any(|(fd, _)| *fd >= 0)
1820            .then_some(GuestHeldPollTargets::Poll { address, entries })
1821    }
1822}
1823
1824/// Whether descriptor `fd` is a named FIFO that joined internal polling
1825/// (`Resources::polls_a_named_fifo`).
1826fn is_named_fifo<G: Guest<Detcore<T>>, T: RecordOrReplay>(guest: &mut G, fd: i32) -> bool {
1827    guest
1828        .thread_state()
1829        .with_detfd(fd, |detfd| detfd.is_named_fifo())
1830        .unwrap_or(false)
1831}
1832
1833/// Whether `call` is a `read` or `readv` of a descriptor Detcore records as
1834/// a pipe (`FdType::Pipe`: a pipe, or a FIFO that joined internal polling),
1835/// the restartable reads a recorded private `SIGCHLD` ends
1836/// (`Resources::private_sigchld_decides`; R2b-2 stage 3,
1837/// https://github.com/rrnewton/hermit/issues/4273). A socket read keeps
1838/// holding it (rev4 1e).
1839fn reads_a_pipe<G: Guest<Detcore<T>>, T: RecordOrReplay>(guest: &mut G, call: Syscall) -> bool {
1840    matches!(call, Syscall::Read(_) | Syscall::Readv(_))
1841        && get_fd(call).is_some_and(|fd| {
1842            guest
1843                .thread_state()
1844                .with_detfd(fd, |detfd| matches!(detfd.ty(), FdType::Pipe))
1845                .unwrap_or(false)
1846        })
1847}
1848
1849/// Does `call` read a descriptor Detcore records as a signalfd
1850/// (`Resources::reads_signalfd`)? Only `read`, `readv` and `preadv2` at the
1851/// current offset can: a positioned read of a signalfd fails with ESPIPE.
1852fn call_reads_signalfd<G: Guest<Detcore<T>>, T: RecordOrReplay>(
1853    guest: &mut G,
1854    call: Syscall,
1855) -> bool {
1856    let fd = match call {
1857        Syscall::Read(c) => c.fd(),
1858        Syscall::Readv(c) => c.fd(),
1859        Syscall::Preadv2(c) => c.fd(),
1860        _ => return false,
1861    };
1862    guest
1863        .thread_state()
1864        .with_detfd(fd, |detfd| detfd.ty() == crate::fd::FdType::Signalfd)
1865        .unwrap_or(false)
1866}
1867
1868/// The descriptor a `read`, `readv` or `preadv2` reads (`Resources::read_fd`).
1869fn read_family_fd(call: Syscall) -> Option<i32> {
1870    match call {
1871        Syscall::Read(c) => Some(c.fd()),
1872        Syscall::Readv(c) => Some(c.fd()),
1873        Syscall::Preadv2(c) => Some(c.fd()),
1874        _ => None,
1875    }
1876}
1877
1878/// Does replay answer this call from the recording rather than by running it?
1879///
1880/// This follows the replayer's dispatch. It serves `accept`, `accept4`, `connect`,
1881/// `sendto`, `sendmsg`, `recvfrom` and `recvmsg` from its log whatever the descriptor
1882/// is, so they count on any descriptor, a listener received over `SCM_RIGHTS` (an
1883/// exported alias, or a new description another process sent) included; an
1884/// untagged accept there would let its stand-in take a slot at a host-timed point.
1885/// `read`, `readv`, `write` and `writev` count only on a
1886/// socket Detcore created with `socket(2)` or `accept(2)`: on other descriptors,
1887/// replay may run them for real as eventfd operations, which can block, so an unknown
1888/// descriptor is not assumed to be served from the log. `recvmmsg` and `sendmmsg` are
1889/// recorded by neither recorder nor replayer (on an accepted connection record/replay
1890/// refuses them, see `refuse_log_served_mmsg`).
1891fn replay_serves_socket_call_from_log<G: Guest<Detcore<T>>, T: RecordOrReplay>(
1892    guest: &mut G,
1893    call: Syscall,
1894) -> bool {
1895    let fd = match call {
1896        Syscall::Accept(_)
1897        | Syscall::Accept4(_)
1898        | Syscall::Connect(_)
1899        | Syscall::Sendto(_)
1900        | Syscall::Sendmsg(_)
1901        | Syscall::Recvfrom(_)
1902        | Syscall::Recvmsg(_) => return true,
1903        Syscall::Read(s) => s.fd(),
1904        Syscall::Readv(s) => s.fd(),
1905        Syscall::Write(s) => s.fd(),
1906        Syscall::Writev(s) => s.fd(),
1907        _ => return false,
1908    };
1909    is_log_served_socket(guest, fd)
1910}
1911
1912/// Does replay answer this otherwise blocking call from the recording alone?
1913///
1914/// The replayer serves `poll`, `ppoll`, `select` and `pselect6` from the log
1915/// without running them. `epoll_wait` is excluded because replay reapplies its
1916/// side effects on live descriptors, which may block.
1917fn replay_serves_blocking_call_from_log(call: Syscall) -> bool {
1918    match call {
1919        Syscall::Ppoll(_) | Syscall::Pselect6(_) => true,
1920        #[cfg(target_arch = "x86_64")]
1921        Syscall::Poll(_) | Syscall::Select(_) => true,
1922        _ => false,
1923    }
1924}
1925
1926/// Is `fd` a socket whose connection record and replay keep only in the log?
1927///
1928/// That is a socket Detcore created with `socket(2)` or `accept(2)`, but not a
1929/// socketpair endpoint: replay recreates a socketpair, and its operations run
1930/// live.
1931fn is_log_served_socket<G: Guest<Detcore<T>>, T: RecordOrReplay>(guest: &mut G, fd: i32) -> bool {
1932    guest
1933        .thread_state()
1934        .with_detfd(fd, |detfd| {
1935            matches!(detfd.ty(), FdType::Socket) && !detfd.is_socketpair_endpoint()
1936        })
1937        .unwrap_or(false)
1938}
1939
1940/// Is `fd` a connection that `accept(2)` or `accept4(2)` returned? Record and
1941/// replay keep such a connection only in the log, and replay stands in an
1942/// eventfd for it, so an operation that runs live during replay meets that
1943/// eventfd rather than a connected socket.
1944pub(crate) fn is_accepted_connection<G: Guest<Detcore<T>>, T: RecordOrReplay>(
1945    guest: &mut G,
1946    fd: i32,
1947) -> bool {
1948    guest
1949        .thread_state()
1950        .with_detfd(fd, |detfd| detfd.is_accepted_connection())
1951        .unwrap_or(false)
1952}
1953
1954/// A large subset of system calls have a single, unique file descriptor argument.  This
1955/// is a convenience function for grabbing that argument.
1956///
1957/// It does not cover system calls with multiple fd arguments, with pointers to heap
1958/// structures that contain fds.
1959pub(crate) fn get_fd(s: Syscall) -> Option<i32> {
1960    match s {
1961        Syscall::Recvfrom(s) => Some(s.fd()),
1962        Syscall::Recvmsg(s) => Some(s.sockfd()),
1963        Syscall::Recvmmsg(s) => Some(s.fd()),
1964        Syscall::Sendto(s) => Some(s.fd()),
1965        Syscall::Sendmsg(s) => Some(s.fd()),
1966        Syscall::Sendmmsg(s) => Some(s.sockfd()),
1967        Syscall::Accept(s) => Some(s.sockfd()),
1968        Syscall::Accept4(s) => Some(s.sockfd()),
1969        Syscall::Connect(s) => Some(s.fd()),
1970        Syscall::Bind(s) => Some(s.fd()),
1971        Syscall::Listen(s) => Some(s.fd()),
1972        Syscall::Getsockname(s) => Some(s.fd()),
1973        Syscall::Getpeername(s) => Some(s.fd()),
1974        Syscall::Setsockopt(s) => Some(s.fd()),
1975        Syscall::Getsockopt(s) => Some(s.fd()),
1976
1977        Syscall::Read(s) => Some(s.fd()),
1978        Syscall::Write(s) => Some(s.fd()),
1979        Syscall::Close(s) => Some(s.fd()),
1980        Syscall::Fstat(s) => Some(s.fd()),
1981        Syscall::Lseek(s) => Some(s.fd()),
1982        Syscall::Mmap(s) => Some(s.fd()),
1983        Syscall::Ioctl(s) => Some(s.fd()),
1984        Syscall::Pread64(s) => Some(s.fd()),
1985        Syscall::Pwrite64(s) => Some(s.fd()),
1986        Syscall::Readv(s) => Some(s.fd()),
1987        Syscall::Writev(s) => Some(s.fd()),
1988
1989        Syscall::Shutdown(s) => Some(s.fd()),
1990        Syscall::Fcntl(s) => Some(s.fd()),
1991        Syscall::Flock(s) => Some(s.fd()),
1992        Syscall::Fsync(s) => Some(s.fd()),
1993        Syscall::Fdatasync(s) => Some(s.fd()),
1994        Syscall::Ftruncate(s) => Some(s.fd()),
1995        Syscall::Fchdir(s) => Some(s.fd()),
1996        Syscall::Fchmod(s) => Some(s.fd()),
1997        Syscall::Fchown(s) => Some(s.fd()),
1998        Syscall::Fstatfs(s) => Some(s.fd()),
1999        Syscall::Readahead(s) => Some(s.fd()),
2000        Syscall::Fsetxattr(s) => Some(s.fd()),
2001        Syscall::Fgetxattr(s) => Some(s.fd()),
2002        Syscall::Flistxattr(s) => Some(s.fd()),
2003        Syscall::Fremovexattr(s) => Some(s.fd()),
2004        Syscall::Fadvise64(s) => Some(s.fd()),
2005        Syscall::InotifyAddWatch(s) => Some(s.fd()),
2006        Syscall::InotifyRmWatch(s) => Some(s.fd()),
2007        #[cfg(target_arch = "x86_64")]
2008        Syscall::SyncFileRange(s) => Some(s.fd()),
2009        Syscall::Vmsplice(s) => Some(s.fd()),
2010        Syscall::Utimensat(s) => Some(s.dirfd()),
2011        #[cfg(target_arch = "x86_64")]
2012        Syscall::Signalfd(s) => Some(s.fd()),
2013        Syscall::Fallocate(s) => Some(s.fd()),
2014        Syscall::TimerfdSettime(s) => Some(s.fd()),
2015        Syscall::TimerfdGettime(s) => Some(s.fd()),
2016        Syscall::Signalfd4(s) => Some(s.fd()),
2017        Syscall::Preadv(s) => Some(s.fd()),
2018        Syscall::Pwritev(s) => Some(s.fd()),
2019        Syscall::Syncfs(s) => Some(s.fd()),
2020        Syscall::Setns(s) => Some(s.fd()),
2021        Syscall::FinitModule(s) => Some(s.fd()),
2022        Syscall::Preadv2(s) => Some(s.fd()),
2023        Syscall::Pwritev2(s) => Some(s.fd()),
2024
2025        Syscall::Openat(s) => Some(s.dirfd()),
2026        Syscall::Mkdirat(s) => Some(s.dirfd()),
2027        Syscall::Mknodat(s) => Some(s.dirfd()),
2028        Syscall::Fchownat(s) => Some(s.dirfd()),
2029        #[cfg(target_arch = "x86_64")]
2030        Syscall::Futimesat(s) => Some(s.dirfd()),
2031        #[cfg(target_arch = "x86_64")]
2032        Syscall::Newfstatat(s) => Some(s.dirfd()),
2033        #[cfg(target_arch = "aarch64")]
2034        Syscall::Fstatat(s) => Some(s.dirfd()),
2035        Syscall::Unlinkat(s) => Some(s.dirfd()),
2036        Syscall::Readlinkat(s) => Some(s.dirfd()),
2037        Syscall::Fchmodat(s) => Some(s.dirfd()),
2038        Syscall::Faccessat(s) => Some(s.dirfd()),
2039        Syscall::NameToHandleAt(s) => Some(s.dirfd()),
2040        Syscall::Execveat(s) => Some(s.dirfd()),
2041        Syscall::Statx(s) => Some(s.dirfd()),
2042        Syscall::Symlinkat(s) => Some(s.newdirfd()),
2043        Syscall::PerfEventOpen(s) => Some(s.group_fd()),
2044        Syscall::OpenByHandleAt(s) => Some(s.mount_fd()),
2045
2046        Syscall::EpollCtl(s) => Some(s.epfd()),
2047        #[cfg(target_arch = "x86_64")]
2048        Syscall::EpollWait(s) => Some(s.epfd()),
2049        Syscall::EpollPwait(s) => Some(s.epfd()),
2050
2051        // Ambiguous, 2 fds, no answer:
2052        #[cfg(target_arch = "x86_64")]
2053        Syscall::Dup2(_) => None,
2054        // Ambiguous, 2 fds, no answer:
2055        Syscall::Sendfile(_) => None,
2056        // Ambiguous, 2 fds, no answer:
2057        Syscall::Renameat(_) => None,
2058        // Ambiguous, 2 fds, no answer:
2059        Syscall::Linkat(_) => None,
2060        // Ambiguous, 2 fds, no answer:
2061        Syscall::FanotifyMark(_) => None,
2062        // Ambiguous, 2 fds, no answer:
2063        Syscall::Renameat2(_) => None,
2064        // Ambiguous, 2 fds, no answer:
2065        Syscall::Dup3(_) => None,
2066        // Ambiguous, 2 fds, no answer:
2067        Syscall::KexecLoad(_) => None,
2068
2069        // Takes a pointer to fd, not directly accessible:
2070        #[cfg(target_arch = "x86_64")]
2071        Syscall::Poll(_) => None,
2072        // Takes a pointer to fd, not directly accessible:
2073        Syscall::Ppoll(_) => None,
2074
2075        _ => None,
2076    }
2077}
2078
2079/// A system call which may or may not block, but which can be MADE nonblocking.
2080// `async_trait` rewrites `into_nonblocking` to return a boxed future and marks it
2081// `#[must_use]`; the future is already `#[must_use]` in its own right, so clippy sees a double
2082// annotation. Both are generated, so the lint's own suggestion -- give the `must_use` an explicit
2083// reason -- cannot be applied at this source. Allowed at the item rather than crate-wide so any
2084// hand-written double `must_use` elsewhere still fails `#![deny(clippy::all)]` (detcore/src/lib.rs:35).
2085// Appeared with nightly-2026-08-08 (rustc 1.99.0-nightly 1a98b1e13) against an unchanged tree.
2086#[allow(clippy::double_must_use)]
2087#[async_trait]
2088pub trait NonblockableSyscall: SyscallInfo {
2089    /// Convert the system call to a nonblocking version of itself.  Sometimes this means
2090    /// setting a zero timeout, and sometimes it means something else.
2091    ///
2092    /// This may need to stack allocate, so it returns a StackGuard.
2093    async fn into_nonblocking<T: RecordOrReplay, G: Guest<Detcore<T>>>(
2094        self,
2095        guest: &mut G,
2096    ) -> (Self, Option<<G::Stack as Stack>::StackGuard>);
2097
2098    /// Check if the result (in nonblocking mode) is analogous to blocking in blocking mode.
2099    /// I.e. the result means "try again".
2100    fn syscall_would_have_blocked(&self, res: Result<i64, Errno>) -> bool {
2101        res == Ok(0)
2102    }
2103
2104    /// Whether the call's own flags keep it from waiting on a file the guest
2105    /// left blocking: `MSG_DONTWAIT` on a socket send or receive, which Linux
2106    /// turns into a zero timeout (`sock_rcvtimeo`, `sock_sndtimeo`), so the
2107    /// call returns at once, failing with `EAGAIN` where it would wait. Linux
2108    /// adds the same flag to every call on an `O_NONBLOCK` socket, so such a
2109    /// call runs once, as on a nonblocking file (`IOAction::PassThru`), instead
2110    /// of being retried until it stops failing with `EAGAIN`.
2111    fn flags_forbid_waiting(&self) -> bool {
2112        false
2113    }
2114
2115    /// Whether the call is a receive with `MSG_ERRQUEUE`, which reads the
2116    /// socket's error queue. Whether that waits depends on the socket's
2117    /// protocol (`crate::fd::error_queue_read_never_waits`): a UDP or TCP socket
2118    /// fails with `EAGAIN` at once when the queue is empty, while AF_UNIX
2119    /// ignores the flag and receives as usual.
2120    fn reads_error_queue(&self) -> bool {
2121        false
2122    }
2123
2124    /// Return the errno used when a signal interrupts this internally polled syscall.
2125    /// Most blocking I/O is restartable when its handler uses `SA_RESTART`.
2126    fn signal_interrupt_errno(&self) -> Errno {
2127        Errno::ERESTARTSYS
2128    }
2129
2130    /// Return the errno for an interrupting signal that ends this wait after it began,
2131    /// on a backend that decides interruption from the kernel's signal state
2132    /// (`supports_blocked_wait_signal_interruption`). The kernel applies the
2133    /// guest's disposition to it on resume, so it must be the restart code Linux uses
2134    /// for this call: a handler then turns it into `EINTR` or a restart, and a signal
2135    /// with no handler restarts the call.
2136    fn kernel_restart_errno(&self) -> Errno {
2137        self.signal_interrupt_errno()
2138    }
2139
2140    /// Whether Linux restarts this call, after [`kernel_restart_errno`](Self::kernel_restart_errno)
2141    /// with no handler, through a restart block that keeps its absolute deadline: a
2142    /// `poll` with a positive timeout (`do_restart_poll`) and a timed `FUTEX_WAIT` or
2143    /// `FUTEX_WAIT_BITSET` (`futex_wait_restart`). Its restart code is then
2144    /// `ERESTART_RESTARTBLOCK`, and
2145    /// Detcore keeps the deadline in a [`RestartBlock`] for the `restart_syscall` that
2146    /// the kernel runs next (<https://github.com/rrnewton/hermit/issues/3358>).
2147    ///
2148    /// Such a wait is also not ended by a default job-control stop signal
2149    /// (`KernelSignalState::interrupting_wait`), so that a stop Linux discards in an
2150    /// orphaned process group does not end the call before its deadline.
2151    fn restart_keeps_deadline(&self) -> bool {
2152        false
2153    }
2154
2155    /// Signals the wait itself accepts rather than being interrupted by, read from
2156    /// guest memory. Only `rt_sigtimedwait` has any.
2157    fn signals_consumed_by_wait<M: MemoryAccess>(&self, _memory: &M) -> KernelSigset {
2158        0
2159    }
2160
2161    /// Whether this call is a receive that Linux ends without waiting once
2162    /// its socket's read side is shut down (`finish_after_read_shutdown`).
2163    fn ends_at_read_shutdown(&self) -> bool {
2164        false
2165    }
2166
2167    /// Convert a physical nonblocking completion into the result expected by the guest.
2168    /// `retried` is true after a prior result was classified as blocked.
2169    fn normalize_nonblocking_result(
2170        &self,
2171        res: Result<i64, Errno>,
2172        _retried: bool,
2173    ) -> Result<i64, Errno> {
2174        res
2175    }
2176}
2177
2178/// A system call which can logically timeout and then would return a given value
2179/// indicating that timeout.
2180pub trait TimeoutableSyscall: SyscallInfo {
2181    /// What would the syscall return IF it timed out?
2182    fn timeout_return_val(&self) -> Result<i64, Errno>;
2183}
2184
2185fn interrupted_write_result<C: NonblockableSyscall>(
2186    call: &C,
2187    written_total: usize,
2188    disposition: Option<WaitSignalDisposition>,
2189) -> Option<Result<i64, Error>> {
2190    match disposition {
2191        None => None,
2192        Some(_) if written_total > 0 => Some(Ok(written_total as i64)),
2193        Some(_) => Some(Err(call.signal_interrupt_errno().into())),
2194    }
2195}
2196
2197fn pipe_writev_resources(
2198    dettid: DetTid,
2199    call: reverie::syscalls::Writev,
2200    guest_held: bool,
2201) -> Resources {
2202    let mut resources = Resources::new(dettid);
2203    resources.insert(ResourceID::InternalIOPolling, Permission::W);
2204    resources.fyi(call.name());
2205    resources.set_signal_interrupt_errno(call.signal_interrupt_errno());
2206    // Polls with every ordinary signal blocked and checks the target's
2207    // disposition itself after a `Signaled` wake, so the scheduler's drain
2208    // must not filter by the kernel mask (`execute_blocking_pipe_writev`).
2209    resources.decides_signal_interruption_itself = true;
2210    resources.polls_guest_state_only = guest_held;
2211    resources.relies_on_guest_held_marks = guest_held;
2212    resources
2213}
2214
2215#[cfg(target_arch = "x86_64")]
2216#[async_trait]
2217impl NonblockableSyscall for reverie::syscalls::Poll {
2218    async fn into_nonblocking<T: RecordOrReplay, G: Guest<Detcore<T>>>(
2219        self,
2220        _guest: &mut G,
2221    ) -> (Self, Option<<G::Stack as Stack>::StackGuard>) {
2222        (self.with_timeout(0), None)
2223    }
2224
2225    fn signal_interrupt_errno(&self) -> Errno {
2226        Errno::EINTR
2227    }
2228
2229    /// Linux ends an interrupted `poll` with a restart code that a handler turns into
2230    /// `EINTR` and that restarts the call when no handler runs.
2231    ///
2232    /// With a positive timeout that code is Linux's own, `ERESTART_RESTARTBLOCK`: the
2233    /// kernel then runs `restart_syscall`, which Detcore resumes with the deadline it
2234    /// kept ([`RestartBlock`]), as `do_restart_poll` does
2235    /// (https://github.com/rrnewton/hermit/issues/3358). An infinite timeout has no
2236    /// deadline to keep, so it returns `ERESTARTNOHAND`, and running `poll` again with
2237    /// its original arguments is the same restart.
2238    fn kernel_restart_errno(&self) -> Errno {
2239        if self.restart_keeps_deadline() {
2240            Errno::ERESTART_RESTARTBLOCK
2241        } else {
2242            Errno::ERESTARTNOHAND
2243        }
2244    }
2245
2246    /// A zero timeout never blocks, and a negative one never expires.
2247    fn restart_keeps_deadline(&self) -> bool {
2248        self.timeout() > 0
2249    }
2250}
2251
2252#[cfg(target_arch = "x86_64")]
2253impl TimeoutableSyscall for reverie::syscalls::Poll {
2254    fn timeout_return_val(&self) -> Result<i64, Errno> {
2255        Ok(0)
2256    }
2257}
2258
2259#[async_trait]
2260impl NonblockableSyscall for reverie::syscalls::Ppoll {
2261    async fn into_nonblocking<T: RecordOrReplay, G: Guest<Detcore<T>>>(
2262        self,
2263        guest: &mut G,
2264    ) -> (Self, Option<<G::Stack as Stack>::StackGuard>) {
2265        let (tp, guard) = zero_timespec(guest).await;
2266        // SAFETY: `tp` points to exclusively owned scratch storage kept alive by `guard`.
2267        let tp = unsafe { tp.into_mut() };
2268        (self.with_timeout(Some(tp)), Some(guard))
2269    }
2270
2271    fn signal_interrupt_errno(&self) -> Errno {
2272        Errno::EINTR
2273    }
2274
2275    /// Linux ends an interrupted `ppoll` with a restart code that a handler turns into
2276    /// `EINTR` and that restarts the call when no handler runs.
2277    fn kernel_restart_errno(&self) -> Errno {
2278        Errno::ERESTARTNOHAND
2279    }
2280}
2281
2282impl TimeoutableSyscall for reverie::syscalls::Ppoll {
2283    fn timeout_return_val(&self) -> Result<i64, Errno> {
2284        Ok(0)
2285    }
2286}
2287
2288#[cfg(target_arch = "x86_64")]
2289#[async_trait]
2290impl NonblockableSyscall for reverie::syscalls::EpollWait {
2291    async fn into_nonblocking<T: RecordOrReplay, G: Guest<Detcore<T>>>(
2292        self,
2293        _guest: &mut G,
2294    ) -> (Self, Option<<G::Stack as Stack>::StackGuard>) {
2295        (self.with_timeout(0), None)
2296    }
2297
2298    fn signal_interrupt_errno(&self) -> Errno {
2299        Errno::EINTR
2300    }
2301}
2302
2303#[cfg(target_arch = "x86_64")]
2304impl TimeoutableSyscall for reverie::syscalls::EpollWait {
2305    fn timeout_return_val(&self) -> Result<i64, Errno> {
2306        Ok(0)
2307    }
2308}
2309
2310// `epoll_pwait` was the one member of the
2311// poll/epoll family with no nonblocking form, even though `Ppoll` -- the
2312// sigmask variant of `poll` -- has had one all along. Programs that issue
2313// `epoll_pwait` DIRECTLY (libuv does, which is how the `cmake` hang surfaced)
2314// therefore reached an unhandled path while the `EpollWait` impl above went
2315// unused by them. Note this is NOT glibc's `epoll_wait(2)` on x86_64: glibc
2316// only spells it `epoll_pwait` where `__NR_epoll_wait` is absent, which x86_64
2317// is not. With a NULL sigmask the two calls are semantically identical, so the
2318// nonblocking form is the same: timeout 0, EINTR, and a 0 (no events) timeout
2319// return.
2320#[async_trait]
2321impl NonblockableSyscall for reverie::syscalls::EpollPwait {
2322    async fn into_nonblocking<T: RecordOrReplay, G: Guest<Detcore<T>>>(
2323        self,
2324        _guest: &mut G,
2325    ) -> (Self, Option<<G::Stack as Stack>::StackGuard>) {
2326        (self.with_timeout(0), None)
2327    }
2328
2329    fn signal_interrupt_errno(&self) -> Errno {
2330        Errno::EINTR
2331    }
2332}
2333
2334impl TimeoutableSyscall for reverie::syscalls::EpollPwait {
2335    fn timeout_return_val(&self) -> Result<i64, Errno> {
2336        Ok(0)
2337    }
2338}
2339
2340/// What to do with the copy of the standard `signal` this thread just took
2341/// (`rt_sigtimedwait`, a signalfd record), after which its queues held
2342/// `after` (when read): drop it, as a copy Linux discarded when it was sent
2343/// or a stale POSIX timer entry, or return it with another siginfo
2344/// (`DiscardedSignalControl::Take`). The scheduler pops and promotes either
2345/// way.
2346pub(crate) async fn take_outcome<T, G>(
2347    guest: &mut G,
2348    signal: i32,
2349    after: Option<(u64, u64)>,
2350) -> crate::scheduler::posix_timer_coalesce::TakeOutcome
2351where
2352    T: RecordOrReplay,
2353    G: Guest<Detcore<T>>,
2354{
2355    use crate::scheduler::discarded_at_send::DiscardedSignalAnswer;
2356    use crate::scheduler::discarded_at_send::DiscardedSignalControl;
2357    use crate::scheduler::posix_timer_coalesce::TakeOutcome;
2358    let at = guest.thread_state().thread_logical_time.as_nanos();
2359    match crate::tool_global::discarded_signal(
2360        guest,
2361        DiscardedSignalControl::Take { signal, at, after },
2362    )
2363    .await
2364    {
2365        DiscardedSignalAnswer::Taken(outcome) => outcome,
2366        _ => TakeOutcome {
2367            skip: false,
2368            rewrite: None,
2369        },
2370    }
2371}
2372
2373async fn zero_timespec<'stack, T: RecordOrReplay, G: Guest<Detcore<T>>>(
2374    guest: &mut G,
2375) -> (Addr<'stack, Timespec>, <G::Stack as Stack>::StackGuard) {
2376    let mut stack = guest.stack().await;
2377    let tp_val = Timespec {
2378        tv_sec: 0,
2379        tv_nsec: 0,
2380    };
2381    let tp = stack.push(tp_val);
2382    let guard = stack.commit().expect("stack.commit to succeed");
2383    (tp, guard)
2384}
2385
2386#[async_trait]
2387impl NonblockableSyscall for reverie::syscalls::Wait4 {
2388    async fn into_nonblocking<T: RecordOrReplay, G: Guest<Detcore<T>>>(
2389        self,
2390        _guest: &mut G,
2391    ) -> (Self, Option<<G::Stack as Stack>::StackGuard>) {
2392        let call2 = self.with_options(self.options() | WaitPidFlag::WNOHANG);
2393        (call2, None)
2394    }
2395
2396    // Child has not changed state yet, so we go to the scheduler and wait to poll again.
2397    // In scenarios with lots of outstanding waits, this polling strategy can change the asymptotic
2398    // complexity of the program. Ideally, we would model the blocking `wait4` (and process state
2399    // transitions) directly in the scheduler, and execute it only when we know it will complete.
2400    //
2401    // The polling backoff strategy mitigates this problem however.
2402    fn syscall_would_have_blocked(&self, res: Result<i64, Errno>) -> bool {
2403        res == Ok(0)
2404    }
2405}
2406
2407#[async_trait]
2408/// Used only for FUTEX_WAIT
2409impl NonblockableSyscall for reverie::syscalls::Futex {
2410    async fn into_nonblocking<T: RecordOrReplay, G: Guest<Detcore<T>>>(
2411        self,
2412        guest: &mut G,
2413    ) -> (Self, Option<<G::Stack as Stack>::StackGuard>) {
2414        let (tp, guard) = zero_timespec(guest).await;
2415        (self.with_timeout(Some(tp)), Some(guard))
2416    }
2417
2418    fn syscall_would_have_blocked(&self, res: Result<i64, Errno>) -> bool {
2419        // EAGAIN can mean the futex wait's compare-and-block failed and we should return that to
2420        // the guest.  With timeout=0, the timeout is what shows that it would have blocked.
2421        res == Err(Errno::ETIMEDOUT)
2422    }
2423
2424    /// Linux restarts an untimed `FUTEX_WAIT` under `SA_RESTART` (`-ERESTARTSYS`), but a
2425    /// timed wait returns `-ERESTART_RESTARTBLOCK`, which a handler always turns into
2426    /// `EINTR` (https://github.com/rrnewton/hermit/issues/3146).
2427    ///
2428    /// A timed `FUTEX_WAIT` or `FUTEX_WAIT_BITSET` returns Linux's code: with no
2429    /// handler the kernel then runs `restart_syscall`, which Detcore resumes with the
2430    /// deadline it kept ([`RestartBlock`]), as `futex_wait_restart` does
2431    /// (https://github.com/rrnewton/hermit/issues/3358). Linux's `futex_wait` keeps
2432    /// the absolute deadline of every timed wait in the restart block, so a
2433    /// `FUTEX_WAIT_BITSET` deadline the guest moves in memory after the wait began
2434    /// does not change when the restarted wait ends. `FUTEX_WAIT_BITSET` used to
2435    /// return `ERESTARTNOHAND`, which ran the call again with its original
2436    /// arguments and so read the deadline again (round-10 finding Medium 4 on
2437    /// https://github.com/rrnewton/hermit/pull/3361).
2438    ///
2439    /// FUTEX_LOCK_PI and FUTEX_LOCK_PI2 return `-ERESTARTNOINTR`, which the
2440    /// kernel restarts whatever the handler (`futex_lock_pi`,
2441    /// kernel/futex/pi.c:1124 at v6.16).
2442    fn kernel_restart_errno(&self) -> Errno {
2443        if matches!(
2444            self.futex_op() & libc::FUTEX_CMD_MASK,
2445            libc::FUTEX_LOCK_PI | libc::FUTEX_LOCK_PI2
2446        ) {
2447            Errno::ERESTARTNOINTR
2448        } else if self.restart_keeps_deadline() {
2449            Errno::ERESTART_RESTARTBLOCK
2450        } else if self.timeout().is_some() {
2451            Errno::ERESTARTNOHAND
2452        } else {
2453            Errno::ERESTARTSYS
2454        }
2455    }
2456
2457    /// Every timed wait: `FUTEX_WAIT`'s relative timeout and `FUTEX_WAIT_BITSET`'s
2458    /// absolute deadline both become the absolute deadline Linux's restart keeps.
2459    fn restart_keeps_deadline(&self) -> bool {
2460        matches!(
2461            self.futex_op() & libc::FUTEX_CMD_MASK,
2462            libc::FUTEX_WAIT | libc::FUTEX_WAIT_BITSET
2463        ) && self.timeout().is_some()
2464    }
2465}
2466
2467impl TimeoutableSyscall for reverie::syscalls::Futex {
2468    fn timeout_return_val(&self) -> Result<i64, Errno> {
2469        Err(Errno::ETIMEDOUT)
2470    }
2471}
2472
2473#[async_trait]
2474impl NonblockableSyscall for reverie::syscalls::RtSigtimedwait {
2475    async fn into_nonblocking<T: RecordOrReplay, G: Guest<Detcore<T>>>(
2476        self,
2477        guest: &mut G,
2478    ) -> (Self, Option<<G::Stack as Stack>::StackGuard>) {
2479        // This is a bit more complicated because we need a new timespec to point to in
2480        // the guest memory.
2481        let (tp, guard) = zero_timespec(guest).await;
2482        (self.with_timeout(Some(tp)), Some(guard))
2483    }
2484
2485    fn syscall_would_have_blocked(&self, res: Result<i64, Errno>) -> bool {
2486        res == Err(Errno::EAGAIN)
2487    }
2488
2489    fn signal_interrupt_errno(&self) -> Errno {
2490        Errno::EINTR
2491    }
2492
2493    /// Signals in the wait's own set are accepted by it, not interrupting. A set
2494    /// that cannot be read counts as empty, which nothing observes: the wait's
2495    /// first probe runs before any pending signal is considered
2496    /// (`KernelSignalWait::inject_first_probe`) and fails with `EFAULT`, as Linux
2497    /// fails the call when it cannot copy the set.
2498    fn signals_consumed_by_wait<M: MemoryAccess>(&self, memory: &M) -> KernelSigset {
2499        self.set()
2500            .and_then(|set| memory.read_value(set.cast::<KernelSigset>()).ok())
2501            .unwrap_or(0)
2502    }
2503}
2504
2505impl TimeoutableSyscall for reverie::syscalls::RtSigtimedwait {
2506    fn timeout_return_val(&self) -> Result<i64, Errno> {
2507        Err(Errno::EAGAIN)
2508    }
2509}
2510
2511/// While the read syscall is quite general, this nonblocking capacity is used
2512/// ONLY for sockets and pipes.
2513#[async_trait]
2514impl NonblockableSyscall for reverie::syscalls::Read {
2515    async fn into_nonblocking<T: RecordOrReplay, G: Guest<Detcore<T>>>(
2516        self,
2517        guest: &mut G,
2518    ) -> (Self, Option<<G::Stack as Stack>::StackGuard>) {
2519        network_comm_syscall(self, guest)
2520    }
2521
2522    fn syscall_would_have_blocked(&self, res: Result<i64, Errno>) -> bool {
2523        // A return value of Ok(0) indicates end of file.
2524        // Note that we've ruled out 0-count reads before this point.
2525        res == Err(Errno::EAGAIN) || res == Err(Errno::EWOULDBLOCK)
2526    }
2527
2528    fn ends_at_read_shutdown(&self) -> bool {
2529        true
2530    }
2531}
2532
2533/// While the read syscall is quite general, this nonblocking capacity is used
2534/// ONLY for sockets and pipes.
2535#[async_trait]
2536impl NonblockableSyscall for reverie::syscalls::Write {
2537    async fn into_nonblocking<T: RecordOrReplay, G: Guest<Detcore<T>>>(
2538        self,
2539        guest: &mut G,
2540    ) -> (Self, Option<<G::Stack as Stack>::StackGuard>) {
2541        network_comm_syscall(self, guest)
2542    }
2543
2544    fn syscall_would_have_blocked(&self, res: Result<i64, Errno>) -> bool {
2545        // A return value of Ok(0) indicates end of file.
2546        // Note that we've ruled out 0-count reads before this point.
2547        res == Err(Errno::EAGAIN) || res == Err(Errno::EWOULDBLOCK)
2548    }
2549}
2550
2551// AUTONOMOUS-BOT-IMPLEMENTED
2552// TODO-HUMAN-REVIEW(#794)
2553/// Vectored reads have the same blocking behavior as scalar reads on pipes and sockets.
2554#[async_trait]
2555impl NonblockableSyscall for reverie::syscalls::Readv {
2556    async fn into_nonblocking<T: RecordOrReplay, G: Guest<Detcore<T>>>(
2557        self,
2558        guest: &mut G,
2559    ) -> (Self, Option<<G::Stack as Stack>::StackGuard>) {
2560        network_comm_syscall(self, guest)
2561    }
2562
2563    fn syscall_would_have_blocked(&self, res: Result<i64, Errno>) -> bool {
2564        res == Err(Errno::EAGAIN) || res == Err(Errno::EWOULDBLOCK)
2565    }
2566
2567    fn ends_at_read_shutdown(&self) -> bool {
2568        true
2569    }
2570}
2571
2572/// Positioned vectored reads have the same blocking behavior as vectored
2573/// reads on pipes and sockets, and `RWF_NOWAIT` keeps the call itself from
2574/// waiting on a file the guest left blocking.
2575#[async_trait]
2576impl NonblockableSyscall for reverie::syscalls::Preadv2 {
2577    async fn into_nonblocking<T: RecordOrReplay, G: Guest<Detcore<T>>>(
2578        self,
2579        guest: &mut G,
2580    ) -> (Self, Option<<G::Stack as Stack>::StackGuard>) {
2581        network_comm_syscall(self, guest)
2582    }
2583
2584    fn syscall_would_have_blocked(&self, res: Result<i64, Errno>) -> bool {
2585        res == Err(Errno::EAGAIN) || res == Err(Errno::EWOULDBLOCK)
2586    }
2587
2588    fn flags_forbid_waiting(&self) -> bool {
2589        self.flags() & libc::RWF_NOWAIT != 0
2590    }
2591}
2592
2593// AUTONOMOUS-BOT-IMPLEMENTED
2594// TODO-HUMAN-REVIEW(#547)
2595/// Vectored writes have the same blocking behavior as scalar writes on pipes and sockets.
2596#[async_trait]
2597impl NonblockableSyscall for reverie::syscalls::Writev {
2598    async fn into_nonblocking<T: RecordOrReplay, G: Guest<Detcore<T>>>(
2599        self,
2600        guest: &mut G,
2601    ) -> (Self, Option<<G::Stack as Stack>::StackGuard>) {
2602        network_comm_syscall(self, guest)
2603    }
2604
2605    fn syscall_would_have_blocked(&self, res: Result<i64, Errno>) -> bool {
2606        res == Err(Errno::EAGAIN) || res == Err(Errno::EWOULDBLOCK)
2607    }
2608}
2609
2610/// Whether socket call flags, which Linux reads as an `unsigned int`, carry
2611/// `MSG_DONTWAIT`.
2612fn has_msg_dontwait(flags: u32) -> bool {
2613    flags & libc::MSG_DONTWAIT as u32 != 0
2614}
2615
2616/// A common helper shared among several network syscalls.
2617/// We can't actually CONVERT these syscalls into nonblocking, but we can assert that they are by
2618/// checking the status of their file descriptor.
2619fn network_comm_syscall<T: RecordOrReplay, G: Guest<Detcore<T>>, C: SyscallInfo + Into<Syscall>>(
2620    call: C,
2621    guest: &mut G,
2622) -> (C, Option<<G::Stack as Stack>::StackGuard>) {
2623    // Already nonblocking because we've assured the socket is.
2624    let fd = get_fd(call.into()).unwrap_or_else(|| {
2625        panic!(
2626            "network_comm_syscall called on invalid syscall / unknown fd: {}",
2627            call.name()
2628        );
2629    });
2630    guest
2631        .thread_state()
2632        .with_detfd(fd, |detfd| {
2633            assert!(
2634                detfd.physically_nonblocking(),
2635                "expecting sockets/pipes to be physically nonblocking"
2636            );
2637        })
2638        .unwrap();
2639    (call, None)
2640}
2641
2642#[async_trait]
2643impl NonblockableSyscall for reverie::syscalls::Accept4 {
2644    async fn into_nonblocking<T: RecordOrReplay, G: Guest<Detcore<T>>>(
2645        self,
2646        guest: &mut G,
2647    ) -> (Self, Option<<G::Stack as Stack>::StackGuard>) {
2648        network_comm_syscall(self, guest)
2649    }
2650
2651    fn syscall_would_have_blocked(&self, res: Result<i64, Errno>) -> bool {
2652        res == Err(Errno::EAGAIN) || res == Err(Errno::EWOULDBLOCK)
2653    }
2654}
2655
2656impl TimeoutableSyscall for reverie::syscalls::Accept4 {
2657    fn timeout_return_val(&self) -> Result<i64, Errno> {
2658        Ok(0)
2659    }
2660}
2661
2662#[async_trait]
2663impl NonblockableSyscall for reverie::syscalls::Recvfrom {
2664    async fn into_nonblocking<T: RecordOrReplay, G: Guest<Detcore<T>>>(
2665        self,
2666        guest: &mut G,
2667    ) -> (Self, Option<<G::Stack as Stack>::StackGuard>) {
2668        network_comm_syscall(self, guest)
2669    }
2670
2671    fn syscall_would_have_blocked(&self, res: Result<i64, Errno>) -> bool {
2672        res == Err(Errno::EAGAIN) || res == Err(Errno::EWOULDBLOCK)
2673    }
2674
2675    fn ends_at_read_shutdown(&self) -> bool {
2676        true
2677    }
2678
2679    fn flags_forbid_waiting(&self) -> bool {
2680        has_msg_dontwait(self.flags() as u32)
2681    }
2682
2683    fn reads_error_queue(&self) -> bool {
2684        self.flags() & libc::MSG_ERRQUEUE != 0
2685    }
2686}
2687
2688#[async_trait]
2689impl NonblockableSyscall for reverie::syscalls::Recvmsg {
2690    async fn into_nonblocking<T: RecordOrReplay, G: Guest<Detcore<T>>>(
2691        self,
2692        guest: &mut G,
2693    ) -> (Self, Option<<G::Stack as Stack>::StackGuard>) {
2694        network_comm_syscall(self, guest)
2695    }
2696
2697    fn syscall_would_have_blocked(&self, res: Result<i64, Errno>) -> bool {
2698        res == Err(Errno::EAGAIN) || res == Err(Errno::EWOULDBLOCK)
2699    }
2700
2701    fn ends_at_read_shutdown(&self) -> bool {
2702        true
2703    }
2704
2705    fn flags_forbid_waiting(&self) -> bool {
2706        has_msg_dontwait(self.flags() as u32)
2707    }
2708
2709    fn reads_error_queue(&self) -> bool {
2710        self.flags() & libc::MSG_ERRQUEUE != 0
2711    }
2712}
2713
2714#[async_trait]
2715impl NonblockableSyscall for reverie::syscalls::Recvmmsg {
2716    // This system call has a timeout argument, but we ignore it because the underlying
2717    // socket is nonblocking anyway (in runs where we call this).
2718    async fn into_nonblocking<T: RecordOrReplay, G: Guest<Detcore<T>>>(
2719        self,
2720        guest: &mut G,
2721    ) -> (Self, Option<<G::Stack as Stack>::StackGuard>) {
2722        network_comm_syscall(self, guest)
2723    }
2724
2725    fn syscall_would_have_blocked(&self, res: Result<i64, Errno>) -> bool {
2726        res == Err(Errno::EAGAIN) || res == Err(Errno::EWOULDBLOCK)
2727    }
2728
2729    fn ends_at_read_shutdown(&self) -> bool {
2730        true
2731    }
2732
2733    /// `MSG_WAITFORONE` does not count: Linux waits for the first datagram and
2734    /// adds `MSG_DONTWAIT` only after it (`do_recvmmsg`), which is what a
2735    /// polled attempt that returns at least one datagram gives.
2736    fn flags_forbid_waiting(&self) -> bool {
2737        has_msg_dontwait(self.flags())
2738    }
2739
2740    fn reads_error_queue(&self) -> bool {
2741        self.flags() & libc::MSG_ERRQUEUE as u32 != 0
2742    }
2743}
2744
2745#[async_trait]
2746impl NonblockableSyscall for reverie::syscalls::Sendto {
2747    async fn into_nonblocking<T: RecordOrReplay, G: Guest<Detcore<T>>>(
2748        self,
2749        guest: &mut G,
2750    ) -> (Self, Option<<G::Stack as Stack>::StackGuard>) {
2751        network_comm_syscall(self, guest)
2752    }
2753
2754    fn syscall_would_have_blocked(&self, res: Result<i64, Errno>) -> bool {
2755        res == Err(Errno::EAGAIN) || res == Err(Errno::EWOULDBLOCK)
2756    }
2757
2758    fn flags_forbid_waiting(&self) -> bool {
2759        has_msg_dontwait(self.flags())
2760    }
2761}
2762
2763#[async_trait]
2764impl NonblockableSyscall for reverie::syscalls::Sendmmsg {
2765    async fn into_nonblocking<T: RecordOrReplay, G: Guest<Detcore<T>>>(
2766        self,
2767        guest: &mut G,
2768    ) -> (Self, Option<<G::Stack as Stack>::StackGuard>) {
2769        network_comm_syscall(self, guest)
2770    }
2771
2772    fn syscall_would_have_blocked(&self, res: Result<i64, Errno>) -> bool {
2773        res == Err(Errno::EAGAIN) || res == Err(Errno::EWOULDBLOCK)
2774    }
2775
2776    fn flags_forbid_waiting(&self) -> bool {
2777        has_msg_dontwait(self.flags() as u32)
2778    }
2779}
2780
2781#[async_trait]
2782impl NonblockableSyscall for reverie::syscalls::Sendmsg {
2783    async fn into_nonblocking<T: RecordOrReplay, G: Guest<Detcore<T>>>(
2784        self,
2785        guest: &mut G,
2786    ) -> (Self, Option<<G::Stack as Stack>::StackGuard>) {
2787        network_comm_syscall(self, guest)
2788    }
2789
2790    fn syscall_would_have_blocked(&self, res: Result<i64, Errno>) -> bool {
2791        res == Err(Errno::EAGAIN) || res == Err(Errno::EWOULDBLOCK)
2792    }
2793
2794    fn flags_forbid_waiting(&self) -> bool {
2795        has_msg_dontwait(self.flags() as u32)
2796    }
2797}
2798
2799#[async_trait]
2800impl NonblockableSyscall for reverie::syscalls::Connect {
2801    async fn into_nonblocking<T: RecordOrReplay, G: Guest<Detcore<T>>>(
2802        self,
2803        guest: &mut G,
2804    ) -> (Self, Option<<G::Stack as Stack>::StackGuard>) {
2805        network_comm_syscall(self, guest)
2806    }
2807
2808    fn syscall_would_have_blocked(&self, res: Result<i64, Errno>) -> bool {
2809        res == Err(Errno::EAGAIN)
2810            || res == Err(Errno::EWOULDBLOCK)
2811            || res == Err(Errno::EINPROGRESS)
2812            || res == Err(Errno::EALREADY)
2813    }
2814
2815    fn normalize_nonblocking_result(
2816        &self,
2817        res: Result<i64, Errno>,
2818        retried: bool,
2819    ) -> Result<i64, Errno> {
2820        match (retried, res) {
2821            (true, Err(Errno::EISCONN)) => Ok(0),
2822            (_, res) => res,
2823        }
2824    }
2825}
2826
2827/// Transform a syscall to nonblocking, then retry it until it returns a successful result.
2828/// Retry a nonblockizable syscall (e.g. a pipe/socket read or write) until it succeeds.
2829///
2830/// `subtool` selects how each poll iteration executes the underlying syscall. Pass
2831/// `Some(detcore)` in record/replay mode for a container-INTERNAL fd (currently pipes):
2832/// each iteration is then routed through `Detcore::record_or_replay`, so the read's
2833/// bytes (and every intervening `EAGAIN`) are captured in the recording and reproduced
2834/// verbatim on replay. Without this, an internal-pipe read on the InternalIOPolling path
2835/// bypasses the recorder and replay reads live from a pipe whose cross-process writer
2836/// schedule is not reproduced -- the reader sees EOF instead of the recorded data and
2837/// replay desyncs. Pass `None` for plain `hermit run` (no recording) or for external fds.
2838pub async fn retry_nonblocking_syscall<T, G, C>(
2839    guest: &mut G,
2840    call: C,
2841    rsrc: Resources,
2842    subtool: Option<&Detcore<T>>,
2843) -> Result<i64, Error>
2844where
2845    C: NonblockableSyscall + Into<Syscall>,
2846    T: RecordOrReplay,
2847    G: Guest<Detcore<T>>,
2848{
2849    // Bogus 99 return value is dead code below:
2850    retry_nonblocking_syscall_helper(guest, call, rsrc, None, subtool).await
2851}
2852
2853/// What a wait that ended with `ERESTART_RESTARTBLOCK` keeps for the
2854/// `restart_syscall` that Linux runs next when no handler runs
2855/// (`NonblockableSyscall::restart_keeps_deadline`). Linux keeps the call and its
2856/// absolute deadline in the thread's restart block (`do_restart_poll`,
2857/// `futex_wait_restart`). Detcore emulates the wait, so the kernel's restart block
2858/// does not describe it, and Detcore keeps its own
2859/// (https://github.com/rrnewton/hermit/issues/3358). A `poll` with a positive
2860/// timeout that Detcore runs in the kernel keeps one too: the kernel's restart
2861/// block describes that poll, but Detcore runs the restart through its
2862/// recorder (`RestartCall::KernelPoll`).
2863///
2864/// The record lives until the thread's next syscall, which takes it
2865/// (`Detcore::handle_syscall_event`). Other first syscalls, sigreturn and exec
2866/// discard the record; separate threads and newly initialized children have
2867/// independent records. A caught handler does not discard it: Linux resets its
2868/// restart block at sigreturn (`restore_sigcontext`), not when it sets up the
2869/// handler, so a handler whose first syscall is `restart_syscall` resumes the
2870/// interrupted wait with the copied arguments and deadline. Detcore does the
2871/// same when that call stops at the interrupted call's address, as it does
2872/// through libc's `syscall()` (the `restartfirst` mode of the
2873/// `external_signal_interrupt` guest). Linux also keeps its restart block across
2874/// the handler's other syscalls and resumes it from any address; Detcore does
2875/// not, so a `restart_syscall` after another syscall or at another address takes
2876/// the unsupported-syscall policy.
2877///
2878/// A wait that Detcore ran in the kernel as the guest made it keeps a record
2879/// too, holding no deadline: its `restart_syscall` then goes to the kernel,
2880/// whose restart block describes the wait (`RestartCall::InKernel`).
2881#[derive(Clone, Copy, Debug, Eq, PartialEq)]
2882pub(crate) struct RestartBlock {
2883    /// The instruction pointer at the interrupted call's syscall stop: the address
2884    /// after its `syscall` instruction. The kernel's restart moves the instruction
2885    /// pointer back onto that instruction with `restart_syscall` in `rax`, so the
2886    /// restart stops at the same address.
2887    pub(crate) rip: u64,
2888    /// The wait's absolute deadline, or `None` for a wait without one and for a
2889    /// kernel-run poll, which keeps its own (`RestartCall::KernelPoll`).
2890    pub(crate) deadline: Option<LogicalTime>,
2891    /// The interrupted call.
2892    pub(crate) call: RestartCall,
2893}
2894
2895/// Guest scratch for one injected `poll` probe, reserved with the rest of a
2896/// caller's scratch before it commits the stack ([`PollProbe::reserve`]).
2897///
2898/// aarch64 has no `poll` syscall. Its `ppoll` runs the same `do_sys_poll`
2899/// (`fs/select.c`) but takes the timeout by pointer, so a probe there needs a
2900/// slot of guest memory for the timeout. x86_64 injects `poll` itself and
2901/// reserves nothing.
2902#[derive(Clone, Copy)]
2903pub(crate) struct PollProbe {
2904    #[cfg(not(target_arch = "x86_64"))]
2905    timeout: AddrMut<'static, Timespec>,
2906}
2907
2908impl PollProbe {
2909    /// Reserve the probe's scratch on `stack`, before the caller commits it.
2910    pub(crate) fn reserve<S: Stack>(stack: &mut S) -> Self {
2911        #[cfg(target_arch = "x86_64")]
2912        return {
2913            let _ = stack;
2914            Self {}
2915        };
2916        #[cfg(not(target_arch = "x86_64"))]
2917        return Self {
2918            timeout: stack.reserve(),
2919        };
2920    }
2921
2922    /// The probe of the `nfds` entries at `fds`, waiting at most
2923    /// `timeout_millis` (forever when negative), as `poll` takes them.
2924    #[cfg(target_arch = "x86_64")]
2925    pub(crate) fn call<M: MemoryAccess>(
2926        self,
2927        _memory: M,
2928        fds: AddrMut<'_, libc::pollfd>,
2929        nfds: libc::nfds_t,
2930        timeout_millis: libc::c_int,
2931    ) -> Result<syscalls::Poll, Errno> {
2932        Ok(syscalls::Poll::new()
2933            .with_fds(Some(fds.cast()))
2934            .with_nfds(nfds)
2935            .with_timeout(timeout_millis))
2936    }
2937
2938    /// The probe of the `nfds` entries at `fds`, waiting at most
2939    /// `timeout_millis` (forever when negative), as `poll` takes them: a
2940    /// `ppoll` with no signal mask, whose timeout this stages first.
2941    #[cfg(not(target_arch = "x86_64"))]
2942    pub(crate) fn call<M: MemoryAccess>(
2943        self,
2944        mut memory: M,
2945        fds: AddrMut<'_, libc::pollfd>,
2946        nfds: libc::nfds_t,
2947        timeout_millis: libc::c_int,
2948    ) -> Result<syscalls::Ppoll, Errno> {
2949        let timeout = if timeout_millis < 0 {
2950            None
2951        } else {
2952            let timeout = Timespec {
2953                tv_sec: libc::time_t::from(timeout_millis / 1000),
2954                tv_nsec: libc::c_long::from(timeout_millis % 1000) * 1_000_000,
2955            };
2956            memory.write_value(self.timeout, &timeout)?;
2957            Some(self.timeout)
2958        };
2959        Ok(syscalls::Ppoll::new()
2960            .with_fds(Some(fds.cast()))
2961            .with_nfds(nfds)
2962            .with_timeout(timeout)
2963            .with_sigmask(None)
2964            .with_sigsetsize(KERNEL_SIGSET_SIZE))
2965    }
2966}
2967
2968/// A call whose restart keeps its deadline.
2969#[derive(Clone, Copy, Debug, Eq, PartialEq)]
2970pub(crate) enum RestartCall {
2971    /// Only x86_64 has `poll`; aarch64's `ppoll` restarts with
2972    /// `ERESTARTNOHAND`, not a restart block.
2973    #[cfg(target_arch = "x86_64")]
2974    Poll(syscalls::Poll),
2975    /// A `poll` with a positive timeout that Detcore ran in the kernel
2976    /// (`Detcore::handle_external_poll`: record and replay, and
2977    /// `--no-sequentialize-threads`), with the end of its timeout. Its restart
2978    /// runs the poll again in the kernel, through the recorder, with the time
2979    /// left (`Detcore::resume_kernel_poll`). Only x86_64 has `poll`.
2980    #[cfg(target_arch = "x86_64")]
2981    KernelPoll(syscalls::Poll, KernelPollDeadline),
2982    Futex(syscalls::Futex),
2983    /// A relative `nanosleep` or `clock_nanosleep` that ended for a claimed
2984    /// `SIGCHLD` (`syscalls::signal::SleepSignal::Claimed`).
2985    Nanosleep(reverie::syscalls::family::NanosleepFamily),
2986    /// A timed wait that Detcore ran in the kernel as the guest made it, which
2987    /// the kernel's own restart block describes (`inject_keeping_kernel_restart`):
2988    /// a sleep or futex wait without sequentialized threads. Its
2989    /// `restart_syscall` goes to the kernel, which resumes the wait until the
2990    /// end it kept.
2991    InKernel(Sysno),
2992}
2993
2994/// Where the timeout of a `poll` Detcore runs in the kernel ends
2995/// (`RestartCall::KernelPoll`), on the host's monotonic clock. That clock is
2996/// the one the kernel wait runs on and the one Linux's restart keeps its end
2997/// time on (`poll_select_set_timeout`, `do_restart_poll`), while the thread's
2998/// logical time stands still as long as it blocks in the kernel, so an end on
2999/// logical time would let every restart wait the whole timeout again.
3000///
3001/// `None` under record and replay: there the restart reruns the poll with the
3002/// timeout it was issued with. The recording holds each poll's arguments and
3003/// replay checks them, and no clock that replay reproduces moves while the
3004/// poll blocks, so a time left is not replayed today.
3005#[cfg(target_arch = "x86_64")]
3006#[derive(Clone, Copy, Debug, Eq, PartialEq)]
3007pub(crate) struct KernelPollDeadline(pub(crate) Option<std::time::Instant>);
3008
3009/// The result of an injected `restart_syscall` (`resume_kernel_wait`).
3010fn resumed_kernel_wait_result(result: Result<i64, Errno>) -> Result<i64, Errno> {
3011    match result {
3012        Err(Errno::ERESTARTSYS) => Err(Errno::ERESTART_RESTARTBLOCK),
3013        other => other,
3014    }
3015}
3016
3017impl RestartBlock {
3018    /// The record, if a `restart_syscall` that stopped at `rip` is the kernel's
3019    /// restart of the interrupted call. At any other address the guest made the
3020    /// call itself.
3021    pub(crate) fn resumed_at(self, rip: u64) -> Option<Self> {
3022        (self.rip == rip).then_some(self)
3023    }
3024}
3025
3026/// Injects `call`, which the kernel runs as the guest made it, and keeps a
3027/// record for the kernel's restart when a signal ends it with
3028/// `ERESTART_RESTARTBLOCK` (`RestartCall::InKernel`). Linux then restarts the
3029/// wait through `restart_syscall` with the end time it stored in the thread's
3030/// restart block (`hrtimer_nanosleep_restart`, `futex_wait_restart`), and that
3031/// block is the one this call left: nothing else Detcore runs for the thread
3032/// before the restart sets or resets it.
3033pub(crate) async fn inject_keeping_kernel_restart<T, G, S>(
3034    guest: &mut G,
3035    call: S,
3036) -> Result<i64, Error>
3037where
3038    T: RecordOrReplay,
3039    G: Guest<Detcore<T>>,
3040    S: SyscallInfo,
3041{
3042    let sysno = call.number();
3043    let result = guest.inject(call).await.map_err(Error::from);
3044    keep_restart_block(guest, &result, RestartCall::InKernel(sysno), None).await;
3045    result
3046}
3047
3048/// The kernel's restart of a wait Detcore ran in the kernel
3049/// (`RestartCall::InKernel`): `restart_syscall` itself, which resumes the wait
3050/// from the thread's restart block. A signal that ends the resumed wait
3051/// leaves `ERESTART_RESTARTBLOCK` and the block again, so the record is kept
3052/// again. A signal that stops the injected call before it runs leaves the
3053/// block unused; the result is then the same `ERESTART_RESTARTBLOCK`, never
3054/// the `ERESTARTSYS` the backend reports, which would make the kernel rerun
3055/// `restart_syscall` without a record (`resumed_kernel_wait_result`).
3056pub(crate) async fn resume_kernel_wait<T, G>(
3057    guest: &mut G,
3058    restart: Syscall,
3059    sysno: Sysno,
3060) -> Result<i64, Error>
3061where
3062    T: RecordOrReplay,
3063    G: Guest<Detcore<T>>,
3064{
3065    let result = resumed_kernel_wait_result(guest.inject(restart).await).map_err(Error::from);
3066    keep_restart_block(guest, &result, RestartCall::InKernel(sysno), None).await;
3067    result
3068}
3069
3070/// Keeps `deadline` for the kernel's restart of `call` when the wait ended with
3071/// `ERESTART_RESTARTBLOCK` (`RestartBlock`).
3072pub(crate) async fn keep_restart_block<T, G>(
3073    guest: &mut G,
3074    result: &Result<i64, Error>,
3075    call: RestartCall,
3076    deadline: Option<LogicalTime>,
3077) where
3078    T: RecordOrReplay,
3079    G: Guest<Detcore<T>>,
3080{
3081    if matches!(result, Err(Error::Errno(errno)) if *errno == Errno::ERESTART_RESTARTBLOCK) {
3082        let rip = crate::arch::instruction_pointer(&guest.regs().await);
3083        let block = RestartBlock {
3084            rip,
3085            deadline,
3086            call,
3087        };
3088        tracing::trace!(
3089            "[tid {}] the wait keeps {:?} for its restart",
3090            guest.tid(),
3091            block
3092        );
3093        guest.thread_state_mut().restart_block = Some(block);
3094    }
3095}
3096
3097/// The restart code that ends a wait for a signal the backend holds and delivers
3098/// when the guest resumes. The backend passes a held signal on without reporting it
3099/// to the tool when the call returns `ERESTART_RESTARTBLOCK`, so for any signal but
3100/// `SIGSTOP`, which it never reports, the wait returns `ERESTARTNOHAND` instead. A
3101/// handler turns either code into `EINTR`, and a signal that kills the process
3102/// leaves no restart. The difference is a stop, which `SIGSTOP` alone can be here:
3103/// a default job-control stop does not end a wait whose restart keeps its deadline
3104/// (`KernelSignalState::interrupting_wait`).
3105///
3106/// A claimed relative `nanosleep` makes the opposite choice on purpose
3107/// (`syscalls::time`, `RestartCall::Nanosleep`): it returns
3108/// `ERESTART_RESTARTBLOCK`, so the held `SIGCHLD` is passed on unreported, and
3109/// the kernel's `restart_syscall` alone says no handler ran
3110/// (https://github.com/rrnewton/hermit/issues/4346).
3111fn held_signal_restart_errno(restart_errno: Errno, signal: i32) -> Errno {
3112    if restart_errno == Errno::ERESTART_RESTARTBLOCK && signal != libc::SIGSTOP {
3113        Errno::ERESTARTNOHAND
3114    } else {
3115        restart_errno
3116    }
3117}
3118
3119/// The deadline a polling request carries so the scheduler can restore a
3120/// backed-off poller once committed virtual time passes it
3121/// (`Resources::poll_deadline`, <https://github.com/rrnewton/hermit/issues/3952>).
3122/// `None` under record/replay, whose schedule must not change.
3123pub(crate) fn poll_deadline_for<T, G>(
3124    guest: &G,
3125    deadline: Option<LogicalTime>,
3126) -> Option<LogicalTime>
3127where
3128    T: RecordOrReplay,
3129    G: Guest<Detcore<T>>,
3130{
3131    if guest.config().recordreplay_modes {
3132        None
3133    } else {
3134        deadline
3135    }
3136}
3137
3138/// Retry a non-blocking syscall until it succeeds. Set the timeout to zero for the actual
3139/// syscalls (retries), while monitoring the clock to see if/when the logical timeout
3140/// should trigger.  Timeout is passed as an ABSOLUTE TIME (not duration).
3141pub async fn retry_nonblocking_syscall_with_timeout<T, G, C>(
3142    guest: &mut G,
3143    call: C,
3144    rsrc: Resources,
3145    // Logical timeout:
3146    maybe_timeout: Option<LogicalTime>,
3147) -> Result<i64, Error>
3148where
3149    C: NonblockableSyscall + TimeoutableSyscall + Into<Syscall>,
3150    T: RecordOrReplay,
3151    G: Guest<Detcore<T>>,
3152{
3153    let maybe_tup = maybe_timeout.map(|t| (t, call.timeout_return_val()));
3154    // Under the in-guest delivery gate the private-mask machinery is never
3155    // taken (A5 step 3c.1c, D50): the helper below decides by the published
3156    // mask instead (`gated_wait_ends`).
3157    if guest
3158        .config()
3159        .backend
3160        .supports_blocked_wait_signal_interruption
3161        && !guest.config().in_guest_signal_delivery
3162    {
3163        return retry_blocking_wait_with_kernel_signal_state(guest, call, rsrc, maybe_tup).await;
3164    }
3165    // poll/epoll_wait/futex/rt_sigtimedwait keep their existing execution (raw
3166    // inject_with_retry): their record/replay handling is out of scope for the internal
3167    // pipe data-ordering fix, and their fds are not necessarily internal pipes.
3168    retry_nonblocking_syscall_helper(guest, call, rsrc, maybe_tup, None).await
3169}
3170
3171/// The task whose kernel signal state the scheduler may read to decide which
3172/// signal ends an internal IO poll: the guest thread itself where guest thread
3173/// IDs name host tasks, and `None` elsewhere (KVM), where
3174/// `/proc/<pid>/task/<tid>` would describe an unrelated host task.
3175pub(crate) fn guest_signal_state_task<T, G>(guest: &G) -> Option<(i32, i32)>
3176where
3177    T: RecordOrReplay,
3178    G: Guest<Detcore<T>>,
3179{
3180    crate::procfs_inode::guest_tids_name_host_tasks(&guest.config().backend, None)
3181        .then(|| (guest.pid().as_raw(), guest.tid().as_raw()))
3182}
3183
3184/// Whether a signal that woke a poll would be delivered to this thread now,
3185/// read from the backend's own state of the thread: its current mask (the
3186/// old set of a query-only `rt_sigprocmask`) and each signal's disposition
3187/// (`wait_signal_disposition`). A blocked or ignored signal, or one whose
3188/// default action ignores it, does not.
3189async fn own_disposition_interrupts<T, G>(
3190    guest: &mut G,
3191    status: ResumeStatus,
3192    mask_addr: AddrMut<'_, KernelSigset>,
3193    action_addr: AddrMut<'_, KernelSigaction>,
3194) -> Result<bool, Error>
3195where
3196    T: RecordOrReplay,
3197    G: Guest<Detcore<T>>,
3198{
3199    let query = syscalls::RtSigprocmask::new()
3200        .with_how(libc::SIG_BLOCK)
3201        .with_set(None)
3202        .with_oldset(Some(mask_addr.cast()))
3203        .with_sigsetsize(KERNEL_SIGSET_SIZE);
3204    guest.inject_with_retry(query).await?;
3205    let mask: KernelSigset = guest.memory().read_value(mask_addr)?;
3206    Ok(
3207        wait_signal_disposition(guest, status, &mask, action_addr, true)
3208            .await?
3209            .is_some(),
3210    )
3211}
3212
3213/// Whether the signal that woke a part of a call that made progress
3214/// (`CallParts::progress`), or a read of an inotify instance that is
3215/// nonblocking for the guest by now (`interrupted_wait_errno`), on a backend
3216/// whose turn does not decide interruption, would be delivered to the caller
3217/// now, by its own mask and dispositions (`own_disposition_interrupts`): a
3218/// blocked or ignored signal leaves the read waiting, as it leaves Linux's
3219/// read waiting.
3220async fn progress_signal_interrupts<T, G>(
3221    guest: &mut G,
3222    status: ResumeStatus,
3223) -> Result<bool, Error>
3224where
3225    T: RecordOrReplay,
3226    G: Guest<Detcore<T>>,
3227{
3228    let mut stack = guest.stack().await;
3229    let mask_addr = stack.reserve::<KernelSigset>();
3230    let action_addr = stack.reserve::<KernelSigaction>();
3231    let _guard = stack.commit()?;
3232    own_disposition_interrupts(guest, status, mask_addr, action_addr).await
3233}
3234
3235/// The open file description of the inotify instance a read began on, if
3236/// `call` is a read of one: Linux's read keeps that file through
3237/// `ksys_read` whatever another thread later puts at its number
3238/// (<https://github.com/torvalds/linux/blob/v6.17/fs/read_write.c#L704-L720>),
3239/// so the errno a signal wake returns is that description's to decide
3240/// (`interrupted_wait_errno`). A clone shares the description with every
3241/// alias, so a later F_SETFL or FIONBIO through any of them shows here.
3242fn inotify_description_of<T, G, C>(guest: &G, call: C) -> Option<crate::fd::DetFd>
3243where
3244    T: RecordOrReplay,
3245    G: Guest<Detcore<T>>,
3246    C: NonblockableSyscall + Into<Syscall>,
3247{
3248    let fd = get_fd(call.into())?;
3249    guest
3250        .thread_state()
3251        .with_detfd(fd, |detfd| {
3252            (detfd.ty() == FdType::Inotify).then(|| detfd.clone())
3253        })
3254        .ok()
3255        .flatten()
3256}
3257
3258/// The errno a polled call that would still wait returns when a signal ends
3259/// the wait: the call's restart code (`NonblockableSyscall::signal_interrupt_errno`),
3260/// except for a read that began on an inotify instance (`began_on`,
3261/// `inotify_description_of`) that is nonblocking for the guest by now, which
3262/// another thread's F_SETFL or FIONBIO made it while the read waited.
3263/// `inotify_read` tests the file's current O_NONBLOCK before it looks for a
3264/// pending signal, so Linux returns EAGAIN there
3265/// (<https://github.com/torvalds/linux/blob/v6.17/fs/notify/inotify/inotify_user.c#L249-L299>).
3266/// A mode change alone ends no wait: Linux's reader sleeps on until an event
3267/// or a signal it would deliver wakes it, and this applies only at such a
3268/// wake (where the turn does not decide, the woken read asks,
3269/// `progress_signal_interrupts`). What another thread puts at the
3270/// descriptor's number while the read waits changes nothing: the
3271/// description the read began on decides.
3272fn interrupted_wait_errno<C>(began_on: Option<&crate::fd::DetFd>, call: C) -> Errno
3273where
3274    C: NonblockableSyscall,
3275{
3276    if began_on.is_some_and(crate::fd::DetFd::is_nonblocking) {
3277        Errno::EAGAIN
3278    } else {
3279        call.signal_interrupt_errno()
3280    }
3281}
3282
3283/// A call Detcore runs in parts, one after another (a readv reading on into a
3284/// later iovec, `Detcore::readv_inotify`; `ThreadState::call_parts`).
3285#[derive(Clone, Debug, Default)]
3286pub(crate) struct CallParts {
3287    /// Whether an earlier part transferred bytes. A wake that would deliver
3288    /// a signal then ends the part now running with those bytes, and any
3289    /// other wake leaves it waiting (`retry_nonblocking_syscall_helper`).
3290    pub(crate) progress: bool,
3291    /// A signal an earlier part completed with pending. It ends the first
3292    /// later part that would wait: Linux's next `->read` finds it with
3293    /// `signal_pending()` before it sleeps, so `do_loop_readv_writev` returns
3294    /// the bytes read so far
3295    /// (<https://github.com/torvalds/linux/blob/v6.17/fs/read_write.c#L835-L866>).
3296    pub(crate) carried: Option<CarriedSignal>,
3297}
3298
3299/// What the turn on which a part of a call run in parts completed knew of a
3300/// signal that would end a later part (`CallParts::carried`).
3301#[derive(Clone, Debug)]
3302pub(crate) enum CarriedSignal {
3303    /// The turn decided it (`TurnSignals::ends`: a named wake or a pending
3304    /// copy that the call's mask and the signal's disposition deliver).
3305    Decided,
3306    /// A wake the turn did not decide: on a backend whose guest threads are
3307    /// not host tasks (KVM), the later part decides from the guest's own mask
3308    /// and dispositions (`progress_signal_interrupts`); a backend's own
3309    /// observation of a signal (`ResumeStatus::Signaled(None)`) is trusted, as
3310    /// a turn trusts it.
3311    Wake(ResumeStatus),
3312}
3313
3314/// The signal a part completing on this turn carries to the parts after it
3315/// (`CallParts::carried`): one the turn found that would end a part after
3316/// progress (`turn`, read where the turn decides), or the turn's signal wake
3317/// where it does not. Under the in-guest delivery gate none is carried: each
3318/// part decides at its own turns (`gated_wait_ends`).
3319fn carried_signal(
3320    turn: Option<TurnSignals>,
3321    resumed: &ResumeStatus,
3322    gated: bool,
3323) -> Option<CarriedSignal> {
3324    match turn {
3325        Some(turn) => turn.ends(true).then_some(CarriedSignal::Decided),
3326        None if gated => None,
3327        None => matches!(resumed, ResumeStatus::Signaled(_))
3328            .then(|| CarriedSignal::Wake(resumed.clone())),
3329    }
3330}
3331
3332/// Whether the signal an earlier part of a call run in parts completed with
3333/// (`CallParts::carried`) ends the part now running, which would wait. Taken
3334/// once: one that would not be delivered leaves the part waiting, and its
3335/// later wakes decide. `decides_itself`: the backend's turn does not decide
3336/// interruption (KVM), so the guest's own mask and dispositions decide
3337/// (`progress_signal_interrupts`).
3338async fn carried_signal_ends<T, G>(
3339    guest: &mut G,
3340    decides_itself: bool,
3341    gated: bool,
3342) -> Result<bool, Error>
3343where
3344    T: RecordOrReplay,
3345    G: Guest<Detcore<T>>,
3346{
3347    let carried = guest
3348        .thread_state_mut()
3349        .call_parts
3350        .as_mut()
3351        .and_then(|parts| parts.carried.take());
3352    match carried {
3353        None => Ok(false),
3354        Some(CarriedSignal::Decided) => Ok(true),
3355        Some(CarriedSignal::Wake(status)) if decides_itself => {
3356            progress_signal_interrupts(guest, status).await
3357        }
3358        Some(CarriedSignal::Wake(_)) => Ok(!gated),
3359    }
3360}
3361
3362// Private helper.
3363async fn retry_nonblocking_syscall_helper<T, G, C>(
3364    guest: &mut G,
3365    call0: C,
3366    mut rsrc: Resources,
3367    maybe_timeout: Option<(LogicalTime, Result<i64, Errno>)>,
3368    subtool: Option<&Detcore<T>>,
3369) -> Result<i64, Error>
3370where
3371    C: NonblockableSyscall + Into<Syscall>,
3372    T: RecordOrReplay,
3373    G: Guest<Detcore<T>>,
3374{
3375    // Capture the same mark-dependent classification that the first retry
3376    // will publish, before conversion or a resource request can yield. An
3377    // ordinary internal pipe's routing alone does not request this capture.
3378    refresh_guest_poll_state(guest, call0.into(), &mut rsrc);
3379    let mut guest_held = GuestHeldPollProof::capture(guest, call0.into(), &mut rsrc);
3380    // The signals the wait takes from the queue (rt_sigtimedwait's set).
3381    let consumed = call0.signals_consumed_by_wait(&guest.memory());
3382    // The memory a vectored call copies when it begins (its iovec array, and
3383    // a message's header, address and control data), which every attempt
3384    // below sees as it was now, as the kernel's one copy would be
3385    // (`EntryImport`). Linux copies it before the call can wait, so a thread
3386    // that rewrites it while the call waits changes nothing the call moves.
3387    // Boxed, so the call's future stays small (`hot_path_futures_stay_small`).
3388    let entry_import = EntryImport::capture(&guest.memory(), &call0.into()).map(Box::new);
3389    let mut completed_observation = None;
3390    // The stack-allocated memory here needs to live across the loop, which means
3391    // surviving multiple syscall injections:
3392    let (mut call, mut maybe_stackguard) = call0.into_nonblocking(guest).await;
3393    rsrc.poll_deadline = poll_deadline_for(guest, maybe_timeout.map(|(deadline, _)| deadline));
3394    // A restartable call parked in internal IO polling, such as a `read` of an
3395    // empty pipe, is interrupted by a signal another task sends, as a parked
3396    // pipe write is (`pipe_writev_resources`). The marker lets the scheduler
3397    // record the signal for it (`Scheduler::notify_signal_pending`) and answer
3398    // its next turn `Signaled`; the call then returns `ERESTARTSYS`, and the
3399    // kernel applies the guest's disposition when it resumes: a handler runs
3400    // and the call fails with `EINTR` or restarts under `SA_RESTART`. Without
3401    // it, the poll never saw the signal and waited forever
3402    // (<https://github.com/rrnewton/hermit/issues/4187>).
3403    if rsrc.signal_interrupt_errno().is_none()
3404        && rsrc.resources.len() == 1
3405        && rsrc.resources.contains_key(&ResourceID::InternalIOPolling)
3406        && call.signal_interrupt_errno() == Errno::ERESTARTSYS
3407    {
3408        rsrc.set_signal_interrupt_errno(Errno::ERESTARTSYS);
3409        // Where guest thread IDs are not host tasks (KVM) the scheduler keeps
3410        // every signal: the call has made no progress when it returns
3411        // ERESTARTSYS, and the backend restarts it unless a signal it would
3412        // deliver is pending, so a blocked or ignored signal leaves it waiting.
3413        rsrc.signal_state_task = guest_signal_state_task(guest);
3414    }
3415    // D50 reader 2 under the in-guest delivery gate (A5 step 3c.1c): no
3416    // private kernel mask; once an attempt would block, the design's
3417    // deliverable set under the thread's published mask decides
3418    // (`gated_wait_ends`), at each turn, the first included.
3419    let gated = guest.config().in_guest_signal_delivery;
3420    let checks_at_turn = !gated
3421        && rsrc.signal_interrupt_errno() == Some(Errno::ERESTARTSYS.into_raw())
3422        && decides_interruption_at_turn(guest, rsrc.signal_state_task);
3423    // A part of a call that transferred bytes in an earlier part (a readv
3424    // reading on into a later iovec, `Detcore::readv_inotify`): Linux's
3425    // per-iovec loop returns those bytes once the next iovec's read ends
3426    // early, so any wake that would deliver a signal ends this part, a named
3427    // wake as well as a copy still pending (`TurnSignals::ends`), while a
3428    // blocked or ignored signal leaves it waiting. Where the turn does not
3429    // decide (a backend whose guest threads are not host tasks), the woken
3430    // part decides from the guest's own mask and dispositions, as a parked
3431    // pipe write does (`execute_blocking_pipe_write`): the restart errno would
3432    // end the call with a short count, which the backend's restart of a call
3433    // with no progress cannot undo.
3434    let progress = guest
3435        .thread_state()
3436        .call_parts
3437        .as_ref()
3438        .is_some_and(|parts| parts.progress);
3439    let decides_itself = !checks_at_turn && !gated;
3440    let decides_progress_itself = progress && decides_itself;
3441    // The inotify instance a read began on, whose own O_NONBLOCK decides the
3442    // errno a signal wake returns (`interrupted_wait_errno`).
3443    let began_on = inotify_description_of(guest, call0);
3444    // Where the turn decides, each probe owns the guest's mask while it blocks
3445    // every blockable signal (`KernelSignalWait::block_in_place`). The mask
3446    // goes back before a retry yields, so target selection still sees which
3447    // signals the guest admits. Even an empty pending
3448    // snapshot needs that protection: a signal arriving afterward could
3449    // stop the probe, move into the backend's held slot, and disappear from
3450    // every later pending read while inject_with_retry kept polling. Two
3451    // such signals could also replace one another in the single held slot
3452    // (https://github.com/rrnewton/hermit/issues/4273, stage 2).
3453    let mut turn_wait = checks_at_turn
3454        .then(|| KernelSignalWait::new(guest, 0, false, call.signal_interrupt_errno()));
3455    let mut published = PublishedWaitMask::default();
3456
3457    let result: Result<i64, Error> = loop {
3458        // A sibling can replace a descriptor while the previous request
3459        // waits. Classify the next attempt against the current table. The
3460        // scheduler's progress/freshness checks prevent the older queued
3461        // request from justifying an idle jump before this new probe.
3462        refresh_guest_poll_state(guest, call.into(), &mut rsrc);
3463        // The refresh above recomputes the classification from the current
3464        // table; a guest-held proof this call already lost stays lost.
3465        guest_held.revalidate(guest, &mut rsrc);
3466        let resumed = match call.into() {
3467            Syscall::Read(read) if maybe_timeout.is_none() && maybe_stackguard.is_none() => {
3468                crate::tool_global::polled_read_request(guest, read, rsrc.clone()).await
3469            }
3470            _ => resource_request(guest, rsrc.clone()).await,
3471        };
3472        guest_held.revalidate(guest, &mut rsrc);
3473        // The turn's signals, read before its first injection
3474        // (`read_turn_signals`): the timerfd publication and the real-time
3475        // action below may inject, and a pending copy could stop that
3476        // injection and move into the backend's held slot. Every turn reads,
3477        // the first included, since a signal sent before the poll filed its
3478        // first request was admitted by no drain
3479        // (https://github.com/rrnewton/hermit/issues/4273). A backend's own
3480        // observation of a signal (`Signaled(None)`) is trusted as it is: that
3481        // signal has already left the kernel's queue.
3482        // Once the call blocks, the turn decides under the guest's own mask,
3483        // which the call owns during each probe, never under the full one installed (Codex
3484        // review of the R2b-2 design rev3, P2-1).
3485        let turn = if checks_at_turn && !matches!(resumed, ResumeStatus::Signaled(None)) {
3486            match turn_signals_with_block(
3487                guest,
3488                &mut turn_wait,
3489                ProbeMaskPolicy::PrivateProbe,
3490                committed_signals(&resumed),
3491                rsrc.private_sigchld_decides,
3492                &mut published,
3493            )
3494            .await
3495            {
3496                Ok(turn) => Some(turn),
3497                Err(error) => break Err(error),
3498            }
3499        } else {
3500            None
3501        };
3502        // A virtual timerfd this probe may observe is readable on the host
3503        // exactly when its virtual deadline has passed by this turn
3504        // (`crate::syscalls::timerfd`); its next deadline restores this
3505        // poller if it backs off.
3506        // Publishing may take the scratch stack, which the nonblocking form
3507        // holds; release it first and convert again after.
3508        let reconvert =
3509            maybe_stackguard.is_some() && crate::syscalls::timerfd::may_publish(guest, call.into());
3510        if reconvert {
3511            maybe_stackguard = None;
3512        }
3513        rsrc.poll_deadline = crate::syscalls::timerfd::publish_before_probe(
3514            guest,
3515            call.into(),
3516            maybe_timeout.map(|(deadline, _)| deadline),
3517        )
3518        .await;
3519        if reconvert {
3520            (call, maybe_stackguard) = call0.into_nonblocking(guest).await;
3521        }
3522        // A signal ends the call only if it would still block, as Linux
3523        // checks for a pending signal only when the call must wait: a byte
3524        // written, or the last writer closed, before the signal is read
3525        // first. The probe below goes through the record/replay path like
3526        // every attempt (Codex review of
3527        // https://github.com/rrnewton/hermit/pull/4198).
3528        // A call that has transferred nothing ends only for a copy still
3529        // pending; a part after progress ends for a named wake too
3530        // (`TurnSignals::ends`, `progress` above).
3531        // A read that began on an inotify instance that is nonblocking for
3532        // the guest by now ends at a signal wake with EAGAIN
3533        // (`interrupted_wait_errno`), which, unlike the restart errno, no
3534        // backend's restart of a call whose signal it would not deliver
3535        // undoes. Where the turn does not decide, it decides as a part after
3536        // progress does: a blocked or ignored signal leaves it waiting, as
3537        // it leaves Linux's reader asleep.
3538        let mode_wake_decides_itself = decides_itself
3539            && began_on
3540                .as_ref()
3541                .is_some_and(crate::fd::DetFd::is_nonblocking);
3542        let signaled = match turn {
3543            Some(turn) => turn.ends(progress),
3544            None if gated => false,
3545            // Continuation parts are scalar reads, whose nonblocking form
3546            // holds no scratch stack (`network_comm_syscall`), as does every
3547            // read and readv.
3548            None if (decides_progress_itself || mode_wake_decides_itself)
3549                && maybe_stackguard.is_none()
3550                && matches!(resumed, ResumeStatus::Signaled(_)) =>
3551            {
3552                match progress_signal_interrupts(guest, resumed.clone()).await {
3553                    Ok(interrupts) => interrupts,
3554                    Err(error) => break Err(error),
3555                }
3556            }
3557            None => matches!(resumed, ResumeStatus::Signaled(_)),
3558        };
3559        // A pending real-time timer expiration in the set this wait takes
3560        // (https://github.com/rrnewton/hermit/issues/4089).
3561        let pending = pending_real_time_among(guest, consumed).await;
3562        act_on_pending_real_time(guest, pending, consumed, 0).await;
3563        // The call's memory as it began, for this attempt and for the reads
3564        // of it below, and what another thread wrote over it meanwhile, which
3565        // goes back once the attempt is made.
3566        let mut displaced = entry_import
3567            .as_ref()
3568            .map(|entry| entry.reinstate(&mut guest.memory()));
3569        // Classify the input the kernel will actually import: sendmsg keeps
3570        // its entry-time header/control, while sendmmsg reads each attempt.
3571        // The FD table may have changed during either call's retry wait.
3572        if Detcore::<T>::passes_descriptors_out_now(guest, call.into()) {
3573            // Never yield with captured bytes installed over a sibling's
3574            // writes. Restore those writes before reporting the export,
3575            // then capture the current displaced view after the new grant.
3576            if let Err(error) = put_back_entry_import(guest, &entry_import, displaced.take(), false)
3577            {
3578                break Err(error);
3579            }
3580            let dettid = guest.thread_state().dettid;
3581            let _ = resource_request(guest, Resources::new(dettid)).await;
3582            displaced = entry_import
3583                .as_ref()
3584                .map(|entry| entry.reinstate(&mut guest.memory()));
3585        }
3586        // A receive whose descriptors Detcore names reads its geometry again
3587        // right before each attempt: a sibling may have changed it while an
3588        // earlier attempt waited (`ThreadState::receive_geometry`).
3589        if guest.thread_state().receive_geometry.is_some() {
3590            let geometry =
3591                crate::syscalls::scm_rights::ReceiveGeometry::before(&guest.memory(), &call.into());
3592            guest.thread_state_mut().receive_geometry = Some(geometry);
3593        }
3594        // A send whose `SCM_RIGHTS` descriptors Detcore inspected reads them
3595        // again right before each attempt: a sibling may have rewritten its
3596        // messages, or replaced what their descriptor numbers name, since the
3597        // inspection or while an earlier attempt waited
3598        // (`ThreadState::send_inspection`). An attempt that would pass other
3599        // placeholders than those whose copies Detcore counted is refused by
3600        // name. Boxed: only a send that may pass a placeholder carries it.
3601        if let Some(inspection) = guest.thread_state().send_inspection.clone()
3602            && let Some(reason) =
3603                Box::pin(Detcore::<T>::changed_placeholder_send(guest, &inspection)).await
3604        {
3605            tracing::error!("Detcore refuses {}: {reason}", inspection.sysno);
3606            if let Err(error) = put_back_entry_import(guest, &entry_import, displaced, false) {
3607                break Err(error);
3608            }
3609            let panic_on_unsupported_syscalls = guest.config().panic_on_unsupported_syscalls;
3610            break refuse_unserviceable_operation_with(
3611                guest,
3612                panic_on_unsupported_syscalls,
3613                inspection.sysno,
3614                Errno::EOPNOTSUPP,
3615                Some(&reason),
3616            )
3617            .await;
3618        }
3619        // A sibling may have replaced the destination descriptor while this
3620        // attempt waited for its turn. The per-attempt check above
3621        // (`passes_descriptors_out_now`) rechecks the channel immediately
3622        // before each send, independently of the mutable control data.
3623
3624        // An inotify read counts its instance's queue right before each
3625        // attempt, which runs next in this turn, so that the count it keeps
3626        // is the one before the attempt that completes
3627        // (`ThreadState::inotify_queue`). Boxed: only such a read counts.
3628        if guest.thread_state().inotify_queue.is_some() {
3629            Box::pin(crate::syscalls::inotify::count_before_attempt(
3630                guest,
3631                call.into(),
3632            ))
3633            .await;
3634        }
3635        // Read actual metadata after reinstatement and all earlier awaits.
3636        // Observation adds no reads or allocation when buffer logging is off.
3637        let attempt_observation =
3638            if guest.config().detlog_io_buffers && crate::io_buffers::observed() {
3639                EntryImport::capture_observation(&guest.memory(), &call.into())
3640            } else {
3641                None
3642            };
3643        // Route through the record/replay subtool for internal pipes so each poll (an
3644        // EAGAIN, or the final data-bearing read) becomes one recorded event that replay
3645        // reproduces deterministically; otherwise execute the syscall directly.
3646        let res = match subtool {
3647            Some(detcore) => {
3648                detcore
3649                    .record_or_replay_preserving_tool_errors(guest, call)
3650                    .await
3651            }
3652            None => {
3653                // The preceding placeholder inspection can await. Publish
3654                // persistent/reference effects at the actual issue cut,
3655                // not from the descriptor or ancillary view before it.
3656                match crate::reserved_input::before_native_call(guest, call.into()) {
3657                    Ok(()) => guest.inject_with_retry(call).await.map_err(Error::from),
3658                    // Keep the normal EntryImport restoration below even
3659                    // when native issue is refused before it begins.
3660                    Err(error) => Err(error),
3661                }
3662            }
3663        };
3664        if let Err(error) = put_back_entry_import(guest, &entry_import, displaced, res.is_ok()) {
3665            break Err(error);
3666        }
3667        // Under record and replay the attempt is a plain injection, which the
3668        // signal that woke this call, still pending, stops before it runs
3669        // (ERESTARTSYS, not run): the call then returns that restart errno,
3670        // as on main, though a byte, end of file or room may have been ready.
3671        // Making the attempt again would replace the held signal with any
3672        // other pending one (https://github.com/rrnewton/reverie/issues/1012),
3673        // and record and replay could not agree on when it is safe.
3674        let syscall_result = match res {
3675            Ok(value) => Ok(value),
3676            Err(Error::Errno(error)) => Err(error),
3677            Err(error) => break Err(error),
3678        };
3679        // Before the signal check: Linux looks for a pending signal only when
3680        // the call must wait, and a receive on a socket shut down for reading
3681        // does not. Record and replay poll a receive here only on a
3682        // socketpair, through `subtool`; finishing it there would add
3683        // recorded calls, a new recording format, so they keep polling.
3684        if subtool.is_none()
3685            && call.syscall_would_have_blocked(syscall_result)
3686            && let Some(result) = finish_after_read_shutdown(guest, call).await
3687        {
3688            break result;
3689        }
3690        // A part after progress that would wait while a signal an earlier
3691        // part completed with is still pending ends, as Linux's next `->read`
3692        // does when it finds `signal_pending()` before it sleeps
3693        // (`CallParts::carried`).
3694        let carried_ends =
3695            if progress && !signaled && call.syscall_would_have_blocked(syscall_result) {
3696                match carried_signal_ends(
3697                    guest,
3698                    decides_progress_itself && maybe_stackguard.is_none(),
3699                    gated,
3700                )
3701                .await
3702                {
3703                    Ok(ends) => ends,
3704                    Err(error) => break Err(error),
3705                }
3706            } else {
3707                false
3708            };
3709        let signaled = signaled || carried_ends;
3710        if signaled && call.syscall_would_have_blocked(syscall_result) {
3711            let errno = interrupted_wait_errno(began_on.as_ref(), call);
3712            tracing::trace!(
3713                "retry_nonblocking_syscall: interrupted by signal while {} would block: {:?}",
3714                call.display(&guest.memory()),
3715                errno
3716            );
3717            break Err(errno.into());
3718        }
3719        // Under the gate the wait ends here, where it would block (as
3720        // Linux checks for a pending signal only then), for a signal sent
3721        // before it parked or one whose commit woke it.
3722        if gated && call.syscall_would_have_blocked(syscall_result) {
3723            match gated_wait_ends(guest, None) {
3724                Ok(true) => break Err(interrupted_wait_errno(began_on.as_ref(), call).into()),
3725                Ok(false) => {}
3726                Err(error) => break Err(error),
3727            }
3728        }
3729        if call.syscall_would_have_blocked(syscall_result) {
3730            if let Some(wait) = turn_wait.as_mut() {
3731                // Both observing time and tracing a retry can yield to the
3732                // scheduler. Its ptrace signal-target selection must see the
3733                // guest's mask, not this probe's private all-blocked mask.
3734                // Only the no-execution setter is safe here: an injected
3735                // restoration could hide a pending signal in the backend's
3736                // held slot before another probe. A refusal ends this call
3737                // before any such yield; final cleanup may still restore it.
3738                if let Err(error) = wait.release_probe_mask_in_place(guest).await {
3739                    break Err(error);
3740                }
3741                publish_wait_mask(guest, None, &mut published).await;
3742            }
3743            rsrc.poll_attempt += 1;
3744            if let Some((timeout, timeout_result)) = maybe_timeout {
3745                let new_time = thread_observe_time(guest).await;
3746                if new_time >= timeout {
3747                    tracing::trace!(
3748                        "Timing out syscall after #{} retries: {}",
3749                        rsrc.poll_attempt - 1,
3750                        call.display(&guest.memory())
3751                    );
3752                    break timeout_result.map_err(|e| e.into());
3753                } else {
3754                    tracing::trace!(
3755                        "Retry #{} for syscall due to result {:?}, {} from timeout: {}",
3756                        rsrc.poll_attempt,
3757                        syscall_result,
3758                        timeout - new_time,
3759                        call.display(&guest.memory())
3760                    );
3761                    record_retry_event(guest, call).await;
3762                }
3763            } else {
3764                tracing::trace!(
3765                    "Retry #{} for syscall due to result {:?}: {}",
3766                    rsrc.poll_attempt,
3767                    syscall_result,
3768                    call.display(&guest.memory())
3769                );
3770                record_retry_event(guest, call).await;
3771            }
3772        } else {
3773            // A part of a call run in parts completes: a signal this turn
3774            // found, which would end a later part, stays pending for the
3775            // parts after it, as Linux's does (`CallParts::carried`).
3776            if let Some(carried) = carried_signal(turn, &resumed, gated)
3777                && let Some(parts) = guest.thread_state_mut().call_parts.as_mut()
3778            {
3779                parts.carried = Some(carried);
3780            }
3781            let normalized =
3782                call.normalize_nonblocking_result(syscall_result, rsrc.poll_attempt > 0);
3783            if normalized != syscall_result {
3784                guest.thread_state_mut().nonblocking_result_normalized = true;
3785            }
3786            let res = normalized.map_err(|e| e.into());
3787            if res.is_ok() {
3788                completed_observation = attempt_observation;
3789            }
3790            tracing::trace!(
3791                "retry_nonblocking_syscall: syscall completed after {} retries: {} = {:?}",
3792                rsrc.poll_attempt,
3793                call.display(&guest.memory()),
3794                res
3795            );
3796            break res;
3797        }
3798    };
3799    let entry_import =
3800        EntryImport::with_observation(entry_import, call0.into(), completed_observation);
3801    keep_entry_view(guest, entry_import);
3802
3803    // The single exit: the guest's mask goes back before the result is
3804    // returned. A mask `block_in_place` installed goes back through
3805    // `Guest::set_blocked_signals`, with no scratch memory, and only a
3806    // refusal falls back to an injected restoration (`KernelSignalWait::restore`).
3807    // A mask that cannot be put back ends the run before the guest resumes.
3808    match turn_wait {
3809        Some(mut wait) if wait.effective_mask().is_some() => {
3810            let restored = wait.restore(guest, None).await;
3811            publish_wait_mask(guest, None, &mut published).await;
3812            match result_after_restore(restored, result) {
3813                Ok(result) => refuse_held_signal_loss(guest, call0.number(), result).await,
3814                Err(error) => Err(error),
3815            }
3816        }
3817        _ => result,
3818    }
3819}
3820
3821/// Ends a polled blocking receive as Linux does once its socket's read side is
3822/// shut down.
3823///
3824/// Detcore polls a blocking receive on a physically nonblocking socket with
3825/// nonblocking attempts (`retry_nonblocking_syscall`). On a datagram socket
3826/// whose read side is shut down, Linux's blocking receive does not wait: with
3827/// nothing queued it returns 0 at once (`__skb_wait_for_more_packets`). The
3828/// nonblocking attempt fails with `EAGAIN` instead, every time, so the polled
3829/// receive never ended, and a thread that shut a reader's socket down and then
3830/// joined the reader waited forever.
3831///
3832/// After an attempt that would block on a socket whose read side a guest shut
3833/// down (`DetFd::read_shutdown_requested`), this asks the kernel whether it is
3834/// still shut (`read_side_shut_down`): a TCP disconnect opens it again. If it
3835/// is, the blocking call cannot wait, so this runs the guest's call once with
3836/// `O_NONBLOCK` cleared, then sets the flag back, and returns what Linux
3837/// returned, with the address lengths it wrote. `None` for any other call or
3838/// socket, or while the read side is open: the attempt's `EAGAIN` stands and
3839/// the poll goes on, with no extra call.
3840async fn finish_after_read_shutdown<T, G, C>(guest: &mut G, call: C) -> Option<Result<i64, Error>>
3841where
3842    C: NonblockableSyscall + Into<Syscall>,
3843    T: RecordOrReplay,
3844    G: Guest<Detcore<T>>,
3845{
3846    let fd = get_fd(call.into()).filter(|_| call.ends_at_read_shutdown())?;
3847    let shut_down_by_guest = guest.thread_state().with_detfd(fd, |detfd| {
3848        matches!(detfd.ty(), FdType::Socket) && detfd.read_shutdown_requested()
3849    });
3850    if shut_down_by_guest != Ok(true) || !read_side_shut_down(guest, fd).await {
3851        return None;
3852    }
3853    let fcntl = |cmd| syscalls::Fcntl::new().with_fd(fd).with_cmd(cmd);
3854    // Nothing has changed if either of these fails: the attempt's EAGAIN stands.
3855    let flags = match guest.inject_with_retry(fcntl(FcntlCmd::F_GETFL)).await {
3856        Ok(flags) => flags as libc::c_int,
3857        Err(errno) => {
3858            tracing::warn!("socket {fd}: F_GETFL before a read-shutdown receive failed: {errno}");
3859            return None;
3860        }
3861    };
3862    let blocking = fcntl(FcntlCmd::F_SETFL(flags & !libc::O_NONBLOCK));
3863    if let Err(errno) = guest.inject_with_retry(blocking).await {
3864        tracing::warn!(
3865            "socket {fd}: clearing O_NONBLOCK for a read-shutdown receive failed: {errno}"
3866        );
3867        return None;
3868    }
3869    let result = guest.inject_with_retry(call).await.map_err(Error::from);
3870    // Every later attempt on this description assumes it is nonblocking; one
3871    // that blocked would hold the turn forever.
3872    if let Err(errno) = guest
3873        .inject_with_retry(fcntl(FcntlCmd::F_SETFL(flags)))
3874        .await
3875    {
3876        panic!("socket {fd}: restoring O_NONBLOCK after a read-shutdown receive failed: {errno}");
3877    }
3878    tracing::trace!(
3879        "retry_nonblocking_syscall: {} ran blocking after a read shutdown: {:?}",
3880        call.display(&guest.memory()),
3881        result
3882    );
3883    Some(result)
3884}
3885
3886/// Whether the kernel reports `fd`'s read side shut down: `POLLRDHUP` from a
3887/// `poll` with a zero timeout, which Linux sets exactly when the socket's
3888/// `RCV_SHUTDOWN` is set (`datagram_poll`, `unix_dgram_poll`, `tcp_poll`).
3889async fn read_side_shut_down<T, G>(guest: &mut G, fd: i32) -> bool
3890where
3891    T: RecordOrReplay,
3892    G: Guest<Detcore<T>>,
3893{
3894    let mut stack = guest.stack().await;
3895    let pollfd: AddrMut<libc::pollfd> = stack.reserve();
3896    let Ok(_guard) = stack.commit() else {
3897        return false;
3898    };
3899    let probe = syscalls::Poll::new()
3900        .with_fds(Some(pollfd.cast()))
3901        .with_nfds(1)
3902        .with_timeout(0);
3903    let request = libc::pollfd {
3904        fd,
3905        events: libc::POLLRDHUP,
3906        revents: 0,
3907    };
3908    guest.memory().write_value(pollfd, &request).is_ok()
3909        && guest.inject_with_retry(probe).await == Ok(1)
3910        && guest
3911            .memory()
3912            .read_value(pollfd)
3913            .is_ok_and(|polled| polled.revents & libc::POLLRDHUP != 0)
3914}
3915
3916/// Put back what another thread wrote over a call's memory as it began
3917/// (`EntryImport::put_back`), after an attempt that `completed` or not. The
3918/// bytes were written once already, and no guest thread has run since, so a
3919/// failure is Detcore's own and ends the call rather than leave the guest's
3920/// memory as the call began.
3921fn put_back_entry_import<T, G>(
3922    guest: &mut G,
3923    entry_import: &Option<Box<EntryImport>>,
3924    displaced: Option<Displaced>,
3925    completed: bool,
3926) -> Result<(), Error>
3927where
3928    T: RecordOrReplay,
3929    G: Guest<Detcore<T>>,
3930{
3931    let (Some(entry), Some(displaced)) = (entry_import, displaced) else {
3932        return Ok(());
3933    };
3934    if displaced.is_empty() {
3935        return Ok(());
3936    }
3937    entry
3938        .put_back(&mut guest.memory(), displaced, completed)
3939        .map_err(|errno| {
3940            tracing::error!(
3941                "Detcore could not put back the guest's rewrite of a vectored call's memory: {errno}"
3942            );
3943            Error::from(errno)
3944        })
3945}
3946
3947/// Keep a call's memory as it began (`EntryImport`) for the observers that
3948/// read the call's arguments after it returns (`ThreadState::entry_import`),
3949/// when another thread has rewritten it since; otherwise those arguments are
3950/// still the ones the call moved its bytes through.
3951fn keep_entry_view<T, G>(guest: &mut G, entry_import: Option<Box<EntryImport>>)
3952where
3953    T: RecordOrReplay,
3954    G: Guest<Detcore<T>>,
3955{
3956    let view =
3957        entry_import.filter(|entry| entry.has_observation() || entry.differs(&guest.memory()));
3958    guest.thread_state_mut().entry_import = view;
3959}
3960
3961/// Retry a blocking wait on a backend that decides signal interruption from the
3962/// kernel's signal state (`supports_blocked_wait_signal_interruption`).
3963///
3964/// A wait ends early only for a signal that would end it natively: one the guest
3965/// does not block and that is caught, or whose default action terminates or stops
3966/// the process (https://github.com/rrnewton/hermit/issues/3146). Blocked,
3967/// ignored, and default-ignored signals do not end it, and neither does a default
3968/// job-control stop when Linux's restart of the call keeps its deadline
3969/// (`NonblockableSyscall::restart_keeps_deadline`). Such a signal that stops one
3970/// of the wait's injections is absorbed, and the wait runs on. A stop that
3971/// `KernelSignalWait::inject_absorbing` cannot absorb ends the wait with a
3972/// transparent restart when the backend holds nothing whose loss matters, and
3973/// otherwise with a [`HeldSignalRefusal`], which `refuse_held_signal_loss`
3974/// turns into the unsupported-operation refusal once the guest's mask is back.
3975///
3976/// The first probe runs under the guest's own mask, and before the first check
3977/// for a pending signal (`KernelSignalWait::inject_first_probe`): Linux reports a
3978/// descriptor that is ready when the call begins, a queued event, and an
3979/// argument error before it looks at pending signals, so the probe's result
3980/// stands whenever the signal arrived. If it would block, every blockable signal
3981/// is blocked for the rest of the wait, so later probes cannot be stopped by one
3982/// and every signal that arrives stays pending in the kernel, where `/proc`
3983/// reports it. Each turn classifies the pending set against the guest's mask and
3984/// dispositions, and an interrupting signal ends the wait with the call's restart
3985/// errno (see `KernelSignalWait::interrupted_with_state`). The guest's mask is
3986/// restored before returning, so the signal is delivered as the call returns.
3987/// The signals a scheduler resume names (`ResumeStatus::Signaled`): signals the
3988/// scheduler committed to the thread at that point.
3989fn committed_signals(resumed: &ResumeStatus) -> KernelSigset {
3990    match resumed {
3991        ResumeStatus::Signaled(Some(signals)) => signals.iter().fold(0, |set, signal| {
3992            set | kernel_sigset_bit(signal.0 as libc::c_int)
3993        }),
3994        _ => 0,
3995    }
3996}
3997
3998/// What a restartable internal IO poll or a parked pipe write finds at a turn,
3999/// read from the thread's own state before the turn's first injection, while it
4000/// is still stopped at its request: every signal sent since is still in the
4001/// kernel's pending set, and none is yet in the backend's held slot. A copy
4002/// the turn's own injections later absorb is counted here
4003/// (<https://github.com/rrnewton/hermit/issues/4273>, R2b-1 rules 2-4).
4004#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
4005struct TurnSignals {
4006    /// The signals the turn's answer names (`committed_signals`) that the
4007    /// call's mask leaves unblocked: a kept send's wake, which Linux's
4008    /// `TIF_SIGPENDING` keeps in force until the call returns, whatever a later
4009    /// flush does. For `writev`, which the drain never filters, a real-time
4010    /// signal, outside the record of copies discarded at send, counts only if
4011    /// its disposition delivers it now (`NamedRealTime::ByDisposition`); for
4012    /// the generic poll and the scalar writer the drain's own disposition test
4013    /// has already decided it, at the wake (R2b-1 rules 1 and 2).
4014    named: KernelSigset,
4015    /// The kept copies pending now that would end the call: unblocked under the
4016    /// call's mask, caught or acting by default, not held (`SIGCHLD` and the
4017    /// host-timed signals, except one the scheduler committed:
4018    /// `futex_wait_held_after_wake`), and not discarded at send
4019    /// (`threads::without_discarded`).
4020    pending: KernelSigset,
4021}
4022
4023impl TurnSignals {
4024    /// Whether a call that would wait now ends. After progress (`progress`),
4025    /// Linux returns the bytes transferred on any wake, so a named wake or a
4026    /// pending copy ends the call. With none, Linux returns `-ERESTARTSYS`,
4027    /// which delivers a pending copy or restarts invisibly, so only a pending
4028    /// copy ends it; otherwise the call keeps polling, which the guest cannot
4029    /// tell from that restart (R2b-1 rule 3).
4030    fn ends(&self, progress: bool) -> bool {
4031        self.pending != 0 || (progress && self.named != 0)
4032    }
4033}
4034
4035/// [`TurnSignals`] for a call whose mask is `mask` (the kernel's, by default:
4036/// the generic poll and the scalar writer install none) and to which the
4037/// scheduler committed `committed` at this wake (`committed_signals`; none
4038/// before a turnless first attempt). A thread that is gone ends the call with
4039/// `ERESTARTNOINTR`, which nothing observes, and a live one whose state cannot
4040/// be read ends the run (`read_wait_signal_state`).
4041async fn read_turn_signals<T, G>(
4042    guest: &mut G,
4043    mask: Option<KernelSigset>,
4044    committed: KernelSigset,
4045    real_time: NamedRealTime,
4046) -> Result<TurnSignals, Error>
4047where
4048    T: RecordOrReplay,
4049    G: Guest<Detcore<T>>,
4050{
4051    Ok(
4052        read_turn_signals_and_state(guest, mask, committed, real_time)
4053            .await?
4054            .0,
4055    )
4056}
4057
4058/// `read_turn_signals`, also returning the thread's signal state that the
4059/// turn read.
4060async fn read_turn_signals_and_state<T, G>(
4061    guest: &mut G,
4062    mask: Option<KernelSigset>,
4063    committed: KernelSigset,
4064    real_time: NamedRealTime,
4065) -> Result<(TurnSignals, KernelSignalState), Error>
4066where
4067    T: RecordOrReplay,
4068    G: Guest<Detcore<T>>,
4069{
4070    let state = read_wait_signal_state(guest.pid(), guest.tid())?;
4071    let mask = mask.unwrap_or(state.blocked);
4072    let host_timed = host_timed_signals(guest).await;
4073    Ok((
4074        turn_signals_from_state(guest, &state, mask, host_timed, committed, real_time, 0).await,
4075        state,
4076    ))
4077}
4078
4079/// [`TurnSignals`] from the thread's state `state` read in this turn, with the
4080/// record of copies Linux discarded at send applied
4081/// (`threads::without_discarded`); `private` as for
4082/// `turn_signals_before_discard`.
4083async fn turn_signals_from_state<T, G>(
4084    guest: &mut G,
4085    state: &KernelSignalState,
4086    mask: KernelSigset,
4087    host_timed: KernelSigset,
4088    committed: KernelSigset,
4089    real_time: NamedRealTime,
4090    private: KernelSigset,
4091) -> TurnSignals
4092where
4093    T: RecordOrReplay,
4094    G: Guest<Detcore<T>>,
4095{
4096    let before =
4097        turn_signals_before_discard(state, mask, host_timed, committed, real_time, private);
4098    let pending =
4099        crate::syscalls::threads::without_discarded(guest, state, before.pending, committed).await;
4100    TurnSignals { pending, ..before }
4101}
4102
4103/// Which private mask a turn may install. The scalar writer retains its
4104/// existing pending-only policy after its first guest attempt. A generic
4105/// read probe needs protection even when its preceding snapshot was empty.
4106#[derive(Clone, Copy)]
4107enum ProbeMaskPolicy {
4108    Guest,
4109    PendingSignals,
4110    PrivateProbe,
4111}
4112
4113/// A restartable poll's turn on a pipe or a socket that decides at its turn:
4114/// read the thread's signal state before the turn's first injection, block
4115/// every blockable signal in place according to `mask_policy`
4116/// (`KernelSignalWait::block_in_place`, stage 2 of
4117/// https://github.com/rrnewton/hermit/issues/4273), then decide the turn's
4118/// signals under the guest's own mask, which the call owns once it blocks.
4119/// `PrivateProbe` also protects an empty snapshot: a signal arriving after it
4120/// stays in the kernel's queue for the next turn, rather than being held by
4121/// a private injection and hidden from subsequent pending-state reads.
4122///
4123/// A `SIGCHLD` a guest sent the thread, which the schedule recorded in its
4124/// private queue, ends the call where `private_decides`
4125/// (`Resources::private_sigchld_decides`), as on Linux (stage 3): it is
4126/// admitted only while the block holds (`saved_mask`), so no injection of
4127/// this turn can take it into the backend's single held slot, and only if
4128/// the read found it in the thread's own queue, from which only this stopped
4129/// thread can take it. Nothing runs between the read and the block. The
4130/// host-timed signals and the record come in one round trip, before any
4131/// injection (`host_timed_and_private_sigchld`).
4132async fn turn_signals_with_block<T, G>(
4133    guest: &mut G,
4134    wait: &mut Option<KernelSignalWait>,
4135    mask_policy: ProbeMaskPolicy,
4136    committed: KernelSigset,
4137    private_decides: bool,
4138    published: &mut PublishedWaitMask,
4139) -> Result<TurnSignals, Error>
4140where
4141    T: RecordOrReplay,
4142    G: Guest<Detcore<T>>,
4143{
4144    let mask = wait.as_ref().and_then(KernelSignalWait::effective_mask);
4145    let state = read_wait_signal_state(guest.pid(), guest.tid())?;
4146    let (host_timed, recorded) = crate::tool_global::host_timed_and_private_sigchld(guest).await;
4147    let mut private = 0;
4148    if let Some(wait) = wait.as_mut() {
4149        // Block before the publication and the probe, so neither can be
4150        // stopped by a blockable signal, and every pending one stays in the
4151        // kernel's queue until the guest resumes.
4152        let needs_protection = match mask_policy {
4153            ProbeMaskPolicy::Guest => false,
4154            ProbeMaskPolicy::PendingSignals => {
4155                state.pending & !state.blocked & blocked_signal_mask() != 0
4156            }
4157            ProbeMaskPolicy::PrivateProbe => true,
4158        };
4159        if needs_protection && wait.needs_block() && wait.block_in_place(guest, host_timed).await? {
4160            publish_wait_mask(guest, wait.effective_mask(), published).await;
4161        }
4162        if private_decides
4163            && wait.saved_mask.is_some()
4164            && state.thread_pending & kernel_sigset_bit(libc::SIGCHLD) != 0
4165        {
4166            private = recorded;
4167        }
4168    }
4169    let mask = mask.unwrap_or(state.blocked);
4170    Ok(turn_signals_from_state(
4171        guest,
4172        &state,
4173        mask,
4174        host_timed,
4175        committed,
4176        NamedRealTime::Woken,
4177        private,
4178    )
4179    .await)
4180}
4181
4182/// [`TurnSignals`] from the thread's state (`state`), the call's mask (`mask`),
4183/// the host-timed signals (`host_timed`) and the scheduler's commitment
4184/// (`committed`), before the record of copies Linux discarded at send is
4185/// applied to `pending` (`threads::without_discarded`). The hold leaves out
4186/// `SIGCHLD` and every host-timed signal, except a signal the scheduler
4187/// committed that no host-timed source can post (`futex_wait_held_after_wake`),
4188/// and a recorded private `SIGCHLD` the turn admits (`private`).
4189fn turn_signals_before_discard(
4190    state: &KernelSignalState,
4191    mask: KernelSigset,
4192    host_timed: KernelSigset,
4193    committed: KernelSigset,
4194    real_time: NamedRealTime,
4195    private: KernelSigset,
4196) -> TurnSignals {
4197    use crate::scheduler::discarded_at_send::STANDARD_SIGNALS;
4198    // A `SIGCHLD` a guest sent this thread, which the schedule recorded in its
4199    // private queue, is not held where the turn admits it (`private`,
4200    // `turn_signals_with_block`).
4201    let held =
4202        crate::syscalls::threads::futex_wait_held_after_wake(host_timed, committed) & !private;
4203    let pending = state.pending_interrupting(mask, false) & !held;
4204    let counted = match real_time {
4205        NamedRealTime::Woken => !0,
4206        NamedRealTime::ByDisposition => {
4207            STANDARD_SIGNALS | (state.interrupting(mask) & !STANDARD_SIGNALS)
4208        }
4209    };
4210    let named = committed & !mask & counted;
4211    TurnSignals { named, pending }
4212}
4213
4214/// How a turn counts a real-time signal its answer names
4215/// ([`TurnSignals::named`]).
4216#[derive(Clone, Copy, Debug, PartialEq, Eq)]
4217enum NamedRealTime {
4218    /// As any named signal: the drain woke the call only for one whose
4219    /// disposition delivered it then, and Linux's wake stands after a later
4220    /// flush (the generic poll and the scalar writer).
4221    Woken,
4222    /// Only if its disposition delivers it now: the drain never filters
4223    /// `writev` (R2b-1 rule 2).
4224    ByDisposition,
4225}
4226
4227/// Whether a poll or a parked pipe write decides interruption at its own turn
4228/// (`signals_interrupting_poll`): where the record of copies Linux discarded
4229/// at send is kept (`discarded_at_send::records`, ptrace with sequentialized
4230/// threads) and the thread's state can be read (`task`, its
4231/// `Resources::signal_state_task`, which needs guest thread IDs that name host
4232/// tasks). Elsewhere the earlier decision stands: on KVM the backend restarts
4233/// the call unless it would deliver a signal, and its polled-read observation
4234/// (`Guest::polled_read_signal_site`) takes a caught signal before the turn,
4235/// where a `/proc` read could not see it (the Claude re-check of the R2b-1
4236/// design, R4-4).
4237fn decides_interruption_at_turn<T, G>(guest: &G, task: Option<(i32, i32)>) -> bool
4238where
4239    T: RecordOrReplay,
4240    G: Guest<Detcore<T>>,
4241{
4242    task.is_some() && crate::scheduler::discarded_at_send::records(guest.config())
4243}
4244
4245/// Install the private mask of a pipe `writev` that decides at its turn
4246/// (`decides_interruption_at_turn`) and goes on polling after its first
4247/// attempt, through `wait` (`KernelSignalWait::block`, scratch `cell`), and
4248/// publish the guest's own mask to the scheduler while it is installed
4249/// (`set_saved_guest_sigmask`). Returns the guest's own mask. A signal that
4250/// stops the installation is held as `inject_absorbing` holds one, a
4251/// host-timed one and a copy Linux discarded at send included
4252/// (`hold_until_return`), and one that would replace a
4253/// held signal whose loss matters ends the call: with the restart errno, as
4254/// `Error::Errno`, when the held one would end it natively, and otherwise with
4255/// a [`HeldSignalRefusal`], which the caller turns into the refusal once the
4256/// guest's mask is back (`refuse_held_signal_loss`; R2b-1 rule 5). A mask that
4257/// cannot be installed ends the call with `EFAULT`, as a failed
4258/// scratch-stack commit does.
4259async fn install_turn_mask<T, G>(
4260    guest: &mut G,
4261    wait: &mut KernelSignalWait,
4262    cell: AddrMut<'_, KernelSigset>,
4263) -> Result<KernelSigset, Error>
4264where
4265    T: RecordOrReplay,
4266    G: Guest<Detcore<T>>,
4267{
4268    // A copy Linux discarded at send that the kernel still holds (a traced
4269    // task's copy is queued whatever its disposition) is left out of the
4270    // turn's `pending` (`without_discarded`), so it must not end the call
4271    // here either: it is held until the call returns, where it is delivered
4272    // late (https://github.com/rrnewton/hermit/issues/3995), as the turn
4273    // decided (the Claude re-check of R2b-1, N1).
4274    let state = read_wait_signal_state(guest.pid(), guest.tid())?;
4275    let kept = crate::syscalls::threads::without_discarded(guest, &state, state.pending, 0).await;
4276    wait.hold_until_return(host_timed_signals(guest).await | (state.pending & !kept));
4277    wait.block(guest, Some(cell)).await?;
4278    let Some(mask) = wait.effective_mask() else {
4279        return Err(Errno::EFAULT.into());
4280    };
4281    crate::tool_global::set_saved_guest_sigmask(guest, Some(mask)).await;
4282    Ok(mask)
4283}
4284
4285/// A5 step 3c.1c (design 16.11, D50): under the in-guest delivery gate, the
4286/// guest mask a wait under the thread's own mask decides by, in the thread's
4287/// own turn: the mask it publishes at its requests
4288/// (`tool_global::publish_request_guest_mask`), or, when it publishes none,
4289/// `fallback`, which is the kernel's `SigBlk` unless the caller knows the
4290/// guest's mask better. No backend records the guest's mask apart from the
4291/// kernel's before step 3c.1e, and step 3c.1b's readers take `SigBlk` then
4292/// too.
4293pub(crate) fn gated_own_mask<T, G>(guest: &G, fallback: KernelSigset) -> KernelSigset
4294where
4295    T: RecordOrReplay,
4296    G: Guest<Detcore<T>>,
4297{
4298    let own = Resources::new(guest.thread_state().dettid);
4299    crate::tool_global::publish_request_guest_mask(guest, &own).unwrap_or(fallback)
4300}
4301
4302/// A5 step 3c.1c (design 16.11, D50): under the in-guest delivery gate, what
4303/// ends this thread's wait now, read in its own turn from its `/proc` status
4304/// and its own published mask ([`gated_own_mask`], with `fallback` for
4305/// `SigBlk` when the caller knows the guest's own mask better). `None` when no
4306/// signal is deliverable, and always with the gate off. A state that cannot be
4307/// read is handled as a wait's first read is (`read_thread_wait_signal_state`):
4308/// a thread that no longer exists ends the call with `ERESTARTNOINTR`, and
4309/// otherwise the run ends with a diagnostic.
4310pub(crate) fn gated_wait_end<T, G>(
4311    guest: &G,
4312    fallback: Option<KernelSigset>,
4313) -> Result<Option<crate::wait_signal_end::WaitSignalEnd>, Error>
4314where
4315    T: RecordOrReplay,
4316    G: Guest<Detcore<T>>,
4317{
4318    gated_wait_end_with_virtual(guest, fallback, 0)
4319}
4320
4321/// [`gated_wait_end`] with `virtual_caught`, caught signals the scheduler
4322/// knows are pending on the process's shared queue with no physical copy
4323/// (signal phase 1's virtual SIGALRM;
4324/// `wait_signal_end::wait_end_with_virtual`).
4325pub(crate) fn gated_wait_end_with_virtual<T, G>(
4326    guest: &G,
4327    fallback: Option<KernelSigset>,
4328    virtual_caught: KernelSigset,
4329) -> Result<Option<crate::wait_signal_end::WaitSignalEnd>, Error>
4330where
4331    T: RecordOrReplay,
4332    G: Guest<Detcore<T>>,
4333{
4334    if !guest.config().in_guest_signal_delivery {
4335        return Ok(None);
4336    }
4337    let state = crate::syscalls::threads::read_thread_wait_signal_state(guest.pid(), guest.tid())?;
4338    let mask = gated_own_mask(guest, fallback.unwrap_or(state.blocked));
4339    Ok(crate::wait_signal_end::wait_end_with_virtual(
4340        &state,
4341        mask,
4342        guest.config().in_guest_reserved_signals,
4343        virtual_caught,
4344    ))
4345}
4346
4347/// [`gated_wait_end`], recording a found end as the current call's D40 flag
4348/// (`ThreadState::wait_signal_end`) for the restart checkpoint. Returns
4349/// whether a signal ends the wait.
4350pub(crate) fn gated_wait_ends<T, G>(
4351    guest: &mut G,
4352    fallback: Option<KernelSigset>,
4353) -> Result<bool, Error>
4354where
4355    T: RecordOrReplay,
4356    G: Guest<Detcore<T>>,
4357{
4358    let end = gated_wait_end(guest, fallback)?;
4359    if let Some(end) = end {
4360        tracing::info!(
4361            "[dtid {}] under the in-guest delivery gate, {:?} ends the wait",
4362            guest.thread_state().dettid,
4363            end
4364        );
4365        guest.thread_state_mut().wait_signal_end = Some(end);
4366    }
4367    Ok(end.is_some())
4368}
4369
4370/// A5 step 3c.1c, design D41 (amending 16.26), with IR-5 and IR-14: under the
4371/// in-guest delivery gate, a default-fatal signal already deliverable when a
4372/// kernel-run call begins (`ResourceID::is_kernel_run_blocking`: a background
4373/// external call, `rt_sigsuspend`, `vfork`) is one Linux would have delivered
4374/// before the call, ending the process. So the call is not run: its handler
4375/// calls this as its first statement, before any guest-visible effect, and
4376/// returns the error this gives, `ERESTARTNOINTR` with the D40 flag `Fatal`
4377/// recorded, which the restart checkpoint turns into `-EINTR` (the death path
4378/// of step 3c.1e follows at the return). A `BehindCaught` end (a caught
4379/// signal first, and a later delivery in the same loop that could end the
4380/// process) means the call is not run either: the flag is recorded as it is,
4381/// and the checkpoint resolves it. `Fatal` gives `-EINTR`. `Caught` (the
4382/// handlers' masks hold the default-fatal signal, or no reset instance is
4383/// reached) would need the handler run and then the call restarted, which
4384/// 3c.1 cannot do, so the checkpoint refuses it by name
4385/// (HERMIT_RESTART_AFTER_HANDLER, fail closed); in that schedule Linux runs
4386/// the handler and, unless it changes the default-fatal signal's disposition
4387/// or mask, ends the process at its `rt_sigreturn`, before the call is made
4388/// again. Only a single caught signal at entry, or none, keeps today's path
4389/// (`None`): the call runs. With the gate off, `None`.
4390pub(crate) fn kernel_run_entry_refusal<T, G>(guest: &mut G) -> Result<Option<Error>, Error>
4391where
4392    T: RecordOrReplay,
4393    G: Guest<Detcore<T>>,
4394{
4395    match gated_wait_end(guest, None)? {
4396        Some(
4397            end @ (crate::wait_signal_end::WaitSignalEnd::Fatal(_)
4398            | crate::wait_signal_end::WaitSignalEnd::BehindCaught(_)),
4399        ) => {
4400            tracing::info!(
4401                "[dtid {}] under the in-guest delivery gate, {:?} is deliverable at a kernel-run \
4402                 call's entry: the call is not run",
4403                guest.thread_state().dettid,
4404                end
4405            );
4406            guest.thread_state_mut().wait_signal_end = Some(end);
4407            Ok(Some(Errno::ERESTARTNOINTR.into()))
4408        }
4409        _ => Ok(None),
4410    }
4411}
4412
4413/// The most instances [`remove_ignored_signals`] dequeues in one call before
4414/// it gives up: far more than a thread's queues can hold, which
4415/// `RLIMIT_SIGPENDING` bounds for real-time signals and one per queue bounds
4416/// for standard ones; reached, the removal fails closed.
4417const MAX_IGNORED_REMOVALS: usize = 1 << 16;
4418
4419/// A5 step 3c.1d, design D37: remove the pending instances of `set` (ignored
4420/// signals Linux would not hold, `ignored_signals::removable`) from the
4421/// calling thread's queues, in its own turn, by repeated trusted zero-timeout
4422/// `rt_sigtimedwait` calls until `EAGAIN`. Each call dequeues one instance,
4423/// the thread's private queue first and then the shared one
4424/// (`dequeue_signal`, kernel/signal.c:626-639 at v6.12), and with a zero
4425/// timeout it never sleeps: no instance left gives `-EAGAIN`
4426/// (`do_sigtimedwait`, :3629-3676). SIGKILL and SIGSTOP are never in `set`
4427/// (they are never ignored), and the kernel drops them from it anyway
4428/// (:3648). Nothing is injected for an empty set. An error other than
4429/// `EAGAIN`, more instances than any queue can hold, or no scratch stack for
4430/// the call's two cells is Hermit's own failure (`Error::Tool`), never an
4431/// errno the guest's call could see.
4432pub(crate) async fn remove_ignored_signals<T, G>(guest: &mut G, set: u64) -> Result<(), Error>
4433where
4434    T: RecordOrReplay,
4435    G: Guest<Detcore<T>>,
4436{
4437    remove_pending_ignored(guest, set, IgnoredRemoval::NotHeldByLinux).await
4438}
4439
4440/// [`remove_ignored_signals`] for the ignored signals a masked wait's
4441/// temporary mask lets in, pending under the thread's own mask, which Linux
4442/// discards as the call returns (`get_signal`, kernel/signal.c), on a
4443/// backend that runs the call itself and cannot discard them inside it
4444/// (`Detcore::settle_masked_ppoll_enosys`). The same removal, with its own
4445/// log line.
4446pub(crate) async fn remove_signals_discarded_under_a_temporary_mask<T, G>(
4447    guest: &mut G,
4448    set: u64,
4449) -> Result<(), Error>
4450where
4451    T: RecordOrReplay,
4452    G: Guest<Detcore<T>>,
4453{
4454    remove_pending_ignored(guest, set, IgnoredRemoval::LetInByTemporaryMask).await
4455}
4456
4457/// [`remove_signals_discarded_under_a_temporary_mask`] in two cells a caller
4458/// that holds the scratch stack reserved itself: `set_cell`, into which `set`
4459/// is written, and `timeout`, which is zeroed. The caller writes back what
4460/// they held.
4461pub(crate) async fn remove_signals_discarded_under_a_temporary_mask_in<T, G>(
4462    guest: &mut G,
4463    set: u64,
4464    set_cell: usize,
4465    timeout: AddrMut<'_, Timespec>,
4466) -> Result<(), Error>
4467where
4468    T: RecordOrReplay,
4469    G: Guest<Detcore<T>>,
4470{
4471    if set == 0 {
4472        return Ok(());
4473    }
4474    let cell = AddrMut::<u64>::from_raw(set_cell).ok_or_else(|| {
4475        Error::Tool(anyhow::anyhow!(
4476            "removing ignored signals {set:#x}: no cell for the set"
4477        ))
4478    })?;
4479    guest.memory().write_value(cell, &set)?;
4480    guest.memory().write_value(
4481        timeout,
4482        &Timespec {
4483            tv_sec: 0,
4484            tv_nsec: 0,
4485        },
4486    )?;
4487    remove_pending_ignored_in(
4488        guest,
4489        set,
4490        cell.cast::<libc::sigset_t>(),
4491        timeout.into(),
4492        IgnoredRemoval::LetInByTemporaryMask,
4493    )
4494    .await
4495}
4496
4497/// Why [`remove_pending_ignored`] removes ignored signals, which its log line
4498/// names.
4499#[derive(Clone, Copy)]
4500enum IgnoredRemoval {
4501    /// D37: ones Linux would not hold, under the in-guest delivery gate.
4502    NotHeldByLinux,
4503    /// Ones a temporary mask lets in, which Linux discards as the call returns.
4504    LetInByTemporaryMask,
4505}
4506
4507/// The removal of [`remove_ignored_signals`], logged for `why`, in two cells
4508/// of its own.
4509async fn remove_pending_ignored<T, G>(
4510    guest: &mut G,
4511    set: u64,
4512    why: IgnoredRemoval,
4513) -> Result<(), Error>
4514where
4515    T: RecordOrReplay,
4516    G: Guest<Detcore<T>>,
4517{
4518    if set == 0 {
4519        return Ok(());
4520    }
4521    let mut stack = guest.stack().await;
4522    let set_cell = stack.push(set);
4523    let timeout = stack.push(Timespec {
4524        tv_sec: 0,
4525        tv_nsec: 0,
4526    });
4527    // A stack with no room for the two cells is Hermit's own failure too, never
4528    // an EFAULT of the guest's call (round-3 ruling 4).
4529    let _stack_guard = stack.commit().map_err(|errno| {
4530        Error::Tool(anyhow::anyhow!(
4531            "removing ignored signals {set:#x}: no scratch stack for the trusted \
4532             rt_sigtimedwait ({errno})"
4533        ))
4534    })?;
4535    // SAFETY: the kernel only reads the set; Reverie types the argument as
4536    // mutable.
4537    let set_cell = unsafe { set_cell.cast::<libc::sigset_t>().into_mut() };
4538    remove_pending_ignored_in(guest, set, set_cell, timeout, why).await
4539}
4540
4541/// The removal of [`remove_ignored_signals`]: trusted zero-timeout
4542/// `rt_sigtimedwait` calls for `set`, held in `set_cell`, with the zero
4543/// timeout in `timeout`, until `EAGAIN`.
4544async fn remove_pending_ignored_in<T, G>(
4545    guest: &mut G,
4546    set: u64,
4547    set_cell: AddrMut<'_, libc::sigset_t>,
4548    timeout: Addr<'_, Timespec>,
4549    why: IgnoredRemoval,
4550) -> Result<(), Error>
4551where
4552    T: RecordOrReplay,
4553    G: Guest<Detcore<T>>,
4554{
4555    for _ in 0..MAX_IGNORED_REMOVALS {
4556        let taken = guest
4557            .inject(
4558                syscalls::RtSigtimedwait::new()
4559                    .with_set(Some(set_cell))
4560                    .with_info(None)
4561                    .with_timeout(Some(timeout))
4562                    .with_sigsetsize(KERNEL_SIGSET_SIZE),
4563            )
4564            .await;
4565        match (taken, why) {
4566            (Ok(signal), IgnoredRemoval::NotHeldByLinux) => tracing::info!(
4567                "[dtid {}] under the in-guest delivery gate, removed ignored signal {} that Linux \
4568                 would not hold (D37)",
4569                guest.thread_state().dettid,
4570                signal
4571            ),
4572            (Ok(signal), IgnoredRemoval::LetInByTemporaryMask) => tracing::info!(
4573                "[dtid {}] removed ignored signal {} that a temporary mask lets in, which Linux \
4574                 discards as the call returns",
4575                guest.thread_state().dettid,
4576                signal
4577            ),
4578            (Err(Errno::EAGAIN), _) => return Ok(()),
4579            // Hermit's own failure, never the guest's result: the guest's call
4580            // (rt_sigaction, rt_sigsuspend, a masked wait) cannot return it.
4581            (Err(errno), _) => {
4582                return Err(Error::Tool(anyhow::anyhow!(
4583                    "removing ignored signals {set:#x}: the trusted rt_sigtimedwait failed with \
4584                     {errno}"
4585                )));
4586            }
4587        }
4588    }
4589    Err(Error::Tool(anyhow::anyhow!(
4590        "removing ignored signals {set:#x} dequeued {MAX_IGNORED_REMOVALS} instances without \
4591         emptying the queues"
4592    )))
4593}
4594
4595/// Which of a thread's two queues [`remove_ignored_from_queue`] empties of a
4596/// signal.
4597#[derive(Clone, Copy, Debug, PartialEq, Eq)]
4598pub(crate) enum OneQueue {
4599    /// The thread's private queue (`SigPnd`).
4600    Private,
4601    /// Its thread group's shared queue (`ShdPnd`).
4602    Shared,
4603}
4604
4605/// A5 step 3c.1e A, D37's R2 per queue: remove every pending instance of the
4606/// ignored signal `signal` from `queue` only, leaving the other queue's
4607/// instances as they are, in the thread's own turn. `rt_sigtimedwait`
4608/// dequeues the private queue first (`dequeue_signal`, kernel/signal.c:
4609/// 626-639 at v6.12), so:
4610///
4611/// - `Private`: one trusted zero-timeout `rt_sigtimedwait` per instance, for
4612///   as long as the thread's `/proc` status shows `signal` in `SigPnd`.
4613/// - `Shared`: the private instances are dequeued first, each with its
4614///   siginfo, then the shared ones until `EAGAIN`, then the private ones are
4615///   queued again, in their order, with their own siginfo, by
4616///   `rt_tgsigqueueinfo` to the thread itself (`do_rt_tgsigqueueinfo` lets a
4617///   thread queue any siginfo to itself, kernel/signal.c:4112-4114; the
4618///   signal is blocked by the kernel's mask then, the guest's own mask, which
4619///   blocks it since Linux kept it, or the in-guest runtime's, so it is
4620///   queued and not discarded, `sig_ignored`, :104-120).
4621///
4622/// Nothing is injected when `signal` is not pending in `queue`. A failed
4623/// injection, more instances than any queue holds, or no scratch stack is
4624/// Hermit's own failure (`Error::Tool`), never an errno of the guest's call.
4625pub(crate) async fn remove_ignored_from_queue<T, G>(
4626    guest: &mut G,
4627    signal: i32,
4628    queue: OneQueue,
4629) -> Result<(), Error>
4630where
4631    T: RecordOrReplay,
4632    G: Guest<Detcore<T>>,
4633{
4634    let bit = crate::syscalls::kernel_sigset_bit(signal);
4635    let failed = |what: &str| {
4636        Error::Tool(anyhow::anyhow!(
4637            "removing ignored signal {signal} from the {queue:?} queue: {what}"
4638        ))
4639    };
4640    let state = read_thread_wait_signal_state(guest.pid(), guest.tid())?;
4641    let in_queue = match queue {
4642        OneQueue::Private => state.thread_pending,
4643        OneQueue::Shared => state.shared_pending,
4644    };
4645    if in_queue & bit == 0 {
4646        return Ok(());
4647    }
4648    let mut stack = guest.stack().await;
4649    let set_cell = stack.push(bit);
4650    let timeout = stack.push(Timespec {
4651        tv_sec: 0,
4652        tv_nsec: 0,
4653    });
4654    // A siginfo cell, kept as bytes: `siginfo_t` holds raw pointers, so an
4655    // address typed by it could not live across an await.
4656    let info = stack.reserve::<[u8; 128]>();
4657    let _stack_guard = stack
4658        .commit()
4659        .map_err(|errno| failed(&format!("no scratch stack ({errno})")))?;
4660    // SAFETY: the kernel only reads the set; Reverie types it as mutable.
4661    let set_cell = unsafe { set_cell.cast::<libc::sigset_t>().into_mut() };
4662    let dequeue = move |with_info: bool| {
4663        syscalls::RtSigtimedwait::new()
4664            .with_set(Some(set_cell))
4665            .with_info(with_info.then(|| info.cast()))
4666            .with_timeout(Some(timeout))
4667            .with_sigsetsize(KERNEL_SIGSET_SIZE)
4668    };
4669    // The private instances, dequeued one at a time while `SigPnd` shows
4670    // them; their siginfo is kept to queue them again for `Shared`.
4671    let mut kept: Vec<[u8; 128]> = Vec::new();
4672    let mut removals = 0;
4673    while read_thread_wait_signal_state(guest.pid(), guest.tid())?.thread_pending & bit != 0 {
4674        removals += 1;
4675        if removals > MAX_IGNORED_REMOVALS {
4676            return Err(failed("too many instances"));
4677        }
4678        match guest.inject(dequeue(queue == OneQueue::Shared)).await {
4679            Ok(_) => {
4680                if queue == OneQueue::Shared {
4681                    kept.push(guest.memory().read_value(info)?);
4682                }
4683            }
4684            Err(errno) => return Err(failed(&format!("rt_sigtimedwait failed with {errno}"))),
4685        }
4686    }
4687    if queue == OneQueue::Private {
4688        tracing::info!(
4689            "[dtid {}] under the in-guest delivery gate, removed ignored signal {} from the \
4690             private queue, which Linux discarded at send (D37)",
4691            guest.thread_state().dettid,
4692            signal
4693        );
4694        return Ok(());
4695    }
4696    loop {
4697        removals += 1;
4698        if removals > MAX_IGNORED_REMOVALS {
4699            return Err(failed("too many instances"));
4700        }
4701        match guest.inject(dequeue(false)).await {
4702            Ok(_) => {}
4703            Err(Errno::EAGAIN) => break,
4704            Err(errno) => return Err(failed(&format!("rt_sigtimedwait failed with {errno}"))),
4705        }
4706    }
4707    for siginfo in kept {
4708        guest.memory().write_value(info, &siginfo)?;
4709        let requeue = syscalls::RtTgsigqueueinfo::new()
4710            .with_tgid(guest.pid().as_raw())
4711            .with_tid(guest.tid().as_raw())
4712            .with_sig(signal)
4713            .with_siginfo(Some(info.cast()));
4714        if let Err(errno) = guest.inject(requeue).await {
4715            return Err(failed(&format!(
4716                "queueing a kept private instance again failed with {errno}"
4717            )));
4718        }
4719    }
4720    tracing::info!(
4721        "[dtid {}] under the in-guest delivery gate, removed ignored signal {} from the shared \
4722         queue, which Linux discarded at send, keeping its private instances (D37)",
4723        guest.thread_state().dettid,
4724        signal
4725    );
4726    Ok(())
4727}
4728
4729/// What a temporary-mask wait's check finds under the in-guest delivery gate
4730/// ([`temporary_mask_check`]).
4731#[derive(Clone, Copy, Debug, PartialEq, Eq)]
4732pub(crate) enum TemporaryMaskCheck {
4733    /// Nothing ends the wait; the ignored signals Linux would not hold were
4734    /// removed (D37 rule (ii)).
4735    Waits,
4736    /// A default-fatal signal the temporary mask lets in ends it: the D40
4737    /// flag `Fatal` is recorded, and the call returns its restart code
4738    /// (`-ERESTARTNOHAND`; `-EINTR` for the `epoll` forms), which the restart
4739    /// checkpoint turns into `-EINTR` for the death path of step 3c.1e.
4740    Fatal,
4741    /// A masked `epoll_pwait` or `epoll_pwait2` at its park: an ignored signal
4742    /// pending at entry that only the temporary mask lets in, removed, and
4743    /// the call returns `EINTR` (D37's exception, IR-3).
4744    EpollInterrupted,
4745    /// A caught signal would end the wait (or be delivered first): its
4746    /// handler would run under the temporary mask, which step 3c.w owns and
4747    /// which 3c.1 never reaches (phase 1 refuses every temporary-mask call in
4748    /// a process that handles SIGALRM, F8). The stopping refusal
4749    /// HERMIT_TEMPORARY_MASK_CAUGHT_END was filed (D49); were the run to go
4750    /// on, the call returns `EINTR`.
4751    Refused,
4752}
4753
4754/// A5 step 3c.1d (design D37, D39, D50, IR-7): the check of a wait under a
4755/// temporary mask `m_temp` (`rt_sigsuspend`, a masked `pselect6`, `ppoll`,
4756/// `epoll_pwait` or `epoll_pwait2`), at the point where Linux tests for a
4757/// pending signal: the call would wait (nothing ready), in the thread's own
4758/// turn, from its `/proc` status (one read).
4759///
4760/// 1. The design's deliverable set under `m_temp` (`wait_signal_end`): a
4761///    default-fatal signal ends the wait (`Fatal`, the D40 flag recorded). A
4762///    caught one would run its handler under the temporary mask (step 3c.w,
4763///    unreachable in 3c.1): a named stopping refusal (`Refused`).
4764/// 2. D37 rule (ii) with IR-7's per-instance masks: the ignored signals `I`
4765///    pending now, less `I ∩ M_req ∩ M_next`, are removed, where `M_next` is
4766///    `m_temp` and `M_req` is the mask in force when the instance was
4767///    queued. `entry` is `Some(!0)` at the wait's first check, which every
4768///    form makes in the call's own turn, before any scheduler request, so
4769///    every instance pending then was pending at entry and was queued under
4770///    the guest's own mask ([`gated_own_mask`]); `None` at any later check,
4771///    where every instance kept from entry is in both masks, so `M_req` =
4772///    `m_temp` for all of them. (A narrower `entry` set limits the at-entry
4773///    rule to those bits.)
4774/// 3. `epoll` (a masked `epoll_pwait` or `epoll_pwait2`) at the first check:
4775///    an instance pending at entry in `I ∩ M_own ∖ M_temp` makes the call
4776///    return `EINTR` (`EpollInterrupted`), as `ep_poll` does
4777///    (`ignored_signals::epoll_interrupting`).
4778///
4779/// With the gate off it is never called.
4780pub(crate) async fn temporary_mask_check<T, G>(
4781    guest: &mut G,
4782    m_temp: KernelSigset,
4783    entry: Option<KernelSigset>,
4784    epoll: bool,
4785) -> Result<TemporaryMaskCheck, Error>
4786where
4787    T: RecordOrReplay,
4788    G: Guest<Detcore<T>>,
4789{
4790    use crate::wait_signal_end::WaitSignalEnd;
4791    let state = read_thread_wait_signal_state(guest.pid(), guest.tid())?;
4792    let reserved = guest.config().in_guest_reserved_signals;
4793    match crate::wait_signal_end::wait_end(&state, m_temp, reserved) {
4794        None => {}
4795        Some(end @ WaitSignalEnd::Fatal(_)) => {
4796            tracing::info!(
4797                "[dtid {}] under the in-guest delivery gate, {:?} ends the wait under its \
4798                 temporary mask {:#x}",
4799                guest.thread_state().dettid,
4800                end,
4801                m_temp
4802            );
4803            guest.thread_state_mut().wait_signal_end = Some(end);
4804            return Ok(TemporaryMaskCheck::Fatal);
4805        }
4806        Some(end) => {
4807            let signal = match end {
4808                WaitSignalEnd::Caught(signal) => signal,
4809                _ => {
4810                    (state.pending & !m_temp & state.caught & !reserved).trailing_zeros() as i32 + 1
4811                }
4812            };
4813            let reason = crate::refused_signal_controls::temporary_mask_caught_end_refusal(
4814                guest.thread_state().dettid.as_raw(),
4815                signal,
4816            );
4817            tracing::error!("{}", reason);
4818            crate::tool_global::stopping_refusal(guest, reason).await;
4819            return Ok(TemporaryMaskCheck::Refused);
4820        }
4821    }
4822    let own = gated_own_mask(guest, state.blocked);
4823    let now = crate::ignored_signals::pending_ignored(&state, reserved);
4824    let (removed, interrupted) = match entry {
4825        Some(entry) => {
4826            let at_entry = KernelSignalState {
4827                pending: state.pending & entry,
4828                ..state
4829            };
4830            let since = KernelSignalState {
4831                pending: state.pending & !entry,
4832                ..state
4833            };
4834            let interrupted = epoll
4835                && crate::ignored_signals::epoll_interrupting(&at_entry, own, m_temp, reserved)
4836                    != 0;
4837            (
4838                crate::ignored_signals::removable(&at_entry, own, m_temp, reserved)
4839                    | crate::ignored_signals::removable(&since, m_temp, m_temp, reserved),
4840                interrupted,
4841            )
4842        }
4843        None => (now & !m_temp, false),
4844    };
4845    remove_ignored_signals(guest, removed).await?;
4846    Ok(if interrupted {
4847        TemporaryMaskCheck::EpollInterrupted
4848    } else {
4849        TemporaryMaskCheck::Waits
4850    })
4851}
4852
4853/// The `sa_flags` of `signal`'s current action, as the guest set it, read in
4854/// this thread's turn with a query `rt_sigaction` (the in-guest runtime
4855/// answers it from the guest's action, phase 1's virtual SIGALRM included):
4856/// for the restart checkpoint's caught row (A5 design D40).
4857pub(crate) async fn caught_action_flags<T, G>(guest: &mut G, signal: i32) -> Result<u64, Error>
4858where
4859    T: RecordOrReplay,
4860    G: Guest<Detcore<T>>,
4861{
4862    Ok(caught_action(guest, signal).await?.flags)
4863}
4864
4865/// The `sa_mask` and `sa_flags` of `signal`'s current action, as the guest set
4866/// it, read in this thread's turn with a query `rt_sigaction` (the in-guest
4867/// runtime answers it from the guest's action, phase 1's virtual SIGALRM
4868/// included): for the restart checkpoint's resolution of a
4869/// `WaitSignalEnd::BehindCaught` (A5 design D40) and its caught row. Called
4870/// only where the thread holds no committed scratch stack.
4871pub(crate) async fn caught_action<T, G>(
4872    guest: &mut G,
4873    signal: i32,
4874) -> Result<crate::wait_signal_end::CaughtAction, Error>
4875where
4876    T: RecordOrReplay,
4877    G: Guest<Detcore<T>>,
4878{
4879    let mut stack = guest.stack().await;
4880    let action = stack.reserve::<KernelSigaction>();
4881    let _stack_guard = stack.commit()?;
4882    guest
4883        .inject_with_retry(
4884            syscalls::RtSigaction::new()
4885                .with_signum(signal)
4886                .with_action(None)
4887                .with_old_action(Some(action.cast()))
4888                .with_sigsetsize(KERNEL_SIGSET_SIZE),
4889        )
4890        .await?;
4891    let action: KernelSigaction = guest.memory().read_value(action)?;
4892    Ok(crate::wait_signal_end::CaughtAction {
4893        mask: action.mask,
4894        flags: action.flags,
4895    })
4896}
4897
4898/// Whether the scheduler keeps a guest mask that differs from the kernel's
4899/// `SigBlk` (`Scheduler::saved_guest_sigmasks`) and reads it in its place, so
4900/// that a thread must publish it: under signal-target modelling
4901/// (`Config::models_signal_targets`, which is where the discarded-at-send
4902/// record is kept, `discarded_at_send::records`), and under the in-guest
4903/// delivery gate (`Config::in_guest_signal_delivery`; A5 step 3c.1b), where the
4904/// kernel's mask may be the in-guest runtime's. With both off, nothing is
4905/// published and every reader takes `SigBlk`, as before step 3c.1b.
4906pub(crate) fn publishes_guest_masks(cfg: &crate::Config) -> bool {
4907    crate::scheduler::discarded_at_send::records(cfg) || cfg.in_guest_signal_delivery
4908}
4909
4910/// The waiting thread's mask as last told to the scheduler
4911/// (`publish_wait_mask`). It is deliberately not `Copy`: a wait loop in an
4912/// `async move` block must reborrow it, or the block would publish into a
4913/// copy and the clear after the block would see nothing to clear.
4914#[derive(Debug, Default)]
4915pub(crate) struct PublishedWaitMask(Option<KernelSigset>);
4916
4917/// Tell the scheduler the waiting thread's effective mask
4918/// (`KernelSignalWait::effective_mask`, or `None` once the wait is over) if it
4919/// differs from `published`, the last one told. The scheduler reads it in
4920/// place of the thread's `SigBlk` (`set_saved_guest_sigmask`), for example to
4921/// decide whether a leader ignores an `ITIMER_REAL` expiry at generation
4922/// (https://github.com/rrnewton/hermit/issues/4139). Only where the scheduler
4923/// reads it for a wait (`publishes_guest_masks`): under signal-target
4924/// modelling, or under the in-guest delivery gate.
4925pub(crate) async fn publish_wait_mask<T, G>(
4926    guest: &mut G,
4927    mask: Option<KernelSigset>,
4928    published: &mut PublishedWaitMask,
4929) where
4930    T: RecordOrReplay,
4931    G: Guest<Detcore<T>>,
4932{
4933    if mask == published.0 || !publishes_guest_masks(guest.config()) {
4934        return;
4935    }
4936    crate::tool_global::set_saved_guest_sigmask(guest, mask).await;
4937    published.0 = mask;
4938}
4939
4940async fn retry_blocking_wait_with_kernel_signal_state<T, G, C>(
4941    guest: &mut G,
4942    call0: C,
4943    rsrc: Resources,
4944    maybe_timeout: Option<(LogicalTime, Result<i64, Errno>)>,
4945) -> Result<i64, Error>
4946where
4947    C: NonblockableSyscall + Into<Syscall>,
4948    T: RecordOrReplay,
4949    G: Guest<Detcore<T>>,
4950{
4951    retry_blocking_wait_under_mask(guest, call0, rsrc, maybe_timeout, None)
4952        .await
4953        .0
4954}
4955
4956/// `retry_blocking_wait_with_kernel_signal_state` for a call that sleeps under
4957/// `wait_mask` instead of the guest's own mask, when it is set: the temporary
4958/// mask of an `epoll_pwait` (`KernelSignalWait::with_wait_mask`). `call0`
4959/// carries no mask, so its probes run under the guest's own mask until every
4960/// blockable signal is blocked, as for any other wait. Also returns whether the
4961/// signal that ended the wait must still be delivered under `wait_mask`: one
4962/// that only `wait_mask` lets in, pending in the kernel under the guest's own
4963/// mask once it is put back, which the caller delivers with
4964/// `inject_signal_delivery`, as a masked pselect6 does.
4965pub(crate) async fn retry_blocking_wait_under_mask<T, G, C>(
4966    guest: &mut G,
4967    call0: C,
4968    mut rsrc: Resources,
4969    maybe_timeout: Option<(LogicalTime, Result<i64, Errno>)>,
4970    wait_mask: Option<KernelSigset>,
4971) -> (Result<i64, Error>, bool)
4972where
4973    C: NonblockableSyscall + Into<Syscall>,
4974    T: RecordOrReplay,
4975    G: Guest<Detcore<T>>,
4976{
4977    let mut guest_held = GuestHeldPollProof::capture(guest, call0.into(), &mut rsrc);
4978    let mut signals = KernelSignalWait::new(
4979        guest,
4980        call0.signals_consumed_by_wait(&guest.memory()),
4981        call0.restart_keeps_deadline(),
4982        call0.kernel_restart_errno(),
4983    )
4984    .with_deadline(maybe_timeout.is_some())
4985    .with_wait_mask(wait_mask);
4986    // Set when a signal that only `wait_mask` lets in ends the wait.
4987    let mut deliver_under_wait_mask = false;
4988    let mut published = PublishedWaitMask::default();
4989    publish_wait_mask(guest, signals.effective_mask(), &mut published).await;
4990    rsrc.poll_deadline = poll_deadline_for(guest, maybe_timeout.map(|(deadline, _)| deadline));
4991    let (mut call, mut guard) = call0.into_nonblocking(guest).await;
4992    let mut first_turn = true;
4993    // Set when only a signal the scheduler committed in this turn ends the
4994    // wait: the turn's probe runs first, and the wait ends with the restart
4995    // errno only if the call has nothing to report.
4996    let mut interrupt_after_probe;
4997
4998    let result = loop {
4999        interrupt_after_probe = false;
5000        // A scheduler `Signaled` answer only says a signal may be pending. The kernel's
5001        // state below decides whether it ends the wait.
5002        let resumed = resource_request(guest, rsrc.clone()).await;
5003        guest_held.revalidate(guest, &mut rsrc);
5004        // Read in this turn, before any injection: another thread may have
5005        // armed a host-timed source since the last turn, and none can until
5006        // this turn ends. The same round trip gives the `SIGCHLD` a guest sent
5007        // this thread that the schedule recorded in its private queue, which
5008        // an injection of this turn (the timerfd publication below) could
5009        // otherwise take into the backend's single held slot (R2b-2 stage 3,
5010        // https://github.com/rrnewton/hermit/issues/4273).
5011        let (host_timed, recorded) =
5012            crate::tool_global::host_timed_and_private_sigchld(guest).await;
5013        signals.hold_until_return(host_timed);
5014        // A `SIGCHLD` the scheduler committed to this thread in this turn, sent
5015        // at a commit point (a child's child-exit timer, or a POSIX timer whose
5016        // signal is `SIGCHLD`), ends the wait, as on Linux
5017        // (https://github.com/rrnewton/hermit/issues/4005), and so does a
5018        // recorded private one: both are admitted (`KernelSignalWait::admit`).
5019        let committed = committed_signals(&resumed) & kernel_sigset_bit(libc::SIGCHLD);
5020        let private = match signals
5021            .admit_under_block(guest, committed, recorded, host_timed, &mut published)
5022            .await
5023        {
5024            Ok(private) => private,
5025            Err(error) => break Err(error),
5026        };
5027        signals.admit(committed, private);
5028        // As in `retry_nonblocking_syscall_helper`: virtual timerfds first.
5029        // As in `retry_nonblocking_syscall_helper`: release the nonblocking
5030        // form's scratch stack before publishing, and convert again after.
5031        let reconvert =
5032            guard.is_some() && crate::syscalls::timerfd::may_publish(guest, call.into());
5033        if reconvert {
5034            guard = None;
5035        }
5036        rsrc.poll_deadline = crate::syscalls::timerfd::publish_before_probe(
5037            guest,
5038            call.into(),
5039            maybe_timeout.map(|(deadline, _)| deadline),
5040        )
5041        .await;
5042        if reconvert {
5043            (call, guard) = call0.into_nonblocking(guest).await;
5044        }
5045        // A pending real-time timer expiration that this wait would consume or
5046        // be ended by (https://github.com/rrnewton/hermit/issues/4089).
5047        let pending = pending_real_time_among(guest, REAL_TIME_SIGNALS).await;
5048        if pending != 0 {
5049            match signals.observable_now() {
5050                Ok(observable) => act_on_pending_real_time(guest, pending, observable, 0).await,
5051                Err(error) => break Err(error),
5052            }
5053        }
5054        let first = std::mem::take(&mut first_turn);
5055        // Never `inject_with_retry`: see `KernelSignalWait`.
5056        let injected = if first {
5057            // The first probe runs before the check, so a source that was ready when
5058            // the call began, or an argument error, is reported as Linux reports it
5059            // whatever signal is pending (`inject_first_probe`).
5060            signals.inject_first_probe(guest, call).await
5061        } else {
5062            let state = match signals.interrupted_with_state() {
5063                Ok((false, state)) => state,
5064                // Linux takes a completion before it returns EINTR: a queued
5065                // signal of rt_sigtimedwait's set (`do_sigtimedwait` dequeues
5066                // first, and again after it sleeps), a ready descriptor of poll
5067                // or epoll_wait. A signal the scheduler committed in this turn
5068                // was sent at the same fixed point as any completion this turn
5069                // can see, so the completion wins, as on Linux (Codex review of
5070                // https://github.com/rrnewton/hermit/pull/4039).
5071                Ok((true, state)) if signals.interrupts_only_by_committed(&state) => {
5072                    interrupt_after_probe = true;
5073                    deliver_under_wait_mask = signals.interrupts_only_under_wait_mask(&state);
5074                    state
5075                }
5076                Ok((true, state)) => {
5077                    deliver_under_wait_mask = signals.interrupts_only_under_wait_mask(&state);
5078                    let errno = call0.kernel_restart_errno();
5079                    tracing::trace!(
5080                        "retry_nonblocking_syscall: pending signals interrupt {}: {:?}",
5081                        call.display(&guest.memory()),
5082                        errno
5083                    );
5084                    break Err(errno.into());
5085                }
5086                Err(error) => break Err(error),
5087            };
5088            signals.inject_absorbing_after(guest, call, state).await
5089        };
5090        let syscall_result = match injected {
5091            Ok(result) => result,
5092            Err(error) => {
5093                tracing::trace!(
5094                    "retry_nonblocking_syscall: a signal stop ends the wait in {}: {:?}",
5095                    call.display(&guest.memory()),
5096                    error
5097                );
5098                break Err(error);
5099            }
5100        };
5101        if !call.syscall_would_have_blocked(syscall_result) {
5102            let normalized =
5103                call.normalize_nonblocking_result(syscall_result, rsrc.poll_attempt > 0);
5104            if normalized != syscall_result {
5105                guest.thread_state_mut().nonblocking_result_normalized = true;
5106            }
5107            let res = normalized.map_err(|e| e.into());
5108            tracing::trace!(
5109                "retry_nonblocking_syscall: syscall completed after {} retries: {} = {:?}",
5110                rsrc.poll_attempt,
5111                call.display(&guest.memory()),
5112                res
5113            );
5114            break res;
5115        }
5116        if interrupt_after_probe {
5117            let errno = call0.kernel_restart_errno();
5118            tracing::trace!(
5119                "retry_nonblocking_syscall: a committed signal interrupts {}: {:?}",
5120                call.display(&guest.memory()),
5121                errno
5122            );
5123            break Err(errno.into());
5124        }
5125        if first {
5126            // The first turn's check, after its probe found nothing to report.
5127            match signals.interrupted_with_state() {
5128                Ok((false, _)) => {}
5129                Ok((true, state)) => {
5130                    deliver_under_wait_mask = signals.interrupts_only_under_wait_mask(&state);
5131                    let errno = call0.kernel_restart_errno();
5132                    tracing::trace!(
5133                        "retry_nonblocking_syscall: pending signals interrupt {}: {:?}",
5134                        call.display(&guest.memory()),
5135                        errno
5136                    );
5137                    break Err(errno.into());
5138                }
5139                Err(error) => break Err(error),
5140            }
5141        }
5142        if signals.needs_block() {
5143            // Only one scratch-stack guard may be live, so release the probe's, block
5144            // signals from a fresh one, and rebuild the probe.
5145            guard = None;
5146            if let Err(error) = signals.block(guest, None).await {
5147                break Err(error);
5148            }
5149            publish_wait_mask(guest, signals.effective_mask(), &mut published).await;
5150            (call, guard) = call0.into_nonblocking(guest).await;
5151        }
5152        rsrc.poll_attempt += 1;
5153        if let Some((timeout, timeout_result)) = maybe_timeout {
5154            let new_time = thread_observe_time(guest).await;
5155            if new_time >= timeout {
5156                tracing::trace!(
5157                    "Timing out syscall after #{} retries: {}",
5158                    rsrc.poll_attempt - 1,
5159                    call.display(&guest.memory())
5160                );
5161                break timeout_result.map_err(|e| e.into());
5162            }
5163            tracing::trace!(
5164                "Retry #{} for syscall due to result {:?}, {} from timeout: {}",
5165                rsrc.poll_attempt,
5166                syscall_result,
5167                timeout - new_time,
5168                call.display(&guest.memory())
5169            );
5170        } else {
5171            tracing::trace!(
5172                "Retry #{} for syscall due to result {:?}: {}",
5173                rsrc.poll_attempt,
5174                syscall_result,
5175                call.display(&guest.memory())
5176            );
5177        }
5178        record_retry_event(guest, call).await;
5179    };
5180
5181    drop(guard);
5182    let restored = signals.restore(guest, None).await;
5183    publish_wait_mask(guest, None, &mut published).await;
5184    let result = match result_after_restore(restored, result) {
5185        Ok(result) => result,
5186        Err(error) => return (Err(error), false),
5187    };
5188    // A signal the backend holds is delivered as the guest resumes, under the
5189    // guest's own mask (https://github.com/rrnewton/reverie/issues/1011).
5190    let deliver = deliver_under_wait_mask && !signals.holds_signal() && result.is_err();
5191    (
5192        refuse_held_signal_loss(guest, call0.number(), result).await,
5193        deliver,
5194    )
5195}
5196
5197/// The internal failure, if any, that `refusal` names, for thread `dettid`
5198/// handling the guest's `sysno`. `UnsupportedOperation::ToolDecided` is a
5199/// backend's statement that it left a call Detcore injected to the Tool,
5200/// because the Tool decides that call on every backend (the KVM executor's
5201/// CPU, scheduling, priority, id, getrandom, prlimit64, membarrier, seccomp
5202/// and getpgrp calls). Detcore answers every such call itself, except the
5203/// getpgrp that a clone3 race passes through to the backend
5204/// (https://github.com/rrnewton/hermit/issues/4531); so one that reached the
5205/// backend is a Detcore defect or that race, never an answer for the guest.
5206/// Every other refusal belongs to the call site that injected it and is not
5207/// judged here.
5208pub(crate) fn tool_decided_injection_failure(
5209    refusal: Option<reverie::UnsupportedRefusal>,
5210    dettid: DetTid,
5211    sysno: Sysno,
5212) -> Option<String> {
5213    refusal
5214        .filter(|refusal| refusal.operation == reverie::UnsupportedOperation::ToolDecided)
5215        .map(|refusal| {
5216            format!(
5217                "HERMIT_INTERNAL_FAILURE class=tool-decided-injection dtid={dettid} \
5218                 syscall={sysno}: the backend refused a call Detcore injected, as one the Tool \
5219                 decides on every backend ({}); Detcore must answer it itself",
5220                refusal.diagnostic
5221            )
5222        })
5223}
5224
5225/// Ends the run as an internal failure when the last call Detcore injected
5226/// while handling the guest's `sysno` came back with a `ToolDecided` refusal
5227/// ([`tool_decided_injection_failure`]), instead of letting its ENOSYS reach
5228/// the guest. The panic text and the error log carry
5229/// `HERMIT_INTERNAL_FAILURE class=tool-decided-injection`; the CLI's own
5230/// summary line classifies the run as `class=container-child-panic`.
5231///
5232/// This is not a general guard. That no Detcore path injects one of these
5233/// calls on KVM rests on an audit of Detcore's handlers. The one known
5234/// exception is a race: under --no-sequentialize-threads a sibling can
5235/// rewrite clone3's argument struct between Detcore's read and the KVM
5236/// executor's, so a child Detcore marks as in a nested PID namespace passes
5237/// its getpgrp through to the backend (tracked in
5238/// https://github.com/rrnewton/hermit/issues/4531). This check mitigates
5239/// that path: it runs once per syscall event, after Detcore's handler
5240/// returns and before the result is used, and a backend's refusal describes
5241/// only the latest injection, so it sees a handler's pass-through when that
5242/// is the event's last injection. It does not see:
5243/// - an injection of one of these calls followed by another injection in the
5244///   same event, which clears the first one's refusal;
5245/// - a refusal that a consumer takes and filters first, such as
5246///   `report_backend_signal_refusal` (signal.rs), behind the
5247///   thread-directed process signal capability that is false on KVM;
5248/// - injections from Tool callbacks other than the syscall handler (signal
5249///   events, thread start, post-exec, exit, timers);
5250/// - `tail_inject`, whose result goes straight to the guest.
5251///
5252/// A general guard needs a Reverie API hook and is not part of this change.
5253///
5254/// Any other refusal it takes counts the call as refused in the syscall
5255/// histogram (`crate::syscall_histogram`). A signalfd operation the backend
5256/// does not model (`UnsupportedOperation::Signalfd`: a read that would wait,
5257/// an epoll registration, a process created while one is open) is named in a
5258/// warning as well: no handler takes that refusal, and its ENOSYS reaches the
5259/// guest as the backend answered.
5260pub(crate) fn fail_on_tool_decided_injection<T, G>(guest: &mut G, sysno: Sysno)
5261where
5262    T: RecordOrReplay,
5263    G: Guest<Detcore<T>>,
5264{
5265    let refusal = guest.take_unsupported_refusal();
5266    judge_backend_refusal::<T, G>(guest, refusal, sysno);
5267}
5268
5269/// [`fail_on_tool_decided_injection`]'s judgement of a refusal already taken
5270/// from the backend: also used by `Detcore::report_backend_refusal` for a
5271/// refusal that is not the one its call can be refused with.
5272pub(crate) fn judge_backend_refusal<T, G>(
5273    guest: &mut G,
5274    refusal: Option<reverie::UnsupportedRefusal>,
5275    sysno: Sysno,
5276) where
5277    T: RecordOrReplay,
5278    G: Guest<Detcore<T>>,
5279{
5280    let dettid = guest.thread_state().dettid;
5281    if refusal.is_some() {
5282        crate::syscall_histogram::mark_refused::<T, G>(guest);
5283    }
5284    if let Some(refusal) =
5285        refusal.filter(|refusal| refusal.operation == reverie::UnsupportedOperation::Signalfd)
5286    {
5287        tracing::warn!(
5288            "[dtid {dettid}] {sysno} refused by the backend: {}",
5289            refusal.diagnostic
5290        );
5291    }
5292    if let Some(failure) = tool_decided_injection_failure(refusal, dettid, sysno) {
5293        tracing::error!("{failure}");
5294        panic!("{failure}");
5295    }
5296}
5297
5298/// Report a named refusal through tracing, and, when the run ends
5299/// (`fail_closed`, the setting `panic_on_unsupported_syscalls`), directly on
5300/// stderr as well, as the `flock` refusal does, so that neither `--log=off` nor
5301/// `--log-file` hides why the run ended. A run that allows unsupported
5302/// operations goes on with the refusal's errno and has the reason in its log
5303/// only, as the refusals of a held signal and of a `pselect6` mask do: on a
5304/// backend whose tool runs inside the guest process, stderr is the guest's own
5305/// descriptor 2, which the guest may have pointed anywhere.
5306pub(crate) fn report_refusal_reason(fail_closed: bool, message: &str) {
5307    tracing::error!("{message}");
5308    if fail_closed {
5309        let _ = std::io::Write::write_fmt(
5310            &mut crate::util::RetryingStderr,
5311            format_args!("hermit: {message}\n"),
5312        );
5313    }
5314}
5315
5316/// The policy of `Detcore::refuse_unserviceable_operation`, for a caller that
5317/// holds the guest but not the tool. `panic_on_unsupported_syscalls` is the
5318/// setting of that name: the tool's own copy in the method, the guest's
5319/// configuration in `refuse_held_signal_loss`.
5320pub(crate) async fn refuse_unserviceable_operation_with<T, G>(
5321    guest: &mut G,
5322    panic_on_unsupported_syscalls: bool,
5323    sysno: Sysno,
5324    fallback: Errno,
5325    reason: Option<&str>,
5326) -> Result<i64, Error>
5327where
5328    T: RecordOrReplay,
5329    G: Guest<Detcore<T>>,
5330{
5331    crate::syscall_histogram::mark_refused::<T, G>(guest);
5332    if !panic_on_unsupported_syscalls {
5333        return Err(fallback.into());
5334    }
5335    if guest.config().shutdown_on_unsupported_syscall {
5336        // Fail-closed policy: the operation is unserviceable and the config
5337        // forbids passing it through. Say why first: on an in-guest backend
5338        // the exit below ends only the guest process, and the launcher learns
5339        // the reason from the coordinator.
5340        let reason = match reason {
5341            Some(reason) => format!("{sysno}: {reason}"),
5342            None => format!(
5343                "{sysno} cannot be serviced on this backend (with \
5344                 --allow-unsupported-syscalls the guest would see {fallback})"
5345            ),
5346        };
5347        crate::tool_global::report_policy_refusal(guest, reason).await;
5348        crate::tool_global::unrecoverable_shutdown(
5349            guest,
5350            detcore_model::HERMIT_POLICY_REFUSAL_EXIT,
5351        )
5352        .await;
5353    }
5354    if guest.config().exit_on_unsupported_syscall {
5355        return Err(Error::Tool(anyhow::Error::new(
5356            crate::UnsupportedSyscallError(sysno),
5357        )));
5358    }
5359    panic!("unserviceable operation on syscall: {sysno:?}");
5360}
5361
5362/// The signals the guest ignores, blocked for one wait that Detcore runs in the
5363/// kernel under the thread's own mask: the `epoll_wait` family (`ep_poll`), and
5364/// `poll`, `select`, and `ppoll` and `pselect6` naming no mask (`do_sys_poll`,
5365/// `core_sys_select`). Linux discards a signal the process ignores when it is
5366/// generated (`sig_ignored`), so it never ends such a wait, but it queues one
5367/// for a traced thread. An epoll wait then returned `EINTR` to the guest. A
5368/// poll-family wait ended with a restart code instead: a `poll` restarts
5369/// through `restart_syscall`, which Detcore refuses, and a `select`,
5370/// `pselect6` or `ppoll` runs again in place, or returns `EINTR` when Linux
5371/// cannot write back its time left (`poll_select_finish`). Blocked for the
5372/// wait, such a signal stays pending without ending it; once the guest's mask
5373/// is back it is delivered and ignored, as it was before.
5374/// The mask changes through the backend (`Guest::set_blocked_signals`, on
5375/// ptrace `PTRACE_SETSIGMASK`), so no syscall the guest or the recorder sees is
5376/// added. Replay, which serves the wait from the recording, raises the same
5377/// shield: the scheduler reads the thread's mask when it commits the wait's
5378/// request (`Resources::blocked_signal_mask`), and record and replay must
5379/// present the same one.
5380#[must_use]
5381pub(crate) struct IgnoredSignalShield {
5382    /// The guest's mask to put back, when the shield is up.
5383    guest_mask: Option<u64>,
5384}
5385
5386impl IgnoredSignalShield {
5387    /// No shield: the wait cannot block, or runs under its own temporary mask
5388    /// (or one the tracer cannot read).
5389    pub(crate) fn none() -> Self {
5390        Self { guest_mask: None }
5391    }
5392
5393    /// Blocks the signals the guest ignores and does not block, read from the
5394    /// kernel (`KernelSignalState::discarded_at_generation`). If the state
5395    /// cannot be read or the backend refuses the change and left the mask as
5396    /// it was, the wait runs under the guest's own mask, as it did before. A
5397    /// change that may have been written without being confirmed ends the
5398    /// run: the guest must not resume as if its mask were known
5399    /// (`MaskChangeRefused::Failed`).
5400    pub(crate) async fn raise<T, G>(guest: &mut G) -> Result<Self, Error>
5401    where
5402        T: RecordOrReplay,
5403        G: Guest<Detcore<T>>,
5404    {
5405        let state = match crate::syscalls::read_thread_signal_state(guest.pid(), guest.tid()) {
5406            Ok(state) => state,
5407            Err(errno) => {
5408                tracing::debug!("[tid {}] no ignored-signal shield: {errno}", guest.tid());
5409                return Ok(Self::none());
5410            }
5411        };
5412        let Some(mask) = ignored_signal_shield_mask(&state) else {
5413            return Ok(Self::none());
5414        };
5415        match guest.set_blocked_signals(mask).await {
5416            Ok(guest_mask) => Ok(Self {
5417                guest_mask: Some(guest_mask),
5418            }),
5419            Err(refused) if shield_refusal_leaves_the_mask_known(&refused) => {
5420                tracing::debug!("[tid {}] no ignored-signal shield: {refused}", guest.tid());
5421                Ok(Self::none())
5422            }
5423            Err(refused) => Err(Error::Tool(anyhow::anyhow!(
5424                "[tid {}] could not block the signals the guest ignores ({mask:#x}) for a wait, \
5425                 and its signal mask is unknown: {refused}",
5426                guest.tid()
5427            ))),
5428        }
5429    }
5430
5431    /// Puts the guest's mask back. The guest must not resume under the
5432    /// shield, so a mask that cannot be put back ends the run.
5433    pub(crate) async fn lower<T, G>(mut self, guest: &mut G) -> Result<(), Error>
5434    where
5435        T: RecordOrReplay,
5436        G: Guest<Detcore<T>>,
5437    {
5438        let Some(guest_mask) = self.guest_mask.take() else {
5439            return Ok(());
5440        };
5441        match guest.set_blocked_signals(guest_mask).await {
5442            Ok(_) | Err(MaskChangeRefused::Gone) => Ok(()),
5443            Err(refused) => Err(Error::Tool(anyhow::anyhow!(
5444                "[tid {}] could not put back the guest's signal mask {guest_mask:#x} after a \
5445                 wait that blocked the signals it ignores: {refused}",
5446                guest.tid()
5447            ))),
5448        }
5449    }
5450}
5451
5452/// A shield is dropped with its mask still to put back only when the
5453/// backend cancelled the handler around it, which it does when the thread
5454/// dies during the wait or an injection (a sibling's `exit_group` or
5455/// `execve`, a SIGKILL), or when a backend error is ending the run. Drop
5456/// cannot tell those apart, so its log claims neither: it says only that
5457/// the handler ended with the mask unrestored.
5458/// The log line for a shield dropped with its mask still to put back.
5459/// It must not claim the thread is gone: Drop cannot prove that (the
5460/// handler may also be cancelled by a backend error ending the run).
5461fn shield_drop_message(guest_mask: u64) -> String {
5462    format!(
5463        "an ignored-signal shield ended with its handler, leaving the guest's mask \
5464         {guest_mask:#x} unrestored; the handler ended before the mask could be put \
5465         back (the thread may have died, or the run may be ending)"
5466    )
5467}
5468
5469impl Drop for IgnoredSignalShield {
5470    fn drop(&mut self) {
5471        if let Some(guest_mask) = self.guest_mask {
5472            tracing::debug!("{}", shield_drop_message(guest_mask));
5473        }
5474    }
5475}
5476
5477/// Whether a refused shield change left the thread's mask as it was, so the
5478/// wait can run under it: every refusal but a failure after which the backend
5479/// could not confirm the mask (`MaskChangeRefused::Failed`'s `mask_unknown`).
5480fn shield_refusal_leaves_the_mask_known(refused: &MaskChangeRefused) -> bool {
5481    !matches!(
5482        refused,
5483        MaskChangeRefused::Failed {
5484            mask_unknown: true,
5485            ..
5486        }
5487    )
5488}
5489
5490/// The mask an ignored-signal shield sets over `state`: the thread's mask and
5491/// the signals it would discard at generation, or `None` when there are none
5492/// (`IgnoredSignalShield`).
5493fn ignored_signal_shield_mask(state: &KernelSignalState) -> Option<KernelSigset> {
5494    let ignored = state.discarded_at_generation();
5495    (ignored != 0).then_some(state.blocked | ignored)
5496}
5497
5498/// Refuse the guest's `sysno` call when its wait ended with a
5499/// [`HeldSignalRefusal`], through the unsupported-operation policy
5500/// (`refuse_unserviceable_operation_with`): a fail-closed run, which ordinary
5501/// `hermit run` is, ends with the policy's exit, and a run started with
5502/// `--allow-unsupported-syscalls` gets `ERESTARTNOINTR`, the transparent
5503/// restart that such a wait returned before (round-10 High 1 on
5504/// https://github.com/rrnewton/hermit/pull/3361). The message names the call,
5505/// the signals and that pull request. Callers pass the wait's result after the
5506/// wait has put the guest's mask back. Every other result passes through.
5507///
5508/// Whether a wait meets such a stop can depend on host timing, as a signal
5509/// sent from outside the guest does, so a refusal can be host-timed: it
5510/// replaces a run that would have gone on to depend on that timing.
5511pub(crate) async fn refuse_held_signal_loss<T, G>(
5512    guest: &mut G,
5513    sysno: Sysno,
5514    result: Result<i64, Error>,
5515) -> Result<i64, Error>
5516where
5517    T: RecordOrReplay,
5518    G: Guest<Detcore<T>>,
5519{
5520    use std::io::Write as _;
5521    let refusal = match &result {
5522        Err(Error::Tool(error)) => match error.downcast_ref::<HeldSignalRefusal>() {
5523            Some(refusal) => HeldSignalRefusal {
5524                wait: Some(sysno),
5525                ..*refusal
5526            },
5527            None => return result,
5528        },
5529        _ => return result,
5530    };
5531    let panic_on_unsupported_syscalls = guest.config().panic_on_unsupported_syscalls;
5532    tracing::error!("[tid {}] {}", guest.tid(), refusal);
5533    if panic_on_unsupported_syscalls {
5534        // The run ends: report why even when logging is disabled or redirected,
5535        // as the refusal of a nonleader exec does.
5536        let _ = writeln!(crate::util::RetryingStderr, "{refusal}");
5537    }
5538    refuse_unserviceable_operation_with(
5539        guest,
5540        panic_on_unsupported_syscalls,
5541        sysno,
5542        Errno::ERESTARTNOINTR,
5543        None,
5544    )
5545    .await
5546}
5547
5548/// Signal handling for one blocking wait whose interruption is decided from the
5549/// kernel's signal state (`supports_blocked_wait_signal_interruption`).
5550///
5551/// A wait ends early only for a signal that would end it natively: one the guest
5552/// does not block and that is caught, or whose default action terminates or stops
5553/// the process (https://github.com/rrnewton/hermit/issues/3146). Blocked,
5554/// ignored, and default-ignored signals do not end it, and neither does a default
5555/// job-control stop when `defers_default_stops` is set
5556/// (`KernelSignalState::interrupting_wait`), apart from the stops that
5557/// `inject_absorbing` cannot absorb safely, which end it with a restart.
5558///
5559/// The first probe runs under the guest's own mask. If it would block, `block`
5560/// blocks every blockable signal for the rest of the wait, so later probes cannot
5561/// be stopped by one and every signal that arrives stays pending in the kernel,
5562/// where `/proc` reports it. `interrupted_with_state` classifies the pending set
5563/// against the guest's mask and dispositions each turn, and `restore` puts the
5564/// guest's mask back before the call returns, so a pending interrupting signal
5565/// is delivered as the call returns its restart errno.
5566///
5567/// Probes and the mask change go through `inject_absorbing`, never
5568/// `inject_with_retry`. A signal that stops the guest around an injection is
5569/// dequeued from the kernel and held by the backend in a single slot, so a blind
5570/// retry that is stopped again would replace it. `inject_absorbing` works out
5571/// from `/proc` which signal the backend holds and injects again only when that
5572/// signal would not end the wait natively. Before the mask is set, a signal
5573/// pending at the turn's `/proc` read that would end the wait is reported by
5574/// `interrupted_with_state`, and any other unblocked one stops the next
5575/// injection and is absorbed; a signal that arrives after the read can stop an
5576/// injection in a way that cannot be identified, and is held without ending the
5577/// wait, so the wait keeps its deadline and its checks, unless the backend held
5578/// a signal whose loss matters, which ends the wait with a
5579/// [`HeldSignalRefusal`] (`inject_absorbing`).
5580/// Afterwards only a signal that cannot be blocked can stop an injection. A stop
5581/// after a probe ran replaces its result, so a probe that consumed something (an
5582/// edge-triggered event, a dequeued signal) loses it; that remains a known gap.
5583///
5584/// No failure of this machinery reaches the guest as an errno the wait call
5585/// cannot return. A `/proc` read that fails for a thread that still exists, or a
5586/// guest mask that cannot be put back, ends the run with a
5587/// [`BlockedWaitSignalError`]; a thread that no longer exists gets
5588/// `ERESTARTNOINTR`, which nothing observes (`read_wait_signal_state`). If the
5589/// mask cannot be changed at all (no scratch room below the guest's stack
5590/// pointer, https://github.com/rrnewton/hermit/issues/3328), `block` leaves the
5591/// guest's own mask in place and every later probe runs under it, as the first
5592/// probe does.
5593/// The real-time signals, 32 to 64, as kernel sigset bits.
5594pub(crate) const REAL_TIME_SIGNALS: KernelSigset = !0 << 31;
5595
5596/// The signals a thread whose mask becomes `mask` lets the kernel take from
5597/// its queues: those `mask` does not block, split into the ones whose delivery
5598/// the guest observes and the ones Linux discards on delivery because their
5599/// disposition is `SIG_IGN`.
5600pub(crate) fn unblocked_by(
5601    state: &KernelSignalState,
5602    mask: KernelSigset,
5603) -> (KernelSigset, KernelSigset) {
5604    unblocked_by_ignored(state.ignored, mask)
5605}
5606
5607/// [`unblocked_by`] given only the thread's ignored set, from wherever it
5608/// comes (the kernel's `/proc` view or Detcore's copy).
5609pub(crate) fn unblocked_by_ignored(
5610    ignored: KernelSigset,
5611    mask: KernelSigset,
5612) -> (KernelSigset, KernelSigset) {
5613    let unblocked = !mask;
5614    (unblocked & !ignored, unblocked & ignored)
5615}
5616
5617/// The real-time timer expirations among `candidates` (a kernel sigset) that
5618/// are pending for the calling thread's process because every thread blocked
5619/// them when they expired (`Scheduler::pending_real_time_expirations`,
5620/// https://github.com/rrnewton/hermit/issues/4089). 0 without asking the
5621/// scheduler until one has been kept pending in this run
5622/// (`scheduler::real_time_expiration_kept_pending`), or when `candidates`
5623/// holds no real-time signal. Asked in the calling thread's turn.
5624pub(crate) async fn pending_real_time_among<T, G>(guest: &mut G, candidates: KernelSigset) -> u64
5625where
5626    T: RecordOrReplay,
5627    G: Guest<Detcore<T>>,
5628{
5629    let candidates = candidates & REAL_TIME_SIGNALS;
5630    if candidates == 0 || !crate::scheduler::real_time_expiration_kept_pending() {
5631        return 0;
5632    }
5633    let Some(detpid) = guest.thread_state().detpid else {
5634        return 0;
5635    };
5636    crate::tool_global::pending_real_time_expirations(guest, detpid).await & candidates
5637}
5638
5639/// Where a call lets the guest observe the signals in `observable` (deliver,
5640/// dequeue or report them), refuse the run by name if one of the `pending`
5641/// real-time timer expirations (`pending_real_time_among`) is among them, and
5642/// discard the pending ones in `discarded`, which Linux discards there.
5643pub(crate) async fn act_on_pending_real_time<T, G>(
5644    guest: &mut G,
5645    pending: KernelSigset,
5646    observable: KernelSigset,
5647    discarded: KernelSigset,
5648) where
5649    T: RecordOrReplay,
5650    G: Guest<Detcore<T>>,
5651{
5652    let observed = pending & observable;
5653    if observed != 0 {
5654        crate::scheduler::refuse_real_time_expiration(observed.trailing_zeros() as i32 + 1);
5655    }
5656    if pending & discarded != 0
5657        && let Some(detpid) = guest.thread_state().detpid
5658    {
5659        crate::tool_global::discard_pending_real_time_expirations(
5660            guest,
5661            detpid,
5662            pending & discarded,
5663        )
5664        .await;
5665    }
5666}
5667
5668/// For a call that runs under the temporary mask `mask`: refuse by name for a
5669/// pending real-time timer expiration the mask lets the kernel deliver, and
5670/// discard one it lets the kernel discard. Reads the thread's dispositions
5671/// only when one is pending.
5672pub(crate) async fn observe_under_mask<T, G>(guest: &mut G, mask: KernelSigset) -> Result<(), Error>
5673where
5674    T: RecordOrReplay,
5675    G: Guest<Detcore<T>>,
5676{
5677    let pending = pending_real_time_among(guest, !mask).await;
5678    if pending != 0 {
5679        let ignored = match crate::syscalls::signal_shadow::copied_dispositions(guest) {
5680            Some(copied) => copied.ignored,
5681            None => read_thread_wait_signal_state(guest.pid(), guest.tid())?.ignored,
5682        };
5683        let (observable, discarded) = unblocked_by_ignored(ignored, mask);
5684        act_on_pending_real_time(guest, pending, observable, discarded).await;
5685    }
5686    Ok(())
5687}
5688
5689pub(crate) struct KernelSignalWait {
5690    pid: reverie::Pid,
5691    tid: reverie::Pid,
5692    /// Signals the wait itself consumes (rt_sigtimedwait's set), which never
5693    /// interrupt it.
5694    consumed: KernelSigset,
5695    /// Whether a default job-control stop leaves the wait running, because Linux's
5696    /// restart of the call keeps its deadline
5697    /// (`NonblockableSyscall::restart_keeps_deadline`).
5698    defers_default_stops: bool,
5699    /// The errno that ends the wait for an interrupting signal: the restart code
5700    /// Linux uses for the call (`NonblockableSyscall::kernel_restart_errno`). A
5701    /// signal the backend holds turns `ERESTART_RESTARTBLOCK` into `ERESTARTNOHAND`
5702    /// (`held_signal_restart_errno`).
5703    restart_errno: Errno,
5704    /// Signals that never end the wait, even ones that would end it natively.
5705    /// They stay pending in the kernel, or held by the backend, and are
5706    /// delivered as the call returns, once the guest's mask is put back. In
5707    /// every wait but `select`'s and `pselect6`'s (`for_select`) this is
5708    /// `SIGCHLD`, whichever process sent it: the kernel also posts one for a
5709    /// child's exit, stop or continue at a moment set by host timing, and
5710    /// `/proc` shows no siginfo that would tell the two apart
5711    /// (https://github.com/rrnewton/hermit/issues/3146). The scheduler's own
5712    /// send of a `SIGCHLD` to this thread at a commit point lifts the hold for the
5713    /// turn whose resume names it (`admit`,
5714    /// https://github.com/rrnewton/hermit/issues/4005). The gated loop adds,
5715    /// in each of its turns, every signal a host-timed source armed by a guest
5716    /// can post to this process (`hold_until_return`).
5717    held_until_return: KernelSigset,
5718    /// Whether the wait has a finite deadline (`with_deadline`). Hermit cannot
5719    /// install the restart block with which Linux keeps the absolute deadline
5720    /// of a restarted `poll` or timed futex wait, and a restarted `ppoll`,
5721    /// `epoll_wait`, `epoll_pwait` or `rt_sigtimedwait` runs again with the
5722    /// guest's unchanged timeout. So a transparent restart would start the
5723    /// timeout again, and such a wait never ends with `ERESTARTNOINTR`
5724    /// (`inject_absorbing`).
5725    deadline: bool,
5726    /// The guest's own mask while the wait runs with every signal blocked.
5727    saved_mask: Option<KernelSigset>,
5728    /// `block` could not change the mask, so probes run under the guest's own.
5729    unblockable: bool,
5730    /// `saved_mask` was installed by `block_in_place`, through
5731    /// `Guest::set_blocked_signals`, so `restore` puts it back the same way,
5732    /// with no injection and no scratch memory.
5733    blocked_in_place: bool,
5734    /// The signal the backend holds since `inject_absorbing` absorbed a stop;
5735    /// `None` also after a stop it could not identify (`held_unidentified`).
5736    held: Option<HeldSignal>,
5737    /// The backend holds a signal from a stop that `inject_absorbing` could not
5738    /// identify, which may be one whose loss matters, so no later stop may
5739    /// replace it (`would_replace_precious`).
5740    held_unidentified: bool,
5741    /// The signals `hold_until_return` added because a host-timed source armed
5742    /// by a guest can post them; a committed signal never lifts these
5743    /// (`admit`).
5744    host_timed: KernelSigset,
5745    /// Signals the scheduler committed to this thread in the current turn,
5746    /// which end the wait even though it holds them (`admit`).
5747    committed: KernelSigset,
5748    /// The `SIGCHLD` a guest sent this thread, which the schedule recorded in
5749    /// its private queue, admitted in the current turn (`admit_under_block`):
5750    /// it ends the wait from the thread's own queue only, even though the
5751    /// wait holds it (R2b-2 stage 3,
5752    /// https://github.com/rrnewton/hermit/issues/4273).
5753    private: KernelSigset,
5754    /// The mask the wait sleeps under, when it is not the guest's own: the
5755    /// temporary mask of a `pselect6` (`with_wait_mask`). A signal decides
5756    /// against it whether it ends the wait; `restore` still puts back the
5757    /// guest's own mask (https://github.com/rrnewton/hermit/issues/3991).
5758    wait_mask: Option<KernelSigset>,
5759}
5760
5761/// A signal that stopped one of a wait's injections and that the backend holds
5762/// for delivery when the guest resumes (`KernelSignalWait::inject_absorbing`).
5763#[derive(Clone, Copy, Debug, Eq, PartialEq)]
5764struct HeldSignal {
5765    signal: i32,
5766    queue: SignalQueue,
5767    kind: HeldKind,
5768}
5769
5770/// Whether losing a held signal, which a later stop would replace, changes what
5771/// the guest or the scheduler observes.
5772#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
5773enum HeldKind {
5774    /// Its delivery does nothing: it is ignored, or default-ignored and not
5775    /// `SIGCHLD`.
5776    Harmless,
5777    /// Its delivery matters, or is kept as if it did: a signal the wait holds
5778    /// until the call returns (`held_until_return`), which may run a handler
5779    /// then, any other `SIGCHLD`, or a default job-control stop that the wait
5780    /// defers.
5781    Precious,
5782}
5783
5784/// How a wait would lose a signal that the backend holds, or deliver it in
5785/// place of the result Linux gives (`HeldSignalRefusal`).
5786#[derive(Clone, Copy, Debug, Eq, PartialEq)]
5787pub(crate) enum HeldSignalLoss {
5788    /// Another pending signal would stop the next injection, and the backend,
5789    /// which has one slot for a held signal, would drop the held one for it.
5790    Replaced,
5791    /// A stop that `/proc` cannot identify replaced a held signal whose loss
5792    /// matters (`HeldKind::Precious`), or one that could not be identified
5793    /// either and so may matter.
5794    UnidentifiedStop,
5795    /// The wait reached `MAX_ABSORBED_STOPS` absorbed stops while the backend
5796    /// held a signal whose loss matters or that could not be identified.
5797    StopBound,
5798    /// A signal that a wait without a deadline consumes stopped an injection,
5799    /// so the backend holds it and delivers it rather than the wait returning
5800    /// it, and either it is one the wait holds until the call returns
5801    /// (`held_until_return`) or it replaced a held signal whose loss matters.
5802    Consumed,
5803    /// A signal stopped an injection of `restore`, which puts the guest's mask
5804    /// back and so must run whatever is pending, and the backend dropped a held
5805    /// signal whose loss matters, or one that could not be identified, for it.
5806    Restoration,
5807}
5808
5809/// A wait that ends because going on, or restarting the call, would lose a
5810/// signal the backend holds, or deliver it in place of the result Linux gives.
5811///
5812/// Every path that used to end such a wait with a fallback that returns
5813/// success (a transparent `ERESTARTNOINTR` restart, or a wait that went on and
5814/// let a stop replace the held signal) returns this instead (round-10 High 1 on
5815/// https://github.com/rrnewton/hermit/pull/3361). The wait puts the guest's mask
5816/// back, and its caller then applies the unsupported-operation policy
5817/// (`refuse_held_signal_loss`). A complete successful repair preserving signal
5818/// information needs additional framework support with the current generic
5819/// API: the backend holds one signal, does not say which, and delivers it
5820/// without the tool. A Detcore-only refusal or capability withdrawal can
5821/// prevent the determinism regression.
5822#[derive(Clone, Copy, Debug, Eq, PartialEq)]
5823pub(crate) struct HeldSignalRefusal {
5824    /// The guest call whose wait ends, once the caller names it
5825    /// (`refuse_held_signal_loss`).
5826    pub(crate) wait: Option<Sysno>,
5827    /// The signal the backend holds that would be lost or delivered wrongly;
5828    /// `None` when it could not be identified.
5829    pub(crate) held: Option<i32>,
5830    /// The signal that would replace it, or that stopped the injection, when
5831    /// known.
5832    pub(crate) replacing: Option<i32>,
5833    /// How it would be lost.
5834    pub(crate) loss: HeldSignalLoss,
5835}
5836
5837/// A signal's name, such as `SIGCHLD`, or its number for a real-time signal.
5838fn signal_label(signal: Option<i32>) -> String {
5839    match signal {
5840        Some(signal) => match nix::sys::signal::Signal::try_from(signal) {
5841            Ok(named) => named.as_str().to_string(),
5842            Err(_) => format!("signal {signal}"),
5843        },
5844        None => "a signal that could not be identified".to_string(),
5845    }
5846}
5847
5848impl std::fmt::Display for HeldSignalRefusal {
5849    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5850        let wait = self
5851            .wait
5852            .map_or_else(|| "a blocking".to_string(), |sysno| sysno.to_string());
5853        let held = match self.held {
5854            Some(_) => format!("the held signal {}", signal_label(self.held)),
5855            None => "a held signal that could not be identified".to_string(),
5856        };
5857        let replacing = signal_label(self.replacing);
5858        match self.loss {
5859            HeldSignalLoss::Replaced => write!(
5860                f,
5861                "unsupported: the {wait} wait cannot keep {held}: the pending {replacing} \
5862                 would stop the next injection and replace it"
5863            )?,
5864            HeldSignalLoss::UnidentifiedStop => write!(
5865                f,
5866                "unsupported: the {wait} wait cannot keep {held}: a signal stop that /proc \
5867                 cannot identify replaced it"
5868            )?,
5869            HeldSignalLoss::StopBound => write!(
5870                f,
5871                "unsupported: the {wait} wait cannot keep {held}: the wait absorbed the \
5872                 maximum number of signal stops"
5873            )?,
5874            HeldSignalLoss::Consumed => {
5875                write!(
5876                    f,
5877                    "unsupported: the {wait} wait cannot return {replacing}, which it consumes: \
5878                     the signal stopped an injection, so the backend would deliver it instead"
5879                )?;
5880                if self.held.is_some() {
5881                    write!(f, ", in place of {held}")?;
5882                }
5883            }
5884            HeldSignalLoss::Restoration => write!(
5885                f,
5886                "unsupported: the {wait} wait cannot keep {held}: {replacing} stopped the \
5887                 injection that puts the guest's signal mask back and replaced it"
5888            )?,
5889        }
5890        write!(
5891            f,
5892            " (scope reduction of https://github.com/rrnewton/hermit/pull/3361; rerun with \
5893             --allow-unsupported-syscalls to restart the call instead)"
5894        )
5895    }
5896}
5897
5898impl std::error::Error for HeldSignalRefusal {}
5899
5900/// What a signal that stopped one of a wait's injections means for the wait.
5901#[derive(Clone, Copy, Debug, Eq, PartialEq)]
5902enum StopVerdict {
5903    /// The wait goes on; the backend holds the signal.
5904    Absorb(HeldKind),
5905    /// The wait ends with this errno.
5906    End(Errno),
5907}
5908
5909/// The first real-time signal number in the kernel. A standard signal below it
5910/// is pending at most once; a second one sent while it is pending is merged.
5911const KERNEL_SIGRTMIN: i32 = 32;
5912
5913impl KernelSignalWait {
5914    pub(crate) fn new<T, G>(
5915        guest: &G,
5916        consumed: KernelSigset,
5917        defers_default_stops: bool,
5918        restart_errno: Errno,
5919    ) -> Self
5920    where
5921        T: RecordOrReplay,
5922        G: Guest<Detcore<T>>,
5923    {
5924        Self {
5925            pid: guest.pid(),
5926            tid: guest.tid(),
5927            consumed,
5928            defers_default_stops,
5929            restart_errno,
5930            held_until_return: kernel_sigset_bit(libc::SIGCHLD),
5931            deadline: false,
5932            saved_mask: None,
5933            unblockable: false,
5934            blocked_in_place: false,
5935            held: None,
5936            held_unidentified: false,
5937            host_timed: 0,
5938            committed: 0,
5939            private: 0,
5940            wait_mask: None,
5941        }
5942    }
5943
5944    /// This wait, with a finite deadline when `deadline` is set (the `deadline`
5945    /// field): no fallback of `inject_absorbing` then ends it with
5946    /// `ERESTARTNOINTR`, which would start the guest's timeout again.
5947    pub(crate) fn with_deadline(self, deadline: bool) -> Self {
5948        Self { deadline, ..self }
5949    }
5950
5951    /// The wait of `select` and of `pselect6`, which holds no signal until the
5952    /// call returns (`held_until_return` is empty); a `pselect6` with a
5953    /// temporary mask adds it with `with_wait_mask`:
5954    /// any pending signal that could interrupt it ends it, `SIGCHLD` included.
5955    ///
5956    /// Linux ends these calls with `ERESTARTNOHAND` whenever no descriptor is
5957    /// ready and a signal is pending (`core_sys_select`), so a caught `SIGCHLD`
5958    /// ends them whoever sent it. Detcore's select and pselect6 waits did the
5959    /// same before this machinery existed: their probes ran under the guest's
5960    /// own mask, and a pending caught signal stopped one.
5961    ///
5962    /// This is a known gap that these waits had before, and it is left as it
5963    /// was: the `SIGCHLD` the kernel posts for a child's exit, stop or continue
5964    /// ends the wait at the first turn whose read sees it, and host timing
5965    /// decides which turn that is (https://github.com/rrnewton/hermit/issues/3146).
5966    /// Every other wait holds `SIGCHLD` until it returns, unless the scheduler
5967    /// commits it to the thread in that turn (`admit`).
5968    pub(crate) fn for_select<T, G>(guest: &G) -> Self
5969    where
5970        T: RecordOrReplay,
5971        G: Guest<Detcore<T>>,
5972    {
5973        Self {
5974            held_until_return: 0,
5975            ..Self::new(guest, 0, false, Errno::ERESTARTNOHAND)
5976        }
5977    }
5978
5979    /// This wait, sleeping under `mask` instead of the guest's own (`wait_mask`):
5980    /// a `pselect6` with a temporary mask. Linux installs that mask for the
5981    /// whole call, so a signal it blocks never ends the call, even one the
5982    /// guest's own mask leaves unblocked, and one it leaves unblocked ends the
5983    /// call although the guest's own mask blocks it.
5984    pub(crate) fn with_wait_mask(self, mask: Option<KernelSigset>) -> Self {
5985        Self {
5986            wait_mask: mask,
5987            ..self
5988        }
5989    }
5990
5991    /// The mask Linux's `blocked` holds for the waiting thread when the
5992    /// kernel's `SigBlk` does not show it: the wait's temporary mask
5993    /// (`wait_mask`), or else the guest's own mask once `block` has replaced
5994    /// it (`saved_mask`). `None` while `SigBlk` is the guest's own mask.
5995    pub(crate) fn effective_mask(&self) -> Option<KernelSigset> {
5996        self.wait_mask.or(self.saved_mask)
5997    }
5998
5999    /// Whether the backend holds a signal from a stop of one of this wait's
6000    /// injections (`held`, `held_unidentified`), which it delivers when the
6001    /// guest resumes.
6002    pub(crate) fn holds_signal(&self) -> bool {
6003        self.held.is_some() || self.held_unidentified
6004    }
6005
6006    /// Whether a signal that ends the wait, pending in the kernel per `state`,
6007    /// is one the guest's own mask blocks: it ends the wait only because the
6008    /// wait mask (`with_wait_mask`) leaves it unblocked, and it stays pending
6009    /// when the guest's own mask is put back. A signal the wait holds until
6010    /// the call returns counts once the scheduler committed it in this turn
6011    /// (`admit`), as in `interrupted_with_state`: otherwise a
6012    /// committed `SIGCHLD` that only the wait mask lets in ended the call
6013    /// without its handler (Claude review of
6014    /// https://github.com/rrnewton/hermit/pull/4217).
6015    pub(crate) fn interrupts_only_under_wait_mask(&self, state: &KernelSignalState) -> bool {
6016        let own_mask = self.saved_mask.unwrap_or(state.blocked);
6017        let held_until_return = self.held_until_return & !self.committed;
6018        let could_interrupt = self.could_interrupt(state);
6019        let interrupting = (state.pending & !held_until_return
6020            | state.thread_pending & self.private)
6021            & could_interrupt;
6022        self.wait_mask.is_some() && interrupting & own_mask != 0
6023    }
6024
6025    /// Adds `signals` to the ones this wait holds until the call returns
6026    /// (`held_until_return`). The gated loop passes, in each of its turns, the
6027    /// signals that a host-timed source armed by a guest can post to this
6028    /// process, such as a parent-death signal (`tool_global::host_timed_signals`):
6029    /// the kernel posts those at a moment set by host timing, so ending the wait
6030    /// for one would make the run depend on host timing. Never called for a
6031    /// `select` wait (`for_select`), which holds nothing.
6032    pub(crate) fn hold_until_return(&mut self, signals: KernelSigset) {
6033        self.held_until_return |= signals;
6034        self.host_timed |= signals;
6035    }
6036
6037    /// Sets the signals the scheduler committed to this thread in this turn
6038    /// (`committed`): its resume named them (`ResumeStatus::Signaled`). Such a
6039    /// signal was sent at a fixed point of the schedule, so a wait may end for
6040    /// it although the wait holds it, unless a host-timed source can post it
6041    /// too (`host_timed`). The loop passes a committed `SIGCHLD`, which the
6042    /// scheduler sends at a commit point (a child's child-exit timer, or a
6043    /// POSIX timer whose signal is `SIGCHLD`); the kernel's
6044    /// own copy of it, posted at a host-timed moment, decides nothing, since
6045    /// only the resume that names it lifts the hold, for that turn alone
6046    /// (https://github.com/rrnewton/hermit/issues/4005).
6047    ///
6048    /// `private` is the `SIGCHLD` a guest sent this thread, recorded in its
6049    /// private queue, that the turn admits (`admit_under_block`): it lifts the
6050    /// hold only against the thread's own queue, for this turn alone.
6051    pub(crate) fn admit(&mut self, committed: KernelSigset, private: KernelSigset) {
6052        self.committed = committed & !self.host_timed;
6053        self.private = private & !self.host_timed;
6054    }
6055
6056    /// The private `SIGCHLD` this turn admits (`admit`), from `recorded`, the
6057    /// schedule's record (`Scheduler::private_sigchld`). A turn that admits a
6058    /// `SIGCHLD`, committed or recorded, first blocks every blockable signal
6059    /// in place, before any injection of the turn, and publishes the guest's
6060    /// mask while it holds (`block_in_place`, `publish_wait_mask`). The
6061    /// recorded one is then kept only while that block holds (`saved_mask`;
6062    /// an earlier injected `block` that failed leaves none), and only if the
6063    /// thread's own queue holds `SIGCHLD`, read after the block: only this
6064    /// stopped thread can take it from there, and no injection can now. If
6065    /// the setter refuses, the wait keeps its fallback, the injected `block`
6066    /// after the first probe (rev4 1f), and holds the recorded `SIGCHLD` as
6067    /// before.
6068    pub(crate) async fn admit_under_block<T, G>(
6069        &mut self,
6070        guest: &mut G,
6071        committed: KernelSigset,
6072        recorded: KernelSigset,
6073        host_timed: KernelSigset,
6074        published: &mut PublishedWaitMask,
6075    ) -> Result<KernelSigset, Error>
6076    where
6077        T: RecordOrReplay,
6078        G: Guest<Detcore<T>>,
6079    {
6080        if (committed | recorded) & !host_timed == 0 {
6081            return Ok(0);
6082        }
6083        if self.needs_block() {
6084            if self.block_in_place(guest, host_timed).await? {
6085                publish_wait_mask(guest, self.effective_mask(), published).await;
6086            } else {
6087                // Keep today's fallback for this wait.
6088                self.unblockable = false;
6089            }
6090        }
6091        if recorded == 0 || self.saved_mask.is_none() {
6092            return Ok(0);
6093        }
6094        let state = read_wait_signal_state(self.pid, self.tid)?;
6095        Ok(recorded & state.thread_pending)
6096    }
6097
6098    /// Whether, of the signals that end the wait per `state`, only signals the
6099    /// scheduler committed in this turn do (`admit`): without them
6100    /// the wait would go on.
6101    pub(crate) fn interrupts_only_by_committed(&self, state: &KernelSignalState) -> bool {
6102        let held = self.held.map_or(0, |held| kernel_sigset_bit(held.signal));
6103        let interrupting = (state.pending | held) & self.could_interrupt(state);
6104        (self.committed | self.private) != 0 && interrupting & !self.held_until_return == 0
6105    }
6106
6107    /// Whether the caller should still call `block`: it has neither blocked the
6108    /// guest's signals nor found that it cannot.
6109    pub(crate) fn needs_block(&self) -> bool {
6110        self.saved_mask.is_none() && !self.unblockable
6111    }
6112
6113    /// The signals that would end the wait natively under the guest's mask, given
6114    /// the kernel's `state`.
6115    /// The signals whose pending presence this wait would observe now: those
6116    /// it consumes, and those that would end it under the guest's mask, or
6117    /// the wait mask when it has one (`could_interrupt`). Read from the kernel's state in this turn.
6118    pub(crate) fn observable_now(&self) -> Result<KernelSigset, Error> {
6119        let state = read_wait_signal_state(self.pid, self.tid)?;
6120        Ok(self.consumed | self.could_interrupt(&state))
6121    }
6122
6123    fn could_interrupt(&self, state: &KernelSignalState) -> KernelSigset {
6124        let guest_mask = self.wait_mask.or(self.saved_mask).unwrap_or(state.blocked);
6125        state.interrupting_wait(guest_mask, self.defers_default_stops) & !self.consumed
6126    }
6127
6128    /// `interrupted_with_state` without the state, for the tests.
6129    #[cfg(test)]
6130    pub(crate) fn interrupted(&self) -> Result<bool, Error> {
6131        Ok(self.interrupted_with_state()?.0)
6132    }
6133
6134    /// Whether a signal that would end the wait natively is pending, in which case
6135    /// the caller returns the call's restart errno, with the kernel's state that
6136    /// this read, which `inject_absorbing_after` takes as its first read in the same
6137    /// turn. The guest thread has been stopped inside the call since it was
6138    /// intercepted, so such a signal arrived while the call was waiting, and Linux
6139    /// returns the restart errno for that. Linux reports a source that was ready
6140    /// when the call began, or an argument error, before any pending signal, so
6141    /// `retry_blocking_wait_with_kernel_signal_state` runs its first probe before
6142    /// this check (`inject_first_probe`). There, a source that becomes ready in a
6143    /// later turn in which a signal is also pending is put behind the signal,
6144    /// because the turn cannot tell which came first. The waits of `select` and
6145    /// `pselect6` run every probe before this check, as Linux's do_select polls
6146    /// every descriptor on each pass before it tests for a pending signal. A
6147    /// signal the wait holds until the call returns (`held_until_return`) never
6148    /// counts. A signal that the backend holds since `inject_absorbing` absorbed
6149    /// it counts as pending. A failed read is never returned as the call's errno
6150    /// (`read_wait_signal_state`).
6151    pub(crate) fn interrupted_with_state(&self) -> Result<(bool, KernelSignalState), Error> {
6152        let state = read_wait_signal_state(self.pid, self.tid)?;
6153        let could_interrupt = self.could_interrupt(&state);
6154        let held = self.held.map_or(0, |held| kernel_sigset_bit(held.signal));
6155        let held_until_return = self.held_until_return & !self.committed;
6156        // An admitted private `SIGCHLD` counts against the thread's own queue
6157        // only (`admit`).
6158        let interrupting = ((state.pending | held) & !held_until_return
6159            | state.thread_pending & self.private)
6160            & could_interrupt;
6161        if interrupting != 0 {
6162            tracing::trace!(
6163                "[tid {}] pending signals {:#x} interrupt a blocking wait",
6164                self.tid,
6165                interrupting
6166            );
6167        }
6168        Ok((interrupting != 0, state))
6169    }
6170
6171    /// Inject `call`, a probe or a mask change, absorbing the signal stops that do
6172    /// not end the wait. `Ok` carries the call's own result; `Err` ends the wait.
6173    ///
6174    /// A signal that stops the guest around an injection is dequeued from the
6175    /// kernel and held by the backend, which delivers it when the guest resumes,
6176    /// and the injection returns a restart errno instead of the call's result.
6177    /// The backend does not say which signal it holds, so the kernel's state is
6178    /// read before each injection (in a polling turn of
6179    /// `retry_blocking_wait_with_kernel_signal_state` under the full mask, the
6180    /// turn's own read: `inject_absorbing_after`), which names the signal the
6181    /// kernel dequeues next (`KernelSignalState::next_dequeued`), and again
6182    /// after a stop. The stop is identified only if exactly that signal left its queue
6183    /// and the mask and dispositions did not change; it then decides:
6184    ///
6185    /// - A signal the wait consumes (`consumed`) is delivered rather than
6186    ///   consumed. In a wait without a deadline it ends the wait with
6187    ///   `ERESTARTNOINTR`, and the call runs again, unless the wait holds it
6188    ///   until the call returns (`held_until_return`), so that its delivery now
6189    ///   could follow host timing: then the wait ends with a
6190    ///   [`HeldSignalRefusal`]. In a wait with a deadline (`deadline`) it is
6191    ///   classified as if the wait did not consume it, by the rules below.
6192    /// - A signal that would end the wait natively ends it with the restart
6193    ///   errno, except a signal the wait holds until the call returns
6194    ///   (`held_until_return`), which is held.
6195    /// - Any other `SIGCHLD`, and a default job-control stop that the wait
6196    ///   defers, are held, and so are an ignored signal and a default-ignored
6197    ///   one. The injection runs again.
6198    /// - A signal that the wait mask (`with_wait_mask`) blocks is held: Linux
6199    ///   keeps it pending until the call returns.
6200    /// - Anything else, which is the backend's own preemption signal, ends the
6201    ///   wait with `ERESTARTNOINTR`, or is held in a wait with a deadline.
6202    ///
6203    /// A pending signal that would end the wait ends it before the injection,
6204    /// with the restart errno, so it is never held, except by a probe made by
6205    /// `inject_first_probe`: the first probe of a wait, and every probe of the
6206    /// waits of `select` and `pselect6`. A stop that cannot be identified, by a
6207    /// signal that arrived after the read, does not end the wait while the
6208    /// backend held nothing whose loss matters: the backend holds that signal
6209    /// in place of any held one and the injection runs again, as the blind
6210    /// retry before this machinery did, so the wait keeps its deadline and
6211    /// every check that ends it. Which signal it was would need the backend to
6212    /// say, so it is recorded only as unidentified (`held_unidentified`), and
6213    /// it is delivered as the call returns.
6214    ///
6215    /// The backend has one slot for a held signal, and a stop replaces what it
6216    /// holds. A held signal whose loss matters (`HeldKind::Precious`), or one
6217    /// that could not be identified and so may matter, is never given up for a
6218    /// result that reads as success, with or without a deadline:
6219    ///
6220    /// - An injection that another pending signal would stop is not made. If
6221    ///   the held signal would end the wait natively, the wait ends with the
6222    ///   restart errno, as Linux ends it for that signal; otherwise it ends
6223    ///   with a `HeldSignalRefusal`. The same standard signal on the same queue
6224    ///   is the exception, because Linux merges the two.
6225    /// - A stop that cannot be identified, which has already replaced such a
6226    ///   signal, ends the wait with a `HeldSignalRefusal`.
6227    /// - After `MAX_ABSORBED_STOPS` absorbed stops, a wait without a deadline
6228    ///   ends with a `HeldSignalRefusal`, or with `ERESTARTNOINTR` when the
6229    ///   backend holds nothing whose loss matters. A wait with a deadline,
6230    ///   whose restart would start the guest's timeout again, absorbs stops
6231    ///   without a bound.
6232    ///
6233    /// Each refusal is a scope reduction. It replaces a transparent
6234    /// `ERESTARTNOINTR` restart, or, in a wait with a deadline, an injection
6235    /// that went on and let a stop replace the held signal (round-10 High 1 on
6236    /// https://github.com/rrnewton/hermit/pull/3361): the restart made the
6237    /// backend deliver the held signal before the wait completed, at a moment
6238    /// host timing could set. A complete successful repair preserving signal
6239    /// information needs additional framework support with the current generic
6240    /// API. A Detcore-only refusal or capability withdrawal can prevent the
6241    /// determinism regression.
6242    ///
6243    /// In a probe made by `inject_first_probe` that two pending signals would
6244    /// stop in turn, the first of which would end the wait, the wait ends with
6245    /// the restart errno before the probe has run, so a ready source, or an
6246    /// argument error, is reported behind the signal: the order compromise that
6247    /// a later turn of `retry_blocking_wait_with_kernel_signal_state` already
6248    /// makes (`interrupted_with_state`).
6249    pub(crate) async fn inject_absorbing<T, G, S>(
6250        &mut self,
6251        guest: &mut G,
6252        call: S,
6253    ) -> Result<Result<i64, Errno>, Error>
6254    where
6255        T: RecordOrReplay,
6256        G: Guest<Detcore<T>>,
6257        S: SyscallInfo,
6258    {
6259        self.inject_absorbing_from(guest, call, None, false).await
6260    }
6261
6262    /// The first probe of a wait, and every probe of the waits of `select` and
6263    /// `pselect6`, which runs before the turn's `interrupted_with_state` check:
6264    /// `inject_absorbing`, except that a signal that would end the wait neither
6265    /// ends it before the injection nor when it stops the injection.
6266    ///
6267    /// Linux reports a descriptor that is ready when the call begins, a queued
6268    /// event, and an argument error before it looks at pending signals:
6269    /// `do_poll` tests `signal_pending` only when no descriptor is ready, and so
6270    /// does `core_sys_select`, whose `max_select_fd` first returns `EBADF` for a
6271    /// descriptor in the sets that is not open; `ep_poll` sends queued events
6272    /// before it tests it, `do_epoll_wait` rejects a bad descriptor or
6273    /// `maxevents` first, and `do_sigtimedwait` copies its set first. So the
6274    /// probe runs even when such a signal is pending. `do_select` also polls
6275    /// every descriptor on each pass before it tests `signal_pending`, so the
6276    /// waits of `select` and `pselect6` make every probe this way; once `block`
6277    /// has blocked every blockable signal, a pending one cannot stop a probe
6278    /// there, and the check that follows a probe that would block counts it.
6279    /// A pending signal that the guest does not block stops the injection
6280    /// before the probe runs; when the stop is identified, the signal is held
6281    /// as a precious one (`HeldKind::Precious`) and the probe runs again, so
6282    /// its result stands, and the backend delivers the signal as the call
6283    /// returns. If the probe would block, the caller's check that follows
6284    /// counts the held signal (`interrupted_with_state`) and ends the wait with
6285    /// the restart errno. A stop that cannot be identified is held, as
6286    /// `inject_absorbing` describes.
6287    pub(crate) async fn inject_first_probe<T, G, S>(
6288        &mut self,
6289        guest: &mut G,
6290        call: S,
6291    ) -> Result<Result<i64, Errno>, Error>
6292    where
6293        T: RecordOrReplay,
6294        G: Guest<Detcore<T>>,
6295        S: SyscallInfo,
6296    {
6297        self.inject_absorbing_from(guest, call, None, true).await
6298    }
6299
6300    /// `inject_absorbing` in a turn whose `interrupted_with_state` read `state`
6301    /// and found no signal that ends the wait.
6302    ///
6303    /// Once `block` has blocked every blockable signal (`saved_mask`), `state`
6304    /// serves as the first read before the injection, so a polling turn reads
6305    /// `/proc` once, as it did before injections absorbed stops. The caller must
6306    /// run nothing between the two that resumes the guest thread or changes its
6307    /// signal state. `retry_blocking_wait_with_kernel_signal_state`, which serves
6308    /// ppoll, poll, epoll_pwait, epoll_wait, rt_sigtimedwait and polling futex
6309    /// waits and is the only caller, runs nothing there. So between the two reads
6310    /// the guest thread stays stopped and only it can change its own mask, and
6311    /// with threads sequentialized no other guest thread runs in its turn, so a
6312    /// signal that arrives meanwhile arrives at a
6313    /// host-timed moment: sent from outside the guest, or posted by the kernel.
6314    /// A signal that the full mask blocks stays pending, cannot stop the
6315    /// injection, and is classified at the next turn's read, as one that arrives
6316    /// just after a fresh read is. One that the full mask leaves unblocked
6317    /// (`SIGKILL`, `SIGSTOP`, glibc's two reserved signals, `PERF_EVENT_SIGNAL`)
6318    /// and that stops the injection is not named by `state`, so the stop is not
6319    /// identified and is handled as a stop by a signal that arrives just after a
6320    /// fresh read is. `interrupted_with_state` found
6321    /// no signal in `state` that ends the wait, so the check before the injection
6322    /// does not end it either.
6323    ///
6324    /// Before `block` takes effect, or when it cannot, the probe runs under the
6325    /// guest's own mask and a fresh read is taken, so a signal that arrived
6326    /// after `state` is still identified or ends the wait before the injection.
6327    pub(crate) async fn inject_absorbing_after<T, G, S>(
6328        &mut self,
6329        guest: &mut G,
6330        call: S,
6331        state: KernelSignalState,
6332    ) -> Result<Result<i64, Errno>, Error>
6333    where
6334        T: RecordOrReplay,
6335        G: Guest<Detcore<T>>,
6336        S: SyscallInfo,
6337    {
6338        let first = self.saved_mask.is_some().then_some(state);
6339        self.inject_absorbing_from(guest, call, first, false).await
6340    }
6341
6342    /// `inject_absorbing`, taking `first`, if any, as the state read before the
6343    /// first injection. For `first_probe`, a signal that would end the wait does
6344    /// not end it, and one that stops the injection is held
6345    /// (`inject_first_probe`).
6346    async fn inject_absorbing_from<T, G, S>(
6347        &mut self,
6348        guest: &mut G,
6349        call: S,
6350        mut first: Option<KernelSignalState>,
6351        first_probe: bool,
6352    ) -> Result<Result<i64, Errno>, Error>
6353    where
6354        T: RecordOrReplay,
6355        G: Guest<Detcore<T>>,
6356        S: SyscallInfo,
6357    {
6358        // Each absorbed stop takes one signal off a kernel queue, so only signals
6359        // sent faster than the injections run can reach this. A wait with a
6360        // deadline has no bound, as the blind retry before this machinery had
6361        // none: ending it would start its timeout again (`deadline`).
6362        const MAX_ABSORBED_STOPS: usize = 64;
6363        let mut absorbed = 0;
6364        loop {
6365            let before = match first.take() {
6366                Some(state) => state,
6367                None => read_wait_signal_state(self.pid, self.tid)?,
6368            };
6369            let ends_wait = self.could_interrupt(&before) & !self.held_until_return;
6370            if !first_probe && before.pending & ends_wait != 0 {
6371                return Err(self.restart_errno.into());
6372            }
6373            let next = before.next_dequeued(before.blocked);
6374            if let Some((signal, queue)) = next
6375                && self.would_replace_precious(signal, queue)
6376            {
6377                if let Some(held) = self.held
6378                    && self.held_ends_wait(&before, held.signal)
6379                {
6380                    tracing::trace!(
6381                        "[tid {}] signal {} would replace the held signal {}, which ends the wait",
6382                        self.tid,
6383                        signal,
6384                        held.signal
6385                    );
6386                    return Err(held_signal_restart_errno(self.restart_errno, held.signal).into());
6387                }
6388                // Round-10 High 1 on https://github.com/rrnewton/hermit/pull/3361:
6389                // this used to end a wait without a deadline with ERESTARTNOINTR,
6390                // and let a wait with one go on and lose the held signal.
6391                tracing::trace!(
6392                    "[tid {}] signal {} would replace the held signal {:?}; the wait is refused",
6393                    self.tid,
6394                    signal,
6395                    self.held
6396                );
6397                return Err(self.refusal(Some(signal), HeldSignalLoss::Replaced));
6398            }
6399            let result = guest.inject(call).await;
6400            let Err(errno) = result else {
6401                return Ok(result);
6402            };
6403            if !probe_was_interrupted_by_signal(errno) {
6404                return Ok(result);
6405            }
6406            let after = read_wait_signal_state(self.pid, self.tid)?;
6407            // A probe that carries the call's temporary mask, as a masked
6408            // ppoll's does (`with_wait_mask`), and that the kernel itself
6409            // ended, for a pending signal the mask lets in, leaves that mask
6410            // installed with its restore pending until the signal is delivered
6411            // as the guest resumes, so `/proc` reports a mask other than the one
6412            // before the injection. That ERESTARTNOHAND is the call's own
6413            // result, not a stop, and no injection may follow it: a repeated
6414            // probe would save the temporary mask as the guest's
6415            // (https://github.com/rrnewton/reverie/issues/1003). A pselect6
6416            // wait's probes carry no mask, so this never holds for one.
6417            if self.wait_mask.is_some()
6418                && errno == Errno::ERESTARTNOHAND
6419                && after.blocked != before.blocked
6420            {
6421                return Ok(result);
6422            }
6423            let identified =
6424                next.filter(|&(signal, queue)| stop_took_only(&before, &after, signal, queue));
6425            let Some((signal, queue)) = identified else {
6426                // A signal that `before` did not show stopped the injection. The
6427                // backend holds it in place of any held one and delivers it when
6428                // the guest resumes, but `/proc` cannot name it. If the backend
6429                // held a signal whose loss matters, or one it could not identify
6430                // either, that signal is gone, and the wait is refused. Otherwise
6431                // the wait goes on and the injection runs again, as the blind
6432                // retry before this machinery did, so the wait keeps its
6433                // deadline; the signals that `/proc` still shows are classified
6434                // as before, and every check that ends the wait still runs at
6435                // the read before the injection and at each turn's
6436                // `interrupted_with_state`.
6437                if self.holds_precious() {
6438                    tracing::trace!(
6439                        "[tid {}] a signal stop that cannot be identified replaced the held signal {:?}; the wait is refused",
6440                        self.tid,
6441                        self.held
6442                    );
6443                    return Err(self.refusal(None, HeldSignalLoss::UnidentifiedStop));
6444                }
6445                if !self.deadline && absorbed >= MAX_ABSORBED_STOPS {
6446                    // The backend now holds the unidentified signal.
6447                    self.held = None;
6448                    self.held_unidentified = true;
6449                    return Err(self.refusal(None, HeldSignalLoss::StopBound));
6450                }
6451                absorbed += 1;
6452                tracing::trace!(
6453                    "[tid {}] a signal stop that cannot be identified is held; the injection runs again",
6454                    self.tid
6455                );
6456                self.held = None;
6457                self.held_unidentified = true;
6458                continue;
6459            };
6460            let bit = kernel_sigset_bit(signal);
6461            let verdict = match self.stop_verdict(&before, signal) {
6462                // A probe made by `inject_first_probe` holds a signal that would
6463                // end the wait and runs again.
6464                StopVerdict::End(_)
6465                    if first_probe
6466                        && self.consumed & bit == 0
6467                        && self.could_interrupt(&before) & bit != 0 =>
6468                {
6469                    StopVerdict::Absorb(HeldKind::Precious)
6470                }
6471                verdict => verdict,
6472            };
6473            match verdict {
6474                StopVerdict::Absorb(kind) => {
6475                    let kind = self.held.map_or(kind, |held| held.kind.max(kind));
6476                    let unidentified = self.held_unidentified;
6477                    self.held = Some(HeldSignal {
6478                        signal,
6479                        queue,
6480                        kind,
6481                    });
6482                    self.held_unidentified = false;
6483                    if !self.deadline && absorbed >= MAX_ABSORBED_STOPS {
6484                        if kind == HeldKind::Precious || unidentified {
6485                            return Err(self.refusal(None, HeldSignalLoss::StopBound));
6486                        }
6487                        return Err(Errno::ERESTARTNOINTR.into());
6488                    }
6489                    absorbed += 1;
6490                    tracing::trace!(
6491                        "[tid {}] signal {} stopped an injection and is held ({:?})",
6492                        self.tid,
6493                        signal,
6494                        kind
6495                    );
6496                }
6497                // A signal that a wait without a deadline consumes, delivered
6498                // rather than returned: refused when the wait holds it until the
6499                // call returns, or when it replaced a held signal whose loss
6500                // matters, and otherwise ended with ERESTARTNOINTR (stop_verdict).
6501                StopVerdict::End(_)
6502                    if !self.deadline
6503                        && self.consumed & bit != 0
6504                        && (self.held_until_return & bit != 0 || self.holds_precious()) =>
6505                {
6506                    tracing::trace!(
6507                        "[tid {}] consumed signal {} stopped an injection; the wait is refused",
6508                        self.tid,
6509                        signal
6510                    );
6511                    return Err(self.refusal(Some(signal), HeldSignalLoss::Consumed));
6512                }
6513                // The backend holds `signal`, which stopped the injection.
6514                StopVerdict::End(errno) => {
6515                    return Err(held_signal_restart_errno(errno, signal).into());
6516                }
6517            }
6518        }
6519    }
6520
6521    /// What a stop by `signal` means for the wait, given the kernel's state
6522    /// `before` the injection (see `inject_absorbing`).
6523    fn stop_verdict(&self, before: &KernelSignalState, signal: i32) -> StopVerdict {
6524        let bit = kernel_sigset_bit(signal);
6525        let interrupts = if self.consumed & bit != 0 {
6526            if !self.deadline {
6527                return StopVerdict::End(Errno::ERESTARTNOINTR);
6528            }
6529            // The backend delivers the signal rather than the wait consuming it,
6530            // as Linux does for one the guest does not block, so it is classified
6531            // as if the wait did not consume it.
6532            let guest_mask = self.wait_mask.or(self.saved_mask).unwrap_or(before.blocked);
6533            before.interrupting_wait(guest_mask, self.defers_default_stops) & bit != 0
6534        } else {
6535            self.could_interrupt(before) & bit != 0
6536        };
6537        if interrupts {
6538            return if self.held_until_return & bit != 0 {
6539                StopVerdict::Absorb(HeldKind::Precious)
6540            } else {
6541                StopVerdict::End(self.restart_errno)
6542            };
6543        }
6544        if signal == libc::SIGCHLD
6545            || (self.defers_default_stops && before.default_job_control_stops() & bit != 0)
6546        {
6547            return StopVerdict::Absorb(HeldKind::Precious);
6548        }
6549        let default_ignored = [libc::SIGCONT, libc::SIGURG, libc::SIGWINCH]
6550            .into_iter()
6551            .fold(0, |set, signal| set | kernel_sigset_bit(signal));
6552        if (before.ignored | (default_ignored & !before.caught)) & bit != 0 {
6553            return StopVerdict::Absorb(HeldKind::Harmless);
6554        }
6555        // A signal that the wait mask blocks, and the guest's own mask does
6556        // not, can stop an injection that runs before `block` takes effect.
6557        // Linux keeps it pending for the whole call, under the temporary mask,
6558        // and delivers it as the call returns, so it is held and never ends
6559        // the wait (review of
6560        // https://github.com/rrnewton/hermit/pull/4051).
6561        if self.deadline || self.wait_mask.is_some_and(|mask| mask & bit != 0) {
6562            StopVerdict::Absorb(HeldKind::Precious)
6563        } else {
6564            StopVerdict::End(Errno::ERESTARTNOINTR)
6565        }
6566    }
6567
6568    /// Whether an injection that `signal`, pending on `queue`, stops would make
6569    /// the backend drop a held signal whose delivery matters
6570    /// (`HeldKind::Precious`), or one it could not identify, which may matter
6571    /// (`held_unidentified`). Linux merges a standard signal into the same one
6572    /// pending on the same queue, so that one replaces nothing.
6573    fn would_replace_precious(&self, signal: i32, queue: SignalQueue) -> bool {
6574        self.held_unidentified
6575            || self.held.is_some_and(|held| {
6576                held.kind == HeldKind::Precious
6577                    && !(signal == held.signal && queue == held.queue && signal < KERNEL_SIGRTMIN)
6578            })
6579    }
6580
6581    /// Whether the backend holds a signal whose loss matters
6582    /// (`HeldKind::Precious`), or one it could not identify, which may matter.
6583    fn holds_precious(&self) -> bool {
6584        self.held_unidentified
6585            || self
6586                .held
6587                .is_some_and(|held| held.kind == HeldKind::Precious)
6588    }
6589
6590    /// The `HeldSignalRefusal` that ends the wait, naming the signal the backend
6591    /// holds, if known, and `replacing`, the one that would replace it or that
6592    /// stopped the injection.
6593    fn refusal(&self, replacing: Option<i32>, loss: HeldSignalLoss) -> Error {
6594        Error::Tool(anyhow::Error::new(HeldSignalRefusal {
6595            wait: None,
6596            held: self.held.map(|held| held.signal),
6597            replacing,
6598            loss,
6599        }))
6600    }
6601
6602    /// Whether a held `signal` would end the wait natively, given the kernel's
6603    /// state `before` an injection: it could interrupt the wait, and the wait
6604    /// does not hold it until the call returns (`held_until_return`).
6605    fn held_ends_wait(&self, before: &KernelSignalState, signal: i32) -> bool {
6606        self.could_interrupt(before) & !self.held_until_return & kernel_sigset_bit(signal) != 0
6607    }
6608
6609    /// Block every blockable signal for the rest of the wait, and keep blocked the
6610    /// signals the guest blocks itself, which the blockable set leaves out (the C
6611    /// library's reserved signals). `scratch` is a guest cell for the mask when
6612    /// the caller holds the only scratch-stack guard; otherwise a fresh guard is
6613    /// taken. The change goes through `inject_absorbing`, so a signal that stops
6614    /// it is handled as one that stops a probe. If that ends the wait, the guest's
6615    /// mask is still recorded, and the caller's final `restore` reads the
6616    /// kernel's mask and puts the guest's back if the change took effect.
6617    ///
6618    /// If the mask cannot be changed for another reason, the guest's mask is
6619    /// unchanged and no wait call can return that error, so the wait continues
6620    /// with every probe under the guest's own mask, as the first probe runs, and
6621    /// `block` is not tried again (`needs_block`). `interrupted_with_state` reads
6622    /// that mask from the kernel each turn. The scratch-stack commit fails this
6623    /// way on a guest stack with no room below its stack pointer
6624    /// (https://github.com/rrnewton/hermit/issues/3328); poll and epoll_wait
6625    /// probes need no scratch of their own, so for them this is the first need.
6626    pub(crate) async fn block<'a, T, G>(
6627        &mut self,
6628        guest: &mut G,
6629        scratch: Option<AddrMut<'a, KernelSigset>>,
6630    ) -> Result<(), Error>
6631    where
6632        T: RecordOrReplay,
6633        G: Guest<Detcore<T>>,
6634    {
6635        let guest_mask = read_wait_signal_state(self.pid, self.tid)?.blocked;
6636        let mask = blocked_signal_mask() | guest_mask;
6637        let result = match scratch {
6638            Some(cell) => match guest.memory().write_value(cell, &mask) {
6639                Ok(()) => {
6640                    self.inject_absorbing(guest, set_signal_mask_call(cell.into()))
6641                        .await
6642                }
6643                Err(errno) => Ok(Err(errno)),
6644            },
6645            None => {
6646                let mut stack = guest.stack().await;
6647                let cell = stack.push(mask);
6648                match stack.commit() {
6649                    Ok(_guard) => {
6650                        self.inject_absorbing(guest, set_signal_mask_call(cell))
6651                            .await
6652                    }
6653                    Err(_) => Ok(Err(Errno::EFAULT)),
6654                }
6655            }
6656        };
6657        match result {
6658            Ok(Ok(_)) => {
6659                self.saved_mask = Some(guest_mask);
6660                Ok(())
6661            }
6662            Ok(Err(errno)) => {
6663                tracing::debug!(
6664                    "[tid {}] cannot block signals for a wait ({}); its probes run under the \
6665                     guest's mask",
6666                    self.tid,
6667                    errno
6668                );
6669                self.unblockable = true;
6670                Ok(())
6671            }
6672            Err(error) => {
6673                self.saved_mask = Some(guest_mask);
6674                Err(error)
6675            }
6676        }
6677    }
6678
6679    /// Block every blockable signal until restored, as `block` does,
6680    /// but through `Guest::set_blocked_signals`: no guest code runs, so no
6681    /// pending signal is taken from the kernel's queues, and every one stays
6682    /// there for the kernel to deliver, in its own order, once `restore` puts
6683    /// the guest's mask back. `block` instead injects `rt_sigprocmask`, which a
6684    /// pending signal can stop before it runs, moving that signal into the
6685    /// backend's single held slot, where a second one replaces it
6686    /// (https://github.com/rrnewton/hermit/issues/4273, stage 2). No scratch
6687    /// memory is needed, here or in `restore` (the Claude review of the R2b-2
6688    /// design, M1).
6689    ///
6690    /// `host_timed` is the turn's host-timed signals, held until the call
6691    /// returns as `hold_until_return` holds them.
6692    ///
6693    /// Returns whether the mask is now blocked. A refusal changes nothing
6694    /// (`MaskChangeRefused::Unsupported`, `RestorePending`), and neither does a
6695    /// failure that put the old mask back, so the wait goes on under the
6696    /// guest's own mask, as before this existed. A failure that leaves the mask
6697    /// unknown ends the run before the guest resumes
6698    /// ([`BlockedWaitSignalError::MaskNotRestored`]).
6699    pub(crate) async fn block_in_place<T, G>(
6700        &mut self,
6701        guest: &mut G,
6702        host_timed: KernelSigset,
6703    ) -> Result<bool, Error>
6704    where
6705        T: RecordOrReplay,
6706        G: Guest<Detcore<T>>,
6707    {
6708        // As `install_turn_mask` does for `writev`: the signals a host-timed
6709        // source can post (`host_timed`) are held until the call returns, so a
6710        // fallback injected restoration holds one that stops it
6711        // (`absorb_restoration_stop`) rather than counting it lost.
6712        self.hold_until_return(host_timed);
6713        let guest_mask = read_wait_signal_state(self.pid, self.tid)?.blocked;
6714        match guest
6715            .set_blocked_signals(blocked_signal_mask() | guest_mask)
6716            .await
6717        {
6718            Ok(old) => {
6719                self.saved_mask = Some(old);
6720                self.blocked_in_place = true;
6721                Ok(true)
6722            }
6723            Err(MaskChangeRefused::Failed {
6724                errno,
6725                mask_unknown: true,
6726            }) => Err(self.mask_not_restored(1, Some(errno))),
6727            Err(refused) => {
6728                tracing::debug!(
6729                    "[tid {}] cannot block signals in place for a wait ({}); its attempts run \
6730                     under the guest's mask",
6731                    self.tid,
6732                    refused
6733                );
6734                // Not tried again for this wait (`needs_block`).
6735                self.unblockable = true;
6736                Ok(false)
6737            }
6738        }
6739    }
6740
6741    /// The error that ends the run when the guest's mask cannot be put back.
6742    fn mask_not_restored(&self, attempts: usize, last_error: Option<Errno>) -> Error {
6743        Error::Tool(anyhow::Error::new(
6744            BlockedWaitSignalError::MaskNotRestored {
6745                pid: self.pid,
6746                tid: self.tid,
6747                attempts,
6748                last_error,
6749            },
6750        ))
6751    }
6752
6753    /// Release an in-place probe mask before yielding, without executing guest
6754    /// code. Linux's `PTRACE_SETSIGMASK` updates the stopped task's mask and
6755    /// leaves pending recalculation until resume (`kernel/ptrace.c`, v7.1.3),
6756    /// so a queued signal stays visible for the next protected probe.
6757    ///
6758    /// Unlike final `restore`, this cannot fall back to an injection: a signal
6759    /// stopping that injection could disappear into the backend's held slot
6760    /// before the next turn reads it. A known-unchanged refusal retains the
6761    /// saved state for final cleanup but is fatal to this retry loop. An unknown
6762    /// mask is fatal without any further speculative restoration.
6763    async fn release_probe_mask_in_place<T, G>(&mut self, guest: &mut G) -> Result<(), Error>
6764    where
6765        T: RecordOrReplay,
6766        G: Guest<Detcore<T>>,
6767    {
6768        let Some(guest_mask) = self.saved_mask else {
6769            // An initial refusal to block still takes the original unmasked
6770            // fallback; there is no private mask to release in that case.
6771            return Ok(());
6772        };
6773        if !self.blocked_in_place {
6774            return Err(self.mask_not_restored(0, None));
6775        }
6776        match guest.set_blocked_signals(guest_mask).await {
6777            Ok(_) => {
6778                self.saved_mask = None;
6779                self.blocked_in_place = false;
6780                Ok(())
6781            }
6782            Err(MaskChangeRefused::Gone) => {
6783                self.saved_mask = None;
6784                self.blocked_in_place = false;
6785                // The same unobserved outcome as read_wait_signal_state for
6786                // a vanished task. Do not hand off or probe it again merely
6787                // to rediscover the exit through procfs.
6788                Err(Errno::ERESTARTNOINTR.into())
6789            }
6790            Err(MaskChangeRefused::Failed {
6791                errno,
6792                mask_unknown: true,
6793            }) => {
6794                self.saved_mask = None;
6795                self.blocked_in_place = false;
6796                Err(self.mask_not_restored(1, Some(errno)))
6797            }
6798            Err(refused) => {
6799                let errno = match refused {
6800                    MaskChangeRefused::Failed { errno, .. } => Some(errno),
6801                    _ => None,
6802                };
6803                Err(self.mask_not_restored(1, errno))
6804            }
6805        }
6806    }
6807
6808    /// Put back the guest's mask, if `block` replaced it. A signal can stop the guest
6809    /// around the call, before or after it takes effect, so success is read back
6810    /// from the kernel and the call is repeated until the kernel reports the
6811    /// guest's mask. The guest never resumes with every signal blocked: if the
6812    /// mask cannot be put back, the run ends with
6813    /// [`BlockedWaitSignalError::MaskNotRestored`].
6814    ///
6815    /// The backend holds the signal that stops one of these injections, in place
6816    /// of any it held, as it does for a probe (`inject_absorbing`). Each such
6817    /// stop is recorded from the read before the injection and the read after it
6818    /// (`absorb_restoration_stop`). If one replaced a held signal whose loss
6819    /// matters, or one that could not be identified, the mask is still put back
6820    /// and then the first such loss is returned as a [`HeldSignalRefusal`]
6821    /// (`HeldSignalLoss::Restoration`), which the caller turns into the wait's
6822    /// result (`result_after_restore`). Round-11 High F1 on
6823    /// https://github.com/rrnewton/hermit/pull/3361: these stops used to be
6824    /// retried without that check, so the held signal was lost silently.
6825    pub(crate) async fn restore<'a, T, G>(
6826        &mut self,
6827        guest: &mut G,
6828        scratch: Option<AddrMut<'a, KernelSigset>>,
6829    ) -> Result<(), Error>
6830    where
6831        T: RecordOrReplay,
6832        G: Guest<Detcore<T>>,
6833    {
6834        let Some(guest_mask) = self.saved_mask else {
6835            return Ok(());
6836        };
6837        // A mask `block_in_place` installed goes back the same way, with no
6838        // injection, so no signal can stop the restoration and none is held.
6839        // That call is refused under the same rules as the block; a refusal
6840        // or a failure that left the mask as it was falls back to the
6841        // injected restoration below, which needs scratch memory (the Codex
6842        // and Claude reviews of https://github.com/rrnewton/reverie/pull/1083,
6843        // F4).
6844        if std::mem::take(&mut self.blocked_in_place) {
6845            match guest.set_blocked_signals(guest_mask).await {
6846                Ok(_) | Err(MaskChangeRefused::Gone) => {
6847                    self.saved_mask = None;
6848                    return Ok(());
6849                }
6850                Err(MaskChangeRefused::Failed {
6851                    errno,
6852                    mask_unknown: true,
6853                }) => {
6854                    self.saved_mask = None;
6855                    return Err(self.mask_not_restored(1, Some(errno)));
6856                }
6857                Err(refused) => {
6858                    tracing::debug!(
6859                        "[tid {}] cannot restore the mask in place ({}); injecting it",
6860                        self.tid,
6861                        refused
6862                    );
6863                }
6864            }
6865        }
6866        // `stop_verdict` reads the guest's mask from `saved_mask`, so it is cleared
6867        // only once the restoration has ended.
6868        let restored = self.restore_mask(guest, guest_mask, scratch).await;
6869        self.saved_mask = None;
6870        restored
6871    }
6872
6873    /// `restore`, for a wait whose guest mask is `guest_mask`.
6874    async fn restore_mask<'a, T, G>(
6875        &mut self,
6876        guest: &mut G,
6877        guest_mask: KernelSigset,
6878        scratch: Option<AddrMut<'a, KernelSigset>>,
6879    ) -> Result<(), Error>
6880    where
6881        T: RecordOrReplay,
6882        G: Guest<Detcore<T>>,
6883    {
6884        // Under the all-blocked mask only a signal that cannot be blocked (SIGKILL,
6885        // SIGSTOP) can stop the call before it takes effect, and each such stop needs
6886        // another signal sent, so a bound this large is never reached in practice.
6887        const ATTEMPTS: usize = 16;
6888        let mut attempts = 0;
6889        let mut last_error = None;
6890        // The kernel's state before an injection that a signal stopped, until the
6891        // next read records the stop.
6892        let mut stopped = None;
6893        // The first held signal that a stop replaced, as the refusal to return once
6894        // the mask is back.
6895        let mut loss = None;
6896        while attempts < ATTEMPTS {
6897            let state = read_wait_signal_state(self.pid, self.tid)?;
6898            if let Some(before) = stopped.take() {
6899                let replaced = self.absorb_restoration_stop(&before, &state);
6900                loss = loss.or(replaced);
6901            }
6902            if state.blocked == guest_mask {
6903                return loss.map_or(Ok(()), Err);
6904            }
6905            attempts += 1;
6906            match inject_signal_mask(guest, guest_mask, scratch).await {
6907                Ok(()) => return loss.map_or(Ok(()), Err),
6908                // Stopped by a signal: the next read records the stop and decides
6909                // whether the mask took effect.
6910                Err(errno) if probe_was_interrupted_by_signal(errno) => {
6911                    last_error = Some(errno);
6912                    stopped = Some(state);
6913                }
6914                // The call could not run, so the mask is unchanged.
6915                Err(errno) => {
6916                    last_error = Some(errno);
6917                    break;
6918                }
6919            }
6920        }
6921        let state = read_wait_signal_state(self.pid, self.tid)?;
6922        if let Some(before) = stopped.take() {
6923            let replaced = self.absorb_restoration_stop(&before, &state);
6924            loss = loss.or(replaced);
6925        }
6926        if state.blocked == guest_mask {
6927            return loss.map_or(Ok(()), Err);
6928        }
6929        Err(Error::Tool(anyhow::Error::new(
6930            BlockedWaitSignalError::MaskNotRestored {
6931                pid: self.pid,
6932                tid: self.tid,
6933                attempts,
6934                last_error,
6935            },
6936        )))
6937    }
6938
6939    /// Record a signal stop of an injection of `restore`, given the kernel's state
6940    /// `before` the injection and `after` it, as `inject_absorbing` records a stop
6941    /// of a probe: the backend holds the signal that stopped it in place of any it
6942    /// held. Returns the `HeldSignalRefusal` when that replaced a held signal
6943    /// whose loss matters, or one that could not be identified
6944    /// (`would_replace_precious`, `holds_precious`), naming the held signal as it
6945    /// was before the stop.
6946    ///
6947    /// A stop is identified only when exactly the signal that the kernel would
6948    /// take next under the mask `before` left its queue and the mask did not
6949    /// change (`stop_took_only`); a stop after which the mask took effect, or one
6950    /// that `before` did not show, is recorded as unidentified. The call's result
6951    /// is already decided, so a signal that would have ended the wait is kept as
6952    /// one whose delivery matters.
6953    fn absorb_restoration_stop(
6954        &mut self,
6955        before: &KernelSignalState,
6956        after: &KernelSignalState,
6957    ) -> Option<Error> {
6958        let identified = before
6959            .next_dequeued(before.blocked)
6960            .filter(|&(signal, queue)| stop_took_only(before, after, signal, queue));
6961        let Some((signal, queue)) = identified else {
6962            let loss = self
6963                .holds_precious()
6964                .then(|| self.refusal(None, HeldSignalLoss::Restoration));
6965            tracing::trace!(
6966                "[tid {}] a signal stop that cannot be identified stopped the mask restoration; it replaced the held signal {:?}",
6967                self.tid,
6968                self.held
6969            );
6970            self.held = None;
6971            self.held_unidentified = true;
6972            return loss;
6973        };
6974        let loss = self
6975            .would_replace_precious(signal, queue)
6976            .then(|| self.refusal(Some(signal), HeldSignalLoss::Restoration));
6977        let kind = match self.stop_verdict(before, signal) {
6978            StopVerdict::Absorb(kind) => kind,
6979            StopVerdict::End(_) => HeldKind::Precious,
6980        };
6981        let kind = self.held.map_or(kind, |held| held.kind.max(kind));
6982        tracing::trace!(
6983            "[tid {}] signal {} stopped the mask restoration and is held ({:?}) in place of {:?}",
6984            self.tid,
6985            signal,
6986            kind,
6987            self.held
6988        );
6989        self.held = Some(HeldSignal {
6990            signal,
6991            queue,
6992            kind,
6993        });
6994        self.held_unidentified = false;
6995        loss
6996    }
6997}
6998
6999/// The wait's result once `restore` has put the guest's mask back. A
7000/// [`HeldSignalRefusal`] from the restoration (`HeldSignalLoss::Restoration`)
7001/// replaces `result`, as a loss during the wait does, unless `result` is
7002/// already such a refusal, which came first, or a fatal mask-restoration
7003/// failure. Cleanup must not turn that failure into an unsupported-operation
7004/// restart through `refuse_held_signal_loss`. Every other
7005/// restoration error, such as [`BlockedWaitSignalError::MaskNotRestored`], is
7006/// returned as the error that ends the run, as before.
7007pub(crate) fn result_after_restore(
7008    restored: Result<(), Error>,
7009    result: Result<i64, Error>,
7010) -> Result<Result<i64, Error>, Error> {
7011    let is_refusal =
7012        |error: &Error| matches!(error, Error::Tool(error) if error.is::<HeldSignalRefusal>());
7013    let is_mask_failure = |error: &Error| {
7014        matches!(error, Error::Tool(error) if matches!(
7015            error.downcast_ref::<BlockedWaitSignalError>(),
7016            Some(BlockedWaitSignalError::MaskNotRestored { .. })
7017        ))
7018    };
7019    match restored {
7020        Ok(()) => Ok(result),
7021        Err(refusal) if is_refusal(&refusal) => match &result {
7022            Err(error) if is_refusal(error) || is_mask_failure(error) => Ok(result),
7023            _ => Ok(Err(refusal)),
7024        },
7025        Err(error) => Err(error),
7026    }
7027}
7028
7029/// Replace the guest's signal mask with `mask`, written to `scratch` or, when the
7030/// caller holds no scratch-stack guard, to a fresh one.
7031async fn inject_signal_mask<'a, T, G>(
7032    guest: &mut G,
7033    mask: KernelSigset,
7034    scratch: Option<AddrMut<'a, KernelSigset>>,
7035) -> Result<(), Errno>
7036where
7037    T: RecordOrReplay,
7038    G: Guest<Detcore<T>>,
7039{
7040    match scratch {
7041        Some(cell) => {
7042            guest.memory().write_value(cell, &mask)?;
7043            guest
7044                .inject(set_signal_mask_call(cell.into()))
7045                .await
7046                .map(drop)
7047        }
7048        None => {
7049            let mut stack = guest.stack().await;
7050            let cell = stack.push(mask);
7051            let _guard = stack.commit().map_err(|_| Errno::EFAULT)?;
7052            guest.inject(set_signal_mask_call(cell)).await.map(drop)
7053        }
7054    }
7055}
7056
7057/// `rt_sigprocmask(SIG_SETMASK, cell, NULL)`.
7058fn set_signal_mask_call(cell: Addr<'_, KernelSigset>) -> syscalls::RtSigprocmask {
7059    syscalls::RtSigprocmask::new()
7060        .with_how(libc::SIG_SETMASK)
7061        .with_set(Some(cell.cast()))
7062        .with_oldset(None)
7063        .with_sigsetsize(KERNEL_SIGSET_SIZE)
7064}
7065
7066/// Whether a signal stop between the kernel states `before` and `after` took
7067/// exactly `signal` off `queue`: it is no longer there, no other signal left
7068/// either queue, and the mask and dispositions did not change. Signals may have
7069/// arrived in between.
7070fn stop_took_only(
7071    before: &KernelSignalState,
7072    after: &KernelSignalState,
7073    signal: i32,
7074    queue: SignalQueue,
7075) -> bool {
7076    let bit = kernel_sigset_bit(signal);
7077    let other = match queue {
7078        SignalQueue::Thread => SignalQueue::Shared,
7079        SignalQueue::Shared => SignalQueue::Thread,
7080    };
7081    before.blocked == after.blocked
7082        && before.ignored == after.ignored
7083        && before.caught == after.caught
7084        && after.queued(queue) & bit == 0
7085        && before.queued(queue) & !bit & !after.queued(queue) == 0
7086        && before.queued(other) & !after.queued(other) == 0
7087}
7088
7089/// Whether an injected call's error means a signal stopped the guest around it
7090/// rather than that the call produced a result: the backend's restart errno, or
7091/// `EINTR` where a backend runs the call itself. None of these calls blocks.
7092pub(crate) fn probe_was_interrupted_by_signal(errno: Errno) -> bool {
7093    matches!(
7094        errno,
7095        Errno::EINTR
7096            | Errno::ERESTARTSYS
7097            | Errno::ERESTARTNOINTR
7098            | Errno::ERESTARTNOHAND
7099            | Errno::ERESTART_RESTARTBLOCK
7100    )
7101}
7102
7103pub(crate) async fn record_retry_event<G, C, T>(guest: &mut G, call: C)
7104where
7105    C: SyscallInfo,
7106    T: RecordOrReplay,
7107    G: Guest<Detcore<T>>,
7108{
7109    let dettid = guest.thread_state().dettid;
7110    let cfg = &guest.config();
7111    if cfg.sequentialize_threads && cfg.observes_schedevents() {
7112        trace_schedevent(
7113            guest,
7114            with_guest_time(
7115                guest,
7116                SchedEvent::syscall(dettid, call.number(), SyscallPhase::Polling),
7117            ),
7118            true,
7119        )
7120        .await;
7121    }
7122}
7123
7124// A helper function for enriching the schedevent with local information.
7125pub fn with_guest_time<G, T>(guest: &G, event: SchedEvent) -> SchedEvent
7126where
7127    G: Guest<Detcore<T>>,
7128    T: RecordOrReplay,
7129{
7130    let dettime = &guest.thread_state().thread_logical_time;
7131    event.with_dettime(dettime)
7132}
7133
7134// Enrich the event with the RIP register from the current guest state, but only if it is unset.
7135pub async fn with_guest_rip<G, T>(guest: &mut G, mut event: SchedEvent) -> SchedEvent
7136where
7137    G: Guest<Detcore<T>>,
7138    T: RecordOrReplay,
7139{
7140    assert!(event.end_rip.is_none());
7141
7142    let regs = guest.regs().await;
7143    let end_rip =
7144        NonZeroUsize::new(crate::arch::instruction_pointer(&regs).try_into().unwrap()).unwrap();
7145    event.end_rip = Some(end_rip);
7146    event
7147}
7148
7149// Convert to absolute logical time point for the timeout.
7150// 0 duration means no timeout, and this will return None.
7151pub async fn millis_duration_to_absolute_timeout<G: Guest<Detcore<T>>, T: RecordOrReplay>(
7152    guest: &mut G,
7153    timeout_millis: i32,
7154) -> Option<LogicalTime> {
7155    match positive_millis_as_nanos(timeout_millis) {
7156        Some(timeout_nanos) => nanos_duration_to_absolute_timeout(guest, timeout_nanos).await,
7157        None => None,
7158    }
7159}
7160
7161/// Milliseconds to nanoseconds for a strictly positive timeout; `None` for the
7162/// non-positive values Linux treats as "return immediately" (0) or "wait
7163/// forever" (-1), neither of which is a deadline.
7164///
7165/// Kept as a separate, unit-bracketed function on purpose. This conversion was
7166/// previously inlined as `* 1000` instead of `* 1_000_000`, which made every
7167/// finite deadline 1000x too short (a 1 ms timeout expired after 1 us). That
7168/// is invisible in an end-to-end test that only checks a syscall's return
7169/// value, so the arithmetic is pinned here by `millis_to_nanos_conversion`.
7170fn positive_millis_as_nanos(timeout_millis: i32) -> Option<u128> {
7171    (timeout_millis > 0).then(|| (timeout_millis as u128) * 1_000_000)
7172}
7173
7174// Convert to absolute logical time point for the timeout.
7175// 0 duration means no timeout, and this will return None.
7176pub async fn nanos_duration_to_absolute_timeout<G: Guest<Detcore<T>>, T: RecordOrReplay>(
7177    guest: &mut G,
7178    timeout_nanos: u128,
7179) -> Option<LogicalTime> {
7180    if timeout_nanos > 0 {
7181        let ns_delta = Duration::from_nanos(timeout_nanos as u64);
7182        let base_time = thread_observe_time(guest).await;
7183        let target_time = base_time + ns_delta;
7184        Some(target_time)
7185    } else {
7186        None
7187    }
7188}
7189
7190/// The number of `pollfd` entries `poll` and `ppoll` read. Linux declares
7191/// their `nfds` parameter `unsigned int`, so only the low 32 bits of the
7192/// register count; a guest may leave the high word set. Every reader of a
7193/// guest's pollfd array sizes it from this, never from `nfds` itself.
7194pub fn poll_nfds(nfds: libc::nfds_t) -> u32 {
7195    nfds as u32
7196}
7197
7198#[cfg(test)]
7199mod tests {
7200    use super::*;
7201
7202    /// The backend drops a handler when its thread dies during a wait, and
7203    /// the shield with it: that must not panic, though the mask was never put
7204    /// back (a debug assertion did, and ended the run).
7205    #[test]
7206    fn a_shield_dropped_with_its_handler_ends_quietly() {
7207        drop(IgnoredSignalShield {
7208            guest_mask: Some(0),
7209        });
7210        drop(IgnoredSignalShield::none());
7211    }
7212
7213    /// The drop log must not assert the thread is gone: Drop runs when
7214    /// the backend cancels the handler, which also happens when a
7215    /// backend error ends the run while the thread still exists.
7216    #[test]
7217    fn a_dropped_shield_log_does_not_claim_the_thread_is_gone() {
7218        let message = shield_drop_message(0x2a);
7219        assert!(message.contains("ended with its handler"), "{message}");
7220        assert!(message.contains("unrestored"), "{message}");
7221        assert!(!message.contains("is gone"), "{message}");
7222        assert!(!message.contains("are gone"), "{message}");
7223    }
7224
7225    #[test]
7226    fn an_ignored_signal_shield_blocks_only_what_the_guest_ignores() {
7227        use crate::syscalls::threads::kernel_sigset_bit as bit;
7228        let state = |blocked, ignored, caught| KernelSignalState {
7229            blocked,
7230            ignored,
7231            caught,
7232            ..Default::default()
7233        };
7234        let default_ignored =
7235            bit(libc::SIGCHLD) | bit(libc::SIGCONT) | bit(libc::SIGURG) | bit(libc::SIGWINCH);
7236        // With every disposition at its default, the shield blocks exactly the
7237        // default-ignored signals, on top of the thread's own mask.
7238        assert_eq!(
7239            ignored_signal_shield_mask(&state(bit(libc::SIGUSR2), 0, 0)),
7240            Some(bit(libc::SIGUSR2) | default_ignored)
7241        );
7242        // A signal set to SIG_IGN joins them; a caught one leaves them.
7243        assert_eq!(
7244            ignored_signal_shield_mask(&state(0, bit(libc::SIGUSR1), bit(libc::SIGCHLD))),
7245            Some(bit(libc::SIGUSR1) | (default_ignored & !bit(libc::SIGCHLD)))
7246        );
7247        // Nothing to block when every ignored signal is caught or already blocked.
7248        assert_eq!(
7249            ignored_signal_shield_mask(&state(
7250                bit(libc::SIGCONT) | bit(libc::SIGURG),
7251                0,
7252                bit(libc::SIGCHLD) | bit(libc::SIGWINCH)
7253            )),
7254            None
7255        );
7256        // A refused change leaves the wait under the guest's own mask, unless the
7257        // backend could not confirm what the mask is.
7258        for refused in [
7259            MaskChangeRefused::Unsupported,
7260            MaskChangeRefused::Gone,
7261            MaskChangeRefused::RestorePending {
7262                saved: 0,
7263                effective: 1,
7264            },
7265            MaskChangeRefused::Failed {
7266                errno: Errno::ESRCH,
7267                mask_unknown: false,
7268            },
7269        ] {
7270            assert!(shield_refusal_leaves_the_mask_known(&refused), "{refused}");
7271        }
7272        assert!(!shield_refusal_leaves_the_mask_known(
7273            &MaskChangeRefused::Failed {
7274                errno: Errno::ESRCH,
7275                mask_unknown: true,
7276            }
7277        ));
7278    }
7279
7280    /// Linux polls every entry with a non-negative descriptor and ignores the
7281    /// rest (fs/select.c `do_pollfd`), so only those decide whether a poll is
7282    /// guest-held; the `events` and `revents` halves are not descriptors.
7283    #[test]
7284    fn pollfd_descriptors_are_the_non_negative_entries() {
7285        let entries = [
7286            libc::pollfd {
7287                fd: 7,
7288                events: libc::POLLIN,
7289                revents: 0,
7290            },
7291            libc::pollfd {
7292                fd: -1,
7293                events: libc::POLLIN,
7294                revents: 0,
7295            },
7296            libc::pollfd {
7297                fd: 0,
7298                events: libc::POLLOUT,
7299                revents: libc::POLLHUP,
7300            },
7301        ];
7302        let bytes: Vec<u8> = entries
7303            .iter()
7304            .flat_map(|entry| {
7305                let mut bytes = entry.fd.to_ne_bytes().to_vec();
7306                bytes.extend(entry.events.to_ne_bytes());
7307                bytes.extend(entry.revents.to_ne_bytes());
7308                bytes
7309            })
7310            .collect();
7311        assert_eq!(pollfd_descriptors(&bytes), vec![7, 0]);
7312        assert!(pollfd_descriptors(&[]).is_empty());
7313    }
7314
7315    /// Bracket the millisecond-to-nanosecond timeout conversion in both
7316    /// directions: the non-positive values that are NOT deadlines, and the
7317    /// positive values whose magnitude must be exactly 1e6 ns per ms.
7318    ///
7319    /// The 1 ms case is the specific regression guard: an inlined `* 1000`
7320    /// yields 1_000 here instead of 1_000_000, i.e. a deadline 1000x too
7321    /// short. Asserting the exact value (not merely "nonzero" or "greater
7322    /// than") is what makes that failure visible.
7323    #[test]
7324    fn millis_to_nanos_conversion() {
7325        // Not deadlines: -1 is "infinite", 0 is "return immediately".
7326        assert_eq!(positive_millis_as_nanos(-1), None);
7327        assert_eq!(positive_millis_as_nanos(i32::MIN), None);
7328        assert_eq!(positive_millis_as_nanos(0), None);
7329
7330        // Positive timeouts: exactly 1e6 nanoseconds per millisecond.
7331        assert_eq!(positive_millis_as_nanos(1), Some(1_000_000));
7332        assert_eq!(positive_millis_as_nanos(1_000), Some(1_000_000_000));
7333        assert_eq!(
7334            positive_millis_as_nanos(i32::MAX),
7335            Some(i32::MAX as u128 * 1_000_000)
7336        );
7337
7338        // The scale itself, stated independently of any single case so a
7339        // future refactor cannot satisfy the above by coincidence.
7340        for millis in [1, 2, 7, 250, 1_000, 86_400_000] {
7341            assert_eq!(
7342                positive_millis_as_nanos(millis),
7343                Some(millis as u128 * 1_000_000),
7344                "1 ms must convert to 1_000_000 ns, not 1_000"
7345            );
7346        }
7347    }
7348
7349    #[test]
7350    fn connect_nonblocking_results() {
7351        let call = reverie::syscalls::Connect::new();
7352        assert!(call.syscall_would_have_blocked(Err(Errno::EINPROGRESS)));
7353        assert!(call.syscall_would_have_blocked(Err(Errno::EALREADY)));
7354        assert_eq!(
7355            call.normalize_nonblocking_result(Err(Errno::EISCONN), true),
7356            Ok(0)
7357        );
7358        assert_eq!(
7359            call.normalize_nonblocking_result(Err(Errno::EISCONN), false),
7360            Err(Errno::EISCONN)
7361        );
7362    }
7363
7364    /// `MSG_DONTWAIT` keeps every socket send and receive from waiting,
7365    /// whatever else its flags carry, and no other flag does: recvmmsg's
7366    /// `MSG_WAITFORONE` still waits for the first datagram. Calls with no
7367    /// per-call flags always follow the file.
7368    #[test]
7369    fn only_msg_dontwait_keeps_a_socket_call_from_waiting() {
7370        use reverie::syscalls::*;
7371        let dontwait = libc::MSG_DONTWAIT;
7372        let others = libc::MSG_PEEK | libc::MSG_WAITALL | libc::MSG_NOSIGNAL | libc::MSG_TRUNC;
7373        let cases: [(&str, bool, bool); 6] = [
7374            (
7375                "recvfrom",
7376                Recvfrom::new()
7377                    .with_flags(others | dontwait)
7378                    .flags_forbid_waiting(),
7379                Recvfrom::new().with_flags(others).flags_forbid_waiting(),
7380            ),
7381            (
7382                "recvmsg",
7383                Recvmsg::new().with_flags(dontwait).flags_forbid_waiting(),
7384                Recvmsg::new().with_flags(others).flags_forbid_waiting(),
7385            ),
7386            (
7387                "recvmmsg",
7388                Recvmmsg::new()
7389                    .with_flags((libc::MSG_WAITFORONE | dontwait) as u32)
7390                    .flags_forbid_waiting(),
7391                Recvmmsg::new()
7392                    .with_flags(libc::MSG_WAITFORONE as u32)
7393                    .flags_forbid_waiting(),
7394            ),
7395            (
7396                "sendto",
7397                Sendto::new()
7398                    .with_flags(dontwait as u32)
7399                    .flags_forbid_waiting(),
7400                Sendto::new()
7401                    .with_flags(others as u32)
7402                    .flags_forbid_waiting(),
7403            ),
7404            (
7405                "sendmsg",
7406                Sendmsg::new().with_flags(dontwait).flags_forbid_waiting(),
7407                Sendmsg::new().with_flags(others).flags_forbid_waiting(),
7408            ),
7409            (
7410                "sendmmsg",
7411                Sendmmsg::new().with_flags(dontwait).flags_forbid_waiting(),
7412                Sendmmsg::new().with_flags(others).flags_forbid_waiting(),
7413            ),
7414        ];
7415        for (name, with_dontwait, without) in cases {
7416            assert!(with_dontwait, "{name} with MSG_DONTWAIT may wait");
7417            assert!(!without, "{name} without MSG_DONTWAIT never waits");
7418        }
7419        assert!(!Read::new().flags_forbid_waiting());
7420        assert!(
7421            Preadv2::new()
7422                .with_flags(libc::RWF_NOWAIT)
7423                .flags_forbid_waiting(),
7424            "preadv2 with RWF_NOWAIT may wait"
7425        );
7426        assert!(
7427            !Preadv2::new()
7428                .with_flags(libc::RWF_HIPRI)
7429                .flags_forbid_waiting(),
7430            "preadv2 without RWF_NOWAIT never waits"
7431        );
7432        assert!(!Accept4::new().flags_forbid_waiting());
7433        assert!(!Connect::new().flags_forbid_waiting());
7434    }
7435
7436    /// The `ioaction_based_on_fd_status` decision itself: a call whose own
7437    /// flags forbid waiting (MSG_DONTWAIT, RWF_NOWAIT) on a descriptor the
7438    /// guest left blocking runs once (PassThru), where the same call without
7439    /// them is retried (NonblockizeRetry). A mutant dropping the flags term
7440    /// fails this test.
7441    #[test]
7442    fn ioaction_decision_runs_a_nowait_call_once_on_a_blocking_descriptor() {
7443        use super::IOAction;
7444        use super::ioaction_for_fd_status;
7445        // FT with no-wait flags: runs once, as Linux does.
7446        assert_eq!(
7447            ioaction_for_fd_status(3, false, true, true),
7448            IOAction::PassThru
7449        );
7450        // FT without them: simulate blocking by retrying.
7451        assert_eq!(
7452            ioaction_for_fd_status(3, false, true, false),
7453            IOAction::NonblockizeRetry
7454        );
7455        // TT already runs once, flags or not; FF can only block externally.
7456        assert_eq!(
7457            ioaction_for_fd_status(3, true, true, false),
7458            IOAction::PassThru
7459        );
7460        assert_eq!(
7461            ioaction_for_fd_status(3, false, false, true),
7462            IOAction::Blocking
7463        );
7464    }
7465
7466    /// `MSG_DONTWAIT` keeps every socket send and receive from waiting,
7467    /// whatever else its flags carry, and no other flag does: recvmmsg's
7468    /// `MSG_WAITFORONE` still waits for the first datagram. Calls with no
7469    /// per-call flags always follow the file.
7470    /// The three receive calls read the error queue exactly when their flags
7471    /// carry `MSG_ERRQUEUE`, which does not by itself keep them from waiting;
7472    /// a send's flags never read it.
7473    #[test]
7474    fn msg_errqueue_marks_only_a_receive_as_an_error_queue_read() {
7475        use reverie::syscalls::*;
7476        let errqueue = libc::MSG_ERRQUEUE;
7477        assert!(Recvfrom::new().with_flags(errqueue).reads_error_queue());
7478        assert!(
7479            Recvmsg::new()
7480                .with_flags(errqueue | libc::MSG_PEEK)
7481                .reads_error_queue()
7482        );
7483        assert!(
7484            Recvmmsg::new()
7485                .with_flags(errqueue as u32)
7486                .reads_error_queue()
7487        );
7488        assert!(
7489            !Recvfrom::new()
7490                .with_flags(libc::MSG_DONTWAIT)
7491                .reads_error_queue()
7492        );
7493        assert!(
7494            !Recvmsg::new()
7495                .with_flags(libc::MSG_PEEK)
7496                .reads_error_queue()
7497        );
7498        assert!(
7499            !Recvmmsg::new()
7500                .with_flags(libc::MSG_WAITFORONE as u32)
7501                .reads_error_queue()
7502        );
7503        assert!(!Recvmsg::new().with_flags(errqueue).flags_forbid_waiting());
7504        assert!(
7505            !Sendto::new()
7506                .with_flags(errqueue as u32)
7507                .reads_error_queue()
7508        );
7509        assert!(!Sendmsg::new().with_flags(errqueue).reads_error_queue());
7510        assert!(!Sendmmsg::new().with_flags(errqueue).reads_error_queue());
7511        assert!(!Read::new().reads_error_queue());
7512    }
7513
7514    #[test]
7515    fn signal_interruption_errno_matches_linux_restart_policy() {
7516        assert_eq!(
7517            reverie::syscalls::Poll::new().signal_interrupt_errno(),
7518            Errno::EINTR
7519        );
7520        assert_eq!(
7521            reverie::syscalls::Ppoll::new().signal_interrupt_errno(),
7522            Errno::EINTR
7523        );
7524        assert_eq!(
7525            reverie::syscalls::EpollWait::new().signal_interrupt_errno(),
7526            Errno::EINTR
7527        );
7528        let sigtimedwait = reverie::syscalls::RtSigtimedwait::new();
7529        assert_eq!(sigtimedwait.signal_interrupt_errno(), Errno::EINTR);
7530        assert!(sigtimedwait.syscall_would_have_blocked(Err(Errno::EAGAIN)));
7531        assert_eq!(sigtimedwait.timeout_return_val(), Err(Errno::EAGAIN));
7532        assert_eq!(
7533            reverie::syscalls::Read::new().signal_interrupt_errno(),
7534            Errno::ERESTARTSYS
7535        );
7536        // A zero-progress writev interruption returns this internal errno on
7537        // ptrace so Linux applies the handler's SA_RESTART policy.
7538        assert_eq!(
7539            reverie::syscalls::Writev::new().signal_interrupt_errno(),
7540            Errno::ERESTARTSYS
7541        );
7542        assert_eq!(
7543            reverie::syscalls::Futex::new().signal_interrupt_errno(),
7544            Errno::ERESTARTSYS
7545        );
7546    }
7547
7548    #[test]
7549    fn kernel_restart_errno_matches_linux_restart_policy() {
7550        // Signal-state interruption hands the kernel the wait's own restart code, so
7551        // the guest's disposition decides between a handler's EINTR and a restart
7552        // (https://github.com/rrnewton/hermit/issues/3146).
7553        // A poll with a positive timeout and a timed futex wait end with Linux's own
7554        // code: a handler turns it into EINTR, and with no handler the kernel runs
7555        // restart_syscall, which Detcore resumes with the deadline it kept
7556        // (`RestartBlock`, https://github.com/rrnewton/hermit/issues/3358).
7557        assert_eq!(
7558            reverie::syscalls::Poll::new()
7559                .with_timeout(300)
7560                .kernel_restart_errno(),
7561            Errno::ERESTART_RESTARTBLOCK
7562        );
7563        // A zero timeout never blocks and a negative one never expires, so there is
7564        // no deadline to keep, and running the call again is Linux's restart.
7565        assert_eq!(
7566            reverie::syscalls::Poll::new().kernel_restart_errno(),
7567            Errno::ERESTARTNOHAND
7568        );
7569        assert_eq!(
7570            reverie::syscalls::Poll::new()
7571                .with_timeout(-1)
7572                .kernel_restart_errno(),
7573            Errno::ERESTARTNOHAND
7574        );
7575        assert_eq!(
7576            reverie::syscalls::Ppoll::new().kernel_restart_errno(),
7577            Errno::ERESTARTNOHAND
7578        );
7579        // epoll_wait and sigtimedwait never restart after a handler.
7580        assert_eq!(
7581            reverie::syscalls::EpollWait::new().kernel_restart_errno(),
7582            Errno::EINTR
7583        );
7584        assert_eq!(
7585            reverie::syscalls::RtSigtimedwait::new().kernel_restart_errno(),
7586            Errno::EINTR
7587        );
7588        assert_eq!(
7589            reverie::syscalls::Futex::new().kernel_restart_errno(),
7590            Errno::ERESTARTSYS
7591        );
7592        // A timed wait is never restarted after a handler runs. FUTEX_WAIT's timeout
7593        // is relative, so its deadline is kept as poll's is.
7594        let timeout = reverie::syscalls::Addr::from_raw(0x1000).unwrap();
7595        assert_eq!(
7596            reverie::syscalls::Futex::new()
7597                .with_timeout(Some(timeout))
7598                .kernel_restart_errno(),
7599            Errno::ERESTART_RESTARTBLOCK
7600        );
7601        // FUTEX_WAIT_BITSET's timeout is an absolute deadline, which Linux copies
7602        // into the restart block as it does FUTEX_WAIT's, so a deadline the guest
7603        // moves in memory after the wait began has no effect. Running the call
7604        // again, which ERESTARTNOHAND did, read the moved deadline (round-10
7605        // finding Medium 4 on https://github.com/rrnewton/hermit/pull/3361).
7606        let bitset = reverie::syscalls::Futex::new()
7607            .with_futex_op(libc::FUTEX_WAIT_BITSET | libc::FUTEX_PRIVATE_FLAG)
7608            .with_val3(-1);
7609        assert_eq!(bitset.kernel_restart_errno(), Errno::ERESTARTSYS);
7610        assert!(!bitset.restart_keeps_deadline());
7611        assert_eq!(
7612            bitset.with_timeout(Some(timeout)).kernel_restart_errno(),
7613            Errno::ERESTART_RESTARTBLOCK
7614        );
7615        assert!(bitset.with_timeout(Some(timeout)).restart_keeps_deadline());
7616        // FUTEX_LOCK_PI and FUTEX_LOCK_PI2 are always restarted, timed or not, and
7617        // the restarted call reads its absolute deadline again (`futex_lock_pi`).
7618        for op in [
7619            libc::FUTEX_LOCK_PI,
7620            libc::FUTEX_LOCK_PI | libc::FUTEX_PRIVATE_FLAG,
7621            libc::FUTEX_LOCK_PI2 | libc::FUTEX_CLOCK_REALTIME,
7622        ] {
7623            let lock = reverie::syscalls::Futex::new().with_futex_op(op);
7624            assert_eq!(lock.kernel_restart_errno(), Errno::ERESTARTNOINTR);
7625            let timed = lock.with_timeout(Some(timeout));
7626            assert_eq!(timed.kernel_restart_errno(), Errno::ERESTARTNOINTR);
7627            assert!(!timed.restart_keeps_deadline());
7628        }
7629    }
7630
7631    #[test]
7632    fn a_held_signal_is_reported_unless_it_is_sigstop() {
7633        // The backend passes a held signal on unreported when the call returns
7634        // ERESTART_RESTARTBLOCK, so a held signal other than SIGSTOP ends the wait
7635        // with ERESTARTNOHAND, which a handler also turns into EINTR.
7636        for signal in [libc::SIGUSR1, libc::SIGTERM, libc::SIGTSTP, libc::SIGCHLD] {
7637            assert_eq!(
7638                held_signal_restart_errno(Errno::ERESTART_RESTARTBLOCK, signal),
7639                Errno::ERESTARTNOHAND,
7640                "signal {signal}"
7641            );
7642        }
7643        // SIGSTOP is never reported, and its restart must keep the deadline.
7644        assert_eq!(
7645            held_signal_restart_errno(Errno::ERESTART_RESTARTBLOCK, libc::SIGSTOP),
7646            Errno::ERESTART_RESTARTBLOCK
7647        );
7648        // Every other restart code is the call's own.
7649        for errno in [
7650            Errno::ERESTARTNOHAND,
7651            Errno::ERESTARTSYS,
7652            Errno::ERESTARTNOINTR,
7653            Errno::EINTR,
7654        ] {
7655            for signal in [libc::SIGUSR1, libc::SIGSTOP] {
7656                assert_eq!(held_signal_restart_errno(errno, signal), errno);
7657            }
7658        }
7659    }
7660
7661    #[test]
7662    fn resumed_kernel_wait_result_maps_only_erestartsys() {
7663        // The backend's code for a signal that stopped the injected restart
7664        // before it ran; the kernel's restart block is still in place.
7665        assert_eq!(
7666            resumed_kernel_wait_result(Err(Errno::ERESTARTSYS)),
7667            Err(Errno::ERESTART_RESTARTBLOCK)
7668        );
7669        // Every other result is the restarted wait's own.
7670        for result in [
7671            Ok(0),
7672            Err(Errno::ERESTART_RESTARTBLOCK),
7673            Err(Errno::EINTR),
7674            Err(Errno::ETIMEDOUT),
7675            Err(Errno::EAGAIN),
7676        ] {
7677            assert_eq!(resumed_kernel_wait_result(result), result);
7678        }
7679    }
7680
7681    #[test]
7682    fn a_restart_block_resumes_only_at_the_interrupted_call() {
7683        let timeout = reverie::syscalls::Addr::from_raw(0x1000).unwrap();
7684        let block = RestartBlock {
7685            rip: 0x4000_1234,
7686            deadline: Some(LogicalTime::from_nanos(300_000_000)),
7687            call: RestartCall::Futex(reverie::syscalls::Futex::new().with_timeout(Some(timeout))),
7688        };
7689        // The kernel's restart runs restart_syscall at the interrupted call's address.
7690        assert_eq!(block.resumed_at(0x4000_1234), Some(block));
7691        // A restart_syscall anywhere else is the guest's own call.
7692        assert_eq!(block.resumed_at(0x4000_1236), None);
7693        assert_eq!(block.resumed_at(0x4000_1232), None);
7694    }
7695
7696    #[test]
7697    fn writev_signal_result_uses_disposition_and_progress() {
7698        let call = reverie::syscalls::Writev::new();
7699        for disposition in [
7700            WaitSignalDisposition::Interrupt,
7701            WaitSignalDisposition::Restart,
7702        ] {
7703            assert!(matches!(
7704                interrupted_write_result(&call, 0, Some(disposition)),
7705                Some(Err(Error::Errno(Errno::ERESTARTSYS)))
7706            ));
7707            assert!(matches!(
7708                interrupted_write_result(&call, 17, Some(disposition)),
7709                Some(Ok(17))
7710            ));
7711        }
7712        assert!(interrupted_write_result(&call, 0, None).is_none());
7713        assert!(interrupted_write_result(&call, 17, None).is_none());
7714    }
7715
7716    #[test]
7717    fn pipe_writev_requests_signal_disposition() {
7718        let dettid = DetTid::from_raw(42);
7719        let call = reverie::syscalls::Writev::new();
7720        let request = pipe_writev_resources(dettid, call, true);
7721
7722        assert_eq!(
7723            request.signal_interrupt_errno(),
7724            Some(Errno::ERESTARTSYS.into_raw())
7725        );
7726        assert!(request.decides_signal_interruption_itself);
7727        assert_eq!(request.resources.len(), 1);
7728        assert!(
7729            request.relies_on_guest_held_marks,
7730            "an escaped read end can make a blocked pipe writev progress"
7731        );
7732        let external = pipe_writev_resources(dettid, call, false);
7733        assert!(
7734            !external.polls_guest_state_only,
7735            "a pipe imported from outside may have a host reader without any guest export"
7736        );
7737        assert!(!external.relies_on_guest_held_marks);
7738    }
7739}
7740
7741/// `KernelSignalWait` against a scripted guest and a scripted `/proc` read
7742/// (`signal_state_read_seam`). No failure of the wait's signal handling may reach
7743/// the guest as an errno its wait call cannot return, and the guest must never
7744/// resume with every signal blocked
7745/// (https://github.com/rrnewton/hermit/issues/3146).
7746#[cfg(test)]
7747pub(crate) mod kernel_signal_wait_failures {
7748    use std::collections::VecDeque;
7749    use std::sync::Arc;
7750    use std::sync::Mutex;
7751
7752    use reverie::GlobalRPC;
7753    use reverie::GlobalTool;
7754    use reverie::Pid;
7755    use reverie::Signal;
7756    use reverie::syscalls::LocalMemory;
7757
7758    use super::*;
7759    use crate::Config;
7760    use crate::GlobalState;
7761    use crate::ThreadState;
7762    use crate::syscalls::threads::KernelSignalState;
7763    use crate::syscalls::threads::kernel_sigset_bit;
7764    use crate::syscalls::threads::signal_state_read_seam;
7765    use crate::types::DetPid;
7766
7767    /// What an injected `rt_sigprocmask` does.
7768    #[derive(Clone, Copy, Debug)]
7769    enum MaskOutcome {
7770        /// The call runs: the mask changes and it returns 0.
7771        Apply,
7772        /// A signal stops the guest before the call runs: the mask is unchanged
7773        /// and the backend reports its restart errno.
7774        StoppedBefore,
7775        /// The call cannot run at all.
7776        Fail(Errno),
7777        /// `StoppedBefore`, by a signal that `/proc` never showed because it
7778        /// arrived after the read before the injection, such as a `SIGSTOP`
7779        /// sent from outside: the backend holds it in its single slot
7780        /// (`FakeKernel::held_slot`).
7781        StoppedBy(i32),
7782    }
7783
7784    /// The kernel's view of the guest thread, shared by the scripted `/proc`
7785    /// read and the scripted guest.
7786    #[derive(Default)]
7787    struct FakeKernel {
7788        /// The thread's mask (`SigBlk`).
7789        blocked: KernelSigset,
7790        /// Pending signals (`SigPnd | ShdPnd`).
7791        pending: KernelSigset,
7792        /// The signals of `pending` pending only on the thread group's shared
7793        /// queue (`ShdPnd`); every other one is on the thread's own
7794        /// (`SigPnd`).
7795        shared: KernelSigset,
7796        /// Signals with a handler (`SigCgt`).
7797        caught: KernelSigset,
7798        /// Signals whose disposition is `SIG_IGN` (`SigIgn`).
7799        ignored: KernelSigset,
7800        /// Whether a pending signal outside the mask stops each injection before
7801        /// the call runs, as on ptrace, where an ignored signal still queues for
7802        /// a traced task: it leaves the queue for the backend to hold
7803        /// (`taken`), and the injection reports a restart errno.
7804        stops_for_pending: bool,
7805        /// The signals that stopped an injection, in order.
7806        taken: Vec<i32>,
7807        /// Errors for the next reads, one per read.
7808        read_failures: VecDeque<Errno>,
7809        /// The backend confirmed exit; every later proc read sees ESRCH.
7810        gone: bool,
7811        /// Outcomes for the next `rt_sigprocmask` calls; `Apply` once empty.
7812        outcomes: VecDeque<MaskOutcome>,
7813        /// Enable an empty nonblocking read, counting the probes that actually
7814        /// run. A signal stop before the probe returns ERESTARTSYS instead,
7815        /// so Guest's real default retry wrapper must retry it.
7816        empty_read_probes: Option<usize>,
7817        /// Bound an actual retry-loop test by making this probe ready, if a
7818        /// lost signal prevents the earlier interruption it must observe.
7819        read_ready_after: Option<usize>,
7820        /// Physical masks at the private read probes that actually execute.
7821        read_probe_masks: Vec<KernelSigset>,
7822        /// Bounded scalar-write probes and their physical masks. A signal
7823        /// posted after the first attempt is first visible on its retry turn.
7824        write_probe_masks: Option<Vec<KernelSigset>>,
7825        pending_after_first_write: KernelSigset,
7826        /// Every mask the guest was asked to set, in order.
7827        requested: Vec<KernelSigset>,
7828        /// How many more signals that stop an injection are sent again, as by
7829        /// a sender faster than the injections: each such signal is pending
7830        /// again from the read after the one that follows the stop, which still
7831        /// shows it gone, so the stop is identified (`pending_repost`).
7832        repost_after_take: usize,
7833        /// The signals sent after the next snapshot, which the following read
7834        /// shows. Also models a first arrival racing an empty pending snapshot.
7835        pending_repost: KernelSigset,
7836        /// The backend's one slot for a held signal: each signal that stops an
7837        /// injection (`taken`, `MaskOutcome::StoppedBy`) replaces the one there,
7838        /// as Reverie's ptrace backend does (`hold_pending_signal`).
7839        held_slot: Option<i32>,
7840        /// The signals that a stop dropped from `held_slot`, in order. A standard
7841        /// signal that replaces itself is merged, as Linux merges a second one
7842        /// sent while the first is pending, and is not lost.
7843        lost: Vec<i32>,
7844        /// Answers for the next `Guest::set_blocked_signals` calls: `None`
7845        /// sets the mask and returns the old one; once empty, every call is
7846        /// refused as `Unsupported`, as the trait's default refuses.
7847        setter: VecDeque<Option<MaskChangeRefused>>,
7848        /// Every mask `Guest::set_blocked_signals` was asked to set, in order.
7849        set_calls: Vec<KernelSigset>,
7850        /// The scheduler's answer to `HostTimedAndPrivateSigchld`: the
7851        /// host-timed signals, then the recorded private `SIGCHLD`. `None`
7852        /// panics, as every other request does.
7853        private_record: Option<(KernelSigset, KernelSigset)>,
7854    }
7855
7856    impl FakeKernel {
7857        /// The backend holds `signal`, which stopped an injection.
7858        fn hold(&mut self, signal: i32) {
7859            if let Some(previous) = self.held_slot.replace(signal)
7860                && !(previous == signal && signal < KERNEL_SIGRTMIN)
7861            {
7862                self.lost.push(previous);
7863            }
7864        }
7865    }
7866
7867    type Kernel = Arc<Mutex<FakeKernel>>;
7868
7869    /// Room for one mask, 8-byte aligned like a real stack slot.
7870    const ARENA_WORDS: usize = 2;
7871
7872    /// A scratch stack in this process's memory whose commit can fail like the
7873    /// ptrace scratch below an `rsp` with no writable memory under it
7874    /// (https://github.com/rrnewton/hermit/issues/3328).
7875    struct WaitStack {
7876        commit_fails: bool,
7877        arena: usize,
7878    }
7879
7880    struct WaitStackGuard;
7881
7882    impl Drop for WaitStackGuard {
7883        fn drop(&mut self) {}
7884    }
7885
7886    impl reverie::Stack for WaitStack {
7887        type StackGuard = WaitStackGuard;
7888
7889        fn size(&self) -> usize {
7890            panic!("a blocked wait must not query the scratch size")
7891        }
7892        fn capacity(&self) -> usize {
7893            panic!("a blocked wait must not query the scratch capacity")
7894        }
7895        fn push<'stack, T>(&mut self, value: T) -> Addr<'stack, T> {
7896            assert!(std::mem::size_of::<T>() <= ARENA_WORDS * std::mem::size_of::<u64>());
7897            // SAFETY: the arena is a live, 8-byte aligned buffer owned by the guest,
7898            // large enough for `T` (asserted above).
7899            unsafe { std::ptr::write(self.arena as *mut T, value) };
7900            Addr::from_raw(self.arena).unwrap()
7901        }
7902        fn reserve<'stack, T>(&mut self) -> AddrMut<'stack, T> {
7903            panic!("a blocked wait pushes its mask rather than reserving room")
7904        }
7905        fn commit(self) -> Result<Self::StackGuard, Errno> {
7906            if self.commit_fails {
7907                Err(Errno::EFAULT)
7908            } else {
7909                Ok(WaitStackGuard)
7910            }
7911        }
7912    }
7913
7914    struct WaitGuest {
7915        config: Config,
7916        thread: ThreadState<()>,
7917        pid: Pid,
7918        tid: Pid,
7919        commit_fails: bool,
7920        arena: Box<[u64; ARENA_WORDS]>,
7921        kernel: Kernel,
7922        proof_script: Option<Arc<Mutex<ProofScript>>>,
7923    }
7924
7925    impl WaitGuest {
7926        /// A guest for thread `tid` of this process, with the guest's own `mask`
7927        /// installed.
7928        fn new(tid: Pid, mask: KernelSigset) -> (Self, Kernel) {
7929            let config = Config::default();
7930            let thread = ThreadState::new(DetPid::from_raw(1), &config, ());
7931            let kernel = Arc::new(Mutex::new(FakeKernel {
7932                blocked: mask,
7933                ..FakeKernel::default()
7934            }));
7935            let guest = Self {
7936                config,
7937                thread,
7938                pid: Pid::from_raw(std::process::id() as i32),
7939                tid,
7940                commit_fails: false,
7941                arena: Box::new([u64::MAX; ARENA_WORDS]),
7942                kernel: kernel.clone(),
7943                proof_script: None,
7944            };
7945            (guest, kernel)
7946        }
7947
7948        /// A guest for the calling thread, which exists.
7949        fn live(mask: KernelSigset) -> (Self, Kernel) {
7950            // SAFETY: gettid has no preconditions.
7951            Self::new(Pid::from_raw(unsafe { libc::gettid() }), mask)
7952        }
7953    }
7954
7955    /// The decision `ioaction_based_on_fd_status` takes from the call's own
7956    /// flags. On a socket the guest left blocking, which Detcore keeps
7957    /// physically nonblocking, a send or receive is retried until it stops
7958    /// failing with `EAGAIN` (`NonblockizeRetry`), unless its flags carry
7959    /// `MSG_DONTWAIT`: then it runs once (`PassThru`), as Linux runs it.
7960    /// recvmmsg's `MSG_WAITFORONE` alone still waits for the first datagram.
7961    /// On a socket the guest made `O_NONBLOCK`, every call runs once, and on
7962    /// one Detcore leaves physically blocking every call takes the blocking
7963    /// path.
7964    #[test]
7965    fn msg_dontwait_runs_a_call_once_on_a_socket_the_guest_left_blocking() {
7966        use std::os::fd::AsRawFd;
7967
7968        use reverie::syscalls::Recvfrom;
7969        use reverie::syscalls::Recvmmsg;
7970        use reverie::syscalls::Recvmsg;
7971        use reverie::syscalls::Sendmmsg;
7972        use reverie::syscalls::Sendmsg;
7973        use reverie::syscalls::Sendto;
7974
7975        let (mut guest, _) = WaitGuest::live(guest_mask());
7976        let (socket, _peer) = std::os::unix::net::UnixDatagram::pair().unwrap();
7977        let fd = socket.as_raw_fd();
7978        guest
7979            .thread
7980            .add_fd(fd, nix::fcntl::OFlag::O_RDWR, FdType::Socket, None)
7981            .unwrap();
7982        let dontwait = libc::MSG_DONTWAIT;
7983        let waitforone = libc::MSG_WAITFORONE;
7984        let once = Ok(IOAction::PassThru);
7985        let retry = Ok(IOAction::NonblockizeRetry);
7986        let decisions = |guest: &mut WaitGuest| {
7987            [
7988                (
7989                    "recvfrom",
7990                    ioaction_based_on_fd_status(
7991                        guest,
7992                        Recvfrom::new().with_fd(fd).with_flags(dontwait),
7993                    ),
7994                    ioaction_based_on_fd_status(guest, Recvfrom::new().with_fd(fd)),
7995                ),
7996                (
7997                    "recvmsg",
7998                    ioaction_based_on_fd_status(
7999                        guest,
8000                        Recvmsg::new().with_sockfd(fd).with_flags(dontwait),
8001                    ),
8002                    ioaction_based_on_fd_status(guest, Recvmsg::new().with_sockfd(fd)),
8003                ),
8004                (
8005                    "recvmmsg",
8006                    ioaction_based_on_fd_status(
8007                        guest,
8008                        Recvmmsg::new()
8009                            .with_fd(fd)
8010                            .with_flags((waitforone | dontwait) as u32),
8011                    ),
8012                    ioaction_based_on_fd_status(
8013                        guest,
8014                        Recvmmsg::new().with_fd(fd).with_flags(waitforone as u32),
8015                    ),
8016                ),
8017                (
8018                    "sendto",
8019                    ioaction_based_on_fd_status(
8020                        guest,
8021                        Sendto::new().with_fd(fd).with_flags(dontwait as u32),
8022                    ),
8023                    ioaction_based_on_fd_status(guest, Sendto::new().with_fd(fd)),
8024                ),
8025                (
8026                    "sendmsg",
8027                    ioaction_based_on_fd_status(
8028                        guest,
8029                        Sendmsg::new().with_fd(fd).with_flags(dontwait),
8030                    ),
8031                    ioaction_based_on_fd_status(guest, Sendmsg::new().with_fd(fd)),
8032                ),
8033                (
8034                    "sendmmsg",
8035                    ioaction_based_on_fd_status(
8036                        guest,
8037                        Sendmmsg::new().with_sockfd(fd).with_flags(dontwait),
8038                    ),
8039                    ioaction_based_on_fd_status(guest, Sendmmsg::new().with_sockfd(fd)),
8040                ),
8041            ]
8042        };
8043        // A socket Detcore leaves physically blocking (an external socket
8044        // under record and replay) keeps the blocking path, flag or not.
8045        for (name, with_dontwait, without) in decisions(&mut guest) {
8046            let blocking = Ok(IOAction::Blocking);
8047            assert_eq!(
8048                with_dontwait, blocking,
8049                "{name} with MSG_DONTWAIT, blocking"
8050            );
8051            assert_eq!(without, blocking, "{name} without MSG_DONTWAIT, blocking");
8052        }
8053        guest
8054            .thread
8055            .with_detfd(fd, |d| d.set_physically_nonblocking())
8056            .unwrap();
8057        for (name, with_dontwait, without) in decisions(&mut guest) {
8058            assert_eq!(with_dontwait, once, "{name} with MSG_DONTWAIT");
8059            assert_eq!(without, retry, "{name} without MSG_DONTWAIT");
8060        }
8061        guest
8062            .thread
8063            .with_detfd(fd, |d| d.set_nonblocking(true))
8064            .unwrap();
8065        for (name, with_dontwait, without) in decisions(&mut guest) {
8066            assert_eq!(with_dontwait, once, "{name} with MSG_DONTWAIT, O_NONBLOCK");
8067            assert_eq!(without, once, "{name} without MSG_DONTWAIT, O_NONBLOCK");
8068        }
8069    }
8070
8071    #[derive(Clone, Copy, Debug)]
8072    enum ProofAction {
8073        None,
8074        Dup { from: i32, to: i32 },
8075        Operand { address: usize, fd: i32 },
8076        Mark { fd: i32, held: bool },
8077    }
8078
8079    #[derive(Default)]
8080    struct ProofScript {
8081        requests: Vec<Resources>,
8082        actions: VecDeque<ProofAction>,
8083        probes: usize,
8084        observe_retry_masks: bool,
8085        published_mask: Option<KernelSigset>,
8086        publications: Vec<Option<KernelSigset>>,
8087        retry_boundaries: Vec<(&'static str, KernelSigset, Option<KernelSigset>)>,
8088        route_child_on_retry: bool,
8089    }
8090
8091    impl WaitGuest {
8092        async fn proof_action(&self, action: ProofAction) {
8093            match action {
8094                ProofAction::None => {}
8095                ProofAction::Dup { from, to } => {
8096                    // Run the real mutation handler in a sibling sharing the
8097                    // descriptor table while the reader awaits its next turn.
8098                    let (mut sibling, _) = Self::new(Pid::from_raw(2), 0);
8099                    sibling.config = self.config.clone();
8100                    sibling.thread.dettid = DetTid::from_raw(2);
8101                    sibling.thread.detpid = self.thread.detpid;
8102                    sibling.thread.file_metadata = self.thread.file_metadata.clone();
8103                    sibling.proof_script = Some(Arc::new(Mutex::new(ProofScript::default())));
8104                    let tool = Detcore {
8105                        detpid: self.thread.detpid.unwrap(),
8106                        cfg: self.config.clone(),
8107                        record_or_replay: crate::record_or_replay::NoopTool,
8108                    };
8109                    assert_eq!(
8110                        tool.handle_dup2(
8111                            &mut sibling,
8112                            syscalls::Dup2::new().with_oldfd(from).with_newfd(to),
8113                        )
8114                        .await
8115                        .unwrap(),
8116                        i64::from(to)
8117                    );
8118                }
8119                ProofAction::Operand { address, fd } => {
8120                    // A sibling can change the shared pollfd array without
8121                    // changing either descriptor-table entry.
8122                    LocalMemory::new()
8123                        .write_value(AddrMut::<i32>::from_raw(address).unwrap(), &fd)
8124                        .unwrap();
8125                }
8126                ProofAction::Mark { fd, held } => {
8127                    self.thread
8128                        .with_detfd(fd, |entry| {
8129                            if held {
8130                                entry.set_guest_held();
8131                            } else {
8132                                entry.clear_guest_held();
8133                            }
8134                        })
8135                        .unwrap();
8136                }
8137            }
8138        }
8139    }
8140
8141    /// Reuse the local-memory guest for descriptor guards that need no injections.
8142    pub(crate) fn fd_test_guest() -> impl Guest<Detcore> {
8143        WaitGuest::live(0).0
8144    }
8145
8146    /// A signal that ends a polled read that would still wait returns the
8147    /// read's restart errno, except for a read that began on an inotify
8148    /// instance that is nonblocking for the guest by then, as another
8149    /// thread's F_SETFL or FIONBIO makes it during the wait: `inotify_read`
8150    /// tests O_NONBLOCK before a pending signal, so Linux returns EAGAIN. A
8151    /// pipe read keeps the restart errno, as `pipe_read`'s interrupted wait
8152    /// returns -ERESTARTSYS itself. The description the read began on
8153    /// decides, not what its number names at the wake: a pipe read whose
8154    /// number a sibling's dup2 points at a nonblocking inotify instance keeps
8155    /// the restart errno, as Linux's read keeps the pipe it began on.
8156    #[test]
8157    fn a_signal_ends_an_inotify_read_made_nonblocking_meanwhile_with_eagain() {
8158        use std::os::fd::AsRawFd;
8159        use std::os::fd::FromRawFd;
8160
8161        let (mut guest, _) = WaitGuest::live(guest_mask());
8162        // SAFETY: inotify_init1 returns a new owned descriptor.
8163        let inotify = unsafe {
8164            std::fs::File::from_raw_fd(libc::inotify_init1(libc::IN_NONBLOCK | libc::IN_CLOEXEC))
8165        };
8166        let (pipe, _writer) = std::os::unix::net::UnixStream::pair().unwrap();
8167        let (inotify, pipe) = (inotify.as_raw_fd(), pipe.as_raw_fd());
8168        guest
8169            .thread
8170            .add_fd(inotify, nix::fcntl::OFlag::O_RDONLY, FdType::Inotify, None)
8171            .unwrap();
8172        guest
8173            .thread
8174            .add_fd(pipe, nix::fcntl::OFlag::O_RDONLY, FdType::Pipe, None)
8175            .unwrap();
8176        for fd in [inotify, pipe] {
8177            guest
8178                .thread
8179                .with_detfd(fd, |d| d.set_physically_nonblocking())
8180                .unwrap();
8181        }
8182        let read = |fd| syscalls::Read::new().with_fd(fd);
8183        let inotify_read = inotify_description_of(&guest, read(inotify));
8184        let pipe_read = inotify_description_of(&guest, read(pipe));
8185        assert!(inotify_read.is_some());
8186        assert!(pipe_read.is_none(), "only an inotify read is bound");
8187        assert_eq!(
8188            interrupted_wait_errno(inotify_read.as_ref(), read(inotify)),
8189            Errno::ERESTARTSYS
8190        );
8191        assert_eq!(
8192            interrupted_wait_errno(pipe_read.as_ref(), read(pipe)),
8193            Errno::ERESTARTSYS
8194        );
8195        for fd in [inotify, pipe] {
8196            guest
8197                .thread
8198                .with_detfd(fd, |d| d.set_logical_nonblocking(true))
8199                .unwrap();
8200        }
8201        assert_eq!(
8202            interrupted_wait_errno(inotify_read.as_ref(), read(inotify)),
8203            Errno::EAGAIN
8204        );
8205        assert_eq!(
8206            interrupted_wait_errno(
8207                inotify_read.as_ref(),
8208                syscalls::Readv::new().with_fd(inotify)
8209            ),
8210            Errno::EAGAIN
8211        );
8212        assert_eq!(
8213            interrupted_wait_errno(pipe_read.as_ref(), read(pipe)),
8214            Errno::ERESTARTSYS
8215        );
8216        // A sibling's dup2 points the pipe read's number at the nonblocking
8217        // inotify instance: the read began on the pipe, whose restart errno
8218        // stands.
8219        guest
8220            .thread
8221            .dup_fd(inotify, pipe, nix::fcntl::OFlag::empty())
8222            .unwrap();
8223        assert_eq!(
8224            interrupted_wait_errno(pipe_read.as_ref(), read(pipe)),
8225            Errno::ERESTARTSYS
8226        );
8227        assert!(inotify_description_of(&guest, read(pipe)).is_some());
8228    }
8229
8230    /// The signal a part of a call run in parts completes with carries to the
8231    /// parts after it (`carried_signal`): one the turn found that would end a
8232    /// part after progress, a pending copy or a named wake; where the turn does
8233    /// not decide, its signal wake; and none under the in-guest delivery gate
8234    /// or for a turn that found nothing. It ends the first later part that
8235    /// would wait, once (`carried_signal_ends`).
8236    #[test]
8237    fn a_part_carries_the_signal_its_turn_found_to_the_next() {
8238        let pending = TurnSignals {
8239            named: 0,
8240            pending: kernel_sigset_bit(libc::SIGUSR1),
8241        };
8242        let named = TurnSignals {
8243            named: kernel_sigset_bit(libc::SIGUSR1),
8244            pending: 0,
8245        };
8246        let wake = ResumeStatus::Signaled(None);
8247        assert!(matches!(
8248            carried_signal(Some(pending), &ResumeStatus::Normal, false),
8249            Some(CarriedSignal::Decided)
8250        ));
8251        assert!(matches!(
8252            carried_signal(Some(named), &wake, false),
8253            Some(CarriedSignal::Decided)
8254        ));
8255        assert!(carried_signal(Some(TurnSignals::default()), &wake, false).is_none());
8256        assert!(matches!(
8257            carried_signal(None, &wake, false),
8258            Some(CarriedSignal::Wake(ResumeStatus::Signaled(None)))
8259        ));
8260        assert!(carried_signal(None, &wake, true).is_none());
8261        assert!(carried_signal(None, &ResumeStatus::Normal, false).is_none());
8262
8263        let (mut guest, _) = WaitGuest::live(guest_mask());
8264        let ends = |guest: &mut WaitGuest, carried, decides_itself, gated| {
8265            guest.thread.call_parts = Some(CallParts {
8266                progress: true,
8267                carried,
8268            });
8269            let ends =
8270                futures::executor::block_on(carried_signal_ends(guest, decides_itself, gated))
8271                    .unwrap();
8272            let left = guest.thread.call_parts.as_ref().unwrap().carried.is_some();
8273            (ends, left)
8274        };
8275        assert_eq!(
8276            ends(&mut guest, Some(CarriedSignal::Decided), false, false),
8277            (true, false)
8278        );
8279        assert_eq!(
8280            ends(
8281                &mut guest,
8282                Some(CarriedSignal::Wake(wake.clone())),
8283                false,
8284                false
8285            ),
8286            (true, false)
8287        );
8288        assert_eq!(
8289            ends(&mut guest, Some(CarriedSignal::Wake(wake)), false, true),
8290            (false, false)
8291        );
8292        assert_eq!(ends(&mut guest, None, false, false), (false, false));
8293    }
8294
8295    #[test]
8296    fn inherited_input_exempts_only_authenticated_reads_from_the_internal_guard() {
8297        use std::os::fd::AsRawFd;
8298        let (mut guest, _) = WaitGuest::live(guest_mask());
8299        let slot = tempfile::tempfile().unwrap();
8300        let fd = slot.as_raw_fd();
8301        let input = reverie::InheritedInput::pipe(fd).unwrap();
8302        let identity = input.identity().clone();
8303        let (reader, _writer) = input.into_reader_and_writer();
8304        assert_eq!(unsafe { libc::dup2(reader.as_raw_fd(), fd) }, fd);
8305        guest
8306            .thread
8307            .add_fd(fd, nix::fcntl::OFlag::O_RDONLY, FdType::Pipe, None)
8308            .unwrap();
8309        let calls = [
8310            Syscall::from(syscalls::Read::new().with_fd(fd)),
8311            Syscall::from(syscalls::Readv::new().with_fd(fd)),
8312            Syscall::from(syscalls::Write::new().with_fd(fd)),
8313            Syscall::from(syscalls::Writev::new().with_fd(fd)),
8314        ];
8315        for call in calls {
8316            assert!(syscall_targets_internal_fd(&mut guest, call));
8317        }
8318        guest
8319            .thread
8320            .file_metadata
8321            .lock()
8322            .unwrap()
8323            .apply_exec_fd_state(
8324                guest.thread.dettid,
8325                crate::tool_local::ExecFdState {
8326                    initial_input: Some(identity),
8327                    ..Default::default()
8328                },
8329                true,
8330            )
8331            .unwrap();
8332        for (index, call) in calls.into_iter().enumerate() {
8333            assert_eq!(syscall_targets_internal_fd(&mut guest, call), index >= 2);
8334        }
8335        assert!(
8336            !guest
8337                .thread
8338                .with_detfd(fd, |d| d.physically_nonblocking())
8339                .unwrap()
8340        );
8341        assert_eq!(
8342            unsafe { libc::fcntl(fd, libc::F_GETFL) } & libc::O_NONBLOCK,
8343            0
8344        );
8345        let (internal, _writer) = nix::unistd::pipe().unwrap();
8346        guest
8347            .thread
8348            .add_fd(
8349                internal.as_raw_fd(),
8350                nix::fcntl::OFlag::O_RDONLY,
8351                FdType::Pipe,
8352                None,
8353            )
8354            .unwrap();
8355        assert!(syscall_targets_internal_fd(
8356            &mut guest,
8357            syscalls::Read::new().with_fd(internal.as_raw_fd()).into()
8358        ));
8359        // Socketpair ownership remains internal as well.
8360        let (socket, _peer) = std::os::unix::net::UnixStream::pair().unwrap();
8361        guest
8362            .thread
8363            .add_fd(
8364                socket.as_raw_fd(),
8365                nix::fcntl::OFlag::O_RDWR,
8366                FdType::Socket,
8367                None,
8368            )
8369            .unwrap();
8370        guest
8371            .thread
8372            .with_detfd(socket.as_raw_fd(), |d| d.set_socketpair_endpoint())
8373            .unwrap();
8374        assert!(syscall_targets_internal_fd(
8375            &mut guest,
8376            syscalls::Readv::new().with_fd(socket.as_raw_fd()).into()
8377        ));
8378    }
8379
8380    #[test]
8381    fn a_socketpair_poll_uses_revocable_provenance_without_changing_internal_routing() {
8382        use std::os::fd::AsRawFd;
8383
8384        use nix::sys::socket::AddressFamily;
8385        use nix::sys::socket::SockFlag;
8386        use nix::sys::socket::SockType;
8387        use nix::sys::socket::socketpair;
8388
8389        let (mut guest, _) = WaitGuest::live(guest_mask());
8390        let pairs = [SockType::Datagram, SockType::Stream, SockType::SeqPacket].map(|kind| {
8391            let (socket, peer) =
8392                socketpair(AddressFamily::Unix, kind, None, SockFlag::empty()).unwrap();
8393            (kind, socket, peer)
8394        });
8395        for (kind, socket, _peer) in &pairs {
8396            let fd = socket.as_raw_fd();
8397            guest
8398                .thread
8399                .add_fd(fd, nix::fcntl::OFlag::O_RDWR, FdType::Socket, None)
8400                .unwrap();
8401            let alias = guest
8402                .thread
8403                .with_detfd(fd, |d| {
8404                    d.set_socketpair_endpoint();
8405                    d.clone()
8406                })
8407                .unwrap();
8408            // Datagram pairs are deliberately not guest-held at creation:
8409            // binding and AF_UNSPEC disconnect can admit outside senders.
8410            // Connected stream/seqpacket pairs have the stronger mark.
8411            let connection = *kind != SockType::Datagram;
8412            if connection {
8413                alias.set_guest_held();
8414            }
8415            let calls = [
8416                syscalls::Read::new().with_fd(fd).into(),
8417                syscalls::Readv::new().with_fd(fd).into(),
8418                syscalls::Recvfrom::new().with_fd(fd).into(),
8419                syscalls::Recvmsg::new().with_sockfd(fd).into(),
8420            ];
8421            for call in calls {
8422                let internal = syscall_targets_internal_fd(&mut guest, call);
8423                assert!(internal, "{kind:?}: live record/replay routing is retained");
8424                let readiness = single_fd_poll_readiness(&mut guest, call, internal);
8425                assert_eq!(
8426                    readiness,
8427                    (connection, connection),
8428                    "{kind:?}: readiness needs revocable provenance"
8429                );
8430                let mut request = Resources::new(DetTid::from_raw(5));
8431                request.insert(ResourceID::InternalIOPolling, Permission::W);
8432                request.poll_attempt = 1;
8433                (
8434                    request.polls_guest_state_only,
8435                    request.relies_on_guest_held_marks,
8436                ) = readiness;
8437                crate::scheduler::assert_poll_export_revocation_for_test(request, connection);
8438            }
8439            // Cached record/replay peer credentials are another routing fact,
8440            // and must not override a socketpair's readiness provenance either.
8441            alias.set_guest_peer();
8442            for call in calls {
8443                let internal = syscall_targets_internal_fd(&mut guest, call);
8444                assert_eq!(
8445                    single_fd_poll_readiness(&mut guest, call, internal),
8446                    (connection, connection),
8447                    "cached guest-peer routing is not socketpair readiness proof"
8448                );
8449            }
8450            alias.clear_guest_held();
8451            for call in calls {
8452                let internal = syscall_targets_internal_fd(&mut guest, call);
8453                assert!(internal, "revocation must not change live routing");
8454                assert_eq!(
8455                    single_fd_poll_readiness(&mut guest, call, internal),
8456                    (false, false),
8457                    "withdrawing a mark through an alias withdraws readiness"
8458                );
8459            }
8460        }
8461
8462        // The socketpair repair does not reclassify other internal descriptors.
8463        let (pipe, _writer) = nix::unistd::pipe().unwrap();
8464        guest
8465            .thread
8466            .add_fd(
8467                pipe.as_raw_fd(),
8468                nix::fcntl::OFlag::O_RDONLY,
8469                FdType::Pipe,
8470                None,
8471            )
8472            .unwrap();
8473        let (socket, _peer) = std::os::unix::net::UnixStream::pair().unwrap();
8474        guest
8475            .thread
8476            .add_fd(
8477                socket.as_raw_fd(),
8478                nix::fcntl::OFlag::O_RDWR,
8479                FdType::Socket,
8480                None,
8481            )
8482            .unwrap();
8483        guest
8484            .thread
8485            .with_detfd(socket.as_raw_fd(), |d| d.set_guest_peer())
8486            .unwrap();
8487        for fd in [pipe.as_raw_fd(), socket.as_raw_fd()] {
8488            let call = syscalls::Read::new().with_fd(fd).into();
8489            let internal = syscall_targets_internal_fd(&mut guest, call);
8490            assert!(internal);
8491            assert_eq!(
8492                single_fd_poll_readiness(&mut guest, call, internal),
8493                (true, false)
8494            );
8495        }
8496    }
8497
8498    /// Answer this thread's `/proc` reads from `kernel` until the result drops.
8499    fn scripted_proc(kernel: &Kernel) -> signal_state_read_seam::Installed {
8500        let kernel = kernel.clone();
8501        signal_state_read_seam::install(move |_, _| {
8502            let mut kernel = kernel.lock().unwrap();
8503            let read = match kernel
8504                .gone
8505                .then_some(Errno::ESRCH)
8506                .or_else(|| kernel.read_failures.pop_front())
8507            {
8508                Some(errno) => Err(errno),
8509                None => Ok(KernelSignalState {
8510                    pending: kernel.pending,
8511                    thread_pending: kernel.pending & !kernel.shared,
8512                    shared_pending: kernel.pending & kernel.shared,
8513                    blocked: kernel.blocked,
8514                    caught: kernel.caught,
8515                    ignored: kernel.ignored,
8516                }),
8517            };
8518            kernel.pending |= std::mem::take(&mut kernel.pending_repost);
8519            Some(read)
8520        })
8521    }
8522
8523    #[reverie::tool]
8524    impl GlobalRPC<GlobalState> for WaitGuest {
8525        async fn send_rpc(
8526            &self,
8527            message: <GlobalState as GlobalTool>::Request,
8528        ) -> <GlobalState as GlobalTool>::Response {
8529            if let Some(script) = &self.proof_script {
8530                use crate::tool_global::GlobalRequest;
8531                use crate::tool_global::GlobalResponse;
8532                match &message.2 {
8533                    GlobalRequest::RequestResources(resources, _) => {
8534                        let (action, signal) = {
8535                            let mut script = script.lock().unwrap();
8536                            assert!(script.requests.len() < 4, "unbounded retry fixture");
8537                            script.requests.push(resources.clone());
8538                            let mut signal = None;
8539                            if script.observe_retry_masks {
8540                                let mut kernel = self.kernel.lock().unwrap();
8541                                let published = script.published_mask;
8542                                script.retry_boundaries.push((
8543                                    "request",
8544                                    kernel.blocked,
8545                                    published,
8546                                ));
8547                                // Model the ptrace target selector: a caught
8548                                // child notification is sent only to a task
8549                                // whose physical mask admits it at this yield.
8550                                if script.route_child_on_retry
8551                                    && script.requests.len() == 2
8552                                    && kernel.blocked & kernel_sigset_bit(libc::SIGCHLD) == 0
8553                                {
8554                                    kernel.pending |= kernel_sigset_bit(libc::SIGCHLD);
8555                                    signal = Some(crate::types::SigWrapper::from(Signal::SIGCHLD));
8556                                }
8557                            }
8558                            (
8559                                script.actions.pop_front().unwrap_or(ProofAction::None),
8560                                signal,
8561                            )
8562                        };
8563                        self.proof_action(action).await;
8564                        return (
8565                            None,
8566                            GlobalResponse::RequestResources(
8567                                signal.map_or(ResumeStatus::Normal, |signal| {
8568                                    ResumeStatus::Signaled(Some(vec![signal]))
8569                                }),
8570                            ),
8571                        );
8572                    }
8573                    GlobalRequest::ReleasePort(_) => {
8574                        return (None, GlobalResponse::ReleasePort(None));
8575                    }
8576                    GlobalRequest::HostTimedAndPrivateSigchld(_) => {
8577                        return (None, GlobalResponse::HostTimedAndPrivateSigchld(0, 0));
8578                    }
8579                    GlobalRequest::SetSavedGuestSigmask(mask) => {
8580                        let mut script = script.lock().unwrap();
8581                        script.published_mask = *mask;
8582                        script.publications.push(*mask);
8583                        return (None, GlobalResponse::SetSavedGuestSigmask(()));
8584                    }
8585                    GlobalRequest::TraceSchedEvent(..) => {
8586                        let mut script = script.lock().unwrap();
8587                        assert!(script.observe_retry_masks);
8588                        let blocked = self.kernel.lock().unwrap().blocked;
8589                        let published = script.published_mask;
8590                        script.retry_boundaries.push(("trace", blocked, published));
8591                        return (
8592                            None,
8593                            GlobalResponse::TraceSchedEvent(
8594                                serde_json::from_value(serde_json::json!({
8595                                    "print_stack_strace": null,
8596                                    "timeslice": null,
8597                                    "schedule_released": false,
8598                                    "schedule_over": false,
8599                                }))
8600                                .unwrap(),
8601                            ),
8602                        );
8603                    }
8604                    GlobalRequest::DiscardedSignal(_) => {
8605                        return (
8606                            None,
8607                            GlobalResponse::DiscardedSignal(
8608                                crate::scheduler::discarded_at_send::DiscardedSignalAnswer::Bits {
8609                                    thread: 0,
8610                                    process: 0,
8611                                },
8612                            ),
8613                        );
8614                    }
8615                    GlobalRequest::PendingRealTimeExpirations(_) => {
8616                        return (None, GlobalResponse::PendingRealTimeExpirations(0));
8617                    }
8618                    _ => panic!("unexpected proof fixture RPC: {:?}", message.2),
8619                }
8620            }
8621            match (&message.2, self.kernel.lock().unwrap().private_record) {
8622                (
8623                    crate::tool_global::GlobalRequest::HostTimedAndPrivateSigchld(_),
8624                    Some((host_timed, private)),
8625                ) => (
8626                    None,
8627                    crate::tool_global::GlobalResponse::HostTimedAndPrivateSigchld(
8628                        host_timed, private,
8629                    ),
8630                ),
8631                _ => panic!("these waits must not send an RPC: {:?}", message.2),
8632            }
8633        }
8634        fn config(&self) -> &Config {
8635            &self.config
8636        }
8637    }
8638
8639    #[reverie::tool]
8640    impl Guest<Detcore> for WaitGuest {
8641        type Memory = LocalMemory;
8642        type Stack = WaitStack;
8643
8644        fn tid(&self) -> Pid {
8645            self.tid
8646        }
8647        fn pid(&self) -> Pid {
8648            self.pid
8649        }
8650        fn ppid(&self) -> Option<Pid> {
8651            None
8652        }
8653        fn memory(&self) -> Self::Memory {
8654            LocalMemory::new()
8655        }
8656        fn thread_state_mut(&mut self) -> &mut ThreadState<()> {
8657            &mut self.thread
8658        }
8659        fn thread_state(&self) -> &ThreadState<()> {
8660            &self.thread
8661        }
8662        async fn regs(&mut self) -> libc::user_regs_struct {
8663            if self
8664                .proof_script
8665                .as_ref()
8666                .is_some_and(|script| script.lock().unwrap().observe_retry_masks)
8667            {
8668                let mut regs: libc::user_regs_struct = unsafe { std::mem::zeroed() };
8669                regs.rip = self.arena.as_ptr() as u64;
8670                return regs;
8671            }
8672            panic!("a blocked wait must not read registers")
8673        }
8674        async fn stack(&mut self) -> Self::Stack {
8675            WaitStack {
8676                commit_fails: self.commit_fails,
8677                arena: self.arena.as_mut_ptr() as usize,
8678            }
8679        }
8680        async fn daemonize(&mut self) {
8681            panic!("a blocked wait must not daemonize")
8682        }
8683        async fn inject<S: SyscallInfo>(&mut self, syscall: S) -> Result<i64, Errno> {
8684            let (number, args) = syscall.into_parts();
8685            if let Some(script) = &self.proof_script {
8686                match Syscall::from_raw(number, args) {
8687                    Syscall::Dup2(call) => {
8688                        // These descriptors are owned by this test. The real
8689                        // handler performs the corresponding model update.
8690                        let result = unsafe { libc::dup2(call.oldfd(), call.newfd()) };
8691                        assert!(result >= 0, "owned fixture descriptor duplication failed");
8692                        return Ok(i64::from(result));
8693                    }
8694                    Syscall::Readv(_) | Syscall::Poll(_) | Syscall::Ppoll(_) => {
8695                        let mut script = script.lock().unwrap();
8696                        script.probes += 1;
8697                        return if script.probes == 4 {
8698                            Ok(1)
8699                        } else if matches!(Syscall::from_raw(number, args), Syscall::Readv(_)) {
8700                            Err(Errno::EAGAIN)
8701                        } else {
8702                            Ok(0)
8703                        };
8704                    }
8705                    _ => {}
8706                }
8707            }
8708            if matches!(Syscall::from_raw(number, args), Syscall::Read(_)) {
8709                let mut kernel = self.kernel.lock().unwrap();
8710                assert!(kernel.empty_read_probes.is_some(), "unexpected read probe");
8711                let deliverable = kernel.pending & !kernel.blocked;
8712                if kernel.stops_for_pending && deliverable != 0 {
8713                    let signal = deliverable.trailing_zeros() as i32 + 1;
8714                    kernel.pending &= !kernel_sigset_bit(signal);
8715                    kernel.taken.push(signal);
8716                    kernel.hold(signal);
8717                    return Err(Errno::ERESTARTSYS);
8718                }
8719                *kernel.empty_read_probes.as_mut().unwrap() += 1;
8720                let blocked = kernel.blocked;
8721                kernel.read_probe_masks.push(blocked);
8722                if kernel
8723                    .read_ready_after
8724                    .is_some_and(|ready| kernel.empty_read_probes.unwrap() >= ready)
8725                {
8726                    return Ok(1);
8727                }
8728                return Err(Errno::EAGAIN);
8729            }
8730            if matches!(Syscall::from_raw(number, args), Syscall::Write(_)) {
8731                let mut kernel = self.kernel.lock().unwrap();
8732                let blocked = kernel.blocked;
8733                let probes = kernel
8734                    .write_probe_masks
8735                    .as_mut()
8736                    .expect("unexpected write probe");
8737                probes.push(blocked);
8738                let attempt = probes.len();
8739                assert!(attempt <= 3, "unbounded scalar-write fixture");
8740                if attempt == 1 {
8741                    let pending = std::mem::take(&mut kernel.pending_after_first_write);
8742                    kernel.pending |= pending;
8743                }
8744                return if attempt == 3 {
8745                    Ok(1)
8746                } else {
8747                    Err(Errno::EAGAIN)
8748                };
8749            }
8750            let Syscall::RtSigprocmask(call) = Syscall::from_raw(number, args) else {
8751                panic!("a blocked wait injected {number}, not rt_sigprocmask")
8752            };
8753            assert_eq!(call.how(), libc::SIG_SETMASK);
8754            assert!(call.oldset().is_none());
8755            let set = call.set().expect("rt_sigprocmask without a mask");
8756            let mask: KernelSigset = LocalMemory::new().read_value(set.cast::<KernelSigset>())?;
8757            let mut kernel = self.kernel.lock().unwrap();
8758            kernel.requested.push(mask);
8759            let deliverable = kernel.pending & !kernel.blocked;
8760            if kernel.stops_for_pending && deliverable != 0 {
8761                let signal = deliverable.trailing_zeros() as i32 + 1;
8762                kernel.pending &= !kernel_sigset_bit(signal);
8763                kernel.taken.push(signal);
8764                kernel.hold(signal);
8765                if kernel.repost_after_take > 0 {
8766                    kernel.repost_after_take -= 1;
8767                    kernel.pending_repost |= kernel_sigset_bit(signal);
8768                }
8769                return Err(Errno::ERESTARTSYS);
8770            }
8771            match kernel.outcomes.pop_front().unwrap_or(MaskOutcome::Apply) {
8772                MaskOutcome::Apply => {
8773                    kernel.blocked = mask;
8774                    Ok(0)
8775                }
8776                MaskOutcome::StoppedBefore => Err(Errno::ERESTARTNOINTR),
8777                MaskOutcome::StoppedBy(signal) => {
8778                    kernel.hold(signal);
8779                    Err(Errno::ERESTARTNOINTR)
8780                }
8781                MaskOutcome::Fail(errno) => Err(errno),
8782            }
8783        }
8784        async fn set_blocked_signals(&mut self, mask: u64) -> Result<u64, MaskChangeRefused> {
8785            let mut kernel = self.kernel.lock().unwrap();
8786            kernel.set_calls.push(mask);
8787            match kernel.setter.pop_front() {
8788                Some(None) => Ok(std::mem::replace(&mut kernel.blocked, mask)),
8789                Some(Some(refused)) => {
8790                    if matches!(refused, MaskChangeRefused::Gone) {
8791                        kernel.gone = true;
8792                    }
8793                    Err(refused)
8794                }
8795                None => Err(MaskChangeRefused::Unsupported),
8796            }
8797        }
8798
8799        async fn tail_inject<S: SyscallInfo>(&mut self, _: S) -> reverie::Never {
8800            panic!("a blocked wait must not retire the guest")
8801        }
8802        fn set_timer(&mut self, _: reverie::TimerSchedule) -> Result<(), Error> {
8803            panic!("a blocked wait must not set a timer")
8804        }
8805        fn set_timer_precise(&mut self, _: reverie::TimerSchedule) -> Result<(), Error> {
8806            panic!("a blocked wait must not set a timer")
8807        }
8808        fn read_clock(&mut self) -> Result<u64, Error> {
8809            panic!("a blocked wait must not read a clock")
8810        }
8811    }
8812
8813    /// Exercise the actual retry/publication path. The host owns the second
8814    /// pipe's writer; replacing the slot or poll operand can therefore make
8815    /// the next physical probe depend on input from outside the guest.
8816    async fn proof_requests(case: &str, kernel_wait: bool, masked_ppoll: bool) -> Vec<Resources> {
8817        proof_requests_from(case, kernel_wait, masked_ppoll, false).await
8818    }
8819
8820    async fn proof_requests_from(
8821        case: &str,
8822        kernel_wait: bool,
8823        masked_ppoll: bool,
8824        generic_fd_entry: bool,
8825    ) -> Vec<Resources> {
8826        use std::os::fd::AsRawFd;
8827
8828        let (mut guest, kernel) = WaitGuest::new(Pid::from_raw(3), 0);
8829        guest.config.sequentialize_threads = true;
8830        guest.config.backend.kernel_signal_targets_are_predictable = false;
8831        guest.thread.dettid = DetTid::from_raw(3);
8832        guest.thread.detpid = Some(DetPid::from_raw(3));
8833        let _proc = scripted_proc(&kernel);
8834        let (internal, _internal_writer) =
8835            nix::unistd::pipe2(nix::fcntl::OFlag::O_NONBLOCK).unwrap();
8836        let (external, _external_writer) =
8837            nix::unistd::pipe2(nix::fcntl::OFlag::O_NONBLOCK).unwrap();
8838        let saved = nix::unistd::dup(&internal).unwrap();
8839        let fd = internal.as_raw_fd();
8840        let external_fd = external.as_raw_fd();
8841        for raw in [fd, external_fd] {
8842            guest
8843                .thread
8844                .add_fd(raw, nix::fcntl::OFlag::O_RDONLY, FdType::Pipe, None)
8845                .unwrap();
8846            guest
8847                .thread
8848                .with_detfd(raw, |entry| entry.set_physically_nonblocking())
8849                .unwrap();
8850        }
8851        let initially_held = !matches!(case, "unclaimed-dup" | "late-mark");
8852        if initially_held {
8853            guest
8854                .thread
8855                .with_detfd(fd, |entry| entry.set_guest_held())
8856                .unwrap();
8857        }
8858        if matches!(case, "dup-guest" | "unclaimed-dup") {
8859            guest
8860                .thread
8861                .with_detfd(external_fd, |entry| entry.set_guest_held())
8862                .unwrap();
8863        }
8864        guest
8865            .thread
8866            .dup_fd(fd, saved.as_raw_fd(), nix::fcntl::OFlag::empty())
8867            .unwrap();
8868        let original = guest
8869            .thread
8870            .with_detfd(fd, |entry| entry.open_file_id())
8871            .unwrap();
8872        let mut operands = [libc::pollfd {
8873            fd,
8874            events: libc::POLLIN,
8875            revents: 0,
8876        }];
8877        let address = operands.as_mut_ptr() as usize;
8878        let (change, restore) = match case {
8879            "same" => (ProofAction::None, ProofAction::None),
8880            "noop-dup" => (ProofAction::Dup { from: fd, to: fd }, ProofAction::None),
8881            "dup" | "dup-guest" | "unclaimed-dup" => (
8882                ProofAction::Dup {
8883                    from: external_fd,
8884                    to: fd,
8885                },
8886                ProofAction::Dup {
8887                    from: saved.as_raw_fd(),
8888                    to: fd,
8889                },
8890            ),
8891            "operand" => (
8892                ProofAction::Operand {
8893                    address,
8894                    fd: external_fd,
8895                },
8896                ProofAction::Operand { address, fd },
8897            ),
8898            "mark" => (
8899                ProofAction::Mark { fd, held: false },
8900                ProofAction::Mark { fd, held: true },
8901            ),
8902            "late-mark" => (ProofAction::Mark { fd, held: true }, ProofAction::None),
8903            "unknown" => {
8904                guest.thread.remove_fd(fd);
8905                (ProofAction::None, ProofAction::None)
8906            }
8907            _ => panic!("unknown proof case {case}"),
8908        };
8909        let script = Arc::new(Mutex::new(ProofScript {
8910            actions: [ProofAction::None, change, restore, ProofAction::None].into(),
8911            ..Default::default()
8912        }));
8913        guest.proof_script = Some(script.clone());
8914        let mut resources = Resources::new(guest.thread.dettid);
8915        resources.insert(ResourceID::InternalIOPolling, Permission::W);
8916        if !generic_fd_entry {
8917            resources.polls_guest_state_only = true;
8918            resources.relies_on_guest_held_marks = true;
8919        }
8920        // Match the real unmasked poll/ppoll producers. Readv's generic
8921        // retry helper itself supplies its restart errno and task identity.
8922        if kernel_wait || matches!(case, "operand" | "unknown") {
8923            resources.set_signal_interrupt_errno(Errno::EINTR);
8924            resources.signal_state_task = guest_signal_state_task(&guest);
8925        }
8926        let mask = 0_u64;
8927        let zero_timeout = reverie::syscalls::Timespec {
8928            tv_sec: 0,
8929            tv_nsec: 0,
8930        };
8931        let mut byte = 0_u8;
8932        let iov = libc::iovec {
8933            iov_base: (&mut byte as *mut u8).cast(),
8934            iov_len: 1,
8935        };
8936        let result = if generic_fd_entry {
8937            let tool = Detcore {
8938                detpid: guest.thread.detpid.unwrap(),
8939                cfg: guest.config.clone(),
8940                record_or_replay: crate::record_or_replay::NoopTool,
8941            };
8942            // The ordinary logically blocking pipe must enter through the
8943            // production classifier, without a pre-armed resource request.
8944            tool.execute_nonblockable_fd_syscall(
8945                &mut guest,
8946                syscalls::Readv::new()
8947                    .with_fd(fd)
8948                    .with_iov(Addr::from_raw(&iov as *const _ as usize))
8949                    .with_len(1),
8950            )
8951            .await
8952        } else if masked_ppoll {
8953            let tool = Detcore {
8954                detpid: guest.thread.detpid.unwrap(),
8955                cfg: guest.config.clone(),
8956                record_or_replay: crate::record_or_replay::NoopTool,
8957            };
8958            tool.masked_ppoll_probe_for_test(
8959                &mut guest,
8960                syscalls::Ppoll::new()
8961                    .with_fds(AddrMut::from_raw(address))
8962                    .with_nfds(1)
8963                    .with_timeout(AddrMut::from_raw(&zero_timeout as *const _ as usize))
8964                    .with_sigmask(Addr::from_raw(&mask as *const _ as usize))
8965                    .with_sigsetsize(KERNEL_SIGSET_SIZE),
8966                &mask as *const _ as usize,
8967            )
8968            .await
8969        } else if kernel_wait {
8970            retry_blocking_wait_under_mask(
8971                &mut guest,
8972                syscalls::Poll::new()
8973                    .with_fds(AddrMut::from_raw(address))
8974                    .with_nfds(1)
8975                    .with_timeout(-1),
8976                resources,
8977                None,
8978                None,
8979            )
8980            .await
8981            .0
8982        } else if matches!(case, "operand" | "unknown") {
8983            retry_nonblocking_syscall_helper(
8984                &mut guest,
8985                syscalls::Poll::new()
8986                    .with_fds(AddrMut::from_raw(address))
8987                    .with_nfds(1)
8988                    .with_timeout(-1),
8989                resources,
8990                None,
8991                None,
8992            )
8993            .await
8994        } else {
8995            retry_nonblocking_syscall_helper(
8996                &mut guest,
8997                syscalls::Readv::new().with_fd(fd),
8998                resources,
8999                None,
9000                None,
9001            )
9002            .await
9003        };
9004        assert_eq!(result.unwrap(), 1, "retry syscall result is unchanged");
9005        let script = script.lock().unwrap();
9006        assert_eq!(script.probes, 4);
9007        assert_eq!(
9008            script
9009                .requests
9010                .iter()
9011                .map(|r| r.poll_attempt)
9012                .collect::<Vec<_>>(),
9013            [0, 1, 2, 3]
9014        );
9015        if case != "unknown" {
9016            assert_eq!(
9017                guest
9018                    .thread
9019                    .with_detfd(fd, |entry| entry.open_file_id())
9020                    .unwrap(),
9021                original
9022            );
9023            if case == "unclaimed-dup" {
9024                assert!(
9025                    !guest
9026                        .thread
9027                        .with_detfd(fd, |entry| entry.is_guest_held())
9028                        .unwrap()
9029                );
9030            } else {
9031                assert!(
9032                    guest
9033                        .thread
9034                        .with_detfd(fd, |entry| entry.is_guest_held())
9035                        .unwrap()
9036                );
9037            }
9038            assert_eq!(
9039                operands[0].fd, fd,
9040                "restored operands cannot restore a lost proof"
9041            );
9042        }
9043        script.requests.clone()
9044    }
9045
9046    #[tokio::test]
9047    async fn a_pipe_readv_entry_retains_only_its_original_poll_identity() {
9048        for case in [
9049            "same",
9050            "noop-dup",
9051            "dup-guest",
9052            "dup",
9053            "mark",
9054            "unclaimed-dup",
9055            "late-mark",
9056        ] {
9057            let requests = proof_requests_from(case, false, false, true).await;
9058            let initially_held = !matches!(case, "unclaimed-dup" | "late-mark");
9059            let valid = matches!(case, "same" | "noop-dup");
9060            for request in &requests[..2] {
9061                assert_eq!(
9062                    request.polls_guest_state_only, initially_held,
9063                    "{case}: before replacement"
9064                );
9065                assert_eq!(
9066                    request.relies_on_guest_held_marks, initially_held,
9067                    "{case}: before replacement"
9068                );
9069            }
9070            for request in &requests[2..] {
9071                assert_eq!(
9072                    crate::scheduler::request_reports_idle_deadlock_for_test(request.clone()),
9073                    valid,
9074                    "{case}, attempt {}: actual entry's terminal verdict",
9075                    request.poll_attempt
9076                );
9077                assert_eq!(request.polls_guest_state_only, valid, "{case}");
9078                assert_eq!(request.relies_on_guest_held_marks, valid, "{case}");
9079            }
9080        }
9081    }
9082
9083    #[tokio::test]
9084    async fn a_retried_descriptor_proof_does_not_survive_sibling_replacement() {
9085        for kernel_wait in [false, true] {
9086            for case in ["same", "noop-dup", "dup", "dup-guest", "mark"] {
9087                let requests = proof_requests(case, kernel_wait, false).await;
9088                let valid = matches!(case, "same" | "noop-dup");
9089                assert!(requests[0].polls_guest_state_only);
9090                assert!(requests[1].polls_guest_state_only);
9091                for request in &requests[2..] {
9092                    assert_eq!(
9093                        crate::scheduler::request_reports_idle_deadlock_for_test(request.clone()),
9094                        valid,
9095                        "{case}, kernel_wait={kernel_wait}: actual published request's terminal verdict"
9096                    );
9097                    assert_eq!(
9098                        request.polls_guest_state_only, valid,
9099                        "{case}, kernel_wait={kernel_wait}"
9100                    );
9101                    assert_eq!(
9102                        request.relies_on_guest_held_marks, valid,
9103                        "{case}, kernel_wait={kernel_wait}"
9104                    );
9105                }
9106            }
9107        }
9108    }
9109
9110    #[tokio::test]
9111    async fn a_retried_poll_proof_does_not_survive_sibling_operand_replacement() {
9112        for kernel_wait in [false, true] {
9113            let requests = proof_requests("operand", kernel_wait, false).await;
9114            assert!(requests[0].polls_guest_state_only);
9115            assert!(requests[1].polls_guest_state_only);
9116            for request in &requests[2..] {
9117                assert!(
9118                    !crate::scheduler::request_reports_idle_deadlock_for_test(request.clone()),
9119                    "kernel_wait={kernel_wait}: replaced operand cannot prove terminal deadlock"
9120                );
9121                assert!(!request.polls_guest_state_only, "kernel_wait={kernel_wait}");
9122                assert!(
9123                    !request.relies_on_guest_held_marks,
9124                    "kernel_wait={kernel_wait}"
9125                );
9126            }
9127        }
9128    }
9129
9130    #[tokio::test]
9131    async fn a_retried_descriptor_proof_requires_an_initial_identity() {
9132        for kernel_wait in [false, true] {
9133            for request in proof_requests("unknown", kernel_wait, false).await {
9134                if request.poll_attempt > 0 {
9135                    assert!(
9136                        !crate::scheduler::request_reports_idle_deadlock_for_test(request.clone()),
9137                        "kernel_wait={kernel_wait}: missing identity cannot prove terminal deadlock"
9138                    );
9139                }
9140                assert!(!request.polls_guest_state_only, "kernel_wait={kernel_wait}");
9141                assert!(
9142                    !request.relies_on_guest_held_marks,
9143                    "kernel_wait={kernel_wait}"
9144                );
9145            }
9146        }
9147    }
9148
9149    #[tokio::test]
9150    async fn a_masked_ppoll_proof_does_not_survive_sibling_replacement() {
9151        for case in ["same", "noop-dup", "dup", "dup-guest", "operand", "mark"] {
9152            let requests = proof_requests(case, false, true).await;
9153            let valid = matches!(case, "same" | "noop-dup");
9154            assert!(requests[0].polls_guest_state_only);
9155            assert!(requests[1].polls_guest_state_only);
9156            for request in &requests[2..] {
9157                assert_eq!(
9158                    crate::scheduler::request_reports_idle_deadlock_for_test(request.clone()),
9159                    valid,
9160                    "{case}: masked ppoll's actual published request"
9161                );
9162                assert_eq!(request.polls_guest_state_only, valid);
9163                assert_eq!(request.relies_on_guest_held_marks, valid);
9164            }
9165        }
9166    }
9167
9168    /// Protect the generic loop's first-turn call site, independently of
9169    /// turn_signals_with_block's internal mask-install condition. A signal
9170    /// arrives after its first empty snapshot and before the private read.
9171    #[tokio::test]
9172    async fn a_generic_reads_first_probe_keeps_a_late_signal_visible() {
9173        use std::os::fd::AsRawFd;
9174
9175        let usr1 = kernel_sigset_bit(libc::SIGUSR1);
9176        for (sent, blocked) in [(true, false), (false, false), (true, true)] {
9177            let own_mask = guest_mask() | if blocked { usr1 } else { 0 };
9178            let (mut guest, kernel) = WaitGuest::new(Pid::from_raw(3), own_mask);
9179            guest.config.sequentialize_threads = true;
9180            guest.config.backend.kernel_signal_targets_are_predictable = true;
9181            guest.thread.dettid = DetTid::from_raw(3);
9182            guest.thread.detpid = Some(DetPid::from_raw(3));
9183            let (reader, _writer) = nix::unistd::pipe2(nix::fcntl::OFlag::O_NONBLOCK).unwrap();
9184            let fd = reader.as_raw_fd();
9185            guest
9186                .thread
9187                .add_fd(fd, nix::fcntl::OFlag::O_RDONLY, FdType::Pipe, None)
9188                .unwrap();
9189            guest
9190                .thread
9191                .with_detfd(fd, |entry| entry.set_physically_nonblocking())
9192                .unwrap();
9193            let script = Arc::new(Mutex::new(ProofScript::default()));
9194            guest.proof_script = Some(script.clone());
9195            {
9196                let mut kernel = kernel.lock().unwrap();
9197                kernel.caught = usr1;
9198                kernel.pending_repost = if sent { usr1 } else { 0 };
9199                kernel.stops_for_pending = true;
9200                kernel.empty_read_probes = Some(0);
9201                kernel.read_ready_after = Some(4);
9202                // A successful in-place setter remains available on every
9203                // probe and restoration, not just the first and final ones.
9204                kernel.setter.extend(std::iter::repeat_n(None, 8));
9205            }
9206            let _proc = scripted_proc(&kernel);
9207            let mut resources = Resources::new(guest.thread.dettid);
9208            resources.insert(ResourceID::InternalIOPolling, Permission::W);
9209            let result = retry_nonblocking_syscall_helper(
9210                &mut guest,
9211                syscalls::Read::new().with_fd(fd).with_len(1),
9212                resources,
9213                None,
9214                None,
9215            )
9216            .await
9217            .map_err(|error| error.into_errno().expect("guest errno"));
9218            let kernel = kernel.lock().unwrap();
9219            assert_eq!(
9220                kernel.blocked, own_mask,
9221                "the original guest mask is restored"
9222            );
9223            assert_eq!(
9224                result,
9225                if sent && !blocked {
9226                    Err(Errno::ERESTARTSYS)
9227                } else {
9228                    Ok(1)
9229                },
9230                "sent={sent}, blocked={blocked}, pending={:#x}, held={:?}, taken={:?}",
9231                kernel.pending,
9232                kernel.held_slot,
9233                kernel.taken
9234            );
9235            assert_eq!(kernel.held_slot, None);
9236            assert_eq!(kernel.pending, if sent { usr1 } else { 0 });
9237            assert_eq!(
9238                kernel.empty_read_probes,
9239                Some(if sent && !blocked { 2 } else { 4 })
9240            );
9241            assert_eq!(
9242                script.lock().unwrap().requests.len(),
9243                if sent && !blocked { 2 } else { 4 }
9244            );
9245        }
9246    }
9247
9248    /// Run the real generic read loop, including its retry TraceSchedEvent.
9249    /// A fourth ready probe bounds a broken implementation without a timeout.
9250    async fn read_observing_retry_masks(
9251        route_child: bool,
9252        late_signal: bool,
9253        setter: Vec<Option<MaskChangeRefused>>,
9254        outcomes: Vec<MaskOutcome>,
9255    ) -> (Result<i64, Error>, Kernel, Arc<Mutex<ProofScript>>) {
9256        use std::os::fd::AsRawFd;
9257
9258        let own_mask = guest_mask();
9259        let (mut guest, kernel) = WaitGuest::new(Pid::from_raw(3), own_mask);
9260        guest.config.sequentialize_threads = true;
9261        guest.config.record_preemptions = true;
9262        guest.config.panic_on_unsupported_syscalls = false;
9263        guest.config.backend.kernel_signal_targets_are_predictable = true;
9264        guest.thread.dettid = DetTid::from_raw(3);
9265        guest.thread.detpid = Some(DetPid::from_raw(3));
9266        let (reader, _writer) = nix::unistd::pipe2(nix::fcntl::OFlag::O_NONBLOCK).unwrap();
9267        let fd = reader.as_raw_fd();
9268        guest
9269            .thread
9270            .add_fd(fd, nix::fcntl::OFlag::O_RDONLY, FdType::Pipe, None)
9271            .unwrap();
9272        guest
9273            .thread
9274            .with_detfd(fd, |entry| entry.set_physically_nonblocking())
9275            .unwrap();
9276        let script = Arc::new(Mutex::new(ProofScript {
9277            observe_retry_masks: true,
9278            route_child_on_retry: route_child,
9279            ..ProofScript::default()
9280        }));
9281        guest.proof_script = Some(script.clone());
9282        {
9283            let mut kernel = kernel.lock().unwrap();
9284            kernel.caught = kernel_sigset_bit(libc::SIGCHLD) | kernel_sigset_bit(libc::SIGUSR1);
9285            kernel.pending_repost = if late_signal {
9286                kernel_sigset_bit(libc::SIGUSR1)
9287            } else {
9288                0
9289            };
9290            kernel.stops_for_pending = true;
9291            kernel.empty_read_probes = Some(0);
9292            kernel.read_ready_after = Some(4);
9293            kernel.setter.extend(setter);
9294            kernel.outcomes.extend(outcomes);
9295        }
9296        let _proc = scripted_proc(&kernel);
9297        let mut resources = Resources::new(guest.thread.dettid);
9298        resources.insert(ResourceID::InternalIOPolling, Permission::W);
9299        let result = retry_nonblocking_syscall_helper(
9300            &mut guest,
9301            syscalls::Read::new().with_fd(fd).with_len(1),
9302            resources,
9303            None,
9304            None,
9305        )
9306        .await;
9307        (result, kernel, script)
9308    }
9309
9310    #[tokio::test]
9311    async fn a_generic_read_restores_its_mask_before_retry_yields() {
9312        for (route_child, late_signal) in [(true, false), (false, true), (false, false)] {
9313            let (result, kernel, script) =
9314                read_observing_retry_masks(route_child, late_signal, vec![None; 8], vec![]).await;
9315            let kernel = kernel.lock().unwrap();
9316            let script = script.lock().unwrap();
9317            assert_eq!(
9318                result.map_err(|error| error.into_errno().expect("guest errno")),
9319                if route_child || late_signal {
9320                    Err(Errno::ERESTARTSYS)
9321                } else {
9322                    Ok(1)
9323                },
9324                "route_child={route_child}, late_signal={late_signal}",
9325            );
9326            let attempts = if route_child || late_signal { 2 } else { 4 };
9327            assert_eq!(script.requests.len(), attempts);
9328            assert_eq!(kernel.empty_read_probes, Some(attempts));
9329            assert_eq!(
9330                kernel.read_probe_masks,
9331                vec![blocked_signal_mask() | guest_mask(); attempts]
9332            );
9333            assert_eq!(script.retry_boundaries.len(), attempts * 2 - 1);
9334            for (boundary, blocked, published) in &script.retry_boundaries {
9335                assert_eq!(
9336                    *blocked,
9337                    guest_mask(),
9338                    "{boundary} must see the guest's mask"
9339                );
9340                assert_eq!(
9341                    *published, None,
9342                    "{boundary} must not keep a private-mask publication"
9343                );
9344            }
9345            assert_eq!(
9346                script.publications,
9347                [Some(guest_mask()), None].repeat(attempts)
9348            );
9349            assert_eq!(kernel.blocked, guest_mask());
9350            assert_eq!(
9351                kernel.pending,
9352                if route_child {
9353                    kernel_sigset_bit(libc::SIGCHLD)
9354                } else if late_signal {
9355                    kernel_sigset_bit(libc::SIGUSR1)
9356                } else {
9357                    0
9358                }
9359            );
9360            assert_eq!(kernel.held_slot, None);
9361            assert!(kernel.taken.is_empty());
9362            assert!(kernel.lost.is_empty());
9363        }
9364    }
9365
9366    #[tokio::test]
9367    async fn a_generic_read_does_not_retry_after_an_in_place_release_refusal() {
9368        for refused in [
9369            MaskChangeRefused::Unsupported,
9370            MaskChangeRefused::RestorePending {
9371                saved: guest_mask(),
9372                effective: blocked_signal_mask() | guest_mask(),
9373            },
9374            MaskChangeRefused::Failed {
9375                errno: Errno::EIO,
9376                mask_unknown: false,
9377            },
9378        ] {
9379            let (result, kernel, script) =
9380                read_observing_retry_masks(false, false, vec![None, Some(refused), None], vec![])
9381                    .await;
9382            assert!(matches!(
9383                diagnostic(result),
9384                BlockedWaitSignalError::MaskNotRestored { attempts: 1, .. }
9385            ));
9386            let kernel = kernel.lock().unwrap();
9387            let script = script.lock().unwrap();
9388            assert_eq!(kernel.empty_read_probes, Some(1));
9389            assert_eq!(script.requests.len(), 1);
9390            assert_eq!(
9391                script.retry_boundaries,
9392                vec![("request", guest_mask(), None)]
9393            );
9394            assert_eq!(
9395                kernel.blocked,
9396                guest_mask(),
9397                "final cleanup still restores the mask"
9398            );
9399            assert_eq!(script.publications, vec![Some(guest_mask()), None]);
9400            assert!(
9401                kernel.requested.is_empty(),
9402                "no injected restoration was needed"
9403            );
9404            assert_eq!(kernel.held_slot, None);
9405        }
9406    }
9407
9408    #[tokio::test]
9409    async fn a_generic_read_stops_immediately_when_release_leaves_the_mask_unknown() {
9410        let (result, kernel, script) = read_observing_retry_masks(
9411            false,
9412            false,
9413            vec![
9414                None,
9415                Some(MaskChangeRefused::Failed {
9416                    errno: Errno::EIO,
9417                    mask_unknown: true,
9418                }),
9419            ],
9420            vec![],
9421        )
9422        .await;
9423        assert!(matches!(
9424            diagnostic(result),
9425            BlockedWaitSignalError::MaskNotRestored {
9426                attempts: 1,
9427                last_error: Some(Errno::EIO),
9428                ..
9429            }
9430        ));
9431        let kernel = kernel.lock().unwrap();
9432        let script = script.lock().unwrap();
9433        assert_eq!(kernel.empty_read_probes, Some(1));
9434        assert_eq!(script.requests.len(), 1);
9435        assert_eq!(
9436            script.retry_boundaries,
9437            vec![("request", guest_mask(), None)]
9438        );
9439        assert_eq!(
9440            kernel.set_calls.len(),
9441            2,
9442            "an unknown mask is not probed or restored speculatively"
9443        );
9444        assert!(kernel.requested.is_empty());
9445    }
9446
9447    #[tokio::test]
9448    async fn a_generic_read_keeps_a_release_failure_fatal_after_loss_during_cleanup() {
9449        let (result, kernel, script) = read_observing_retry_masks(
9450            false,
9451            false,
9452            vec![
9453                None,
9454                Some(MaskChangeRefused::Unsupported),
9455                Some(MaskChangeRefused::Unsupported),
9456            ],
9457            vec![
9458                MaskOutcome::StoppedBy(libc::SIGUSR1),
9459                MaskOutcome::StoppedBy(libc::SIGUSR2),
9460                MaskOutcome::Apply,
9461            ],
9462        )
9463        .await;
9464        // allow-unsupported must not turn this failed atomic release into the
9465        // HeldSignalRefusal policy's transparent ERESTARTNOINTR retry.
9466        assert!(matches!(
9467            diagnostic(result),
9468            BlockedWaitSignalError::MaskNotRestored { attempts: 1, .. }
9469        ));
9470        let kernel = kernel.lock().unwrap();
9471        let script = script.lock().unwrap();
9472        assert_eq!(kernel.empty_read_probes, Some(1));
9473        assert_eq!(script.requests.len(), 1);
9474        assert_eq!(
9475            script.retry_boundaries,
9476            vec![("request", guest_mask(), None)]
9477        );
9478        assert_eq!(kernel.blocked, guest_mask());
9479        assert_eq!(kernel.requested, vec![guest_mask(); 3]);
9480        assert_eq!(kernel.lost, vec![libc::SIGUSR1]);
9481        assert_eq!(kernel.held_slot, Some(libc::SIGUSR2));
9482    }
9483
9484    #[tokio::test]
9485    async fn a_scalar_write_keeps_its_pending_only_probe_mask_policy() {
9486        use std::os::fd::AsRawFd;
9487
9488        let usr1 = kernel_sigset_bit(libc::SIGUSR1);
9489        for (sent, blocked) in [(false, false), (true, false), (true, true)] {
9490            let own_mask = guest_mask() | if blocked { usr1 } else { 0 };
9491            let (mut guest, kernel) = WaitGuest::new(Pid::from_raw(3), own_mask);
9492            guest.config.sequentialize_threads = true;
9493            guest.config.backend.kernel_signal_targets_are_predictable = true;
9494            guest.thread.dettid = DetTid::from_raw(3);
9495            guest.thread.detpid = Some(DetPid::from_raw(3));
9496            let (_reader, writer) = nix::unistd::pipe2(nix::fcntl::OFlag::O_NONBLOCK).unwrap();
9497            let fd = writer.as_raw_fd();
9498            guest
9499                .thread
9500                .add_fd(fd, nix::fcntl::OFlag::O_WRONLY, FdType::Pipe, None)
9501                .unwrap();
9502            let open_file = guest
9503                .thread
9504                .with_detfd(fd, |entry| {
9505                    entry.set_physically_nonblocking();
9506                    entry.open_file_id()
9507                })
9508                .unwrap();
9509            let script = Arc::new(Mutex::new(ProofScript::default()));
9510            guest.proof_script = Some(script.clone());
9511            {
9512                let mut kernel = kernel.lock().unwrap();
9513                kernel.caught = usr1;
9514                kernel.write_probe_masks = Some(vec![]);
9515                kernel.pending_after_first_write = if sent { usr1 } else { 0 };
9516                kernel.setter.extend([None, None]);
9517            }
9518            let _proc = scripted_proc(&kernel);
9519            let tool = Detcore {
9520                detpid: guest.thread.detpid.unwrap(),
9521                cfg: guest.config.clone(),
9522                record_or_replay: crate::record_or_replay::NoopTool,
9523            };
9524            let byte = 0u8;
9525            let result = tool
9526                .execute_blocking_pipe_write(
9527                    &mut guest,
9528                    syscalls::Write::new()
9529                        .with_fd(fd)
9530                        .with_buf(Addr::from_ptr(&byte))
9531                        .with_len(1),
9532                    open_file,
9533                )
9534                .await
9535                .map_err(|error| error.into_errno().expect("guest errno"));
9536            let kernel = kernel.lock().unwrap();
9537            let script = script.lock().unwrap();
9538            if sent && !blocked {
9539                assert_eq!(result, Err(Errno::ERESTARTSYS));
9540                assert_eq!(
9541                    kernel.write_probe_masks.as_deref(),
9542                    Some([own_mask, blocked_signal_mask() | own_mask].as_slice())
9543                );
9544                assert_eq!(
9545                    kernel.set_calls,
9546                    vec![blocked_signal_mask() | own_mask, own_mask]
9547                );
9548                assert_eq!(script.publications, vec![Some(own_mask), None]);
9549                assert_eq!(script.requests.len(), 1);
9550            } else {
9551                assert_eq!(result, Ok(1));
9552                assert_eq!(
9553                    kernel.write_probe_masks.as_deref(),
9554                    Some([own_mask; 3].as_slice()),
9555                    "sent={sent}, blocked={blocked}"
9556                );
9557                assert!(
9558                    kernel.set_calls.is_empty(),
9559                    "an empty or guest-blocked snapshot must not change the scalar writer's mask"
9560                );
9561                assert!(script.publications.is_empty());
9562                assert_eq!(script.requests.len(), 2);
9563            }
9564            assert_eq!(kernel.blocked, own_mask);
9565            assert_eq!(kernel.pending, if sent { usr1 } else { 0 });
9566            assert_eq!(kernel.held_slot, None);
9567            assert!(kernel.lost.is_empty());
9568        }
9569    }
9570
9571    #[tokio::test]
9572    async fn a_generic_read_does_not_handoff_after_release_reports_exit() {
9573        let (result, kernel, script) = read_observing_retry_masks(
9574            false,
9575            false,
9576            vec![None, Some(MaskChangeRefused::Gone)],
9577            vec![],
9578        )
9579        .await;
9580        // read_wait_signal_state uses this same outcome for a confirmed
9581        // vanished thread; no thread observes it (threads.rs).
9582        assert_eq!(
9583            result.map_err(|error| error.into_errno().expect("gone-thread errno")),
9584            Err(Errno::ERESTARTNOINTR)
9585        );
9586        let kernel = kernel.lock().unwrap();
9587        let script = script.lock().unwrap();
9588        assert!(kernel.gone);
9589        assert_eq!(kernel.empty_read_probes, Some(1));
9590        assert_eq!(script.requests.len(), 1);
9591        assert_eq!(
9592            script.retry_boundaries,
9593            vec![("request", guest_mask(), None)]
9594        );
9595        assert_eq!(kernel.set_calls.len(), 2);
9596        assert!(kernel.requested.is_empty());
9597    }
9598
9599    /// The guest's own mask in these tests.
9600    fn guest_mask() -> KernelSigset {
9601        kernel_sigset_bit(libc::SIGUSR2)
9602    }
9603
9604    /// The diagnostic a result ends the run with.
9605    fn diagnostic<V: std::fmt::Debug>(result: Result<V, Error>) -> BlockedWaitSignalError {
9606        match result {
9607            Err(Error::Tool(error)) => *error
9608                .downcast_ref::<BlockedWaitSignalError>()
9609                .unwrap_or_else(|| panic!("not a blocked-wait diagnostic: {error:#}")),
9610            other => panic!("expected the run to end with a diagnostic, got {other:?}"),
9611        }
9612    }
9613
9614    /// The held-signal refusal a result ends the wait with.
9615    fn refusal<V: std::fmt::Debug>(result: Result<V, Error>) -> HeldSignalRefusal {
9616        match result {
9617            Err(Error::Tool(error)) => *error
9618                .downcast_ref::<HeldSignalRefusal>()
9619                .unwrap_or_else(|| panic!("not a held-signal refusal: {error:#}")),
9620            other => panic!("expected the wait to end with a held-signal refusal, got {other:?}"),
9621        }
9622    }
9623
9624    /// A wait whose guest mask `block` replaced with the all-blocked mask.
9625    async fn blocked_wait(guest: &mut WaitGuest, kernel: &Kernel) -> KernelSignalWait {
9626        let mut wait = KernelSignalWait::new(guest, 0, false, Errno::ERESTARTSYS);
9627        wait.block(guest, None).await.unwrap();
9628        assert!(!wait.needs_block());
9629        assert_eq!(kernel.lock().unwrap().blocked, blocked_signal_mask());
9630        wait
9631    }
9632
9633    /// Each stopped attempt is a stop by a signal that `/proc` cannot identify,
9634    /// which the backend holds in place of the one before. `restore` used to
9635    /// return success here; the second stop drops the first unidentified one,
9636    /// which may matter, so the mask is still put back and the wait now ends
9637    /// with a `HeldSignalRefusal`, as a wait loop that meets two such stops
9638    /// does (round-11 High F1 on
9639    /// https://github.com/rrnewton/hermit/pull/3361).
9640    #[tokio::test]
9641    async fn restore_repeats_the_mask_change_until_the_kernel_reports_it() {
9642        let (mut guest, kernel) = WaitGuest::live(guest_mask());
9643        let _proc = scripted_proc(&kernel);
9644        let mut wait = blocked_wait(&mut guest, &kernel).await;
9645        kernel
9646            .lock()
9647            .unwrap()
9648            .outcomes
9649            .extend([MaskOutcome::StoppedBefore; 5]);
9650
9651        let result = wait.restore(&mut guest, None).await;
9652
9653        assert_eq!(
9654            refusal(result),
9655            HeldSignalRefusal {
9656                wait: None,
9657                held: None,
9658                replacing: None,
9659                loss: HeldSignalLoss::Restoration,
9660            }
9661        );
9662        let kernel = kernel.lock().unwrap();
9663        assert_eq!(kernel.blocked, guest_mask());
9664        assert_eq!(
9665            kernel.requested.len(),
9666            1 + 6,
9667            "block, then five stopped attempts and one that ran"
9668        );
9669        assert!(
9670            kernel.requested[1..]
9671                .iter()
9672                .all(|&mask| mask == guest_mask())
9673        );
9674    }
9675
9676    #[tokio::test]
9677    async fn a_mask_that_signals_keep_stopping_ends_the_run_instead_of_resuming_the_guest() {
9678        let (mut guest, kernel) = WaitGuest::live(guest_mask());
9679        let _proc = scripted_proc(&kernel);
9680        let mut wait = blocked_wait(&mut guest, &kernel).await;
9681        kernel
9682            .lock()
9683            .unwrap()
9684            .outcomes
9685            .extend([MaskOutcome::StoppedBefore; 64]);
9686
9687        let error = diagnostic(wait.restore(&mut guest, None).await);
9688
9689        assert_eq!(
9690            error,
9691            BlockedWaitSignalError::MaskNotRestored {
9692                pid: guest.pid,
9693                tid: guest.tid,
9694                attempts: 16,
9695                last_error: Some(Errno::ERESTARTNOINTR),
9696            }
9697        );
9698        let message = error.to_string();
9699        assert!(
9700            message.starts_with("cannot restore the signal mask of guest thread")
9701                && message.contains("(16 attempts, last error ")
9702                && message.contains("ERESTARTNOINTR")
9703                && message.ends_with("); it would resume with every signal blocked"),
9704            "{message}"
9705        );
9706        assert_eq!(kernel.lock().unwrap().requested.len(), 1 + 16);
9707    }
9708
9709    #[tokio::test]
9710    async fn a_mask_change_that_cannot_run_ends_the_run_instead_of_resuming_the_guest() {
9711        let (mut guest, kernel) = WaitGuest::live(guest_mask());
9712        let _proc = scripted_proc(&kernel);
9713        let mut wait = blocked_wait(&mut guest, &kernel).await;
9714        kernel
9715            .lock()
9716            .unwrap()
9717            .outcomes
9718            .push_back(MaskOutcome::Fail(Errno::EFAULT));
9719
9720        let error = diagnostic(wait.restore(&mut guest, None).await);
9721
9722        assert_eq!(
9723            error,
9724            BlockedWaitSignalError::MaskNotRestored {
9725                pid: guest.pid,
9726                tid: guest.tid,
9727                attempts: 1,
9728                last_error: Some(Errno::EFAULT),
9729            }
9730        );
9731    }
9732
9733    /// `block_in_place` blocks through `Guest::set_blocked_signals`, with no
9734    /// injection, and `restore` puts the guest's mask back the same way: no
9735    /// scratch memory is touched, so a stack with no room below it (a failing
9736    /// commit) changes nothing (Claude review of the R2b-2 design, M1).
9737    #[tokio::test]
9738    async fn an_in_place_block_and_its_restore_make_no_injection() {
9739        let (mut guest, kernel) = WaitGuest::live(guest_mask());
9740        let _proc = scripted_proc(&kernel);
9741        guest.commit_fails = true;
9742        kernel.lock().unwrap().setter.extend([None, None]);
9743        let mut wait = KernelSignalWait::new(&guest, 0, false, Errno::ERESTARTSYS);
9744        assert!(wait.block_in_place(&mut guest, 0).await.unwrap());
9745        assert_eq!(wait.effective_mask(), Some(guest_mask()));
9746        assert_eq!(
9747            kernel.lock().unwrap().blocked,
9748            blocked_signal_mask() | guest_mask()
9749        );
9750        wait.restore(&mut guest, None).await.unwrap();
9751        let kernel = kernel.lock().unwrap();
9752        assert_eq!(kernel.blocked, guest_mask());
9753        assert_eq!(
9754            kernel.set_calls,
9755            vec![blocked_signal_mask() | guest_mask(), guest_mask()]
9756        );
9757        assert!(
9758            kernel.requested.is_empty(),
9759            "no rt_sigprocmask was injected"
9760        );
9761        assert_eq!(wait.effective_mask(), None);
9762    }
9763
9764    /// The in-place block holds the turn's host-timed signals until the call
9765    /// returns (`hold_until_return`), as `install_turn_mask` does for
9766    /// `writev`, so a fallback injected restoration that one of them stops
9767    /// holds it rather than counting it lost (Claude re-check of R2b-2 stage
9768    /// 2, F2).
9769    #[tokio::test]
9770    async fn an_in_place_block_holds_the_host_timed_signals() {
9771        let (mut guest, kernel) = WaitGuest::live(guest_mask());
9772        let _proc = scripted_proc(&kernel);
9773        kernel.lock().unwrap().setter.push_back(None);
9774        let host_timed = kernel_sigset_bit(libc::SIGIO) | kernel_sigset_bit(libc::SIGHUP);
9775        let mut wait = KernelSignalWait::new(&guest, 0, false, Errno::ERESTARTSYS);
9776        assert!(wait.block_in_place(&mut guest, host_timed).await.unwrap());
9777        assert_eq!(wait.held_until_return & host_timed, host_timed);
9778        assert_eq!(wait.host_timed & host_timed, host_timed);
9779    }
9780
9781    /// A refused in-place block changes nothing: the wait goes on under the
9782    /// guest's own mask, does not try again (`needs_block`), and has nothing
9783    /// to restore.
9784    #[tokio::test]
9785    async fn a_refused_in_place_block_leaves_the_guest_mask() {
9786        let (mut guest, kernel) = WaitGuest::live(guest_mask());
9787        let _proc = scripted_proc(&kernel);
9788        kernel
9789            .lock()
9790            .unwrap()
9791            .setter
9792            .push_back(Some(MaskChangeRefused::RestorePending {
9793                saved: guest_mask(),
9794                effective: 0,
9795            }));
9796        let mut wait = KernelSignalWait::new(&guest, 0, false, Errno::ERESTARTSYS);
9797        assert!(!wait.block_in_place(&mut guest, 0).await.unwrap());
9798        assert!(!wait.needs_block());
9799        assert_eq!(wait.effective_mask(), None);
9800        wait.restore(&mut guest, None).await.unwrap();
9801        let kernel = kernel.lock().unwrap();
9802        assert_eq!(kernel.blocked, guest_mask());
9803        assert_eq!(kernel.set_calls.len(), 1);
9804        assert!(kernel.requested.is_empty());
9805    }
9806
9807    /// A restore the setter refuses falls back to the injected restoration,
9808    /// which puts the guest's mask back (the reviews of
9809    /// https://github.com/rrnewton/reverie/pull/1083, F4).
9810    #[tokio::test]
9811    async fn a_refused_in_place_restore_falls_back_to_an_injection() {
9812        let (mut guest, kernel) = WaitGuest::live(guest_mask());
9813        let _proc = scripted_proc(&kernel);
9814        kernel
9815            .lock()
9816            .unwrap()
9817            .setter
9818            .extend([None, Some(MaskChangeRefused::Unsupported)]);
9819        let mut wait = KernelSignalWait::new(&guest, 0, false, Errno::ERESTARTSYS);
9820        assert!(wait.block_in_place(&mut guest, 0).await.unwrap());
9821        wait.restore(&mut guest, None).await.unwrap();
9822        let kernel = kernel.lock().unwrap();
9823        assert_eq!(kernel.blocked, guest_mask());
9824        assert_eq!(kernel.requested, vec![guest_mask()], "one injected restore");
9825    }
9826
9827    /// A block or restore whose failure leaves the mask unknown ends the run
9828    /// before the guest resumes.
9829    #[tokio::test]
9830    async fn an_unknown_mask_after_an_in_place_change_ends_the_run() {
9831        let unknown = Some(MaskChangeRefused::Failed {
9832            errno: Errno::EIO,
9833            mask_unknown: true,
9834        });
9835        let (mut guest, kernel) = WaitGuest::live(guest_mask());
9836        let _proc = scripted_proc(&kernel);
9837        let not_restored = BlockedWaitSignalError::MaskNotRestored {
9838            pid: guest.pid,
9839            tid: guest.tid,
9840            attempts: 1,
9841            last_error: Some(Errno::EIO),
9842        };
9843        kernel.lock().unwrap().setter.push_back(unknown);
9844        let mut wait = KernelSignalWait::new(&guest, 0, false, Errno::ERESTARTSYS);
9845        assert_eq!(
9846            diagnostic(wait.block_in_place(&mut guest, 0).await),
9847            not_restored
9848        );
9849
9850        kernel.lock().unwrap().setter.extend([None, unknown]);
9851        let mut wait = KernelSignalWait::new(&guest, 0, false, Errno::ERESTARTSYS);
9852        assert!(wait.block_in_place(&mut guest, 0).await.unwrap());
9853        assert_eq!(
9854            diagnostic(wait.restore(&mut guest, None).await),
9855            not_restored
9856        );
9857        assert!(kernel.lock().unwrap().requested.is_empty());
9858    }
9859
9860    /// A thread gone when an ignored-signal shield is lowered needs no
9861    /// restore: `lower` succeeds on `MaskChangeRefused::Gone`.
9862    #[tokio::test]
9863    async fn a_shield_lowered_for_a_gone_thread_succeeds() {
9864        let (mut guest, kernel) = WaitGuest::live(guest_mask());
9865        kernel
9866            .lock()
9867            .unwrap()
9868            .setter
9869            .push_back(Some(MaskChangeRefused::Gone));
9870        let shield = IgnoredSignalShield {
9871            guest_mask: Some(guest_mask()),
9872        };
9873        shield.lower(&mut guest).await.unwrap();
9874        assert_eq!(kernel.lock().unwrap().set_calls.len(), 1);
9875    }
9876
9877    /// A thread that is gone when the mask goes back has nothing to restore.
9878    #[tokio::test]
9879    async fn a_gone_thread_needs_no_restore() {
9880        let (mut guest, kernel) = WaitGuest::live(guest_mask());
9881        let _proc = scripted_proc(&kernel);
9882        kernel
9883            .lock()
9884            .unwrap()
9885            .setter
9886            .extend([None, Some(MaskChangeRefused::Gone)]);
9887        let mut wait = KernelSignalWait::new(&guest, 0, false, Errno::ERESTARTSYS);
9888        assert!(wait.block_in_place(&mut guest, 0).await.unwrap());
9889        wait.restore(&mut guest, None).await.unwrap();
9890        assert!(kernel.lock().unwrap().requested.is_empty());
9891    }
9892
9893    #[tokio::test]
9894    async fn an_unreadable_signal_state_of_a_live_thread_ends_the_run() {
9895        let (mut guest, kernel) = WaitGuest::live(guest_mask());
9896        let _proc = scripted_proc(&kernel);
9897        let (pid, tid) = (guest.pid, guest.tid);
9898        let expected = |errno| BlockedWaitSignalError::StateUnreadable { pid, tid, errno };
9899
9900        // The turn's check, before the mask is set.
9901        let mut wait = KernelSignalWait::new(&guest, 0, false, Errno::ERESTARTSYS);
9902        kernel.lock().unwrap().read_failures.push_back(Errno::EIO);
9903        assert_eq!(diagnostic(wait.interrupted()), expected(Errno::EIO));
9904
9905        // `block`'s read of the guest's mask.
9906        kernel.lock().unwrap().read_failures.push_back(Errno::ESRCH);
9907        assert_eq!(
9908            diagnostic(wait.block(&mut guest, None).await),
9909            expected(Errno::ESRCH)
9910        );
9911        assert!(kernel.lock().unwrap().requested.is_empty());
9912
9913        // `restore`'s read, with every signal blocked.
9914        wait.block(&mut guest, None).await.unwrap();
9915        kernel.lock().unwrap().read_failures.push_back(Errno::ESRCH);
9916        assert_eq!(
9917            diagnostic(wait.restore(&mut guest, None).await),
9918            expected(Errno::ESRCH)
9919        );
9920        let error = expected(Errno::EIO).to_string();
9921        assert!(
9922            error.starts_with("cannot read the signal state of guest thread"),
9923            "{error}"
9924        );
9925    }
9926
9927    #[tokio::test]
9928    async fn a_thread_that_no_longer_exists_ends_its_wait_with_erestartnointr() {
9929        // SAFETY: gettid has no preconditions.
9930        let exited = std::thread::spawn(|| unsafe { libc::gettid() })
9931            .join()
9932            .unwrap();
9933        // `join` returns when the kernel clears the thread's TID word, early in
9934        // its exit; the thread stays in `/proc` and answers `tgkill` until the
9935        // kernel releases it a moment later. Measured on the development host,
9936        // 62 of 20000 joined threads were still listed right after `join`, and
9937        // a GitHub-hosted run read such a thread's status here
9938        // (https://github.com/rrnewton/hermit/actions/runs/37683205050). So wait
9939        // for the release. The path is in this process's own task directory, so
9940        // no other process's thread can appear under it, and Linux hands out
9941        // TIDs cyclically, so this one is not reissued here before the counter
9942        // wraps.
9943        let task = format!("/proc/self/task/{exited}");
9944        let deadline = std::time::Instant::now() + std::time::Duration::from_secs(10);
9945        while std::path::Path::new(&task).exists() {
9946            assert!(
9947                std::time::Instant::now() < deadline,
9948                "thread {exited} is still in /proc 10 s after it was joined"
9949            );
9950            std::thread::sleep(std::time::Duration::from_millis(1));
9951        }
9952        let (guest, _kernel) = WaitGuest::new(Pid::from_raw(exited), guest_mask());
9953        // No script: the read goes to the real `/proc`, where the thread is gone.
9954
9955        let wait = KernelSignalWait::new(&guest, 0, false, Errno::ERESTARTSYS);
9956        assert!(matches!(
9957            wait.interrupted(),
9958            Err(Error::Errno(Errno::ERESTARTNOINTR))
9959        ));
9960        assert!(matches!(
9961            read_wait_signal_state(guest.pid, guest.tid),
9962            Err(Error::Errno(Errno::ERESTARTNOINTR))
9963        ));
9964    }
9965
9966    #[tokio::test]
9967    async fn a_stack_with_no_scratch_room_leaves_the_wait_under_the_guest_mask() {
9968        let (mut guest, kernel) = WaitGuest::live(guest_mask());
9969        guest.commit_fails = true;
9970        let _proc = scripted_proc(&kernel);
9971        let mut wait = KernelSignalWait::new(&guest, 0, false, Errno::ERESTARTSYS);
9972
9973        wait.block(&mut guest, None).await.unwrap();
9974
9975        assert!(!wait.needs_block(), "block must not be tried again");
9976        assert_eq!(kernel.lock().unwrap().blocked, guest_mask());
9977        assert!(kernel.lock().unwrap().requested.is_empty());
9978
9979        // Later turns classify against the guest's own mask, read each turn.
9980        {
9981            let mut kernel = kernel.lock().unwrap();
9982            kernel.caught = kernel_sigset_bit(libc::SIGUSR1) | kernel_sigset_bit(libc::SIGUSR2);
9983            kernel.pending = kernel_sigset_bit(libc::SIGUSR2);
9984        }
9985        assert!(!wait.interrupted().unwrap(), "SIGUSR2 is blocked");
9986        kernel.lock().unwrap().pending = kernel_sigset_bit(libc::SIGUSR1);
9987        assert!(wait.interrupted().unwrap(), "SIGUSR1 is not blocked");
9988
9989        wait.restore(&mut guest, None).await.unwrap();
9990        assert!(
9991            kernel.lock().unwrap().requested.is_empty(),
9992            "nothing to put back"
9993        );
9994        assert_eq!(kernel.lock().unwrap().blocked, guest_mask());
9995    }
9996
9997    /// A guest whose injections a pending signal outside the mask stops, as on
9998    /// ptrace, with `pending` queued and `ignored` set to `SIG_IGN`.
9999    fn stopping_guest(pending: KernelSigset, ignored: KernelSigset) -> (WaitGuest, Kernel) {
10000        let (guest, kernel) = WaitGuest::live(guest_mask());
10001        {
10002            let mut kernel = kernel.lock().unwrap();
10003            kernel.stops_for_pending = true;
10004            kernel.pending = pending;
10005            kernel.ignored = ignored;
10006        }
10007        (guest, kernel)
10008    }
10009
10010    /// Inject a mask change to `mask` through `wait.inject_absorbing`.
10011    async fn inject_mask_absorbing(
10012        wait: &mut KernelSignalWait,
10013        guest: &mut WaitGuest,
10014        mask: KernelSigset,
10015    ) -> Result<Result<i64, Errno>, Error> {
10016        let mut stack = guest.stack().await;
10017        let cell = stack.push(mask);
10018        let _guard = stack.commit().unwrap();
10019        wait.inject_absorbing(guest, set_signal_mask_call(cell))
10020            .await
10021    }
10022
10023    /// An ignored signal that was already pending stops the mask change of
10024    /// `block`, which used to end the wait with a restart (review finding F2 on
10025    /// https://github.com/rrnewton/hermit/pull/3361). Linux would not end the
10026    /// wait for it, so the stop is absorbed: the backend holds the signal, the
10027    /// change runs again, and the held signal does not interrupt the wait.
10028    #[tokio::test]
10029    async fn an_ignored_signal_that_stops_the_mask_change_does_not_end_the_wait() {
10030        let usr1 = kernel_sigset_bit(libc::SIGUSR1);
10031        let (mut guest, kernel) = stopping_guest(usr1, usr1);
10032        let _proc = scripted_proc(&kernel);
10033        let mut wait = KernelSignalWait::new(&guest, 0, false, Errno::ERESTARTSYS);
10034
10035        wait.block(&mut guest, None).await.unwrap();
10036
10037        assert!(!wait.needs_block());
10038        let all_blocked = blocked_signal_mask() | guest_mask();
10039        {
10040            let kernel = kernel.lock().unwrap();
10041            assert_eq!(kernel.taken, vec![libc::SIGUSR1]);
10042            assert_eq!(kernel.requested, vec![all_blocked, all_blocked]);
10043            assert_eq!(kernel.blocked, all_blocked);
10044        }
10045        assert_eq!(
10046            wait.held,
10047            Some(HeldSignal {
10048                signal: libc::SIGUSR1,
10049                queue: SignalQueue::Thread,
10050                kind: HeldKind::Harmless,
10051            })
10052        );
10053        assert!(
10054            !wait.interrupted().unwrap(),
10055            "an ignored signal does not end the wait"
10056        );
10057        wait.restore(&mut guest, None).await.unwrap();
10058        assert_eq!(kernel.lock().unwrap().blocked, guest_mask());
10059    }
10060
10061    /// A default-ignored signal that stops an injection is held the same way,
10062    /// and the injection then returns its own result (review finding F2 on
10063    /// https://github.com/rrnewton/hermit/pull/3361).
10064    #[tokio::test]
10065    async fn a_default_ignored_signal_that_stops_an_injection_is_held() {
10066        let (mut guest, kernel) = stopping_guest(kernel_sigset_bit(libc::SIGWINCH), 0);
10067        let _proc = scripted_proc(&kernel);
10068        let mut wait = KernelSignalWait::new(&guest, 0, false, Errno::ERESTARTSYS);
10069
10070        let result = inject_mask_absorbing(&mut wait, &mut guest, guest_mask()).await;
10071
10072        assert!(matches!(result, Ok(Ok(0))), "{result:?}");
10073        assert_eq!(kernel.lock().unwrap().taken, vec![libc::SIGWINCH]);
10074        assert_eq!(kernel.lock().unwrap().requested.len(), 2);
10075        assert_eq!(wait.held.map(|held| held.kind), Some(HeldKind::Harmless));
10076        assert!(!wait.interrupted().unwrap());
10077    }
10078
10079    /// A pending signal that would end the wait natively ends it before any
10080    /// injection, with the restart errno, and stays queued for the guest.
10081    #[tokio::test]
10082    async fn a_pending_caught_signal_ends_the_wait_before_the_injection() {
10083        let usr1 = kernel_sigset_bit(libc::SIGUSR1);
10084        let (mut guest, kernel) = stopping_guest(usr1, 0);
10085        kernel.lock().unwrap().caught = usr1;
10086        let _proc = scripted_proc(&kernel);
10087        let mut wait = KernelSignalWait::new(&guest, 0, false, Errno::ERESTARTSYS);
10088
10089        let result = inject_mask_absorbing(&mut wait, &mut guest, guest_mask()).await;
10090
10091        assert!(
10092            matches!(result, Err(Error::Errno(Errno::ERESTARTSYS))),
10093            "{result:?}"
10094        );
10095        let kernel = kernel.lock().unwrap();
10096        assert!(kernel.requested.is_empty(), "nothing is injected");
10097        assert_eq!(kernel.pending, usr1);
10098        assert_eq!(wait.held, None);
10099    }
10100
10101    /// The backend holds one signal. Once it holds one whose loss would matter,
10102    /// a `SIGCHLD` here, an injection that a different pending signal would stop
10103    /// is not made. The wait used to end with `ERESTARTNOINTR`, a transparent
10104    /// restart; it now ends with a `HeldSignalRefusal`, which its caller turns
10105    /// into the unsupported-operation refusal (round-10 High 1 on
10106    /// https://github.com/rrnewton/hermit/pull/3361).
10107    #[tokio::test]
10108    async fn a_held_sigchld_is_never_replaced_by_another_stop() {
10109        let (mut guest, kernel) = stopping_guest(
10110            kernel_sigset_bit(libc::SIGCHLD) | kernel_sigset_bit(libc::SIGWINCH),
10111            0,
10112        );
10113        let _proc = scripted_proc(&kernel);
10114        let mut wait = KernelSignalWait::new(&guest, 0, false, Errno::ERESTARTSYS);
10115
10116        let result = inject_mask_absorbing(&mut wait, &mut guest, guest_mask()).await;
10117
10118        assert_eq!(
10119            refusal(result),
10120            HeldSignalRefusal {
10121                wait: None,
10122                held: Some(libc::SIGCHLD),
10123                replacing: Some(libc::SIGWINCH),
10124                loss: HeldSignalLoss::Replaced,
10125            }
10126        );
10127        let kernel = kernel.lock().unwrap();
10128        assert_eq!(kernel.taken, vec![libc::SIGCHLD]);
10129        assert_eq!(
10130            kernel.requested.len(),
10131            1,
10132            "the second injection is not made"
10133        );
10134        assert_eq!(kernel.pending, kernel_sigset_bit(libc::SIGWINCH));
10135        assert_eq!(wait.held.map(|held| held.kind), Some(HeldKind::Precious));
10136    }
10137
10138    /// A wait whose `block` was stopped by a default `SIGCHLD`, which the
10139    /// backend holds as a signal whose loss matters (`HeldKind::Precious`).
10140    async fn wait_holding_sigchld(guest: &mut WaitGuest, kernel: &Kernel) -> KernelSignalWait {
10141        let mut wait = KernelSignalWait::new(&*guest, 0, false, Errno::ERESTARTSYS);
10142        wait.block(guest, None).await.unwrap();
10143        assert_eq!(
10144            wait.held.map(|held| (held.signal, held.kind)),
10145            Some((libc::SIGCHLD, HeldKind::Precious))
10146        );
10147        let kernel = kernel.lock().unwrap();
10148        assert_eq!(kernel.blocked, blocked_signal_mask() | guest_mask());
10149        assert_eq!(kernel.held_slot, Some(libc::SIGCHLD));
10150        wait
10151    }
10152
10153    /// A signal that `/proc` never showed, a `SIGSTOP` sent from outside here,
10154    /// stops the injection that puts the guest's mask back before it runs, and
10155    /// the next attempt runs. The backend holds one signal, so the `SIGSTOP`
10156    /// replaced the held `SIGCHLD`, and `restore` used to return success, so
10157    /// the guest resumed without its `SIGCHLD` (round-11 High F1 on
10158    /// https://github.com/rrnewton/hermit/pull/3361). The mask is still put
10159    /// back, and the wait now ends with a `HeldSignalRefusal`.
10160    #[tokio::test]
10161    async fn a_stop_of_the_mask_restoration_that_replaces_a_held_sigchld_is_refused() {
10162        let (mut guest, kernel) = stopping_guest(kernel_sigset_bit(libc::SIGCHLD), 0);
10163        let _proc = scripted_proc(&kernel);
10164        let mut wait = wait_holding_sigchld(&mut guest, &kernel).await;
10165        kernel
10166            .lock()
10167            .unwrap()
10168            .outcomes
10169            .push_back(MaskOutcome::StoppedBy(libc::SIGSTOP));
10170
10171        let result = wait.restore(&mut guest, None).await;
10172
10173        {
10174            let kernel = kernel.lock().unwrap();
10175            assert_eq!(kernel.blocked, guest_mask(), "the guest's mask is back");
10176            assert_eq!(
10177                kernel.requested[2..],
10178                [guest_mask(), guest_mask()],
10179                "one stopped restoration attempt and one that ran"
10180            );
10181            assert_eq!(kernel.held_slot, Some(libc::SIGSTOP));
10182            assert_eq!(kernel.lost, vec![libc::SIGCHLD]);
10183        }
10184        assert_eq!(
10185            refusal(result),
10186            HeldSignalRefusal {
10187                wait: None,
10188                held: Some(libc::SIGCHLD),
10189                replacing: None,
10190                loss: HeldSignalLoss::Restoration,
10191            }
10192        );
10193    }
10194
10195    /// The same with a signal that `/proc` shows before the restoration and
10196    /// that the all-blocked mask leaves unblocked: signal 32, which the C
10197    /// library reserves and leaves out of the blockable set. The refusal names
10198    /// it.
10199    #[tokio::test]
10200    async fn an_identified_stop_of_the_mask_restoration_that_replaces_a_held_sigchld_is_refused() {
10201        const RESERVED: i32 = 32;
10202        assert_eq!(blocked_signal_mask() & kernel_sigset_bit(RESERVED), 0);
10203        let (mut guest, kernel) = stopping_guest(kernel_sigset_bit(libc::SIGCHLD), 0);
10204        let _proc = scripted_proc(&kernel);
10205        let mut wait = wait_holding_sigchld(&mut guest, &kernel).await;
10206        kernel.lock().unwrap().pending = kernel_sigset_bit(RESERVED);
10207
10208        let result = wait.restore(&mut guest, None).await;
10209
10210        {
10211            let kernel = kernel.lock().unwrap();
10212            assert_eq!(kernel.blocked, guest_mask(), "the guest's mask is back");
10213            assert_eq!(kernel.taken, vec![libc::SIGCHLD, RESERVED]);
10214            assert_eq!(kernel.held_slot, Some(RESERVED));
10215            assert_eq!(kernel.lost, vec![libc::SIGCHLD]);
10216        }
10217        let refused = refusal(result);
10218        assert_eq!(
10219            refused,
10220            HeldSignalRefusal {
10221                wait: None,
10222                held: Some(libc::SIGCHLD),
10223                replacing: Some(RESERVED),
10224                loss: HeldSignalLoss::Restoration,
10225            }
10226        );
10227        let message = refused.to_string();
10228        assert!(
10229            message.starts_with(
10230                "unsupported: the a blocking wait cannot keep the held signal SIGCHLD: signal 32 \
10231                 stopped the injection that puts the guest's signal mask back and replaced it"
10232            ),
10233            "{message}"
10234        );
10235    }
10236
10237    /// A refusal from the mask restoration replaces the wait's result, whether
10238    /// that is a value or an errno, unless the wait already ended with a refusal
10239    /// of its own, which came first. A mask that could not be put back still
10240    /// ends the run.
10241    #[test]
10242    fn a_restoration_refusal_replaces_the_result_unless_the_wait_refused_first() {
10243        let tool = |refusal: HeldSignalRefusal| Error::Tool(anyhow::Error::new(refusal));
10244        let restoration = HeldSignalRefusal {
10245            wait: None,
10246            held: Some(libc::SIGCHLD),
10247            replacing: None,
10248            loss: HeldSignalLoss::Restoration,
10249        };
10250        let replaced = HeldSignalRefusal {
10251            wait: None,
10252            held: Some(libc::SIGCHLD),
10253            replacing: Some(libc::SIGUSR1),
10254            loss: HeldSignalLoss::Replaced,
10255        };
10256        let not_restored = BlockedWaitSignalError::MaskNotRestored {
10257            pid: Pid::from_raw(1),
10258            tid: Pid::from_raw(1),
10259            attempts: 16,
10260            last_error: Some(Errno::ERESTARTNOINTR),
10261        };
10262
10263        assert_eq!(result_after_restore(Ok(()), Ok(3)).unwrap().unwrap(), 3);
10264        assert_eq!(
10265            refusal(result_after_restore(Err(tool(restoration)), Ok(3)).unwrap()),
10266            restoration
10267        );
10268        assert_eq!(
10269            refusal(
10270                result_after_restore(Err(tool(restoration)), Err(Errno::ERESTARTSYS.into()))
10271                    .unwrap()
10272            ),
10273            restoration
10274        );
10275        assert_eq!(
10276            refusal(result_after_restore(Err(tool(restoration)), Err(tool(replaced))).unwrap()),
10277            replaced
10278        );
10279        assert_eq!(
10280            diagnostic(result_after_restore(
10281                Err(Error::Tool(anyhow::Error::new(not_restored))),
10282                Ok(3)
10283            )),
10284            not_restored
10285        );
10286    }
10287
10288    /// A guest whose next `stops` injections are stopped by a signal that
10289    /// `/proc` never shows, as one that arrives after the read before an
10290    /// injection and that the backend then holds: nothing leaves a queue, so the
10291    /// stop cannot be identified.
10292    fn unidentified_stops_guest(stops: usize) -> (WaitGuest, Kernel) {
10293        let (guest, kernel) = WaitGuest::live(guest_mask());
10294        kernel.lock().unwrap().outcomes =
10295            std::iter::repeat_n(MaskOutcome::StoppedBefore, stops).collect();
10296        (guest, kernel)
10297    }
10298
10299    /// A stop that cannot be identified, such as a default-ignored `SIGCHLD`
10300    /// that arrives after the read, used to end the wait with `ERESTARTNOHAND`
10301    /// for a call whose restart code is not a kernel restart code. With no
10302    /// handler to run, the call then restarted, and a relative `poll` timeout
10303    /// or a timed polling `FUTEX_WAIT` started again with a fresh deadline
10304    /// (round-8 High 1 on https://github.com/rrnewton/hermit/pull/3361). The
10305    /// stop is now held and the injection runs again, so the wait goes on with
10306    /// its absolute deadline, as the blind retry before this machinery did.
10307    #[tokio::test]
10308    async fn a_stop_that_cannot_be_identified_keeps_a_timed_wait_and_its_deadline() {
10309        for deadline in [true, false] {
10310            let (mut guest, kernel) = unidentified_stops_guest(1);
10311            let _proc = scripted_proc(&kernel);
10312            let mut wait = KernelSignalWait::new(&guest, 0, true, Errno::ERESTARTNOHAND)
10313                .with_deadline(deadline);
10314
10315            let result = inject_mask_absorbing(&mut wait, &mut guest, guest_mask()).await;
10316
10317            assert!(
10318                matches!(result, Ok(Ok(0))),
10319                "deadline={deadline}: {result:?}"
10320            );
10321            assert_eq!(
10322                kernel.lock().unwrap().requested.len(),
10323                2,
10324                "deadline={deadline}"
10325            );
10326            assert_eq!(wait.held, None, "deadline={deadline}");
10327        }
10328    }
10329
10330    /// A wait with a deadline absorbs stops without the bound that ends any
10331    /// other wait after `MAX_ABSORBED_STOPS`, because a restart would start its
10332    /// timeout again (round-8 High 1 on
10333    /// https://github.com/rrnewton/hermit/pull/3361). A wait without one still
10334    /// ends at the bound, and, while the backend holds only a signal whose loss
10335    /// does not matter (an ignored `SIGUSR1` sent again and again here), with
10336    /// `ERESTARTNOINTR`: it is not refused.
10337    #[tokio::test]
10338    async fn only_a_wait_without_a_deadline_ends_after_the_absorbed_stop_bound() {
10339        let stops = 100;
10340        let usr1 = kernel_sigset_bit(libc::SIGUSR1);
10341        let (mut guest, kernel) = stopping_guest(usr1, usr1);
10342        kernel.lock().unwrap().repost_after_take = stops - 1;
10343        let _proc = scripted_proc(&kernel);
10344        let mut wait =
10345            KernelSignalWait::new(&guest, 0, true, Errno::ERESTARTNOHAND).with_deadline(true);
10346        let result = inject_mask_absorbing(&mut wait, &mut guest, guest_mask()).await;
10347        assert!(matches!(result, Ok(Ok(0))), "{result:?}");
10348        assert_eq!(kernel.lock().unwrap().requested.len(), stops + 1);
10349        assert_eq!(wait.held.map(|held| held.kind), Some(HeldKind::Harmless));
10350
10351        let (mut guest, kernel) = stopping_guest(usr1, usr1);
10352        kernel.lock().unwrap().repost_after_take = stops - 1;
10353        let _proc = scripted_proc(&kernel);
10354        let mut wait = KernelSignalWait::new(&guest, 0, true, Errno::ERESTARTNOHAND);
10355        let result = inject_mask_absorbing(&mut wait, &mut guest, guest_mask()).await;
10356        assert!(
10357            matches!(result, Err(Error::Errno(Errno::ERESTARTNOINTR))),
10358            "{result:?}"
10359        );
10360        assert_eq!(kernel.lock().unwrap().requested.len(), 65);
10361    }
10362
10363    /// At the same bound, a wait without a deadline whose backend holds a
10364    /// signal whose loss matters (a default `SIGCHLD`, sent again and again)
10365    /// used to end with `ERESTARTNOINTR` too. It now ends with a
10366    /// `HeldSignalRefusal` (round-10 High 1 on
10367    /// https://github.com/rrnewton/hermit/pull/3361).
10368    #[tokio::test]
10369    async fn a_wait_that_reaches_the_stop_bound_holding_a_sigchld_is_refused() {
10370        let (mut guest, kernel) = stopping_guest(kernel_sigset_bit(libc::SIGCHLD), 0);
10371        kernel.lock().unwrap().repost_after_take = 100;
10372        let _proc = scripted_proc(&kernel);
10373        let mut wait = KernelSignalWait::new(&guest, 0, false, Errno::ERESTARTSYS);
10374
10375        let result = inject_mask_absorbing(&mut wait, &mut guest, guest_mask()).await;
10376
10377        assert_eq!(
10378            refusal(result),
10379            HeldSignalRefusal {
10380                wait: None,
10381                held: Some(libc::SIGCHLD),
10382                replacing: None,
10383                loss: HeldSignalLoss::StopBound,
10384            }
10385        );
10386        assert_eq!(kernel.lock().unwrap().requested.len(), 65);
10387    }
10388
10389    /// At the same bound, a stop that cannot be identified, after stops by a
10390    /// signal whose loss does not matter (an ignored `SIGUSR1` sent again and
10391    /// again), used to end a wait without a deadline with `ERESTARTNOINTR`,
10392    /// which let the backend deliver the unidentified signal, possibly a
10393    /// `SIGCHLD`, at a moment host timing could set. It now ends with a
10394    /// `HeldSignalRefusal`; a wait with a deadline, which has no bound, goes on
10395    /// (round-10 High 1 on https://github.com/rrnewton/hermit/pull/3361).
10396    #[tokio::test]
10397    async fn an_unidentified_stop_at_the_stop_bound_is_refused() {
10398        let usr1 = kernel_sigset_bit(libc::SIGUSR1);
10399        for deadline in [false, true] {
10400            let (mut guest, kernel) = stopping_guest(usr1, usr1);
10401            {
10402                let mut kernel = kernel.lock().unwrap();
10403                // 64 identified stops, the bound, then one that `/proc` never
10404                // shows.
10405                kernel.repost_after_take = 63;
10406                kernel.outcomes = VecDeque::from([MaskOutcome::StoppedBefore]);
10407            }
10408            let _proc = scripted_proc(&kernel);
10409            let mut wait = KernelSignalWait::new(&guest, 0, true, Errno::ERESTARTNOHAND)
10410                .with_deadline(deadline);
10411
10412            let result = inject_mask_absorbing(&mut wait, &mut guest, guest_mask()).await;
10413
10414            if deadline {
10415                assert!(matches!(result, Ok(Ok(0))), "{result:?}");
10416                assert_eq!(kernel.lock().unwrap().requested.len(), 66);
10417                assert!(wait.held_unidentified);
10418            } else {
10419                assert_eq!(
10420                    refusal(result),
10421                    HeldSignalRefusal {
10422                        wait: None,
10423                        held: None,
10424                        replacing: None,
10425                        loss: HeldSignalLoss::StopBound,
10426                    }
10427                );
10428                assert_eq!(kernel.lock().unwrap().requested.len(), 65);
10429            }
10430            assert_eq!(
10431                kernel.lock().unwrap().taken.len(),
10432                64,
10433                "deadline={deadline}"
10434            );
10435        }
10436    }
10437
10438    /// A stop that cannot be identified while the backend holds a `SIGCHLD`
10439    /// replaced the `SIGCHLD` in the backend's one slot. The wait used to go on
10440    /// and lose it; it now ends with a `HeldSignalRefusal`, with or without a
10441    /// deadline (round-10 High 1 on https://github.com/rrnewton/hermit/pull/3361).
10442    #[tokio::test]
10443    async fn an_unidentified_stop_over_a_held_sigchld_is_refused() {
10444        for deadline in [true, false] {
10445            let (mut guest, kernel) = stopping_guest(kernel_sigset_bit(libc::SIGCHLD), 0);
10446            kernel.lock().unwrap().outcomes = VecDeque::from([MaskOutcome::StoppedBefore]);
10447            let _proc = scripted_proc(&kernel);
10448            let mut wait =
10449                KernelSignalWait::new(&guest, 0, false, Errno::ERESTARTSYS).with_deadline(deadline);
10450
10451            let result = inject_mask_absorbing(&mut wait, &mut guest, guest_mask()).await;
10452
10453            assert_eq!(
10454                refusal(result),
10455                HeldSignalRefusal {
10456                    wait: None,
10457                    held: Some(libc::SIGCHLD),
10458                    replacing: None,
10459                    loss: HeldSignalLoss::UnidentifiedStop,
10460                },
10461                "deadline={deadline}"
10462            );
10463            let kernel = kernel.lock().unwrap();
10464            assert_eq!(kernel.requested.len(), 2, "deadline={deadline}");
10465            assert_eq!(kernel.taken, vec![libc::SIGCHLD], "deadline={deadline}");
10466        }
10467    }
10468
10469    /// A second stop that cannot be identified replaced a first one, which may
10470    /// have been a signal whose loss matters. The wait used to go on; it now
10471    /// ends with a `HeldSignalRefusal`, with or without a deadline (round-10
10472    /// High 1 on https://github.com/rrnewton/hermit/pull/3361).
10473    #[tokio::test]
10474    async fn a_second_unidentified_stop_is_refused() {
10475        for deadline in [true, false] {
10476            let (mut guest, kernel) = unidentified_stops_guest(2);
10477            let _proc = scripted_proc(&kernel);
10478            let mut wait = KernelSignalWait::new(&guest, 0, true, Errno::ERESTARTNOHAND)
10479                .with_deadline(deadline);
10480
10481            let result = inject_mask_absorbing(&mut wait, &mut guest, guest_mask()).await;
10482
10483            assert_eq!(
10484                refusal(result),
10485                HeldSignalRefusal {
10486                    wait: None,
10487                    held: None,
10488                    replacing: None,
10489                    loss: HeldSignalLoss::UnidentifiedStop,
10490                },
10491                "deadline={deadline}"
10492            );
10493            assert_eq!(
10494                kernel.lock().unwrap().requested.len(),
10495                2,
10496                "deadline={deadline}"
10497            );
10498        }
10499    }
10500
10501    /// A `SIGCHLD` that arrives after the read before an injection stops it in a
10502    /// way that cannot be identified, and a second one, pending at the next
10503    /// read, would stop the injection made again and replace the first in the
10504    /// backend's one slot. The first may have been a signal whose loss matters,
10505    /// so the wait is refused before that injection, with or without a
10506    /// deadline (round-10 High 1 on https://github.com/rrnewton/hermit/pull/3361),
10507    /// and the backend still holds the first signal. This is the refusal a
10508    /// flood of external `SIGCHLD` racing a timed futex wait's first injections
10509    /// met on hermit main ccbe5ae8 (`external_signal_interrupt`'s test of the
10510    /// flood, now
10511    /// `ptrace_timed_futex_wait_and_poll_keep_their_deadline_after_a_sigchld_flood`,
10512    /// which sends its flood before the wait starts).
10513    #[tokio::test]
10514    async fn a_sigchld_after_an_unidentified_stop_is_refused_before_it_replaces_it() {
10515        let chld = kernel_sigset_bit(libc::SIGCHLD);
10516        for deadline in [true, false] {
10517            let (mut guest, kernel) = stopping_guest(0, 0);
10518            {
10519                let mut kernel = kernel.lock().unwrap();
10520                // Pending only after the first read: the injection's stop is not
10521                // identified. Sent once more, so it is pending again at the read
10522                // before the injection made again.
10523                kernel.pending_repost = chld;
10524                kernel.repost_after_take = 1;
10525            }
10526            let _proc = scripted_proc(&kernel);
10527            let mut wait = KernelSignalWait::new(&guest, 0, true, Errno::ERESTARTNOHAND)
10528                .with_deadline(deadline);
10529
10530            let result = inject_mask_absorbing(&mut wait, &mut guest, guest_mask()).await;
10531
10532            let refused = refusal(result);
10533            assert_eq!(
10534                refused,
10535                HeldSignalRefusal {
10536                    wait: None,
10537                    held: None,
10538                    replacing: Some(libc::SIGCHLD),
10539                    loss: HeldSignalLoss::Replaced,
10540                },
10541                "deadline={deadline}"
10542            );
10543            let message = HeldSignalRefusal {
10544                wait: Some(Sysno::futex),
10545                ..refused
10546            }
10547            .to_string();
10548            assert!(
10549                message.starts_with(
10550                    "unsupported: the futex wait cannot keep a held signal that could not be \
10551                     identified: the pending SIGCHLD would stop the next injection and replace it"
10552                ),
10553                "deadline={deadline}: {message}"
10554            );
10555            assert!(wait.held_unidentified, "deadline={deadline}");
10556            let kernel = kernel.lock().unwrap();
10557            assert_eq!(
10558                kernel.requested.len(),
10559                1,
10560                "deadline={deadline}: the second injection is not made"
10561            );
10562            assert_eq!(kernel.taken, vec![libc::SIGCHLD], "deadline={deadline}");
10563            assert_eq!(kernel.held_slot, Some(libc::SIGCHLD), "deadline={deadline}");
10564            assert!(kernel.lost.is_empty(), "deadline={deadline}");
10565            assert_eq!(kernel.pending, chld, "deadline={deadline}");
10566        }
10567    }
10568
10569    /// In a wait with a deadline, a held `SIGCHLD` that would not end the wait
10570    /// is not dropped when another pending signal would stop the next
10571    /// injection. The injection used to be made, and the stop replaced the held
10572    /// signal, as the blind retry allowed (round-8 High 1 on
10573    /// https://github.com/rrnewton/hermit/pull/3361); a restart would start the
10574    /// timeout again. The wait now ends with a `HeldSignalRefusal` before the
10575    /// injection (round-10 High 1 on the same pull request).
10576    #[tokio::test]
10577    async fn a_timed_wait_refuses_to_let_another_stop_replace_a_held_sigchld() {
10578        let (mut guest, kernel) = stopping_guest(
10579            kernel_sigset_bit(libc::SIGCHLD) | kernel_sigset_bit(libc::SIGWINCH),
10580            0,
10581        );
10582        let _proc = scripted_proc(&kernel);
10583        let mut wait =
10584            KernelSignalWait::new(&guest, 0, false, Errno::ERESTARTNOHAND).with_deadline(true);
10585
10586        let result = inject_mask_absorbing(&mut wait, &mut guest, guest_mask()).await;
10587
10588        assert_eq!(
10589            refusal(result),
10590            HeldSignalRefusal {
10591                wait: None,
10592                held: Some(libc::SIGCHLD),
10593                replacing: Some(libc::SIGWINCH),
10594                loss: HeldSignalLoss::Replaced,
10595            }
10596        );
10597        let kernel = kernel.lock().unwrap();
10598        assert_eq!(kernel.taken, vec![libc::SIGCHLD]);
10599        assert_eq!(
10600            kernel.requested.len(),
10601            1,
10602            "the second injection is not made"
10603        );
10604        assert_eq!(kernel.pending, kernel_sigset_bit(libc::SIGWINCH));
10605    }
10606
10607    /// In a wait with a deadline, a held signal that would end the wait natively
10608    /// ends it with the restart errno before another stop can replace it, as
10609    /// Linux ends the wait for that signal.
10610    #[tokio::test]
10611    async fn a_timed_wait_ends_for_a_held_signal_that_would_end_it() {
10612        let usr1 = kernel_sigset_bit(libc::SIGUSR1);
10613        let (mut guest, kernel) = stopping_guest(usr1 | kernel_sigset_bit(libc::SIGWINCH), 0);
10614        let _proc = scripted_proc(&kernel);
10615        let mut wait =
10616            KernelSignalWait::new(&guest, 0, false, Errno::ERESTARTNOHAND).with_deadline(true);
10617        wait.held = Some(HeldSignal {
10618            signal: libc::SIGUSR1,
10619            queue: SignalQueue::Thread,
10620            kind: HeldKind::Precious,
10621        });
10622        kernel.lock().unwrap().pending = kernel_sigset_bit(libc::SIGWINCH);
10623        kernel.lock().unwrap().caught = usr1;
10624
10625        let result = inject_mask_absorbing(&mut wait, &mut guest, guest_mask()).await;
10626
10627        assert!(
10628            matches!(result, Err(Error::Errno(Errno::ERESTARTNOHAND))),
10629            "{result:?}"
10630        );
10631        assert!(
10632            kernel.lock().unwrap().requested.is_empty(),
10633            "nothing is injected"
10634        );
10635    }
10636
10637    /// In a wait without a deadline, a held signal that would end the wait
10638    /// natively also ends it with the restart errno before another stop can
10639    /// replace it, as Linux ends the wait for that signal. It used to end with
10640    /// `ERESTARTNOINTR`, which restarted the call after the handler ran
10641    /// (round-10 High 1 on https://github.com/rrnewton/hermit/pull/3361).
10642    #[tokio::test]
10643    async fn a_wait_without_a_deadline_ends_for_a_held_signal_that_would_end_it() {
10644        let usr1 = kernel_sigset_bit(libc::SIGUSR1);
10645        let (mut guest, kernel) = stopping_guest(kernel_sigset_bit(libc::SIGWINCH), 0);
10646        kernel.lock().unwrap().caught = usr1;
10647        let _proc = scripted_proc(&kernel);
10648        let mut wait = KernelSignalWait::new(&guest, 0, false, Errno::ERESTARTSYS);
10649        wait.held = Some(HeldSignal {
10650            signal: libc::SIGUSR1,
10651            queue: SignalQueue::Thread,
10652            kind: HeldKind::Precious,
10653        });
10654
10655        let result = inject_mask_absorbing(&mut wait, &mut guest, guest_mask()).await;
10656
10657        assert!(
10658            matches!(result, Err(Error::Errno(Errno::ERESTARTSYS))),
10659            "{result:?}"
10660        );
10661        assert!(
10662            kernel.lock().unwrap().requested.is_empty(),
10663            "nothing is injected"
10664        );
10665    }
10666
10667    /// A signal that a wait without a deadline consumes and that stops an
10668    /// injection is delivered rather than returned. One whose loss does not
10669    /// matter still ends the wait with `ERESTARTNOINTR`, and is not refused;
10670    /// a `SIGCHLD`, which the wait holds until the call returns, used to end it
10671    /// the same way and now ends it with a `HeldSignalRefusal` (round-10 High 1
10672    /// on https://github.com/rrnewton/hermit/pull/3361).
10673    #[tokio::test]
10674    async fn a_consumed_sigchld_that_stops_an_injection_is_refused() {
10675        let usr1 = kernel_sigset_bit(libc::SIGUSR1);
10676        let (mut guest, kernel) = stopping_guest(usr1, 0);
10677        let _proc = scripted_proc(&kernel);
10678        let mut wait = KernelSignalWait::new(&guest, usr1, false, Errno::ERESTARTSYS);
10679        let result = inject_mask_absorbing(&mut wait, &mut guest, guest_mask()).await;
10680        assert!(
10681            matches!(result, Err(Error::Errno(Errno::ERESTARTNOINTR))),
10682            "{result:?}"
10683        );
10684        assert_eq!(kernel.lock().unwrap().taken, vec![libc::SIGUSR1]);
10685        drop(_proc);
10686
10687        let chld = kernel_sigset_bit(libc::SIGCHLD);
10688        let (mut guest, kernel) = stopping_guest(chld, 0);
10689        let _proc = scripted_proc(&kernel);
10690        let mut wait = KernelSignalWait::new(&guest, chld, false, Errno::ERESTARTSYS);
10691        let result = inject_mask_absorbing(&mut wait, &mut guest, guest_mask()).await;
10692        assert_eq!(
10693            refusal(result),
10694            HeldSignalRefusal {
10695                wait: None,
10696                held: None,
10697                replacing: Some(libc::SIGCHLD),
10698                loss: HeldSignalLoss::Consumed,
10699            }
10700        );
10701        assert_eq!(kernel.lock().unwrap().taken, vec![libc::SIGCHLD]);
10702    }
10703
10704    /// A stop by a signal outside every class above, such as the backend's own
10705    /// preemption signal, still ends a wait without a deadline with
10706    /// `ERESTARTNOINTR`: the backend holds nothing whose loss matters, so it is
10707    /// not refused.
10708    #[tokio::test]
10709    async fn a_stop_by_the_preemption_signal_still_restarts_a_wait_without_a_deadline() {
10710        let signal = reverie::PERF_EVENT_SIGNAL as i32;
10711        let (mut guest, kernel) = stopping_guest(kernel_sigset_bit(signal), 0);
10712        let _proc = scripted_proc(&kernel);
10713        let mut wait = KernelSignalWait::new(&guest, 0, false, Errno::ERESTARTSYS);
10714
10715        let result = inject_mask_absorbing(&mut wait, &mut guest, guest_mask()).await;
10716
10717        assert!(
10718            matches!(result, Err(Error::Errno(Errno::ERESTARTNOINTR))),
10719            "{result:?}"
10720        );
10721        assert_eq!(kernel.lock().unwrap().taken, vec![signal]);
10722    }
10723
10724    /// A wait that ends with a `HeldSignalRefusal` is refused through the
10725    /// unsupported-operation policy that `refuse_unserviceable_operation`
10726    /// applies: a run that is not fail-closed gets the `ERESTARTNOINTR` restart
10727    /// it got before, and a fail-closed one with `exit_on_unsupported_syscall`
10728    /// ends with the typed `UnsupportedSyscallError` for the wait's call. Every
10729    /// other result passes through, and the message names the wait, the
10730    /// signals and the pull request.
10731    #[tokio::test]
10732    async fn a_held_signal_refusal_follows_the_unsupported_operation_policy() {
10733        let refused = || {
10734            Err::<i64, Error>(Error::Tool(anyhow::Error::new(HeldSignalRefusal {
10735                wait: None,
10736                held: Some(libc::SIGCHLD),
10737                replacing: Some(libc::SIGWINCH),
10738                loss: HeldSignalLoss::Replaced,
10739            })))
10740        };
10741        let (mut guest, _kernel) = WaitGuest::live(guest_mask());
10742        guest.config.panic_on_unsupported_syscalls = false;
10743        let result = refuse_held_signal_loss(&mut guest, Sysno::poll, refused()).await;
10744        assert!(
10745            matches!(result, Err(Error::Errno(Errno::ERESTARTNOINTR))),
10746            "{result:?}"
10747        );
10748        let result = refuse_held_signal_loss(&mut guest, Sysno::poll, Ok(3)).await;
10749        assert!(matches!(result, Ok(3)), "{result:?}");
10750        let result =
10751            refuse_held_signal_loss(&mut guest, Sysno::poll, Err(Errno::ERESTARTSYS.into())).await;
10752        assert!(
10753            matches!(result, Err(Error::Errno(Errno::ERESTARTSYS))),
10754            "{result:?}"
10755        );
10756
10757        guest.config.panic_on_unsupported_syscalls = true;
10758        guest.config.exit_on_unsupported_syscall = true;
10759        guest.config.shutdown_on_unsupported_syscall = false;
10760        let result = refuse_held_signal_loss(&mut guest, Sysno::poll, refused()).await;
10761        match result {
10762            Err(Error::Tool(error)) => assert!(
10763                matches!(
10764                    error.downcast_ref::<crate::UnsupportedSyscallError>(),
10765                    Some(crate::UnsupportedSyscallError(Sysno::poll))
10766                ),
10767                "{error:#}"
10768            ),
10769            other => panic!("expected the typed refusal, got {other:?}"),
10770        }
10771
10772        let message = HeldSignalRefusal {
10773            wait: Some(Sysno::poll),
10774            held: Some(libc::SIGCHLD),
10775            replacing: Some(libc::SIGWINCH),
10776            loss: HeldSignalLoss::Replaced,
10777        }
10778        .to_string();
10779        for part in [
10780            "poll",
10781            "SIGCHLD",
10782            "SIGWINCH",
10783            "https://github.com/rrnewton/hermit/pull/3361",
10784        ] {
10785            assert!(message.contains(part), "{part} missing from: {message}");
10786        }
10787    }
10788
10789    /// A guest with a caught `SIGCHLD` pending, outside its mask. With
10790    /// `gated_backend`, its configuration is the one under which the scheduler
10791    /// models signal targets: serialized threads and a backend that reports the
10792    /// kernel's signal state.
10793    fn caught_sigchld_guest(gated_backend: bool) -> (WaitGuest, Kernel) {
10794        let (mut guest, kernel) = WaitGuest::live(guest_mask());
10795        guest.config.sequentialize_threads = gated_backend;
10796        detcore_model::config::set_kernel_signal_facts(&mut guest.config.backend, gated_backend);
10797        {
10798            let mut kernel = kernel.lock().unwrap();
10799            kernel.pending = kernel_sigset_bit(libc::SIGCHLD);
10800            kernel.caught = kernel_sigset_bit(libc::SIGCHLD);
10801        }
10802        (guest, kernel)
10803    }
10804
10805    /// A caught `SIGCHLD` pending outside the guest's mask, which the scheduler
10806    /// did not commit in this turn (`admit`), never ends a gated
10807    /// wait, on any backend and whoever sent it: `/proc` cannot tell a
10808    /// `SIGCHLD` a guest or the scheduler sent at a deterministic point from the
10809    /// one the kernel posts for a child event at a moment set by host timing
10810    /// (https://github.com/rrnewton/hermit/issues/3146). The signal stays queued
10811    /// and is delivered when the call returns. The waits of `select` and
10812    /// `pselect6` hold nothing and end for it, as Linux ends them for any caught
10813    /// signal (`core_sys_select`).
10814    #[tokio::test]
10815    async fn a_pending_caught_sigchld_ends_a_select_wait_but_no_gated_wait() {
10816        for gated_backend in [false, true] {
10817            let (guest, kernel) = caught_sigchld_guest(gated_backend);
10818            let _proc = scripted_proc(&kernel);
10819
10820            let gated = KernelSignalWait::new(&guest, 0, false, Errno::ERESTARTNOHAND);
10821            assert!(
10822                !gated.interrupted().unwrap(),
10823                "a pending caught SIGCHLD does not end a gated wait \
10824                 (gated_backend={gated_backend})"
10825            );
10826
10827            let select = KernelSignalWait::for_select(&guest);
10828            assert!(
10829                select.interrupted().unwrap(),
10830                "the pending caught SIGCHLD ends a select wait (gated_backend={gated_backend})"
10831            );
10832            assert_eq!(
10833                kernel.lock().unwrap().pending,
10834                kernel_sigset_bit(libc::SIGCHLD),
10835                "the signal stays queued for the guest's handler"
10836            );
10837        }
10838    }
10839
10840    /// A caught `SIGCHLD` the scheduler committed to the thread ends a gated
10841    /// wait, but only in the turn whose resume names it
10842    /// (`KernelSignalWait::admit`), and never when a host-timed
10843    /// source a guest armed can post `SIGCHLD` too: that copy could arrive at a
10844    /// moment host timing sets (https://github.com/rrnewton/hermit/issues/4005).
10845    #[tokio::test]
10846    async fn a_committed_sigchld_ends_a_gated_wait_unless_a_host_timed_source_can_post_it() {
10847        let chld = kernel_sigset_bit(libc::SIGCHLD);
10848        let (guest, kernel) = caught_sigchld_guest(true);
10849        let _proc = scripted_proc(&kernel);
10850
10851        let mut wait = KernelSignalWait::new(&guest, 0, false, Errno::ERESTARTNOHAND);
10852        assert!(
10853            !wait.interrupted().unwrap(),
10854            "an uncommitted SIGCHLD is held"
10855        );
10856        wait.admit(chld, 0);
10857        assert!(
10858            wait.interrupted().unwrap(),
10859            "a committed SIGCHLD ends the wait"
10860        );
10861        wait.admit(0, 0);
10862        assert!(
10863            !wait.interrupted().unwrap(),
10864            "the next turn's resume names nothing, so the SIGCHLD is held again"
10865        );
10866
10867        let mut host_timed = KernelSignalWait::new(&guest, 0, false, Errno::ERESTARTNOHAND);
10868        host_timed.hold_until_return(chld);
10869        host_timed.admit(chld, 0);
10870        assert!(
10871            !host_timed.interrupted().unwrap(),
10872            "a SIGCHLD a host-timed source can post stays held although committed"
10873        );
10874    }
10875
10876    /// A `SIGCHLD` a guest sent this thread, which the schedule recorded in its
10877    /// private queue and the turn admits (`KernelSignalWait::admit`), ends a
10878    /// gated wait from the thread's own queue only: one pending only on the
10879    /// shared queue, which a host-timed child event can fill, decides nothing.
10880    /// It is admitted for the turn alone, and never when a host-timed source
10881    /// can post `SIGCHLD` (R2b-2 stage 3,
10882    /// https://github.com/rrnewton/hermit/issues/4273).
10883    #[tokio::test]
10884    async fn an_admitted_private_sigchld_ends_a_gated_wait_from_the_threads_own_queue_only() {
10885        let chld = kernel_sigset_bit(libc::SIGCHLD);
10886        let (guest, kernel) = caught_sigchld_guest(true);
10887        let _proc = scripted_proc(&kernel);
10888
10889        let mut wait = KernelSignalWait::new(&guest, 0, false, Errno::ERESTARTNOHAND);
10890        wait.admit(0, chld);
10891        assert!(
10892            wait.interrupted().unwrap(),
10893            "the thread's own copy ends the wait"
10894        );
10895        assert!(
10896            wait.interrupts_only_by_committed(
10897                &read_wait_signal_state(guest.pid(), guest.tid()).unwrap()
10898            ),
10899            "only the admitted copy ends it, so the turn's probe runs first"
10900        );
10901        kernel.lock().unwrap().shared = chld;
10902        assert!(
10903            !wait.interrupted().unwrap(),
10904            "a SIGCHLD pending only on the shared queue is held"
10905        );
10906        kernel.lock().unwrap().shared = 0;
10907        wait.admit(0, 0);
10908        assert!(!wait.interrupted().unwrap(), "the next turn holds it again");
10909
10910        let mut host_timed = KernelSignalWait::new(&guest, 0, false, Errno::ERESTARTNOHAND);
10911        host_timed.hold_until_return(chld);
10912        host_timed.admit(0, chld);
10913        assert!(
10914            !host_timed.interrupted().unwrap(),
10915            "a SIGCHLD a host-timed source can post stays held"
10916        );
10917    }
10918
10919    /// An admitted private `SIGCHLD` that only the wait mask lets in is
10920    /// delivered under that mask (`interrupts_only_under_wait_mask`), as a
10921    /// committed one is: an `epoll_pwait` whose temporary mask unblocks a
10922    /// `SIGCHLD` the thread's own mask blocks (R2b-2 stage 3).
10923    #[tokio::test]
10924    async fn an_admitted_private_sigchld_that_only_the_wait_mask_lets_in_is_delivered_under_it() {
10925        let chld = kernel_sigset_bit(libc::SIGCHLD);
10926        let (guest, kernel) = caught_sigchld_guest(true);
10927        let _proc = scripted_proc(&kernel);
10928        kernel.lock().unwrap().blocked |= chld;
10929        let mut wait =
10930            KernelSignalWait::new(&guest, 0, false, Errno::EINTR).with_wait_mask(Some(0));
10931        let state = read_wait_signal_state(guest.pid(), guest.tid()).unwrap();
10932        assert!(
10933            !wait.interrupts_only_under_wait_mask(&state),
10934            "held while not admitted"
10935        );
10936        wait.admit(0, chld);
10937        assert!(wait.interrupts_only_under_wait_mask(&state));
10938    }
10939
10940    /// `admit_under_block` keeps a recorded private `SIGCHLD` only once every
10941    /// blockable signal is blocked in place, and only if the thread's own
10942    /// queue holds `SIGCHLD`, read after the block. A turn that admits nothing
10943    /// makes no request; a refused setter keeps the wait's fallback, the
10944    /// injected `block` after the first probe; a wait whose earlier injected
10945    /// `block` failed (`unblockable`, no `saved_mask`) keeps nothing (R2b-2
10946    /// stage 3, M3c of the rev5 review).
10947    #[tokio::test]
10948    async fn a_recorded_private_sigchld_is_kept_only_under_a_block_in_place() {
10949        let chld = kernel_sigset_bit(libc::SIGCHLD);
10950        // The block publishes the guest's own mask, already published here,
10951        // so no request leaves the test (`publish_wait_mask`).
10952        let mut published = PublishedWaitMask(Some(guest_mask()));
10953
10954        let (mut guest, kernel) = caught_sigchld_guest(true);
10955        let _proc = scripted_proc(&kernel);
10956        kernel.lock().unwrap().setter.push_back(None);
10957        let mut wait = KernelSignalWait::new(&guest, 0, false, Errno::ERESTARTNOHAND);
10958        assert_eq!(
10959            wait.admit_under_block(&mut guest, 0, chld, 0, &mut published)
10960                .await
10961                .unwrap(),
10962            chld,
10963            "blocked in place, and the thread's own queue holds it"
10964        );
10965        assert!(wait.blocked_in_place);
10966        wait.restore(&mut guest, None).await.unwrap();
10967        drop(_proc);
10968
10969        let (mut guest, kernel) = caught_sigchld_guest(true);
10970        let _proc = scripted_proc(&kernel);
10971        let mut wait = KernelSignalWait::new(&guest, 0, false, Errno::ERESTARTNOHAND);
10972        assert_eq!(
10973            wait.admit_under_block(&mut guest, 0, 0, 0, &mut published)
10974                .await
10975                .unwrap(),
10976            0
10977        );
10978        assert!(
10979            kernel.lock().unwrap().set_calls.is_empty(),
10980            "nothing admitted, no request"
10981        );
10982        assert_eq!(
10983            wait.admit_under_block(&mut guest, 0, chld, chld, &mut published)
10984                .await
10985                .unwrap(),
10986            0,
10987            "a SIGCHLD a host-timed source can post is not admitted"
10988        );
10989        assert!(kernel.lock().unwrap().set_calls.is_empty());
10990        drop(_proc);
10991
10992        let (mut guest, kernel) = caught_sigchld_guest(true);
10993        let _proc = scripted_proc(&kernel);
10994        kernel.lock().unwrap().shared = chld;
10995        kernel.lock().unwrap().setter.push_back(None);
10996        let mut wait = KernelSignalWait::new(&guest, 0, false, Errno::ERESTARTNOHAND);
10997        assert_eq!(
10998            wait.admit_under_block(&mut guest, 0, chld, 0, &mut published)
10999                .await
11000                .unwrap(),
11001            0,
11002            "only on the shared queue: not the recorded copy"
11003        );
11004        wait.restore(&mut guest, None).await.unwrap();
11005        drop(_proc);
11006
11007        let (mut guest, kernel) = caught_sigchld_guest(true);
11008        let _proc = scripted_proc(&kernel);
11009        let mut wait = KernelSignalWait::new(&guest, 0, false, Errno::ERESTARTNOHAND);
11010        assert_eq!(
11011            wait.admit_under_block(&mut guest, 0, chld, 0, &mut published)
11012                .await
11013                .unwrap(),
11014            0,
11015            "the setter refuses (no answer queued)"
11016        );
11017        assert!(
11018            wait.needs_block(),
11019            "the injected block after the first probe stays"
11020        );
11021        drop(_proc);
11022
11023        let (mut guest, kernel) = caught_sigchld_guest(true);
11024        let _proc = scripted_proc(&kernel);
11025        let mut wait = KernelSignalWait::new(&guest, 0, false, Errno::ERESTARTNOHAND);
11026        wait.unblockable = true;
11027        assert_eq!(
11028            wait.admit_under_block(&mut guest, 0, chld, 0, &mut published)
11029                .await
11030                .unwrap(),
11031            0,
11032            "an earlier injected block failed: no block, no private copy"
11033        );
11034        assert!(kernel.lock().unwrap().set_calls.is_empty());
11035    }
11036
11037    /// A host signal can arrive after a pipe turn read an empty pending set.
11038    /// The private probe must not take it into the backend's held slot and
11039    /// retry to EAGAIN: later /proc reads cannot see that slot, and the guest
11040    /// cannot run its handler until the polling call returns. This uses the
11041    /// real Guest::inject_with_retry, with the ptrace outcome modeled by
11042    /// WaitGuest: the first stopped attempt holds the signal and returns
11043    /// ERESTARTSYS; a retried empty probe returns EAGAIN.
11044    #[tokio::test]
11045    async fn a_signal_after_an_empty_pipe_turn_snapshot_still_interrupts() {
11046        let usr1 = kernel_sigset_bit(libc::SIGUSR1);
11047        for (sent, blocked) in [(true, false), (false, false), (true, true)] {
11048            let own_mask = guest_mask() | if blocked { usr1 } else { 0 };
11049            let (mut guest, kernel) = WaitGuest::new(Pid::from_raw(1), own_mask);
11050            {
11051                let mut kernel = kernel.lock().unwrap();
11052                kernel.caught = usr1;
11053                kernel.pending_repost = if sent { usr1 } else { 0 };
11054                kernel.stops_for_pending = true;
11055                kernel.empty_read_probes = Some(0);
11056                kernel.private_record = Some((0, 0));
11057                kernel.setter.extend([None, None]);
11058            }
11059            let _proc = scripted_proc(&kernel);
11060            let mut wait = Some(KernelSignalWait::new(&guest, 0, false, Errno::ERESTARTSYS));
11061            let mut published = PublishedWaitMask(Some(own_mask));
11062            let read = syscalls::Read::new()
11063                .with_fd(3)
11064                .with_buf(AddrMut::from_raw(guest.arena.as_mut_ptr() as usize))
11065                .with_len(1);
11066            let first = turn_signals_with_block(
11067                &mut guest,
11068                &mut wait,
11069                ProbeMaskPolicy::PrivateProbe,
11070                0,
11071                false,
11072                &mut published,
11073            )
11074            .await
11075            .unwrap();
11076            assert!(!first.ends(false), "the snapshot preceded the signal");
11077            assert_eq!(
11078                guest.inject_with_retry(read).await,
11079                Err(Errno::EAGAIN),
11080                "an empty nonblocking read transfers no bytes"
11081            );
11082            let next = turn_signals_with_block(
11083                &mut guest,
11084                &mut wait,
11085                ProbeMaskPolicy::PrivateProbe,
11086                0,
11087                false,
11088                &mut published,
11089            )
11090            .await
11091            .unwrap();
11092            wait.as_mut()
11093                .unwrap()
11094                .restore(&mut guest, None)
11095                .await
11096                .unwrap();
11097            let kernel = kernel.lock().unwrap();
11098            assert_eq!(kernel.blocked, own_mask, "the guest's mask is restored");
11099            assert_eq!(kernel.empty_read_probes, Some(1));
11100            assert_eq!(
11101                next.ends(false),
11102                sent && !blocked,
11103                "sent={sent}, blocked={blocked}, pending={:#x}, held={:?}, taken={:?}",
11104                kernel.pending,
11105                kernel.held_slot,
11106                kernel.taken
11107            );
11108            assert_eq!(kernel.pending, if sent { usr1 } else { 0 });
11109            assert_eq!(kernel.held_slot, None, "the signal stays kernel-pending");
11110        }
11111    }
11112
11113    /// A pipe turn (`turn_signals_with_block`) counts the recorded private
11114    /// `SIGCHLD` only where the gate holds, a block is in force and the
11115    /// thread's own queue holds it. Not on the scalar writer's first attempt,
11116    /// the guest's own write, which blocks nothing (`ProbeMaskPolicy::Guest`): an
11117    /// injection of that turn could take the copy into the backend's single
11118    /// held slot. Not without the gate, as on a socket. Not from the shared
11119    /// queue alone (R2b-2 stage 3).
11120    #[tokio::test]
11121    async fn a_pipe_turn_counts_a_recorded_private_sigchld_only_under_a_block_in_place() {
11122        let chld = kernel_sigset_bit(libc::SIGCHLD);
11123        let turn = |may_block: bool, gate: bool, shared: bool| async move {
11124            // Thread 1, the thread state's own number, so a request may leave.
11125            let (mut guest, kernel) = WaitGuest::new(Pid::from_raw(1), guest_mask());
11126            {
11127                let mut kernel = kernel.lock().unwrap();
11128                kernel.pending = chld;
11129                kernel.caught = chld;
11130                kernel.shared = if shared { chld } else { 0 };
11131                kernel.setter.push_back(None);
11132                kernel.private_record = Some((0, chld));
11133            }
11134            let _proc = scripted_proc(&kernel);
11135            let mut wait = Some(KernelSignalWait::new(&guest, 0, false, Errno::ERESTARTSYS));
11136            let mut published = PublishedWaitMask(Some(guest_mask()));
11137            let pending = turn_signals_with_block(
11138                &mut guest,
11139                &mut wait,
11140                if may_block {
11141                    ProbeMaskPolicy::PendingSignals
11142                } else {
11143                    ProbeMaskPolicy::Guest
11144                },
11145                0,
11146                gate,
11147                &mut published,
11148            )
11149            .await
11150            .unwrap()
11151            .pending;
11152            if let Some(wait) = wait.as_mut() {
11153                wait.restore(&mut guest, None).await.unwrap();
11154            }
11155            let blocked_in_place = !kernel.lock().unwrap().set_calls.is_empty();
11156            (pending & chld, blocked_in_place)
11157        };
11158        assert_eq!(
11159            turn(true, true, false).await,
11160            (chld, true),
11161            "blocked in place: admitted"
11162        );
11163        assert_eq!(
11164            turn(false, true, false).await,
11165            (0, false),
11166            "the writer's first attempt blocks nothing: held"
11167        );
11168        assert_eq!(turn(true, false, false).await, (0, true), "no gate: held");
11169        assert_eq!(
11170            turn(true, true, true).await,
11171            (0, true),
11172            "the shared queue alone: held"
11173        );
11174    }
11175
11176    /// The pipe path's hold leaves out an admitted private `SIGCHLD`
11177    /// (`turn_signals_before_discard`'s `private`): it counts in the turn's
11178    /// `pending`, where without it the hold keeps it out (R2b-2 stage 3).
11179    #[test]
11180    fn a_pipe_turn_counts_an_admitted_private_sigchld() {
11181        let chld = kernel_sigset_bit(libc::SIGCHLD);
11182        let state = KernelSignalState {
11183            pending: chld,
11184            thread_pending: chld,
11185            caught: chld,
11186            ..KernelSignalState::default()
11187        };
11188        assert_eq!(
11189            turn_signals_before_discard(&state, 0, 0, 0, NamedRealTime::Woken, 0).pending,
11190            0,
11191            "held"
11192        );
11193        assert_eq!(
11194            turn_signals_before_discard(&state, 0, 0, 0, NamedRealTime::Woken, chld).pending,
11195            chld,
11196            "admitted"
11197        );
11198    }
11199
11200    /// The same `SIGCHLD` ends a select wait before an injection that is not a
11201    /// probe (`inject_absorbing`: the change of mask that blocks signals for the
11202    /// wait), with `ERESTARTNOHAND`, which the handler's run turns into `EINTR`.
11203    /// The waits of `select` and `pselect6` make every probe with
11204    /// `inject_first_probe`. A gated wait makes the injection, and the `SIGCHLD`
11205    /// that stops it is held until the call returns.
11206    #[tokio::test]
11207    async fn a_pending_caught_sigchld_ends_a_select_wait_before_the_injection() {
11208        let (mut guest, kernel) = caught_sigchld_guest(true);
11209        kernel.lock().unwrap().stops_for_pending = true;
11210        let _proc = scripted_proc(&kernel);
11211
11212        let mut select = KernelSignalWait::for_select(&guest);
11213        let result = inject_mask_absorbing(&mut select, &mut guest, guest_mask()).await;
11214        assert!(
11215            matches!(result, Err(Error::Errno(Errno::ERESTARTNOHAND))),
11216            "{result:?}"
11217        );
11218        {
11219            let kernel = kernel.lock().unwrap();
11220            assert!(kernel.requested.is_empty(), "nothing is injected");
11221            assert_eq!(kernel.pending, kernel_sigset_bit(libc::SIGCHLD));
11222        }
11223        assert_eq!(select.held, None);
11224
11225        let mut gated = KernelSignalWait::new(&guest, 0, false, Errno::ERESTARTNOHAND);
11226        let result = inject_mask_absorbing(&mut gated, &mut guest, guest_mask()).await;
11227        assert!(matches!(result, Ok(Ok(0))), "{result:?}");
11228        {
11229            let kernel = kernel.lock().unwrap();
11230            assert_eq!(kernel.taken, vec![libc::SIGCHLD]);
11231            assert_eq!(kernel.requested.len(), 2, "stopped once, then ran");
11232        }
11233        assert_eq!(gated.held.map(|held| held.kind), Some(HeldKind::Precious));
11234    }
11235
11236    /// The kernel's next dequeue: the private queue before the shared one, a
11237    /// synchronous signal first, then the lowest number, outside the mask.
11238    #[test]
11239    fn next_dequeued_follows_the_kernel_order() {
11240        let bit = kernel_sigset_bit;
11241        let state = KernelSignalState {
11242            thread_pending: bit(libc::SIGUSR2) | bit(libc::SIGSEGV),
11243            shared_pending: bit(libc::SIGHUP),
11244            ..KernelSignalState::default()
11245        };
11246        assert_eq!(
11247            state.next_dequeued(0),
11248            Some((libc::SIGSEGV, SignalQueue::Thread))
11249        );
11250        assert_eq!(
11251            state.next_dequeued(bit(libc::SIGSEGV)),
11252            Some((libc::SIGUSR2, SignalQueue::Thread))
11253        );
11254        assert_eq!(
11255            state.next_dequeued(bit(libc::SIGSEGV) | bit(libc::SIGUSR2)),
11256            Some((libc::SIGHUP, SignalQueue::Shared))
11257        );
11258        assert_eq!(state.next_dequeued(!0), None);
11259    }
11260
11261    /// A stop is identified only when exactly the predicted signal left its
11262    /// queue and the mask and dispositions are unchanged; arrivals do not matter.
11263    #[test]
11264    fn a_stop_is_identified_only_by_exactly_its_signal() {
11265        let bit = kernel_sigset_bit;
11266        let before = KernelSignalState {
11267            thread_pending: bit(libc::SIGUSR1) | bit(libc::SIGWINCH),
11268            shared_pending: bit(libc::SIGHUP),
11269            ..KernelSignalState::default()
11270        };
11271        let took = |after: KernelSignalState, signal, queue| {
11272            stop_took_only(&before, &after, signal, queue)
11273        };
11274        let after = KernelSignalState {
11275            thread_pending: bit(libc::SIGWINCH) | bit(libc::SIGUSR2),
11276            ..before
11277        };
11278        assert!(took(after, libc::SIGUSR1, SignalQueue::Thread));
11279        assert!(
11280            !took(after, libc::SIGWINCH, SignalQueue::Thread),
11281            "SIGWINCH is still queued"
11282        );
11283        let two_left = KernelSignalState {
11284            thread_pending: 0,
11285            ..before
11286        };
11287        assert!(!took(two_left, libc::SIGUSR1, SignalQueue::Thread));
11288        let shared_left = KernelSignalState {
11289            thread_pending: bit(libc::SIGWINCH),
11290            shared_pending: 0,
11291            ..before
11292        };
11293        assert!(!took(shared_left, libc::SIGUSR1, SignalQueue::Thread));
11294        let mask_changed = KernelSignalState {
11295            blocked: bit(libc::SIGUSR2),
11296            ..after
11297        };
11298        assert!(!took(mask_changed, libc::SIGUSR1, SignalQueue::Thread));
11299        let disposition_changed = KernelSignalState {
11300            ignored: bit(libc::SIGWINCH),
11301            ..after
11302        };
11303        assert!(!took(
11304            disposition_changed,
11305            libc::SIGUSR1,
11306            SignalQueue::Thread
11307        ));
11308    }
11309
11310    /// A select wait with a pselect6 temporary mask (`with_wait_mask`) holds a
11311    /// caught signal that the temporary mask blocks, and the guest's own mask
11312    /// does not, when it stops an injection before `block` takes effect, as
11313    /// Linux keeps it pending for the whole call; without the temporary mask
11314    /// the same stop ends the wait, and a signal only the temporary mask
11315    /// unblocks ends it too (review of
11316    /// https://github.com/rrnewton/hermit/pull/4051).
11317    #[test]
11318    fn a_signal_the_temporary_mask_blocks_is_held_not_ended() {
11319        let bit = kernel_sigset_bit;
11320        let (guest, _kernel) = WaitGuest::live(guest_mask());
11321        let before = KernelSignalState {
11322            pending: bit(libc::SIGALRM),
11323            thread_pending: bit(libc::SIGALRM),
11324            blocked: guest_mask(),
11325            caught: bit(libc::SIGALRM) | bit(libc::SIGUSR2),
11326            ..KernelSignalState::default()
11327        };
11328        let plain = KernelSignalWait::for_select(&guest);
11329        assert_eq!(
11330            plain.stop_verdict(&before, libc::SIGALRM),
11331            StopVerdict::End(Errno::ERESTARTNOHAND)
11332        );
11333        let masked = KernelSignalWait::for_select(&guest).with_wait_mask(Some(bit(libc::SIGALRM)));
11334        assert_eq!(
11335            masked.stop_verdict(&before, libc::SIGALRM),
11336            StopVerdict::Absorb(HeldKind::Precious)
11337        );
11338        let unblocking = KernelSignalWait::for_select(&guest).with_wait_mask(Some(0));
11339        assert_eq!(
11340            unblocking.stop_verdict(&before, libc::SIGUSR2),
11341            StopVerdict::End(Errno::ERESTARTNOHAND)
11342        );
11343    }
11344}
11345
11346#[cfg(test)]
11347mod turn_signal_tests {
11348    use super::*;
11349
11350    fn bit(signal: libc::c_int) -> KernelSigset {
11351        kernel_sigset_bit(signal)
11352    }
11353
11354    fn state(
11355        pending: KernelSigset,
11356        caught: KernelSigset,
11357        blocked: KernelSigset,
11358    ) -> KernelSignalState {
11359        KernelSignalState {
11360            pending,
11361            thread_pending: pending,
11362            caught,
11363            blocked,
11364            ..Default::default()
11365        }
11366    }
11367
11368    /// Rule 3 of R2b-1 (https://github.com/rrnewton/hermit/issues/4273): with
11369    /// no progress only a pending copy ends the call; after progress a named
11370    /// wake does too.
11371    #[test]
11372    fn a_named_wake_ends_a_call_only_after_progress() {
11373        let named = TurnSignals {
11374            named: bit(libc::SIGUSR1),
11375            pending: 0,
11376        };
11377        assert!(!named.ends(false));
11378        assert!(named.ends(true));
11379        let pending = TurnSignals {
11380            named: 0,
11381            pending: bit(libc::SIGUSR1),
11382        };
11383        assert!(pending.ends(false));
11384        assert!(pending.ends(true));
11385        assert!(!TurnSignals::default().ends(true));
11386    }
11387
11388    /// A caught SIGCHLD pending at a poll's turn is held unless the scheduler
11389    /// committed it, and a host-timed signal is held even then (arm A8).
11390    #[test]
11391    fn the_hold_leaves_out_an_uncommitted_sigchld_and_every_host_timed_signal() {
11392        let chld = bit(libc::SIGCHLD);
11393        let hup = bit(libc::SIGHUP);
11394        let s = state(chld | hup, chld | hup, 0);
11395        assert_eq!(
11396            turn_signals_before_discard(&s, 0, hup, 0, NamedRealTime::Woken, 0).pending,
11397            0
11398        );
11399        assert_eq!(
11400            turn_signals_before_discard(&s, 0, hup, chld, NamedRealTime::Woken, 0).pending,
11401            chld
11402        );
11403        assert_eq!(
11404            turn_signals_before_discard(&s, 0, hup, chld | hup, NamedRealTime::Woken, 0).pending,
11405            chld
11406        );
11407    }
11408
11409    /// A named signal counts only if the call's mask leaves it unblocked
11410    /// (rule 2). A real-time one counts only if its disposition delivers it
11411    /// for `writev`, which the drain never filters (rule 2), and as woken for
11412    /// the generic poll and the scalar writer, whose drain already tested it
11413    /// (rule 1), so a flush after the wake does not undo it (the Claude
11414    /// implementation review of R2b-1, F1).
11415    #[test]
11416    fn a_named_signal_counts_only_unblocked_and_a_real_time_one_by_its_call() {
11417        use NamedRealTime::ByDisposition;
11418        use NamedRealTime::Woken;
11419        let usr1 = bit(libc::SIGUSR1);
11420        let rt = bit(libc::SIGRTMIN());
11421        let s = state(0, usr1, usr1);
11422        for real_time in [Woken, ByDisposition] {
11423            assert_eq!(
11424                turn_signals_before_discard(&s, usr1, 0, usr1, real_time, 0).named,
11425                0
11426            );
11427            assert_eq!(
11428                turn_signals_before_discard(&s, 0, 0, usr1, real_time, 0).named,
11429                usr1
11430            );
11431        }
11432        let ignored_rt = KernelSignalState {
11433            ignored: rt,
11434            ..state(0, 0, 0)
11435        };
11436        assert_eq!(
11437            turn_signals_before_discard(&ignored_rt, 0, 0, rt, ByDisposition, 0).named,
11438            0
11439        );
11440        assert_eq!(
11441            turn_signals_before_discard(&ignored_rt, 0, 0, rt, Woken, 0).named,
11442            rt
11443        );
11444        assert_eq!(
11445            turn_signals_before_discard(&ignored_rt, rt, 0, rt, Woken, 0).named,
11446            0
11447        );
11448        let caught_rt = state(0, rt, 0);
11449        assert_eq!(
11450            turn_signals_before_discard(&caught_rt, 0, 0, rt, ByDisposition, 0).named,
11451            rt
11452        );
11453    }
11454}
11455
11456/// The mask a thread is stopped under where its blocking call returned, until
11457/// its continuation turn (`continuation_mask`).
11458#[derive(Clone, Copy, Debug, PartialEq, Eq)]
11459pub(crate) enum ContinuationMask {
11460    /// The call's temporary mask: the call ran and a signal ended it.
11461    Temporary,
11462    /// Not known to the scheduler, in record and replay alike.
11463    Unknown,
11464    /// The thread's own mask, which the kernel restored before the call
11465    /// returned and holds in record and replay alike (with the signals it
11466    /// ignores blocked over it until after the continuation, when the call
11467    /// raised an `IgnoredSignalShield`).
11468    Restored,
11469}
11470
11471/// The mask `call`, having returned `result`, leaves its thread stopped under,
11472/// decided from what the kernel did, which the raw result tells and replay
11473/// serves as recorded:
11474/// - pselect6 and ppoll that a signal ended return `-ERESTARTNOHAND`, or the
11475///   `-EINTR` that `poll_select_finish` can still turn it into after keeping
11476///   the mask (a timeout write-back that faults, or the STICKY_TIMEOUTS
11477///   personality); epoll_pwait and epoll_pwait2 return `-EINTR`. Linux then
11478///   delivers the signal under the temporary mask and `rt_sigreturn` restores
11479///   the saved one (`restore_saved_sigmask_unless`, fs/select.c, fs/eventpoll.c
11480///   and kernel/signal.c at v6.9): `Temporary`.
11481/// - Their normal return: Linux restored the saved mask before returning. Their
11482///   `-ERESTARTSYS`, which the host reports when the signal reached the thread
11483///   before the call ran: the kernel never installed the mask, and the
11484///   recording holds that result. Either way `Restored`.
11485/// - rt_sigsuspend: `Unknown`, whatever its result. Whether it ran is not
11486///   recorded: record injects it live, where a signal arriving first yields a
11487///   host-timed `-ERESTARTSYS` (the kernel never installed the mask), and
11488///   replay re-injects it with retry, which never shows that. A rule on its
11489///   result would differ between the two (the Claude review of d1b9404c), and
11490///   a known temporary mask would claim one the kernel may not have installed
11491///   (the Codex review of da662bb2). The scheduler treats an unknown mask
11492///   alike in both: no `SIGCHLD` taker or signal target, no discard.
11493/// - Any other call: `Restored`; it installed no temporary mask.
11494pub(crate) fn continuation_mask(call: Syscall, result: &Result<i64, Error>) -> ContinuationMask {
11495    let ended_by = |errno: Errno| matches!(result, Err(Error::Errno(error)) if *error == errno);
11496    let kept = match call {
11497        Syscall::RtSigsuspend(_) => return ContinuationMask::Unknown,
11498        Syscall::Pselect6(_) | Syscall::Ppoll(_) => {
11499            ended_by(Errno::ERESTARTNOHAND) || ended_by(Errno::EINTR)
11500        }
11501        Syscall::EpollPwait(_) | Syscall::Other(Sysno::epoll_pwait2, _) => ended_by(Errno::EINTR),
11502        _ => false,
11503    };
11504    if kept {
11505        ContinuationMask::Temporary
11506    } else {
11507        ContinuationMask::Restored
11508    }
11509}
11510
11511#[cfg(test)]
11512mod temporary_mask_tests {
11513    use super::*;
11514
11515    /// The continuation mask follows what the kernel did: the temporary mask
11516    /// for an interrupted pselect6 or ppoll (`-ERESTARTNOHAND`, or the `-EINTR`
11517    /// it can become after the kernel kept the mask) and an interrupted
11518    /// epoll_pwait or epoll_pwait2 (`-EINTR`); the restored own mask for their
11519    /// normal return, a call the signal reached before it ran (`-ERESTARTSYS`)
11520    /// or another errno; unknown for every rt_sigsuspend, whose `-ERESTARTSYS`
11521    /// is host-timed in record and absent in replay; restored for a call
11522    /// without a temporary mask.
11523    #[test]
11524    fn the_continuation_mask_follows_what_the_kernel_did() {
11525        let restart = Err(Error::Errno(Errno::ERESTARTNOHAND));
11526        let eintr = Err(Error::Errno(Errno::EINTR));
11527        let pselect = Syscall::Pselect6(syscalls::Pselect6::new());
11528        let ppoll = Syscall::Ppoll(syscalls::Ppoll::new());
11529        let epoll = Syscall::EpollPwait(syscalls::EpollPwait::new());
11530        let epoll2 = Syscall::Other(
11531            Sysno::epoll_pwait2,
11532            reverie::syscalls::SyscallArgs::new(0, 0, 0, 0, 0, 0),
11533        );
11534        let before_entry = Err(Error::Errno(Errno::ERESTARTSYS));
11535        let sigsuspend = Syscall::RtSigsuspend(syscalls::RtSigsuspend::new());
11536        let read = Syscall::Read(syscalls::Read::new());
11537        let ebadf = Err(Error::Errno(Errno::EBADF));
11538        use ContinuationMask::*;
11539        for call in [pselect, ppoll] {
11540            assert_eq!(continuation_mask(call, &restart), Temporary, "{call:?}");
11541            assert_eq!(continuation_mask(call, &eintr), Temporary, "{call:?}");
11542            assert_eq!(continuation_mask(call, &before_entry), Restored, "{call:?}");
11543            assert_eq!(continuation_mask(call, &ebadf), Restored, "{call:?}");
11544            assert_eq!(continuation_mask(call, &Ok(1)), Restored, "{call:?}");
11545        }
11546        for result in [&restart, &eintr, &before_entry, &Ok(0)] {
11547            assert_eq!(continuation_mask(sigsuspend, result), Unknown, "{result:?}");
11548        }
11549        for call in [epoll, epoll2] {
11550            assert_eq!(continuation_mask(call, &eintr), Temporary, "{call:?}");
11551            assert_eq!(continuation_mask(call, &restart), Restored, "{call:?}");
11552            assert_eq!(continuation_mask(call, &before_entry), Restored, "{call:?}");
11553            assert_eq!(continuation_mask(call, &Ok(0)), Restored, "{call:?}");
11554        }
11555        assert_eq!(continuation_mask(read, &restart), Restored);
11556    }
11557}
11558
11559#[cfg(test)]
11560mod tool_decided_injection_tests {
11561    use super::*;
11562
11563    fn refusal(operation: reverie::UnsupportedOperation) -> Option<reverie::UnsupportedRefusal> {
11564        Some(reverie::UnsupportedRefusal::new(
11565            operation,
11566            Errno::ENOSYS,
11567            "KVM leaves this call to the Tool",
11568        ))
11569    }
11570
11571    /// Only a `ToolDecided` refusal of an injected call is a Detcore defect;
11572    /// no refusal, or a refusal another call site owns (flock, process
11573    /// groups, a signal), is not judged here.
11574    #[test]
11575    fn only_a_tool_decided_refusal_is_an_internal_failure() {
11576        let dettid = DetTid::from_raw(7);
11577        let failure = tool_decided_injection_failure(
11578            refusal(reverie::UnsupportedOperation::ToolDecided),
11579            dettid,
11580            Sysno::getpgrp,
11581        )
11582        .expect("a ToolDecided refusal is an internal failure");
11583        assert!(
11584            failure.starts_with(
11585                "HERMIT_INTERNAL_FAILURE class=tool-decided-injection dtid=7 syscall=getpgrp"
11586            ),
11587            "{failure}"
11588        );
11589        assert!(
11590            failure.contains("KVM leaves this call to the Tool"),
11591            "{failure}"
11592        );
11593        assert_eq!(
11594            tool_decided_injection_failure(None, dettid, Sysno::getpgrp),
11595            None
11596        );
11597        for operation in [
11598            reverie::UnsupportedOperation::Flock,
11599            reverie::UnsupportedOperation::ProcessGroups,
11600            reverie::UnsupportedOperation::Signal,
11601            reverie::UnsupportedOperation::NonLeaderExec,
11602        ] {
11603            assert_eq!(
11604                tool_decided_injection_failure(refusal(operation), dettid, Sysno::getpgrp),
11605                None,
11606                "{operation:?}"
11607            );
11608        }
11609    }
11610}
11611
11612#[cfg(test)]
11613mod backend_refusal_tests {
11614    use reverie::Pid;
11615    use reverie::Tool;
11616
11617    use super::*;
11618    use crate::config::Config;
11619    use crate::record_or_replay::NoopTool;
11620    use crate::syscall_hot_path_tests::InjectGuest;
11621
11622    const DIAGNOSTIC: &str = "this backend does not implement the call";
11623
11624    /// A fail-closed tool whose policy returns the typed error rather than
11625    /// ending the process, and a guest whose last injection the backend
11626    /// refused with `refusal`.
11627    fn fail_closed(
11628        refusal: Option<reverie::UnsupportedRefusal>,
11629    ) -> (Detcore<NoopTool>, InjectGuest<NoopTool>) {
11630        let config = Config {
11631            panic_on_unsupported_syscalls: true,
11632            exit_on_unsupported_syscall: true,
11633            shutdown_on_unsupported_syscall: false,
11634            ..Config::default()
11635        };
11636        let tool = <Detcore<NoopTool> as Tool>::new(Pid::from_raw(4242), &config);
11637        let mut guest = InjectGuest::<NoopTool>::new(Ok(0));
11638        guest.config = config;
11639        if let Some(refusal) = refusal {
11640            guest = guest.with_refusal(refusal);
11641        }
11642        (tool, guest)
11643    }
11644
11645    fn refusal(operation: reverie::UnsupportedOperation) -> reverie::UnsupportedRefusal {
11646        reverie::UnsupportedRefusal::new(operation, Errno::ENOSYS, DIAGNOSTIC)
11647    }
11648
11649    const CALLS: [(Sysno, reverie::UnsupportedOperation); 4] = [
11650        (
11651            Sysno::pkey_alloc,
11652            reverie::UnsupportedOperation::ProtectionKeys,
11653        ),
11654        (
11655            Sysno::pkey_mprotect,
11656            reverie::UnsupportedOperation::ProtectionKeys,
11657        ),
11658        (
11659            Sysno::pkey_free,
11660            reverie::UnsupportedOperation::ProtectionKeys,
11661        ),
11662        (
11663            Sysno::userfaultfd,
11664            reverie::UnsupportedOperation::Userfaultfd,
11665        ),
11666    ];
11667
11668    /// The backend's refusal of the call is refused by policy under the
11669    /// call's name, fail-closed: the guest never sees the backend's ENOSYS,
11670    /// and the call counts as refused.
11671    #[test]
11672    fn the_calls_own_refusal_is_refused_by_policy() {
11673        futures::executor::block_on(async {
11674            for (sysno, operation) in CALLS {
11675                let (tool, mut guest) = fail_closed(Some(refusal(operation)));
11676                let result = tool
11677                    .report_backend_refusal(&mut guest, sysno, operation, Err(Errno::ENOSYS.into()))
11678                    .await;
11679                match result {
11680                    Err(Error::Tool(error)) => assert_eq!(
11681                        error
11682                            .downcast_ref::<crate::UnsupportedSyscallError>()
11683                            .expect("the policy's named refusal")
11684                            .0,
11685                        sysno
11686                    ),
11687                    other => panic!("{sysno}: expected the typed refusal, got {other:?}"),
11688                }
11689                assert!(guest.thread_state().stats.syscall_refused, "{sysno}");
11690                assert!(
11691                    guest.injected().is_empty(),
11692                    "{sysno}: nothing more is injected"
11693                );
11694            }
11695        })
11696    }
11697
11698    /// Anything else keeps the call's own result: a success (the refusal is
11699    /// left for the event's end), an errno the backend did not explain, and a
11700    /// refusal of another operation or with another errno than the call's.
11701    #[test]
11702    fn anything_else_keeps_the_calls_result() {
11703        futures::executor::block_on(async {
11704            for (sysno, operation) in CALLS {
11705                let (tool, mut guest) = fail_closed(Some(refusal(operation)));
11706                let result = tool
11707                    .report_backend_refusal(&mut guest, sysno, operation, Ok(3))
11708                    .await;
11709                assert!(matches!(result, Ok(3)), "{sysno}: {result:?}");
11710                assert!(
11711                    guest.take_unsupported_refusal().is_some(),
11712                    "{sysno}: a success leaves the refusal in place"
11713                );
11714
11715                let (tool, mut guest) = fail_closed(None);
11716                let result = tool
11717                    .report_backend_refusal(&mut guest, sysno, operation, Err(Errno::ENOSYS.into()))
11718                    .await;
11719                assert!(
11720                    matches!(result, Err(Error::Errno(Errno::ENOSYS))),
11721                    "{sysno}: {result:?}"
11722                );
11723                assert!(!guest.thread_state().stats.syscall_refused, "{sysno}");
11724
11725                let other = if operation == reverie::UnsupportedOperation::Userfaultfd {
11726                    reverie::UnsupportedOperation::ProtectionKeys
11727                } else {
11728                    reverie::UnsupportedOperation::Userfaultfd
11729                };
11730                for (backend, errno) in [
11731                    (refusal(other), Errno::ENOSYS),
11732                    (
11733                        reverie::UnsupportedRefusal::new(operation, Errno::EINVAL, DIAGNOSTIC),
11734                        Errno::ENOSYS,
11735                    ),
11736                ] {
11737                    let (tool, mut guest) = fail_closed(Some(backend));
11738                    let result = tool
11739                        .report_backend_refusal(&mut guest, sysno, operation, Err(errno.into()))
11740                        .await;
11741                    assert!(
11742                        matches!(result, Err(Error::Errno(Errno::ENOSYS))),
11743                        "{sysno}: {result:?}"
11744                    );
11745                    // Judged as the event's last refusal: counted, not answered.
11746                    assert!(guest.thread_state().stats.syscall_refused, "{sysno}");
11747                }
11748            }
11749        })
11750    }
11751}