Skip to main content

detcore/syscalls/
time.rs

1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 * All rights reserved.
4 *
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the root directory of this source tree.
7 */
8
9//! System calls for dealing with threads and concurrency.
10use std::time::Duration;
11
12use nix::sys::signal::Signal;
13use reverie::Error;
14use reverie::Guest;
15use reverie::Stack;
16use reverie::syscalls;
17use reverie::syscalls::AddrMut;
18use reverie::syscalls::ClockId;
19use reverie::syscalls::Errno;
20use reverie::syscalls::MemoryAccess;
21use reverie::syscalls::Syscall;
22use reverie::syscalls::Timespec;
23use reverie::syscalls::Timeval;
24use reverie::syscalls::family::NanosleepFamily;
25use tracing::error;
26use tracing::info;
27use tracing::trace;
28
29use crate::detlog;
30use crate::procmaps;
31use crate::record_or_replay::RecordOrReplay;
32use crate::resources::Permission;
33use crate::resources::ResourceID;
34use crate::resources::Resources;
35use crate::scheduler::Priority;
36use crate::scheduler::entropy_to_priority;
37use crate::tool_global::ResumeStatus;
38use crate::tool_global::register_posix_timer;
39use crate::tool_global::thread_observe_time;
40use crate::tool_local::Detcore;
41use crate::types::LogicalTime;
42
43fn time_from_resources(rsrcs: &Resources) -> Option<LogicalTime> {
44    if rsrcs.resources.len() > 1 {
45        panic!(
46            "time_from_resources: multiple resource ids in resource request: {:?}",
47            rsrcs
48        );
49    }
50    for rs in rsrcs.resources.iter() {
51        if let (ResourceID::SleepUntil(tm), _) = rs {
52            return Some(*tm);
53        }
54    }
55    None
56}
57
58/// Flatten a `timespec` to nanoseconds. Negative fields are not valid for the
59/// timer syscalls we handle; treat them as zero rather than panicking.
60fn timespec_to_ns(ts: libc::timespec) -> u64 {
61    let secs = ts.tv_sec.max(0) as u64;
62    let nsec = ts.tv_nsec.max(0) as u64;
63    secs.saturating_mul(1_000_000_000).saturating_add(nsec)
64}
65
66/// Inverse of [`timespec_to_ns`].
67fn ns_to_timespec(ns: u64) -> libc::timespec {
68    libc::timespec {
69        tv_sec: (ns / 1_000_000_000) as libc::time_t,
70        tv_nsec: (ns % 1_000_000_000) as libc::c_long,
71    }
72}
73
74// AUTONOMOUS-BOT-IMPLEMENTED
75// TODO-HUMAN-REVIEW(PR-857): Query-only timex mode boundary.
76fn timex_mode_is_query(modes: libc::c_uint) -> bool {
77    modes == 0 || modes == libc::ADJ_OFFSET_SS_READ
78}
79
80// AUTONOMOUS-BOT-IMPLEMENTED
81// TODO-HUMAN-REVIEW(PR-857): Host-independent NTP discipline snapshot.
82fn deterministic_timex(now: Timespec) -> libc::timex {
83    // SAFETY: `libc::timex` contains only integer fields and padding; zero is a
84    // valid baseline for the fields not modeled by Hermit.
85    let mut tx: libc::timex = unsafe { std::mem::zeroed() };
86    tx.status = libc::STA_UNSYNC;
87    tx.tick = 10_000;
88    tx.time = libc::timeval {
89        tv_sec: now.tv_sec,
90        tv_usec: now.tv_nsec / 1_000,
91    };
92    tx
93}
94
95// AUTONOMOUS-BOT-IMPLEMENTED
96// TODO-HUMAN-REVIEW(PR-845): Review SaBRe thread-local guest clock reads.
97pub(crate) async fn guest_clock_time<G, T>(guest: &mut G) -> LogicalTime
98where
99    G: Guest<Detcore<T>>,
100    T: RecordOrReplay,
101{
102    let raw = thread_observe_time(guest).await;
103    guest.thread_state().observe_guest_clock(raw)
104}
105
106/// Replacing host time in the `tv` of a `gettimeofday` that failed with EFAULT
107/// could not finish. `tv` may still hold host wall-clock time, so this is a
108/// failed run, not a guest errno.
109#[derive(Debug, Clone, Copy, PartialEq, Eq)]
110struct TvRepairFailure {
111    /// The word being stored: `tv_sec` or `tv_usec`.
112    field: &'static str,
113    kind: TvRepairFailureKind,
114}
115
116#[derive(Debug, Clone, Copy, PartialEq, Eq)]
117enum TvRepairFailureKind {
118    /// The backend could not execute the `time(2)` probe. EFAULT is not a
119    /// failure: it means Linux stopped before storing this word.
120    ProbeFailed(Errno),
121    /// After a store probe returned EFAULT, `time(NULL)`, which Linux cannot
122    /// fail, failed too. Something other than the store, such as a seccomp
123    /// filter, produced the EFAULT, so it says nothing about `tv`.
124    ControlProbeFailed(Errno),
125    /// A word at or after the one whose store probe returned EFAULT no longer
126    /// reads as it did before the call, so the original call stored it and the
127    /// EFAULT did not come from the store.
128    StoppedWordChanged,
129    /// A word at or after the one whose store probe returned EFAULT could not
130    /// be read before or after the call, although every byte of it is mapped.
131    /// The read failure may not be a fault: a seccomp filter can deny the
132    /// read while the store went through, so nothing shows the word unstored.
133    StoppedWordUnreadable,
134    /// The guest's memory map, needed to tell an unmapped word from an
135    /// unreadable one, could not be read or was empty.
136    MapsUnavailable,
137    /// The exact overwrite after a successful probe failed. The probe just
138    /// stored host seconds, so the run cannot safely continue.
139    OverwriteFailed(Errno),
140    /// The one-shot overwrite reported a count other than the whole word.
141    OverwriteCount { expected: usize, reported: usize },
142    /// Adding the field offset to the guest's `timeval` address overflowed.
143    AddressOverflow,
144    /// A recorded EFAULT does not prove that this replay execution performed
145    /// the original stores, so live probing would mix replayed and host state.
146    ReplayMode,
147}
148
149impl std::fmt::Display for TvRepairFailure {
150    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
151        write!(
152            f,
153            "replacing host time in the {} of a failed gettimeofday: ",
154            self.field
155        )?;
156        match self.kind {
157            TvRepairFailureKind::ProbeFailed(errno) => {
158                write!(f, "time(2) store probe failed: {errno}")
159            }
160            TvRepairFailureKind::ControlProbeFailed(errno) => write!(
161                f,
162                "time(NULL) control probe failed: {errno}, so the store probe's EFAULT was not a store fault"
163            ),
164            TvRepairFailureKind::StoppedWordChanged => f.write_str(
165                "the word changed during the call although its store probe returned EFAULT",
166            ),
167            TvRepairFailureKind::StoppedWordUnreadable => f.write_str(
168                "the word is mapped but could not be read, so its store probe's EFAULT is unconfirmed",
169            ),
170            TvRepairFailureKind::MapsUnavailable => {
171                f.write_str("the guest's memory map could not be read")
172            }
173            TvRepairFailureKind::OverwriteFailed(errno) => {
174                write!(f, "virtual-time overwrite failed: {errno}")
175            }
176            TvRepairFailureKind::OverwriteCount { expected, reported } => write!(
177                f,
178                "virtual-time overwrite reported {reported} of {expected} bytes"
179            ),
180            TvRepairFailureKind::AddressOverflow => f.write_str("field address overflowed"),
181            TvRepairFailureKind::ReplayMode => {
182                f.write_str("cannot probe a recorded or replayed EFAULT")
183            }
184        }
185    }
186}
187
188impl std::error::Error for TvRepairFailure {}
189
190fn tv_repair_error(field: &'static str, kind: TvRepairFailureKind) -> Error {
191    Error::Tool(anyhow::Error::new(TvRepairFailure { field, kind }))
192}
193
194#[derive(Debug, Clone, Copy, PartialEq, Eq)]
195enum TimeStoreProbe {
196    Stored,
197    Stopped,
198}
199
200fn classify_time_store_probe(
201    field: &'static str,
202    result: Result<i64, Errno>,
203) -> Result<TimeStoreProbe, Error> {
204    match result {
205        Ok(_) => Ok(TimeStoreProbe::Stored),
206        Err(Errno::EFAULT) => Ok(TimeStoreProbe::Stopped),
207        Err(errno) => Err(tv_repair_error(
208            field,
209            TvRepairFailureKind::ProbeFailed(errno),
210        )),
211    }
212}
213
214/// `time(NULL)` stores nothing and cannot fail on Linux, so any error means
215/// the injected `time(2)` never reached the native call.
216fn require_native_time_control_probe(
217    field: &'static str,
218    result: Result<i64, Errno>,
219) -> Result<(), Error> {
220    match result {
221        Ok(_) => Ok(()),
222        Err(errno) => Err(tv_repair_error(
223            field,
224            TvRepairFailureKind::ControlProbeFailed(errno),
225        )),
226    }
227}
228
229/// How a word that Linux did not store reads after the call.
230#[derive(Debug, Clone, Copy, PartialEq, Eq)]
231enum StoppedWord {
232    /// Readable, with the value it held before the call.
233    Unchanged,
234    /// Unreadable both before and after the call. This proves nothing by
235    /// itself; see [`require_unmapped_unreadable_word`].
236    Unreadable,
237}
238
239/// A word Linux did not store keeps both its contents and its readability.
240fn require_unchanged_stopped_word(
241    field: &'static str,
242    before: Result<libc::time_t, Errno>,
243    after: Result<libc::time_t, Errno>,
244) -> Result<StoppedWord, Error> {
245    match (before, after) {
246        (Ok(before), Ok(after)) if before == after => Ok(StoppedWord::Unchanged),
247        (Err(_), Err(_)) => Ok(StoppedWord::Unreadable),
248        _ => Err(tv_repair_error(
249            field,
250            TvRepairFailureKind::StoppedWordChanged,
251        )),
252    }
253}
254
255/// Whether every byte of the `time_t` at `word` lies in one of `ranges`, the
256/// half-open address ranges of the guest's mappings.
257fn time_word_is_fully_mapped(mut ranges: Vec<(u64, u64)>, word: u64) -> bool {
258    let Some(end) = word.checked_add(std::mem::size_of::<libc::time_t>() as u64) else {
259        return false;
260    };
261    ranges.sort_unstable();
262    let mut covered = word;
263    for (start, stop) in ranges {
264        if start <= covered && covered < stop {
265            covered = stop;
266            if covered >= end {
267                return true;
268            }
269        }
270    }
271    false
272}
273
274/// An unreadable word is accepted as unstored only when part of it is
275/// unmapped: Linux's eight-byte `put_user` faults there before storing any
276/// byte. A failed read of a fully mapped word is not evidence of a fault: a
277/// seccomp filter can deny the read while the store succeeds. Ptrace reads use
278/// `FOLL_FORCE`, which bypasses page permissions, but some listed mappings
279/// still refuse the read: file pages beyond end of file, hardware-poisoned
280/// pages, `MADV_GUARD_INSTALL` guard regions, `VM_PFNMAP` mappings without an
281/// `access` operation, missing pages of a userfaultfd region in SIGBUS mode,
282/// and `[vsyscall]` in xonly mode. Backends
283/// that read with `process_vm_readv` also cannot read `PROT_NONE` pages. A
284/// `gettimeofday` whose store faults on any of these ends the run rather than
285/// returning EFAULT. An empty map is refused because a filter that fakes a
286/// successful zero-byte read would otherwise make every word look unmapped.
287fn require_unmapped_unreadable_word(
288    field: &'static str,
289    maps: Result<Vec<(u64, u64)>, Error>,
290    word: u64,
291) -> Result<(), Error> {
292    let ranges = match maps {
293        Ok(ranges) if !ranges.is_empty() => ranges,
294        Ok(_) => {
295            error!("gettimeofday tv repair: the guest's memory map is empty");
296            return Err(tv_repair_error(field, TvRepairFailureKind::MapsUnavailable));
297        }
298        Err(err) => {
299            error!("gettimeofday tv repair: reading the guest's memory map: {err}");
300            return Err(tv_repair_error(field, TvRepairFailureKind::MapsUnavailable));
301        }
302    };
303    if time_word_is_fully_mapped(ranges, word) {
304        Err(tv_repair_error(
305            field,
306            TvRepairFailureKind::StoppedWordUnreadable,
307        ))
308    } else {
309        Ok(())
310    }
311}
312
313/// What Detcore's memory reader returned for `tv_sec` and `tv_usec` before a
314/// `gettimeofday`, with the read error where it failed. Under ptrace the read
315/// ignores page protection and protection keys; backends that read with
316/// `process_vm_readv` respect `VM_READ`.
317type TimevalWordSnapshot = [Result<libc::time_t, Errno>; 2];
318
319const TIMEVAL_WORDS: [(&str, usize); 2] = [
320    ("tv_sec", std::mem::offset_of!(Timeval, tv_sec)),
321    ("tv_usec", std::mem::offset_of!(Timeval, tv_usec)),
322];
323
324fn snapshot_timeval_words<'a, G, T>(
325    guest: &mut G,
326    tv_addr: AddrMut<'a, Timeval>,
327) -> TimevalWordSnapshot
328where
329    G: Guest<Detcore<T>>,
330    T: RecordOrReplay,
331{
332    TIMEVAL_WORDS.map(|(field, offset)| {
333        let addr = timeval_word_addr(field, tv_addr, offset).map_err(|_| Errno::EFAULT)?;
334        guest.memory().read_value(addr)
335    })
336}
337
338fn timeval_word_addr<'a>(
339    field: &'static str,
340    tv_addr: AddrMut<'a, Timeval>,
341    offset: usize,
342) -> Result<AddrMut<'a, libc::time_t>, Error> {
343    tv_addr
344        .as_raw()
345        .checked_add(offset)
346        .and_then(AddrMut::<libc::time_t>::from_raw)
347        .ok_or_else(|| tv_repair_error(field, TvRepairFailureKind::AddressOverflow))
348}
349
350fn require_complete_time_word_overwrite(
351    field: &'static str,
352    expected: usize,
353    result: Result<usize, Errno>,
354) -> Result<(), Error> {
355    match result {
356        Ok(reported) if reported == expected => Ok(()),
357        Ok(reported) => Err(tv_repair_error(
358            field,
359            TvRepairFailureKind::OverwriteCount { expected, reported },
360        )),
361        Err(errno) => Err(tv_repair_error(
362            field,
363            TvRepairFailureKind::OverwriteFailed(errno),
364        )),
365    }
366}
367
368fn require_live_time_store_probe(replay_data_is_some: bool) -> Result<(), Error> {
369    if replay_data_is_some {
370        Err(tv_repair_error("tv", TvRepairFailureKind::ReplayMode))
371    } else {
372        Ok(())
373    }
374}
375
376fn should_repair_failed_gettimeofday_tv(backend_is_kvm: bool) -> bool {
377    !backend_is_kvm
378}
379
380/// Replaces the host wall-clock time that a `gettimeofday` failing with EFAULT
381/// may have stored in `tv` with virtual time, in exactly the words Linux
382/// stored.
383///
384/// Linux stores `tv_sec`, then `tv_usec`, each with one eight-byte `put_user`,
385/// and only then copies `tz`; the first fault ends the call with EFAULT. A
386/// store that faults on either page it touches commits nothing, so each word
387/// is stored whole or not at all, and nothing after an unstored word is
388/// attempted. `time(2)` uses the same eight-byte `put_user` store for its
389/// `tloc`, so the repair injects it at each word in kernel order. EFAULT means
390/// the original call stopped at that word too. Success means the probe itself
391/// just stored host seconds in those exact bytes; only then does Hermit replace
392/// the word with its virtual value. Other probe errors and overwrite failures
393/// fail closed because host time may remain.
394///
395/// An EFAULT from the probe is only evidence about `tv` if it came from the
396/// store. A seccomp filter can return EFAULT for `time(2)` without running it,
397/// and would otherwise end the repair while host time remains in a writable
398/// `tv`. Two checks therefore confirm every EFAULT before it is trusted.
399/// First, `time(NULL)` is injected: it stores nothing and cannot fail on
400/// Linux, so any error means the probes are not reaching the native call.
401/// Second, every word from the stopped one onward must still read exactly as
402/// it did before the original call: readable with the same value, or
403/// unreadable both times and not fully mapped, so that the store could only
404/// have faulted. A changed word was stored by that call, and an unreadable
405/// word that is fully mapped may have been, because a failed read is not
406/// itself a fault. Each of these failures ends the run, including for the
407/// mapped but unreadable pages listed at `require_unmapped_unreadable_word`.
408///
409/// These checks are observations Detcore makes with its own syscalls, so they
410/// trust those syscalls' results, as every Detcore handler that reads back a
411/// result does. They catch a filter that makes a syscall fail, whether it is
412/// the guest's probe or Detcore's own read. A seccomp filter installed on
413/// Hermit itself can also make one of Detcore's own syscalls report success
414/// without running it, and that is outside this guarantee. A guest cannot
415/// install a filter: Detcore refuses `seccomp(2)` and `PR_SET_SECCOMP`.
416///
417/// The repair never writes a word the kernel could not store, and it does not
418/// test writability by rewriting a word, because a remote write ignores
419/// protection keys that deny the guest's own stores. The pre-call snapshot is
420/// a read only. The host seconds written by a successful
421/// probe exist transiently until the overwrite. Default thread
422/// sequentialization prevents another guest thread from observing that
423/// interval, but another process sharing the page can observe it; such
424/// external shared state is outside Hermit's determinism guarantee.
425/// Standard record/replay disables time virtualization, so this handler is not
426/// used there. A custom configuration that combines replay data with virtual
427/// time fails closed before probing: a recorded EFAULT does not establish that
428/// this execution performed any stores for a live `time(2)` to reproduce.
429/// KVM does not call this repair: its executor implements `gettimeofday`
430/// directly with an all-zero timeval, so it never stores host time, and it
431/// does not implement the injected `time(2)` probe. Its original EFAULT is
432/// therefore already safe and must be returned unchanged.
433async fn overwrite_failed_gettimeofday_tv<'a, G, T>(
434    guest: &mut G,
435    tv_addr: AddrMut<'a, Timeval>,
436    tv: &Timeval,
437    before: &TimevalWordSnapshot,
438) -> Result<(), Error>
439where
440    G: Guest<Detcore<T>>,
441    T: RecordOrReplay,
442{
443    let values = [tv.tv_sec as libc::time_t, tv.tv_usec as libc::time_t];
444    for (index, (field, offset)) in TIMEVAL_WORDS.into_iter().enumerate() {
445        let addr = timeval_word_addr(field, tv_addr, offset)?;
446        let probe = syscalls::Time::new().with_tloc(Some(addr));
447        match classify_time_store_probe(field, guest.inject(probe).await)? {
448            TimeStoreProbe::Stored => {
449                let bytes = values[index].to_ne_bytes();
450                let overwrite = guest
451                    .memory()
452                    .write_with_user_access(addr.cast::<u8>(), &bytes);
453                require_complete_time_word_overwrite(field, bytes.len(), overwrite)?;
454            }
455            TimeStoreProbe::Stopped => {
456                let control = syscalls::Time::new().with_tloc(None);
457                require_native_time_control_probe(field, guest.inject(control).await)?;
458                for (stopped, (field, offset)) in TIMEVAL_WORDS.into_iter().enumerate().skip(index)
459                {
460                    let addr = timeval_word_addr(field, tv_addr, offset)?;
461                    let after = guest.memory().read_value(addr);
462                    match require_unchanged_stopped_word(field, before[stopped], after)? {
463                        StoppedWord::Unchanged => {}
464                        StoppedWord::Unreadable => {
465                            let maps = procmaps::from_pid(guest.pid(), |_| true)
466                                .map(|maps| maps.into_iter().map(|map| map.address).collect());
467                            require_unmapped_unreadable_word(field, maps, addr.as_raw() as u64)?;
468                        }
469                    }
470                }
471                break;
472            }
473        }
474    }
475    Ok(())
476}
477
478fn remaining_sleep_duration(target: LogicalTime, now: LogicalTime) -> Duration {
479    if target > now {
480        target.duration_since(now)
481    } else {
482        Duration::ZERO
483    }
484}
485
486impl<T: RecordOrReplay> Detcore<T> {
487    /// Convenience function for constructing a sleep request with a nanosecond offset from "now".
488    pub async fn sleep_request<G: Guest<Self>>(guest: &mut G, ns_delta: Duration) -> Resources {
489        let base_time = thread_observe_time(guest).await;
490        let target_time = base_time + ns_delta;
491        let resource = ResourceID::SleepUntil(target_time);
492        guest.thread_state().mk_request(resource, Permission::W)
493    }
494
495    /// Convenience function for constructing a sleep request with a absolute nanosecond value from the realtime clock.
496    pub async fn sleep_request_abs<G: Guest<Self>>(guest: &mut G, time: LogicalTime) -> Resources {
497        // TODO T124594597 Record-replay case requires better handling of time
498        let resource = ResourceID::SleepUntil(time);
499        guest.thread_state().mk_request(resource, Permission::W)
500    }
501
502    /// Convenience function for constructing a thread yield request.
503    /// Implemented as a sleep ending at the epoch (in the past).
504    pub fn yield_request<G: Guest<Self>>(guest: &mut G) -> Resources {
505        let resource = ResourceID::SleepUntil(LogicalTime::from_nanos(0));
506        guest.thread_state().mk_request(resource, Permission::W)
507    }
508
509    /// Construct a request for a strong, one-turn scheduler yield.
510    pub fn sched_yield_request<G: Guest<Self>>(guest: &mut G) -> Resources {
511        guest
512            .thread_state()
513            .mk_request(ResourceID::SchedYield, Permission::W)
514    }
515
516    /// Construct a random PriorityChangePoint request using the local PRNG.
517    pub fn random_priority_changepoint_request<G: Guest<Self>>(
518        guest: &mut G,
519        change_time: LogicalTime,
520    ) -> Resources {
521        let entropy = guest.thread_state_mut().chaos_prng_next_u64("priority");
522        let new_priority = entropy_to_priority(entropy);
523        Self::priority_changepoint_request(guest, change_time, new_priority)
524    }
525
526    /// Construct a PriorityChangePoint request using the supplied time and priority.
527    pub fn priority_changepoint_request<G: Guest<Self>>(
528        guest: &mut G,
529        change_time: LogicalTime,
530        new_priority: Priority,
531    ) -> Resources {
532        // AUTONOMOUS-BOT-IMPLEMENTED
533        // TODO-HUMAN-REVIEW(PR-1151)
534        let epochs = guest.thread_state_mut().take_pending_chaos_epochs();
535        let rcbs = guest.thread_state().committed_clock_value;
536        let resource = ResourceID::PriorityChangePoint(new_priority, change_time, rcbs, epochs);
537        guest.thread_state().mk_request(resource, Permission::W)
538    }
539
540    /// gettimeofday
541    pub async fn handle_gettimeofday<G: Guest<Self>>(
542        &self,
543        guest: &mut G,
544        call: syscalls::Gettimeofday,
545    ) -> Result<i64, Error> {
546        let time_ns = guest_clock_time(guest).await;
547
548        let repair_on_efault = should_repair_failed_gettimeofday_tv(guest.config().backend_is_kvm);
549        // What Detcore could read in `tv` before the call; the repair uses it
550        // to confirm which words a failing call left alone.
551        let before = match call.tv() {
552            Some(tp) if repair_on_efault => Some(snapshot_timeval_words(guest, tp.into())),
553            _ => None,
554        };
555
556        // A call failing with EFAULT may still have stored host wall-clock time
557        // in `tv`, so keep its result until `tv` holds virtual time.
558        let result = self
559            .record_or_replay_preserving_tool_errors(guest, call)
560            .await;
561
562        let tv: Timeval = time_ns.into();
563
564        if let Some(tp) = call.tv() {
565            match (&result, &before) {
566                (Ok(_), _) => guest.memory().write_value(tp, &tv)?,
567                // Linux's gettimeofday fails only with EFAULT, which is taken
568                // to be its own even when a seccomp filter returned it without
569                // running the call. Any other error came from the backend, the
570                // tool, a seccomp filter or a replayed log, and says nothing
571                // about what reached `tv`, so memory is left alone.
572                (Err(Error::Errno(Errno::EFAULT)), Some(before)) => {
573                    require_live_time_store_probe(self.cfg.replay_data.is_some())?;
574                    overwrite_failed_gettimeofday_tv(guest, tp.into(), &tv, before).await?
575                }
576                (Err(_), _) => {}
577            }
578        }
579
580        result
581    }
582
583    /// time
584    pub async fn handle_time<G: Guest<Self>>(
585        &self,
586        guest: &mut G,
587        call: syscalls::Time,
588    ) -> Result<i64, Error> {
589        let time_ns = guest_clock_time(guest).await;
590        let secs = time_ns.as_secs() as i64;
591
592        if let Some(tloc) = call.tloc() {
593            let mut memory = guest.memory();
594            memory.write_value(tloc, &secs)?;
595        }
596
597        Ok(secs)
598    }
599
600    /// clock_gettime
601    pub async fn handle_clock_gettime<G: Guest<Self>>(
602        &self,
603        guest: &mut G,
604        call: syscalls::ClockGettime,
605    ) -> Result<i64, Error> {
606        let time_ns = guest_clock_time(guest).await;
607        trace!("Converting nanoseconds into clock_gettime: {}", time_ns);
608
609        let tp = call.tp().ok_or(Errno::EFAULT)?;
610
611        let t: Timespec = time_ns.into();
612
613        guest.memory().write_value(tp, &t)?;
614
615        Ok(0)
616    }
617
618    /// clock_gettime
619    pub async fn handle_clock_getres<G: Guest<Self>>(
620        &self,
621        guest: &mut G,
622        call: syscalls::ClockGetres,
623    ) -> Result<i64, Error> {
624        // A NULL `res` pointer is valid for clock_getres: the kernel validates
625        // the clockid and returns 0 without storing the resolution. GHC's RTS
626        // probes the per-thread CPU clock exactly this way
627        // (clock_getres(clockid, NULL)) in getCurrentThreadCPUTime, so
628        // returning EFAULT here spuriously aborts the guest. Only write the
629        // resolution when the caller supplied a destination.
630        if let Some(res) = call.res() {
631            // For now we report a constant clock res of 10ms:
632            let clock_res = 10;
633
634            let t = Timespec {
635                tv_sec: 0,
636                tv_nsec: 1000 * clock_res as i64,
637            };
638
639            guest.memory().write_value(res, &t)?;
640        }
641
642        Ok(0)
643    }
644
645    // AUTONOMOUS-BOT-IMPLEMENTED
646    // TODO-HUMAN-REVIEW(PR-857): Deterministic adjtimex query/refusal policy.
647    /// Report Hermit's virtual clock with a fixed unsynchronized discipline.
648    /// Adjustment modes are capability-gated host mutations and receive EPERM.
649    pub async fn handle_adjtimex<G: Guest<Self>>(
650        &self,
651        guest: &mut G,
652        call: syscalls::Adjtimex,
653    ) -> Result<i64, Error> {
654        self.write_deterministic_timex(guest, call.buf()).await
655    }
656
657    // AUTONOMOUS-BOT-IMPLEMENTED
658    // TODO-HUMAN-REVIEW(PR-857): Deterministic clock_adjtime query/refusal policy.
659    /// Apply the adjtimex policy to CLOCK_REALTIME. Linux does not permit NTP
660    /// adjustment of the other fixed clock IDs, so reject them with EOPNOTSUPP.
661    pub async fn handle_clock_adjtime<G: Guest<Self>>(
662        &self,
663        guest: &mut G,
664        call: syscalls::ClockAdjtime,
665    ) -> Result<i64, Error> {
666        if call.clockid() != ClockId::CLOCK_REALTIME {
667            return Err(Errno::EOPNOTSUPP.into());
668        }
669        self.write_deterministic_timex(guest, call.buf()).await
670    }
671
672    async fn write_deterministic_timex<G: Guest<Self>>(
673        &self,
674        guest: &mut G,
675        buf: Option<reverie::syscalls::AddrMut<'_, libc::timex>>,
676    ) -> Result<i64, Error> {
677        let buf = buf.ok_or(Errno::EFAULT)?;
678        let request: libc::timex = guest.memory().read_value(buf)?;
679        if !timex_mode_is_query(request.modes) {
680            return Err(Errno::EPERM.into());
681        }
682
683        let now: Timespec = thread_observe_time(guest).await.into();
684        guest.memory().write_value(buf, &deterministic_timex(now))?;
685        Ok(libc::TIME_ERROR as i64)
686    }
687
688    /// Helper function to wait a given period, which may either succeed or be interrupted by a signal.
689    /// Return 0 or EINTR respectively.
690    async fn wait_and_return<R: Guest<Self>>(
691        guest: &mut R,
692        request: Resources,
693        call: NanosleepFamily,
694    ) -> Result<i64, Error> {
695        let target_time = time_from_resources(&request).expect("a sleepuntil resource request");
696        match crate::tool_global::parked_wait_request(
697            guest,
698            request,
699            crate::scheduler::parked::ParkedWaitPolicy::NanosleepNoHandlerRestart {
700                absolute_deadline: target_time,
701            },
702        )
703        .await
704        {
705            ResumeStatus::Normal => Ok(0),
706            ResumeStatus::Signaled(_) => {
707                let now = thread_observe_time(guest).await;
708                let delta = remaining_sleep_duration(target_time, now);
709                // Linux never touches remain for TIMER_ABSTIME, even when
710                // a caught signal interrupts the absolute sleep.
711                let addr2 = if call.flags() & libc::TIMER_ABSTIME == 0 {
712                    call.rem()
713                } else {
714                    None
715                };
716                if let Some(addr2) = addr2 {
717                    info!(
718                        "[interrupted] sleep till (until {}), woke up {:?} early, writing into nanosleep rem argument.",
719                        target_time, delta
720                    );
721                    let t = Timespec {
722                        tv_sec: delta.as_secs() as i64,
723                        tv_nsec: delta.subsec_nanos() as i64,
724                    };
725                    guest.memory().write_value(addr2, &t)?;
726                } else {
727                    info!("[interrupted] nanosleep rem argument is null, not writing it.")
728                }
729                Err(reverie::Error::Errno(Errno::EINTR))
730            }
731        }
732    }
733
734    /// clock_nanosleep and nanosleep
735    pub async fn handle_nanosleep_family<R: Guest<Self>>(
736        &self,
737        guest: &mut R,
738        call: NanosleepFamily,
739    ) -> Result<i64, Error> {
740        if call.flags() > libc::TIMER_ABSTIME {
741            trace!("Unhandled clock_nanosleep flags, letting syscall through...");
742            return Ok(guest.inject(Syscall::from(call)).await?);
743        }
744
745        let addr = call.req().ok_or(Errno::EFAULT)?;
746        let t: Timespec = guest.memory().read_value(addr)?;
747
748        // Linux validates the requested interval BEFORE sleeping: nanosleep(2)
749        // and clock_nanosleep(2) both fail EINVAL when tv_nsec is outside
750        // [0, 999999999] or tv_sec is negative.
751        //
752        // Detcore skipped that check and fed the raw fields through `as u64`.
753        // `Timespec` stores both as i64, so `tv_sec = -1` wrapped to
754        // u64::MAX (~1.8e19 seconds) and became `SleepUntil(INDEFINITE)`: the
755        // only guest thread parked with no deadline, the run queue emptied, and
756        // `step2d_handle_empty_queue` deliberately never jumps the clock for an
757        // indefinite waiter (doing so would also wake a `pause(2)`). The
758        // container then died -- exit 1, "Sandbox container exited
759        // unexpectedly" -- where Linux returns an errno and keeps running.
760        //
761        // A past *absolute* deadline is NOT an error and must still return 0,
762        // so this rejects only malformed fields, never an early deadline.
763        if t.tv_sec < 0 || t.tv_nsec < 0 || t.tv_nsec > 999_999_999 {
764            return Err(Errno::EINVAL.into());
765        }
766
767        match call.flags() {
768            0 => {
769                if self.cfg.sequentialize_threads {
770                    let time = Duration::from_secs(t.tv_sec as u64)
771                        + Duration::from_nanos(t.tv_nsec as u64);
772                    let request = Self::sleep_request(guest, time).await;
773                    trace!(
774                        "nanosleep adding delta {:?} to yield request {:?}",
775                        time, &request
776                    );
777                    Self::wait_and_return(guest, request, call).await
778                } else {
779                    trace!("Not sequentializing threads, letting nanosleep through...");
780                    Ok(guest.inject(Syscall::from(call)).await?)
781                }
782            }
783            libc::TIMER_ABSTIME => {
784                let target_time = LogicalTime::from_secs(t.tv_sec as u64)
785                    + LogicalTime::from_nanos(t.tv_nsec as u64);
786                if self.cfg.sequentialize_threads {
787                    if self.cfg.virtualize_time {
788                        let request = Self::sleep_request_abs(guest, target_time).await;
789                        trace!(
790                            "nanosleep setting absolute time {:?} to yield request {:?}",
791                            target_time, &request
792                        );
793                        Self::wait_and_return(guest, request, call).await
794                    } else {
795                        // TODO T124594597: Record-replay case here, need better ideas to enable proper handling of this case.
796                        error!(
797                            "Sequentializing but not virtualizing, so can't rely on passed abs time, especially when replaying a recording, just yelding"
798                        );
799                        let request = Self::yield_request(guest);
800                        Self::wait_and_return(guest, request, call).await
801                    }
802                } else if self.cfg.virtualize_time {
803                    trace!(
804                        "Not sequentializing, but virtualizing so calculating relative time and invoking nanosleep..."
805                    );
806                    let relative_ts = Self::relative_time_from_abs_target(guest, target_time).await;
807                    let mut stack = guest.stack().await;
808                    let req = stack.push(relative_ts);
809                    stack.commit()?;
810                    let modified_call = syscalls::Nanosleep::new().with_req(Some(req));
811                    Ok(guest.inject(modified_call).await?)
812                } else {
813                    trace!(
814                        "Not sequentializing threads not virtualizing, letting nanosleep through..."
815                    );
816                    Ok(guest.inject(Syscall::from(call)).await?)
817                }
818            }
819            _ => unreachable!("Unexpected, unhandled flag value"),
820        }
821    }
822
823    async fn relative_time_from_abs_target<G: Guest<Self>>(
824        guest: &mut G,
825        target_time: LogicalTime,
826    ) -> Timespec {
827        let base_time = thread_observe_time(guest).await;
828
829        // An absolute deadline already in the past is NOT an error on Linux --
830        // clock_nanosleep(TIMER_ABSTIME) simply returns 0 without sleeping.
831        // `LogicalTime`'s `Sub` is a plain subtraction (unlike its `Add` impls,
832        // which saturate deliberately), so `target_time - base_time` underflows
833        // for a past deadline: a debug build panics with "attempt to subtract
834        // with overflow", and a release build wraps to an enormous interval --
835        // the same effectively-indefinite sleep this handler exists to avoid.
836        //
837        // Clamped here rather than by making the shared operator saturate,
838        // because this is the only subtraction of two `LogicalTime`s in the
839        // tree and a silently-saturating operator could hide a real underflow
840        // in some future caller.
841        let relative_logical = if target_time <= base_time {
842            LogicalTime::from_nanos(0)
843        } else {
844            target_time - base_time
845        };
846
847        Timespec {
848            tv_sec: relative_logical.as_secs() as i64,
849            tv_nsec: relative_logical.subsec_nanos() as i64,
850        }
851    }
852
853    // AUTONOMOUS-BOT-IMPLEMENTED
854    // TODO-HUMAN-REVIEW(#869)
855    /// timer_create: allocate a per-process POSIX timer and hand back a
856    /// deterministic id, retaining any scheduler-deliverable signal.
857    pub async fn handle_timer_create<G: Guest<Self>>(
858        &self,
859        guest: &mut G,
860        call: syscalls::TimerCreate,
861    ) -> Result<i64, Error> {
862        // The kernel writes the new timer id here; a null pointer is EFAULT.
863        let timerid_ptr = call.timerid().ok_or(Errno::EFAULT)?;
864        let clockid = call.clockid();
865        let signal = if let Some(event_ptr) = call.sevp() {
866            let event: libc::sigevent = guest.memory().read_value(event_ptr)?;
867            match event.sigev_notify {
868                libc::SIGEV_NONE => None,
869                // Linux uses 4 for SIGEV_THREAD_ID. Treat it as process-directed
870                // until Detcore tracks per-timer thread targeting.
871                libc::SIGEV_SIGNAL | 4 => {
872                    if !(1..=64).contains(&event.sigev_signo) {
873                        return Err(Errno::EINVAL.into());
874                    }
875                    Signal::try_from(event.sigev_signo).ok()
876                }
877                _ => return Err(Errno::ENOSYS.into()),
878            }
879        } else {
880            Some(Signal::SIGALRM)
881        };
882        let id = {
883            let mut timers = guest.thread_state().posix_timers.lock().unwrap();
884            timers.create(signal.map(|sig| sig as i32))
885        };
886        guest
887            .memory()
888            .write_value(timerid_ptr, &(id as libc::c_int))?;
889        detlog!(
890            "[dtid {}] timer_create(clockid={:?}) => deterministic timer id {}, signal {:?}",
891            guest.thread_state().dettid,
892            clockid,
893            id,
894            signal,
895        );
896        Ok(0)
897    }
898
899    // AUTONOMOUS-BOT-IMPLEMENTED
900    // TODO-HUMAN-REVIEW(#869)
901    /// timer_settime: arm or disarm a timer against the deterministic virtual
902    /// clock. The old arming is reported through `old_value` when requested.
903    pub async fn handle_timer_settime<G: Guest<Self>>(
904        &self,
905        guest: &mut G,
906        call: syscalls::TimerSettime,
907    ) -> Result<i64, Error> {
908        let id = call.timerid();
909        let new_ptr = call.new_value().ok_or(Errno::EINVAL)?;
910        let new: libc::itimerspec = guest.memory().read_value(new_ptr)?;
911        let interval_ns = timespec_to_ns(new.it_interval);
912        let value_ns = timespec_to_ns(new.it_value);
913
914        let now = thread_observe_time(guest).await;
915        let deadline = if value_ns == 0 {
916            None
917        } else if call.flags() & libc::TIMER_ABSTIME != 0 {
918            // Absolute expiration is interpreted against the same virtual clock.
919            Some(LogicalTime::from_nanos(value_ns))
920        } else {
921            Some(now + Duration::from_nanos(value_ns))
922        };
923
924        let (old, signal_number) = {
925            let mut timers = guest.thread_state().posix_timers.lock().unwrap();
926            let old = timers.settime(id, interval_ns, deadline, now);
927            let signal = timers.signal(id);
928            (old, signal)
929        };
930        let (old_remaining_ns, old_interval_ns) = old.ok_or(Errno::EINVAL)?;
931        let signal_number = signal_number.ok_or(Errno::EINVAL)?;
932
933        if let Some(old_ptr) = call.old_value() {
934            let old_spec = libc::itimerspec {
935                it_interval: ns_to_timespec(old_interval_ns),
936                it_value: ns_to_timespec(old_remaining_ns),
937            };
938            guest.memory().write_value(old_ptr, &old_spec)?;
939        }
940
941        if let Some(signal) = signal_number.and_then(|signum| Signal::try_from(signum).ok()) {
942            register_posix_timer(
943                guest,
944                id,
945                deadline,
946                LogicalTime::from_nanos(interval_ns),
947                signal,
948            )
949            .await;
950        }
951
952        detlog!(
953            "[dtid {}] timer_settime(id={}, interval_ns={}, value_ns={}) armed against virtual clock",
954            guest.thread_state().dettid,
955            id,
956            interval_ns,
957            value_ns,
958        );
959        Ok(0)
960    }
961
962    /// timer_gettime: report the time remaining until the next expiration and
963    /// the reload interval, both computed from the virtual clock.
964    pub async fn handle_timer_gettime<G: Guest<Self>>(
965        &self,
966        guest: &mut G,
967        call: syscalls::TimerGettime,
968    ) -> Result<i64, Error> {
969        let id = call.timerid();
970        let value_ptr = call.value().ok_or(Errno::EFAULT)?;
971        let now = thread_observe_time(guest).await;
972        let cur = {
973            let timers = guest.thread_state().posix_timers.lock().unwrap();
974            timers.gettime(id, now)
975        };
976        let (remaining_ns, interval_ns) = cur.ok_or(Errno::EINVAL)?;
977        let spec = libc::itimerspec {
978            it_interval: ns_to_timespec(interval_ns),
979            it_value: ns_to_timespec(remaining_ns),
980        };
981        guest.memory().write_value(value_ptr, &spec)?;
982        Ok(0)
983    }
984
985    /// timer_getoverrun: coalesced expiration accounting is not modeled, so the
986    /// overrun count is always 0 for a live timer.
987    pub async fn handle_timer_getoverrun<G: Guest<Self>>(
988        &self,
989        guest: &mut G,
990        call: syscalls::TimerGetoverrun,
991    ) -> Result<i64, Error> {
992        let id = call.timerid();
993        let exists = guest
994            .thread_state()
995            .posix_timers
996            .lock()
997            .unwrap()
998            .contains(id);
999        if exists {
1000            Ok(0)
1001        } else {
1002            Err(Errno::EINVAL.into())
1003        }
1004    }
1005
1006    // AUTONOMOUS-BOT-IMPLEMENTED
1007    // TODO-HUMAN-REVIEW(#869)
1008    /// timer_delete: destroy a timer created by `timer_create`.
1009    pub async fn handle_timer_delete<G: Guest<Self>>(
1010        &self,
1011        guest: &mut G,
1012        call: syscalls::TimerDelete,
1013    ) -> Result<i64, Error> {
1014        let id = call.timerid();
1015        let signal_number = guest
1016            .thread_state()
1017            .posix_timers
1018            .lock()
1019            .unwrap()
1020            .signal(id)
1021            .ok_or(Errno::EINVAL)?;
1022        let existed = {
1023            let mut timers = guest.thread_state().posix_timers.lock().unwrap();
1024            timers.remove(id)
1025        };
1026        if existed {
1027            if let Some(signal) = signal_number.and_then(|signum| Signal::try_from(signum).ok()) {
1028                register_posix_timer(guest, id, None, LogicalTime::ZERO, signal).await;
1029            }
1030            detlog!(
1031                "[dtid {}] timer_delete(id={})",
1032                guest.thread_state().dettid,
1033                id,
1034            );
1035            Ok(0)
1036        } else {
1037            Err(Errno::EINVAL.into())
1038        }
1039    }
1040}
1041
1042#[cfg(test)]
1043mod tests {
1044    use super::*;
1045
1046    #[test]
1047    fn timex_policy_distinguishes_queries_from_mutations() {
1048        assert!(timex_mode_is_query(0));
1049        assert!(timex_mode_is_query(libc::ADJ_OFFSET_SS_READ));
1050        assert!(!timex_mode_is_query(libc::ADJ_OFFSET));
1051        assert!(!timex_mode_is_query(libc::ADJ_FREQUENCY));
1052    }
1053
1054    #[test]
1055    fn timex_snapshot_is_unsynchronized_and_uses_virtual_time() {
1056        let tx = deterministic_timex(Timespec {
1057            tv_sec: 123,
1058            tv_nsec: 456_789_000,
1059        });
1060        assert_eq!(tx.status, libc::STA_UNSYNC);
1061        assert_eq!(tx.tick, 10_000);
1062        assert_eq!(tx.time.tv_sec, 123);
1063        assert_eq!(tx.time.tv_usec, 456_789);
1064    }
1065
1066    #[test]
1067    fn interrupted_sleep_remaining_time_floors_at_zero() {
1068        let target = LogicalTime::from_nanos(1_000);
1069
1070        assert_eq!(
1071            remaining_sleep_duration(target, LogicalTime::from_nanos(750)),
1072            Duration::from_nanos(250)
1073        );
1074        assert_eq!(remaining_sleep_duration(target, target), Duration::ZERO);
1075        assert_eq!(
1076            remaining_sleep_duration(target, LogicalTime::from_nanos(1_250)),
1077            Duration::ZERO
1078        );
1079    }
1080
1081    mod failed_gettimeofday_tv {
1082        use super::*;
1083
1084        fn failure(error: Error) -> TvRepairFailure {
1085            match error {
1086                Error::Tool(error) => *error
1087                    .downcast_ref::<TvRepairFailure>()
1088                    .expect("a typed repair failure"),
1089                other => panic!("expected a Tool error, got {other:?}"),
1090            }
1091        }
1092
1093        #[test]
1094        fn successful_time_store_probe_requires_an_overwrite() {
1095            assert_eq!(
1096                classify_time_store_probe("tv_sec", Ok(1)).unwrap(),
1097                TimeStoreProbe::Stored
1098            );
1099        }
1100
1101        #[test]
1102        fn efault_time_store_probe_stops_without_an_overwrite() {
1103            assert_eq!(
1104                classify_time_store_probe("tv_usec", Err(Errno::EFAULT)).unwrap(),
1105                TimeStoreProbe::Stopped
1106            );
1107        }
1108
1109        #[test]
1110        fn any_control_probe_error_means_the_efault_was_not_a_store_fault() {
1111            require_native_time_control_probe("tv_sec", Ok(1_767_225_600)).unwrap();
1112            for errno in [Errno::EFAULT, Errno::EPERM, Errno::ENOSYS] {
1113                let error = require_native_time_control_probe("tv_usec", Err(errno))
1114                    .expect_err("time(NULL) cannot fail natively");
1115                assert_eq!(
1116                    failure(error),
1117                    TvRepairFailure {
1118                        field: "tv_usec",
1119                        kind: TvRepairFailureKind::ControlProbeFailed(errno),
1120                    }
1121                );
1122            }
1123        }
1124
1125        #[test]
1126        fn a_stopped_word_must_keep_its_contents_and_readability() {
1127            assert_eq!(
1128                require_unchanged_stopped_word("tv_sec", Ok(7), Ok(7)).unwrap(),
1129                StoppedWord::Unchanged
1130            );
1131            for (before, after) in [(Errno::EFAULT, Errno::EFAULT), (Errno::EIO, Errno::EPERM)] {
1132                assert_eq!(
1133                    require_unchanged_stopped_word("tv_sec", Err(before), Err(after)).unwrap(),
1134                    StoppedWord::Unreadable
1135                );
1136            }
1137            for (before, after) in [
1138                (Ok(7), Ok(1_791_041_091)),
1139                (Ok(7), Err(Errno::EFAULT)),
1140                (Err(Errno::EFAULT), Ok(7)),
1141            ] {
1142                let error = require_unchanged_stopped_word("tv_usec", before, after)
1143                    .expect_err("a changed stopped word means the call stored it");
1144                assert_eq!(
1145                    failure(error),
1146                    TvRepairFailure {
1147                        field: "tv_usec",
1148                        kind: TvRepairFailureKind::StoppedWordChanged,
1149                    }
1150                );
1151            }
1152        }
1153
1154        #[test]
1155        fn an_unreadable_word_is_unstored_only_if_part_of_it_is_unmapped() {
1156            const PAGE: u64 = 0x1000;
1157            let two_pages = || Ok(vec![(3 * PAGE, 4 * PAGE), (2 * PAGE, 3 * PAGE)]);
1158            // Outside every mapping, and straddling either end of the mapped pages.
1159            for word in [1, 2 * PAGE - 4, 4 * PAGE - 4, 5 * PAGE, u64::MAX - 3] {
1160                require_unmapped_unreadable_word("tv_sec", two_pages(), word).unwrap();
1161            }
1162            // Inside one page, and across the boundary of two adjacent mappings.
1163            for word in [2 * PAGE, 3 * PAGE - 4, 4 * PAGE - 8] {
1164                let error = require_unmapped_unreadable_word("tv_usec", two_pages(), word)
1165                    .expect_err("a failed read of a mapped word is not a fault");
1166                assert_eq!(
1167                    failure(error),
1168                    TvRepairFailure {
1169                        field: "tv_usec",
1170                        kind: TvRepairFailureKind::StoppedWordUnreadable,
1171                    }
1172                );
1173            }
1174        }
1175
1176        #[test]
1177        fn an_unreadable_word_needs_a_readable_nonempty_map() {
1178            let unreadable = Err(Error::Errno(Errno::EPERM));
1179            for maps in [Ok(Vec::new()), unreadable] {
1180                let error = require_unmapped_unreadable_word("tv_sec", maps, 1)
1181                    .expect_err("without a map an unreadable word proves nothing");
1182                assert_eq!(
1183                    failure(error),
1184                    TvRepairFailure {
1185                        field: "tv_sec",
1186                        kind: TvRepairFailureKind::MapsUnavailable,
1187                    }
1188                );
1189            }
1190        }
1191
1192        #[test]
1193        fn failed_gettimeofday_repair_is_skipped_only_for_kvm() {
1194            assert!(should_repair_failed_gettimeofday_tv(false));
1195            assert!(!should_repair_failed_gettimeofday_tv(true));
1196        }
1197
1198        #[test]
1199        fn other_time_store_probe_errors_fail_closed() {
1200            for errno in [Errno::ENOSYS, Errno::EPERM, Errno::EIO] {
1201                let error = classify_time_store_probe("tv_sec", Err(errno))
1202                    .expect_err("a non-EFAULT probe error must fail the repair");
1203                assert_eq!(
1204                    failure(error),
1205                    TvRepairFailure {
1206                        field: "tv_sec",
1207                        kind: TvRepairFailureKind::ProbeFailed(errno),
1208                    }
1209                );
1210            }
1211        }
1212
1213        #[test]
1214        fn overwrite_errors_and_nonexact_counts_fail_closed() {
1215            assert_eq!(
1216                failure(
1217                    require_complete_time_word_overwrite("tv_usec", 8, Err(Errno::EFAULT),)
1218                        .unwrap_err()
1219                ),
1220                TvRepairFailure {
1221                    field: "tv_usec",
1222                    kind: TvRepairFailureKind::OverwriteFailed(Errno::EFAULT),
1223                }
1224            );
1225            for reported in [0, 3, 7, 9] {
1226                assert_eq!(
1227                    failure(
1228                        require_complete_time_word_overwrite("tv_sec", 8, Ok(reported))
1229                            .unwrap_err()
1230                    ),
1231                    TvRepairFailure {
1232                        field: "tv_sec",
1233                        kind: TvRepairFailureKind::OverwriteCount {
1234                            expected: 8,
1235                            reported,
1236                        },
1237                    }
1238                );
1239            }
1240            require_complete_time_word_overwrite("tv_sec", 8, Ok(8)).unwrap();
1241
1242            require_live_time_store_probe(false).unwrap();
1243            assert_eq!(
1244                failure(require_live_time_store_probe(true).unwrap_err()),
1245                TvRepairFailure {
1246                    field: "tv",
1247                    kind: TvRepairFailureKind::ReplayMode,
1248                }
1249            );
1250        }
1251
1252        #[test]
1253        fn timeval_word_addresses_follow_kernel_order_and_overflow_fails_closed() {
1254            assert_eq!(TIMEVAL_WORDS, [("tv_sec", 0), ("tv_usec", 8)]);
1255            let tv_addr = AddrMut::<Timeval>::from_raw(0x10_0000).unwrap();
1256            assert_eq!(
1257                timeval_word_addr("tv_sec", tv_addr, std::mem::offset_of!(Timeval, tv_sec))
1258                    .unwrap()
1259                    .as_raw(),
1260                tv_addr.as_raw()
1261            );
1262            assert_eq!(
1263                timeval_word_addr("tv_usec", tv_addr, std::mem::offset_of!(Timeval, tv_usec))
1264                    .unwrap()
1265                    .as_raw(),
1266                tv_addr.as_raw() + 8
1267            );
1268
1269            let overflowing = AddrMut::<Timeval>::from_raw(usize::MAX - 3).unwrap();
1270            let error = timeval_word_addr("tv_usec", overflowing, 8)
1271                .expect_err("overflowing field address must fail the repair");
1272            assert_eq!(
1273                failure(error),
1274                TvRepairFailure {
1275                    field: "tv_usec",
1276                    kind: TvRepairFailureKind::AddressOverflow,
1277                }
1278            );
1279        }
1280    }
1281}