1use crate::{Clock, LeapIndicator, TimeOffset, Timestamp};
2use std::time::Duration;
3#[cfg(target_os = "linux")]
4use std::{
5 os::unix::io::{IntoRawFd, RawFd},
6 path::Path,
7};
8
9#[derive(Debug, Clone, Copy)]
11pub struct UnixClock {
12 clock: libc::clockid_t,
13 #[cfg(target_os = "linux")]
14 fd: Option<RawFd>,
15}
16
17impl UnixClock {
18 pub const CLOCK_REALTIME: Self = UnixClock {
33 clock: libc::CLOCK_REALTIME,
34 #[cfg(target_os = "linux")]
35 fd: None,
36 };
37
38 #[cfg(target_os = "linux")]
53 pub const CLOCK_TAI: Self = UnixClock {
54 clock: libc::CLOCK_TAI,
55 fd: None,
56 };
57
58 #[cfg(target_os = "linux")]
73 pub fn open(path: impl AsRef<Path>) -> std::io::Result<Self> {
74 let file = std::fs::OpenOptions::new()
75 .write(true)
76 .read(true)
77 .open(path)?;
78
79 Ok(Self::safe_from_raw_fd(file.into_raw_fd()))
81 }
82
83 #[cfg(target_os = "linux")]
87 fn safe_from_raw_fd(fd: RawFd) -> Self {
88 let clock = ((!(fd as libc::clockid_t)) << 3) | 3;
89
90 Self {
91 clock,
92 fd: Some(fd),
93 }
94 }
95
96 #[cfg(target_os = "linux")]
100 pub fn system_offset(&self) -> Result<(Timestamp, Timestamp, Timestamp), Error> {
101 let Some(fd) = self.fd else {
102 return Err(Error::Invalid);
103 };
104
105 #[repr(C)]
107 #[derive(Default, Clone, Copy, PartialEq, Eq)]
108 #[allow(non_camel_case_types)]
109 struct ptp_clock_time {
110 sec: libc::__s64,
111 nsec: libc::__u32,
112 reserved: libc::__u32,
113 }
114
115 #[repr(C)]
116 #[derive(Clone, Copy, PartialEq, Eq)]
117 #[allow(non_camel_case_types)]
118 struct ptp_sys_offset {
119 n_samples: libc::c_uint,
120 rsv: [libc::c_uint; 3],
121 ts: [ptp_clock_time; 51],
122 }
123
124 impl Default for ptp_sys_offset {
126 fn default() -> Self {
127 Self {
128 n_samples: Default::default(),
129 rsv: Default::default(),
130 ts: [Default::default(); 51],
131 }
132 }
133 }
134
135 const PTP_SYS_OFFSET: libc::c_uint = 0x43403d05;
136
137 let mut offset = ptp_sys_offset {
138 n_samples: 1,
139 ..Default::default()
140 };
141
142 if unsafe { libc::ioctl(fd, PTP_SYS_OFFSET as _, &mut offset as *mut ptp_sys_offset) } != 0
146 {
147 let t1 = Self::CLOCK_TAI.now();
148 let tp = self.now();
149 let t2 = Self::CLOCK_TAI.now();
150
151 Ok((t1?, tp?, t2?))
152 } else {
153 let tai_offset = Self::CLOCK_TAI.get_tai()?;
154
155 Ok((
156 Timestamp {
157 seconds: (offset.ts[0].sec + tai_offset as i64) as _,
158 nanos: offset.ts[0].nsec as _,
159 },
160 Timestamp {
161 seconds: offset.ts[1].sec as _,
162 nanos: offset.ts[1].nsec as _,
163 },
164 Timestamp {
165 seconds: (offset.ts[2].sec + tai_offset as i64) as _,
166 nanos: offset.ts[2].nsec as _,
167 },
168 ))
169 }
170 }
171
172 fn clock_adjtime(&self, timex: &mut libc::timex) -> Result<(), Error> {
173 #[cfg(target_os = "linux")]
184 use libc::clock_adjtime as adjtime;
185
186 #[cfg(any(target_os = "freebsd", target_os = "macos"))]
187 unsafe fn adjtime(clk_id: libc::clockid_t, buf: *mut libc::timex) -> libc::c_int {
188 assert_eq!(
189 clk_id,
190 libc::CLOCK_REALTIME,
191 "only the REALTIME clock is supported"
192 );
193
194 libc::ntp_adjtime(buf)
195 }
196
197 if unsafe { adjtime(self.clock, timex) } == -1 {
198 Err(convert_errno())
199 } else {
200 Ok(())
201 }
202 }
203
204 fn ntp_adjtime(timex: &mut libc::timex) -> Result<(), Error> {
205 #[cfg(any(target_os = "freebsd", target_os = "macos", target_env = "gnu"))]
206 use libc::ntp_adjtime as adjtime;
207
208 #[cfg(all(target_os = "linux", target_env = "musl"))]
212 use libc::adjtimex as adjtime;
213
214 if unsafe { adjtime(timex) } == -1 {
219 Err(convert_errno())
220 } else {
221 Ok(())
222 }
223 }
224
225 fn adjtime(&self, timex: &mut libc::timex) -> Result<(), Error> {
233 if self.clock == libc::CLOCK_REALTIME {
234 Self::ntp_adjtime(timex)
235 } else {
236 self.clock_adjtime(timex)
237 }
238 }
239
240 #[cfg_attr(target_os = "linux", allow(unused))]
241 fn clock_gettime(&self) -> Result<libc::timespec, Error> {
242 let mut timespec = EMPTY_TIMESPEC;
243
244 cerr(unsafe { libc::clock_gettime(self.clock, &mut timespec) })?;
251
252 Ok(timespec)
253 }
254
255 #[cfg_attr(target_os = "linux", allow(unused))]
256 fn clock_settime(&self, mut timespec: libc::timespec) -> Result<(), Error> {
257 while timespec.tv_nsec > 1_000_000_000 {
258 timespec.tv_sec += 1;
259 timespec.tv_nsec -= 1_000_000_000;
260 }
261
262 unsafe { cerr(libc::clock_settime(self.clock, ×pec))? };
269
270 Ok(())
271 }
272
273 #[cfg_attr(target_os = "linux", allow(unused))]
274 fn step_clock_by_timespec(&self, offset: TimeOffset) -> Result<Timestamp, Error> {
275 let mut timespec = self.clock_gettime()?;
276
277 #[cfg_attr(target_env = "musl", allow(deprecated))]
279 {
280 timespec.tv_sec += offset.seconds as libc::time_t;
281 timespec.tv_nsec += offset.nanos as libc::c_long;
282 }
283
284 self.clock_settime(timespec)?;
285
286 Ok(current_time_timespec(timespec, Precision::Nano))
287 }
288
289 fn error_estimate_timex(est_error: Duration, max_error: Duration) -> libc::timex {
290 let modes = libc::MOD_ESTERROR | libc::MOD_MAXERROR;
291
292 let esterror = est_error.as_nanos() as libc::c_long / 1000;
294 let maxerror = max_error.as_nanos() as libc::c_long / 1000;
295
296 libc::timex {
297 modes,
298 esterror,
299 maxerror,
300 ..EMPTY_TIMEX
301 }
302 }
303
304 #[cfg(target_os = "linux")]
305 fn step_clock_timex(offset: TimeOffset) -> libc::timex {
306 let modes = libc::ADJ_SETOFFSET | libc::ADJ_NANO;
308
309 let time = libc::timeval {
310 tv_sec: offset.seconds,
311 tv_usec: offset.nanos as libc::suseconds_t,
312 };
313
314 libc::timex {
315 modes,
316 time,
317 ..EMPTY_TIMEX
318 }
319 }
320
321 #[cfg(target_os = "linux")]
322 fn step_clock_by_timex(&self, offset: TimeOffset) -> Result<Timestamp, Error> {
323 let mut timex = Self::step_clock_timex(offset);
324 self.adjtime(&mut timex)?;
325 self.extract_current_time(&timex)
326 }
327
328 fn extract_current_time(&self, _timex: &libc::timex) -> Result<Timestamp, Error> {
329 #[cfg(target_os = "linux")]
330 if _timex.time.tv_sec != 0 && _timex.time.tv_usec != 0 {
332 let precision = match _timex.status & libc::STA_NANO {
334 0 => Precision::Micro,
335 _ => Precision::Nano,
336 };
337
338 return Ok(current_time_timeval(_timex.time, precision));
339 }
340
341 let timespec = self.clock_gettime()?;
343 Ok(current_time_timespec(timespec, Precision::Nano))
344 }
345
346 #[inline(always)]
347 fn update_timex<F>(&self, f: F) -> Result<(), Error>
348 where
349 F: FnOnce(libc::timex) -> libc::timex,
350 {
351 let mut timex = EMPTY_TIMEX;
352 self.adjtime(&mut timex)?;
353
354 timex = f(timex);
355
356 self.adjtime(&mut timex)
357 }
358
359 #[inline(always)]
360 fn update_status<F>(&self, f: F) -> Result<(), Error>
361 where
362 F: FnOnce(libc::c_int) -> libc::c_int,
363 {
364 self.update_timex(|mut timex| {
365 timex.modes = libc::MOD_STATUS;
367
368 timex.status = f(timex.status);
370
371 timex
372 })
373 }
374
375 fn set_frequency_timex(ppm: f64) -> libc::timex {
376 let mut timex = EMPTY_TIMEX;
378
379 timex.modes = libc::MOD_FREQUENCY;
381
382 let frequency = (ppm * 65536.0).round() as libc::c_long;
385
386 timex.freq = frequency.clamp(-32_768_000 + 1, 32_768_000 - 1);
390
391 timex
392 }
393}
394
395impl Clock for UnixClock {
396 type Error = Error;
397
398 fn now(&self) -> Result<Timestamp, Self::Error> {
399 let mut ntp_kapi_timex = EMPTY_TIMEX;
400
401 if self.adjtime(&mut ntp_kapi_timex).is_ok() {
402 self.extract_current_time(&ntp_kapi_timex)
403 } else {
404 self.clock_gettime()
405 .map(|ts| current_time_timespec(ts, Precision::Nano))
406 }
407 }
408
409 fn resolution(&self) -> Result<Timestamp, Self::Error> {
410 let mut timespec = EMPTY_TIMESPEC;
411
412 cerr(unsafe { libc::clock_getres(self.clock, &mut timespec) })?;
413
414 Ok(current_time_timespec(timespec, Precision::Nano))
415 }
416
417 fn get_frequency(&self) -> Result<f64, Self::Error> {
418 let mut timex = EMPTY_TIMEX;
419 self.adjtime(&mut timex)?;
420
421 Ok((timex.freq as f64) / 65536.0)
422 }
423
424 fn set_frequency(&self, frequency: f64) -> Result<Timestamp, Self::Error> {
425 let mut timex = Self::set_frequency_timex(frequency);
426 self.adjtime(&mut timex)?;
427 self.extract_current_time(&timex)
428 }
429
430 #[cfg(target_os = "linux")]
431 fn step_clock(&self, offset: TimeOffset) -> Result<Timestamp, Self::Error> {
432 self.step_clock_by_timex(offset)
433 }
434
435 #[cfg(any(target_os = "freebsd", target_os = "macos"))]
436 fn step_clock(&self, offset: TimeOffset) -> Result<Timestamp, Self::Error> {
437 self.step_clock_by_timespec(offset)
438 }
439
440 fn set_leap_seconds(&self, leap_status: LeapIndicator) -> Result<(), Self::Error> {
441 self.update_status(|status| {
442 (status & !(libc::STA_UNSYNC | libc::STA_INS | libc::STA_DEL))
443 | leap_status.as_status_bit()
444 })
445 }
446
447 fn error_estimate_update(
448 &self,
449 est_error: Duration,
450 max_error: Duration,
451 ) -> Result<(), Self::Error> {
452 let mut timex = Self::error_estimate_timex(est_error, max_error);
453 Error::ignore_not_supported(self.adjtime(&mut timex))
454 }
455
456 fn disable_kernel_ntp_algorithm(&self) -> Result<(), Self::Error> {
457 let mut timex = EMPTY_TIMEX;
458 self.adjtime(&mut timex)?;
459
460 timex.modes = libc::MOD_STATUS;
462
463 timex.status &= !(libc::STA_PLL | libc::STA_FLL | libc::STA_PPSTIME | libc::STA_PPSFREQ);
465
466 Error::ignore_not_supported(self.adjtime(&mut timex))
468 }
469
470 #[cfg(target_os = "linux")]
471 fn set_tai(&self, tai_offset: i32) -> Result<(), Error> {
472 let mut timex = libc::timex {
473 modes: libc::ADJ_TAI,
474 constant: tai_offset as _,
475 ..EMPTY_TIMEX
476 };
477
478 self.clock_adjtime(&mut timex)
479 }
480
481 #[cfg(not(target_os = "linux"))]
482 fn set_tai(&self, _tai_offset: i32) -> Result<(), Error> {
483 Err(Error::NotSupported)
484 }
485
486 #[cfg(target_os = "linux")]
487 fn get_tai(&self) -> Result<i32, Error> {
488 let mut timex = EMPTY_TIMEX;
489 if self.clock_adjtime(&mut timex).is_ok() {
490 Ok(timex.tai)
491 } else {
492 Ok(0)
494 }
495 }
496
497 #[cfg(not(target_os = "linux"))]
498 fn get_tai(&self) -> Result<i32, Error> {
499 Err(Error::NotSupported)
500 }
501}
502
503#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
505pub enum Error {
506 NoPermission,
508 NoAccess,
510 Invalid,
512 NoDevice,
514 NotSupported,
516}
517
518impl core::fmt::Display for Error {
519 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
520 use Error::*;
521
522 let msg = match self {
523 NoPermission => "Insufficient permissions to interact with the clock.",
524 NoAccess => "No access to the clock.",
525 Invalid => "Invalid operation requested",
526 NoDevice => "Clock device has gone away",
527 NotSupported => "Clock operation requested is not supported by operating system.",
528 };
529
530 f.write_str(msg)
531 }
532}
533
534impl std::error::Error for Error {}
535
536impl Error {
537 pub fn ignore_not_supported(res: Result<(), Error>) -> Result<(), Error> {
541 match res {
542 Err(Error::NotSupported) => Ok(()),
543 other => other,
544 }
545 }
546
547 fn into_raw_os_error(self) -> i32 {
549 match self {
550 Self::NoPermission => libc::EPERM,
551 Self::NoAccess => libc::EACCES,
552 Self::Invalid => libc::EINVAL,
553 Self::NoDevice => libc::ENODEV,
554 Self::NotSupported => libc::EOPNOTSUPP,
555 }
556 }
557}
558
559impl From<Error> for std::io::Error {
560 fn from(value: Error) -> Self {
561 std::io::Error::from_raw_os_error(value.into_raw_os_error())
562 }
563}
564
565fn error_number() -> libc::c_int {
566 #[cfg(target_os = "linux")]
567 unsafe {
568 *libc::__errno_location()
569 }
570
571 #[cfg(not(target_os = "linux"))]
572 unsafe {
573 *libc::__error()
574 }
575}
576
577fn convert_errno() -> Error {
582 match error_number() {
583 libc::EINVAL => Error::Invalid,
584 libc::ENODEV => Error::NoDevice,
587 libc::EOPNOTSUPP => Error::NotSupported,
588 libc::EPERM => Error::NoPermission,
589 libc::EACCES => Error::NoAccess,
590 libc::EFAULT => unreachable!("we always pass in valid (accessible) buffers"),
591 other => {
593 let error = std::io::Error::from_raw_os_error(other);
594 unreachable!("error code `{other}` ({error:?}) should not occur")
595 }
596 }
597}
598
599fn cerr(c_int: libc::c_int) -> Result<(), Error> {
600 if c_int == -1 {
601 Err(convert_errno())
602 } else {
603 Ok(())
604 }
605}
606
607pub(crate) enum Precision {
608 Nano,
609 #[cfg_attr(any(target_os = "freebsd", target_os = "macos"), allow(unused))]
610 Micro,
611}
612
613#[cfg_attr(target_os = "linux", allow(unused))]
614fn current_time_timespec(timespec: libc::timespec, precision: Precision) -> Timestamp {
615 let mut seconds = timespec.tv_sec;
616
617 let nanos: i32 = timespec.tv_nsec as _;
618
619 let mut nanos = match precision {
620 Precision::Nano => nanos,
621 Precision::Micro => nanos.checked_mul(1000).unwrap_or_default(),
622 };
623
624 while nanos > 1_000_000_000 {
626 seconds = seconds.wrapping_add(1);
627 nanos -= 1_000_000_000;
628 }
629
630 while nanos < 0 {
632 seconds = seconds.wrapping_sub(1);
633 nanos += 1_000_000_000;
634 }
635
636 Timestamp {
637 seconds,
638 nanos: nanos as u32,
639 }
640}
641
642#[cfg_attr(not(target_os = "linux"), allow(unused))]
643fn current_time_timeval(timespec: libc::timeval, precision: Precision) -> Timestamp {
644 let seconds = timespec.tv_sec;
645 let nanos = match precision {
646 Precision::Nano => timespec.tv_usec as u32,
647 Precision::Micro => (timespec.tv_usec as u32)
648 .checked_mul(1000)
649 .unwrap_or_default(),
650 };
651
652 Timestamp { seconds, nanos }
653}
654
655const EMPTY_TIMESPEC: libc::timespec = libc::timespec {
656 tv_sec: 0,
657 tv_nsec: 0,
658};
659
660#[cfg(all(target_os = "linux", target_env = "gnu"))]
663pub const EMPTY_TIMEX: libc::timex = libc::timex {
664 modes: 0,
665 offset: 0,
666 freq: 0,
667 maxerror: 0,
668 esterror: 0,
669 status: 0,
670 constant: 0,
671 precision: 0,
672 tolerance: 0,
673 time: libc::timeval {
674 tv_sec: 0,
675 tv_usec: 0,
676 },
677 tick: 0,
678 ppsfreq: 0,
679 jitter: 0,
680 shift: 0,
681 stabil: 0,
682 jitcnt: 0,
683 calcnt: 0,
684 errcnt: 0,
685 stbcnt: 0,
686 tai: 0,
687 __unused1: 0,
688 __unused2: 0,
689 __unused3: 0,
690 __unused4: 0,
691 __unused5: 0,
692 __unused6: 0,
693 __unused7: 0,
694 __unused8: 0,
695 __unused9: 0,
696 __unused10: 0,
697 __unused11: 0,
698};
699
700#[cfg(all(target_os = "linux", target_env = "musl"))]
701pub const EMPTY_TIMEX: libc::timex = libc::timex {
702 modes: 0,
703 offset: 0,
704 freq: 0,
705 maxerror: 0,
706 esterror: 0,
707 status: 0,
708 constant: 0,
709 precision: 0,
710 tolerance: 0,
711 time: libc::timeval {
712 tv_sec: 0,
713 tv_usec: 0,
714 },
715 tick: 0,
716 ppsfreq: 0,
717 jitter: 0,
718 shift: 0,
719 stabil: 0,
720 jitcnt: 0,
721 calcnt: 0,
722 errcnt: 0,
723 stbcnt: 0,
724 tai: 0,
725 __padding: [0; 11],
726};
727
728#[cfg(any(target_os = "freebsd", target_os = "macos"))]
729pub const EMPTY_TIMEX: libc::timex = libc::timex {
730 modes: 0,
731 offset: 0,
732 freq: 0,
733 maxerror: 0,
734 esterror: 0,
735 status: 0,
736 constant: 0,
737 precision: 0,
738 tolerance: 0,
739 ppsfreq: 0,
740 jitter: 0,
741 shift: 0,
742 stabil: 0,
743 jitcnt: 0,
744 calcnt: 0,
745 errcnt: 0,
746 stbcnt: 0,
747};
748
749impl LeapIndicator {
750 fn as_status_bit(self) -> libc::c_int {
751 match self {
752 LeapIndicator::NoWarning => 0,
753 LeapIndicator::Leap61 => libc::STA_INS,
754 LeapIndicator::Leap59 => libc::STA_DEL,
755 LeapIndicator::Unknown => libc::STA_UNSYNC,
756 }
757 }
758}
759
760#[cfg(test)]
761mod tests {
762 use super::*;
763
764 #[test]
765 fn test_time_now_does_not_crash() {
766 let clock = UnixClock::CLOCK_REALTIME;
767 assert_ne!(clock.now().unwrap(), Timestamp::default(),);
768 }
769
770 #[test]
771 fn realtime_gettime() {
772 let clock = UnixClock::CLOCK_REALTIME;
773 let time = clock.clock_gettime().unwrap();
774
775 assert_ne!((time.tv_sec, time.tv_nsec), (0, 0))
776 }
777
778 #[test]
779 #[ignore = "requires permissions, useful for testing permissions"]
780 fn ptp0_gettime() {
781 let clock = UnixClock::CLOCK_REALTIME;
782 let time = clock.clock_gettime().unwrap();
783
784 assert_ne!((time.tv_sec, time.tv_nsec), (0, 0))
785 }
786
787 #[test]
788 #[ignore = "requires permissions, useful for testing permissions"]
789 fn step_clock() {
790 UnixClock::CLOCK_REALTIME
791 .step_clock(TimeOffset {
792 seconds: 0,
793 nanos: 0,
794 })
795 .unwrap();
796 }
797
798 #[cfg(target_os = "linux")]
799 #[test]
800 fn test_step_clock() {
801 let offset = TimeOffset {
802 seconds: 1,
803 nanos: 200000000,
804 };
805 let timex = UnixClock::step_clock_timex(offset);
806
807 assert_eq!(timex.modes, libc::ADJ_SETOFFSET | libc::ADJ_NANO);
808
809 assert_eq!(timex.time.tv_sec, 1);
810 assert_eq!(timex.time.tv_usec, 200_000_000);
811 }
812
813 #[test]
814 fn test_error_estimate() {
815 let est_error = Duration::from_secs_f64(0.5);
816 let max_error = Duration::from_secs_f64(1.2);
817 let timex = UnixClock::error_estimate_timex(est_error, max_error);
818
819 assert_eq!(timex.modes, libc::MOD_ESTERROR | libc::MOD_MAXERROR);
820
821 assert_eq!(timex.esterror, 500_000);
823 assert_eq!(timex.maxerror, 1_200_000);
824 }
825
826 #[test]
827 fn test_now() {
828 let resolution = UnixClock::CLOCK_REALTIME.now().unwrap();
829
830 assert_ne!(resolution, Timestamp::default());
831 }
832
833 #[test]
834 fn test_resolution() {
835 let resolution = UnixClock::CLOCK_REALTIME.resolution().unwrap();
836
837 assert_ne!(resolution, Timestamp::default());
838 }
839}