microsandbox_agentd/
clock.rs1use std::io;
4
5const NANOS_PER_SECOND: u64 = 1_000_000_000;
10const CLOCK_SYNC_TOLERANCE_NANOS: u64 = 100 * 1_000_000;
11
12pub 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
34pub 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#[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}