Skip to main content

microsandbox_agentd/
clock.rs

1//! Guest clock utilities for boot timing measurement.
2
3use std::io;
4
5//--------------------------------------------------------------------------------------------------
6// Constants
7//--------------------------------------------------------------------------------------------------
8
9const NANOS_PER_SECOND: u64 = 1_000_000_000;
10const CLOCK_SYNC_TOLERANCE_NANOS: u64 = 100 * 1_000_000;
11
12//--------------------------------------------------------------------------------------------------
13// Functions
14//--------------------------------------------------------------------------------------------------
15
16/// Returns the current `CLOCK_BOOTTIME` value in nanoseconds.
17///
18/// `CLOCK_BOOTTIME` counts from kernel boot and includes time spent in suspend,
19/// making it ideal for measuring total time since the VM kernel started.
20///
21/// # Panics
22///
23/// Panics if `clock_gettime` fails, which should never happen for `CLOCK_BOOTTIME`.
24pub fn boottime_ns() -> u64 {
25    let mut ts = libc::timespec {
26        tv_sec: 0,
27        tv_nsec: 0,
28    };
29    let ret = unsafe { libc::clock_gettime(libc::CLOCK_BOOTTIME, &mut ts) };
30    assert!(ret == 0, "clock_gettime(CLOCK_BOOTTIME) failed");
31    (ts.tv_sec as u64) * 1_000_000_000 + (ts.tv_nsec as u64)
32}
33
34/// Synchronizes `CLOCK_REALTIME` from a Unix timestamp in nanoseconds.
35///
36/// Used by the host runtime to correct the guest wall clock after suspend,
37/// resume, or other host-side pauses. Small deltas are ignored so normal
38/// message delivery latency does not cause tiny backwards or forwards clock
39/// steps.
40pub fn sync_realtime_unix_nanos(unix_time_nanos: u64) -> io::Result<()> {
41    let current = realtime_unix_nanos()?;
42    if !clock_delta_exceeds_tolerance(current, unix_time_nanos) {
43        return Ok(());
44    }
45
46    set_realtime_unix_nanos(unix_time_nanos)
47}
48
49fn realtime_unix_nanos() -> io::Result<u64> {
50    let mut ts = libc::timespec {
51        tv_sec: 0,
52        tv_nsec: 0,
53    };
54    let ret = unsafe { libc::clock_gettime(libc::CLOCK_REALTIME, &mut ts) };
55    if ret == 0 {
56        unix_nanos_from_timespec(ts)
57    } else {
58        Err(io::Error::last_os_error())
59    }
60}
61
62fn set_realtime_unix_nanos(unix_time_nanos: u64) -> io::Result<()> {
63    let ts = timespec_from_unix_nanos(unix_time_nanos)?;
64    let ret = unsafe { libc::clock_settime(libc::CLOCK_REALTIME, &ts) };
65    if ret == 0 {
66        Ok(())
67    } else {
68        Err(std::io::Error::last_os_error())
69    }
70}
71
72fn unix_nanos_from_timespec(ts: libc::timespec) -> io::Result<u64> {
73    let seconds = u64::try_from(ts.tv_sec)
74        .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "negative realtime seconds"))?;
75    let nanos = u64::try_from(ts.tv_nsec)
76        .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "negative realtime nanoseconds"))?;
77    seconds
78        .checked_mul(NANOS_PER_SECOND)
79        .and_then(|n| n.checked_add(nanos))
80        .ok_or_else(|| {
81            io::Error::new(
82                io::ErrorKind::InvalidData,
83                "realtime timestamp does not fit in u64 nanoseconds",
84            )
85        })
86}
87
88fn timespec_from_unix_nanos(unix_time_nanos: u64) -> io::Result<libc::timespec> {
89    let tv_sec = (unix_time_nanos / NANOS_PER_SECOND)
90        .try_into()
91        .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "realtime seconds overflow"))?;
92    let tv_nsec = (unix_time_nanos % NANOS_PER_SECOND)
93        .try_into()
94        .map_err(|_| {
95            io::Error::new(io::ErrorKind::InvalidInput, "realtime nanoseconds overflow")
96        })?;
97
98    Ok(libc::timespec { tv_sec, tv_nsec })
99}
100
101fn clock_delta_exceeds_tolerance(current_unix_nanos: u64, target_unix_nanos: u64) -> bool {
102    current_unix_nanos.abs_diff(target_unix_nanos) > CLOCK_SYNC_TOLERANCE_NANOS
103}
104
105//--------------------------------------------------------------------------------------------------
106// Tests
107//--------------------------------------------------------------------------------------------------
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112
113    #[test]
114    fn timespec_from_unix_nanos_splits_seconds_and_nanos() {
115        let ts = timespec_from_unix_nanos(1_700_000_000_123_456_789).unwrap();
116
117        assert_eq!(ts.tv_sec, 1_700_000_000);
118        assert_eq!(ts.tv_nsec, 123_456_789);
119    }
120
121    #[test]
122    fn unix_nanos_from_timespec_combines_seconds_and_nanos() {
123        let ts = libc::timespec {
124            tv_sec: 1_700_000_000,
125            tv_nsec: 123_456_789,
126        };
127
128        assert_eq!(
129            unix_nanos_from_timespec(ts).unwrap(),
130            1_700_000_000_123_456_789
131        );
132    }
133
134    #[test]
135    fn clock_delta_exceeds_tolerance_only_for_meaningful_drift() {
136        assert!(!clock_delta_exceeds_tolerance(
137            1_000_000_000,
138            1_000_000_000 + CLOCK_SYNC_TOLERANCE_NANOS
139        ));
140        assert!(clock_delta_exceeds_tolerance(
141            1_000_000_000,
142            1_000_000_001 + CLOCK_SYNC_TOLERANCE_NANOS
143        ));
144        assert!(clock_delta_exceeds_tolerance(
145            1_000_000_001 + CLOCK_SYNC_TOLERANCE_NANOS,
146            1_000_000_000
147        ));
148    }
149}