Skip to main content

rtime_clock/
unix.rs

1use rtime_core::clock::{Clock, ClockError};
2use rtime_core::timestamp::{NtpDuration, NtpTimestamp};
3
4use crate::adjtime::Timex;
5
6/// FreeBSD MOD_FREQUENCY constant for ntp_adjtime().
7#[cfg(target_os = "freebsd")]
8const MOD_FREQUENCY: u32 = 0x0002;
9
10/// Real system clock using POSIX clock_gettime(CLOCK_REALTIME).
11pub struct UnixClock {
12    adjustable: bool,
13}
14
15impl UnixClock {
16    /// Create a new UnixClock. Probes whether clock adjustment is available.
17    pub fn new() -> Self {
18        let adjustable = Self::probe_adjustable();
19        Self { adjustable }
20    }
21
22    fn read_clock() -> Result<nix::sys::time::TimeSpec, ClockError> {
23        nix::time::clock_gettime(nix::time::ClockId::CLOCK_REALTIME)
24            .map_err(|e| ClockError::Os(e.into()))
25    }
26
27    // --- probe_adjustable ---
28
29    #[cfg(target_os = "linux")]
30    fn probe_adjustable() -> bool {
31        let mut tx = Timex::new(); // modes = 0 => read-only query
32        crate::adjtime::adjtimex(&mut tx).is_ok()
33    }
34
35    #[cfg(target_os = "freebsd")]
36    fn probe_adjustable() -> bool {
37        let mut tx = Timex::new(); // modes = 0 => read-only query
38        crate::adjtime::ntp_adjtime(&mut tx).is_ok()
39    }
40
41    #[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
42    fn probe_adjustable() -> bool {
43        false
44    }
45
46    // --- step (platform-specific helpers) ---
47
48    #[cfg(target_os = "linux")]
49    fn step_impl(&self, offset: NtpDuration) -> Result<(), ClockError> {
50        let nanos = offset.to_nanos();
51        let mut tx = Timex::new();
52        tx.0.modes = libc::ADJ_SETOFFSET | libc::ADJ_NANO;
53        tx.0.time.tv_sec = nanos / 1_000_000_000;
54        tx.0.time.tv_usec = nanos % 1_000_000_000;
55
56        crate::adjtime::adjtimex(&mut tx).map_err(|err| {
57            if err.raw_os_error() == Some(libc::EPERM) {
58                ClockError::PermissionDenied
59            } else {
60                ClockError::Os(err)
61            }
62        })?;
63        Ok(())
64    }
65
66    #[cfg(target_os = "freebsd")]
67    fn step_impl(&self, offset: NtpDuration) -> Result<(), ClockError> {
68        use nix::sys::time::TimeSpec;
69        // FreeBSD lacks ADJ_SETOFFSET. Read current time, add offset, set.
70        let ts = nix::time::clock_gettime(nix::time::ClockId::CLOCK_REALTIME)
71            .map_err(|e| ClockError::Os(e.into()))?;
72
73        let nanos = offset.to_nanos();
74        let mut tv_sec = ts.tv_sec() + nanos / 1_000_000_000;
75        let mut tv_nsec = ts.tv_nsec() + (nanos % 1_000_000_000) as libc::c_long;
76
77        // Normalize tv_nsec into [0, 999_999_999]
78        while tv_nsec >= 1_000_000_000 {
79            tv_sec += 1;
80            tv_nsec -= 1_000_000_000;
81        }
82        while tv_nsec < 0 {
83            tv_sec -= 1;
84            tv_nsec += 1_000_000_000;
85        }
86
87        let new_ts = TimeSpec::new(tv_sec, tv_nsec);
88        nix::time::clock_settime(nix::time::ClockId::CLOCK_REALTIME, new_ts).map_err(|e| {
89            let err: std::io::Error = e.into();
90            if err.raw_os_error() == Some(libc::EPERM) {
91                ClockError::PermissionDenied
92            } else {
93                ClockError::Os(err)
94            }
95        })
96    }
97
98    #[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
99    fn step_impl(&self, _offset: NtpDuration) -> Result<(), ClockError> {
100        Err(ClockError::NotSupported)
101    }
102
103    // --- adjust_frequency (platform-specific helpers) ---
104
105    #[cfg(target_os = "linux")]
106    fn adjust_frequency_impl(&self, ppm: f64) -> Result<(), ClockError> {
107        // adjtimex freq is in units of 2^-16 ppm (scaled ppm)
108        let freq = (ppm * 65536.0) as i64;
109
110        let mut tx = Timex::new();
111        tx.0.modes = libc::ADJ_FREQUENCY;
112        tx.0.freq = freq;
113
114        crate::adjtime::adjtimex(&mut tx).map_err(|err| {
115            if err.raw_os_error() == Some(libc::EPERM) {
116                ClockError::PermissionDenied
117            } else {
118                ClockError::Os(err)
119            }
120        })?;
121        Ok(())
122    }
123
124    #[cfg(target_os = "freebsd")]
125    fn adjust_frequency_impl(&self, ppm: f64) -> Result<(), ClockError> {
126        let freq = (ppm * 65536.0) as i64;
127
128        let mut tx = Timex::new();
129        tx.0.modes = MOD_FREQUENCY as u32;
130        tx.0.freq = freq;
131
132        crate::adjtime::ntp_adjtime(&mut tx).map_err(|err| {
133            if err.raw_os_error() == Some(libc::EPERM) {
134                ClockError::PermissionDenied
135            } else {
136                ClockError::Os(err)
137            }
138        })?;
139        Ok(())
140    }
141
142    #[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
143    fn adjust_frequency_impl(&self, _ppm: f64) -> Result<(), ClockError> {
144        Err(ClockError::NotSupported)
145    }
146
147    // --- frequency_offset (platform-specific helpers) ---
148
149    #[cfg(target_os = "linux")]
150    fn frequency_offset_impl(&self) -> Result<f64, ClockError> {
151        let mut tx = Timex::new(); // modes = 0 => query
152        crate::adjtime::adjtimex(&mut tx).map_err(ClockError::Os)?;
153        Ok(tx.0.freq as f64 / 65536.0)
154    }
155
156    #[cfg(target_os = "freebsd")]
157    fn frequency_offset_impl(&self) -> Result<f64, ClockError> {
158        let mut tx = Timex::new(); // modes = 0 => query
159        crate::adjtime::ntp_adjtime(&mut tx).map_err(ClockError::Os)?;
160        Ok(tx.0.freq as f64 / 65536.0)
161    }
162
163    #[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
164    fn frequency_offset_impl(&self) -> Result<f64, ClockError> {
165        Err(ClockError::NotSupported)
166    }
167}
168
169impl Default for UnixClock {
170    fn default() -> Self {
171        Self::new()
172    }
173}
174
175impl Clock for UnixClock {
176    fn now(&self) -> Result<NtpTimestamp, ClockError> {
177        let ts = Self::read_clock()?;
178        let st = std::time::UNIX_EPOCH
179            + std::time::Duration::new(ts.tv_sec() as u64, ts.tv_nsec() as u32);
180        Ok(NtpTimestamp::from_system_time(st))
181    }
182
183    fn step(&self, offset: NtpDuration) -> Result<(), ClockError> {
184        if !self.adjustable {
185            return Err(ClockError::PermissionDenied);
186        }
187        self.step_impl(offset)
188    }
189
190    fn adjust_frequency(&self, ppm: f64) -> Result<(), ClockError> {
191        if !self.adjustable {
192            return Err(ClockError::PermissionDenied);
193        }
194        self.adjust_frequency_impl(ppm)
195    }
196
197    fn frequency_offset(&self) -> Result<f64, ClockError> {
198        self.frequency_offset_impl()
199    }
200
201    fn resolution(&self) -> NtpDuration {
202        // CLOCK_REALTIME typically has ~1ns resolution
203        NtpDuration::from_nanos(1)
204    }
205
206    fn max_frequency_adjustment(&self) -> f64 {
207        // Kernel limit: +/-500 ppm (both Linux and FreeBSD)
208        500.0
209    }
210
211    fn is_adjustable(&self) -> bool {
212        self.adjustable
213    }
214}