//! `Timespec` and related types, which are used by multiple public API //! modules. #![allow(dead_code)] use core::num::TryFromIntError; use core::ops::{Add, AddAssign, Neg, Sub, SubAssign}; use core::time::Duration; use crate::backend::c; #[allow(unused)] use crate::ffi; #[cfg(not(fix_y2038))] use core::ptr::null; /// `struct timespec`—A quantity of time in seconds plus nanoseconds. #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)] #[repr(C)] pub struct Timespec { /// Seconds. pub tv_sec: Secs, /// Nanoseconds. Must be less than 1_000_000_000. /// /// When passed to [`rustix::fs::utimensat`], this field may instead be /// assigned the values [`UTIME_NOW`] or [`UTIME_OMIT`]. /// /// [`UTIME_NOW`]: crate::fs::UTIME_NOW /// [`UTIME_OMIT`]: crate::fs::UTIME_OMIT /// [`rustix::fs::utimensat`]: crate::fs::utimensat pub tv_nsec: Nsecs, } /// A type for the `tv_sec` field of [`Timespec`]. pub type Secs = i64; /// A type for the `tv_nsec` field of [`Timespec`]. #[cfg(any( fix_y2038, linux_raw, all(libc, target_arch = "x86_64", target_pointer_width = "32") ))] pub type Nsecs = i64; /// A type for the `tv_nsec` field of [`Timespec`]. #[cfg(all( not(fix_y2038), libc, not(all(target_arch = "x86_64", target_pointer_width = "32")) ))] pub type Nsecs = ffi::c_long; impl Timespec { /// Checked `Timespec` addition. Returns `None` if overflow occurred. /// /// # Panics /// /// If `0 <= .tv_nsec < 1_000_000_000` doesn't hold, this function may /// panic or return unexpected results. /// /// # Example /// /// ``` /// use rustix::event::Timespec; /// /// assert_eq!( /// Timespec { /// tv_sec: 1, /// tv_nsec: 2 /// } /// .checked_add(Timespec { /// tv_sec: 30, /// tv_nsec: 40 /// }), /// Some(Timespec { /// tv_sec: 31, /// tv_nsec: 42 /// }) /// ); /// assert_eq!( /// Timespec { /// tv_sec: 0, /// tv_nsec: 999_999_999 /// } /// .checked_add(Timespec { /// tv_sec: 0, /// tv_nsec: 2 /// }), /// Some(Timespec { /// tv_sec: 1, /// tv_nsec: 1 /// }) /// ); /// assert_eq!( /// Timespec { /// tv_sec: i64::MAX, /// tv_nsec: 999_999_999 /// } /// .checked_add(Timespec { /// tv_sec: 0, /// tv_nsec: 1 /// }), /// None /// ); /// ``` pub const fn checked_add(self, rhs: Self) -> Option { if let Some(mut tv_sec) = self.tv_sec.checked_add(rhs.tv_sec) { let mut tv_nsec = self.tv_nsec + rhs.tv_nsec; if tv_nsec >= 1_000_000_000 { tv_nsec -= 1_000_000_000; if let Some(carried_sec) = tv_sec.checked_add(1) { tv_sec = carried_sec; } else { return None; } } Some(Self { tv_sec, tv_nsec }) } else { None } } /// Checked `Timespec` subtraction. Returns `None` if overflow occurred. /// /// # Panics /// /// If `0 <= .tv_nsec < 1_000_000_000` doesn't hold, this function may /// panic or return unexpected results. /// /// # Example /// /// ``` /// use rustix::event::Timespec; /// /// assert_eq!( /// Timespec { /// tv_sec: 31, /// tv_nsec: 42 /// } /// .checked_sub(Timespec { /// tv_sec: 30, /// tv_nsec: 40 /// }), /// Some(Timespec { /// tv_sec: 1, /// tv_nsec: 2 /// }) /// ); /// assert_eq!( /// Timespec { /// tv_sec: 1, /// tv_nsec: 1 /// } /// .checked_sub(Timespec { /// tv_sec: 0, /// tv_nsec: 2 /// }), /// Some(Timespec { /// tv_sec: 0, /// tv_nsec: 999_999_999 /// }) /// ); /// assert_eq!( /// Timespec { /// tv_sec: i64::MIN, /// tv_nsec: 0 /// } /// .checked_sub(Timespec { /// tv_sec: 0, /// tv_nsec: 1 /// }), /// None /// ); /// ``` pub const fn checked_sub(self, rhs: Self) -> Option { if let Some(mut tv_sec) = self.tv_sec.checked_sub(rhs.tv_sec) { let mut tv_nsec = self.tv_nsec - rhs.tv_nsec; if tv_nsec < 0 { tv_nsec += 1_000_000_000; if let Some(borrowed_sec) = tv_sec.checked_sub(1) { tv_sec = borrowed_sec; } else { return None; } } Some(Self { tv_sec, tv_nsec }) } else { None } } /// Convert from `Timespec` to `c::c_int` milliseconds, rounded up. pub(crate) fn as_c_int_millis(&self) -> Option { let secs = self.tv_sec; if secs < 0 { return None; } secs.checked_mul(1000) .and_then(|millis| { // Add the nanoseconds, converted to milliseconds, rounding up. // With Rust 1.73.0 this can use `div_ceil`. millis.checked_add((i64::from(self.tv_nsec) + 999_999) / 1_000_000) }) .and_then(|millis| c::c_int::try_from(millis).ok()) } } impl TryFrom for Duration { type Error = TryFromIntError; fn try_from(ts: Timespec) -> Result { Ok(Self::new(ts.tv_sec.try_into()?, ts.tv_nsec as _)) } } impl TryFrom for Timespec { type Error = TryFromIntError; fn try_from(dur: Duration) -> Result { Ok(Self { tv_sec: dur.as_secs().try_into()?, tv_nsec: dur.subsec_nanos() as _, }) } } impl Add for Timespec { type Output = Self; fn add(self, rhs: Self) -> Self { self.checked_add(rhs) .expect("overflow when adding timespecs") } } impl AddAssign for Timespec { fn add_assign(&mut self, rhs: Self) { *self = *self + rhs; } } impl Sub for Timespec { type Output = Self; fn sub(self, rhs: Self) -> Self { self.checked_sub(rhs) .expect("overflow when subtracting timespecs") } } impl SubAssign for Timespec { fn sub_assign(&mut self, rhs: Self) { *self = *self - rhs; } } impl Neg for Timespec { type Output = Self; fn neg(self) -> Self { Self::default() - self } } /// On 32-bit glibc platforms, `timespec` has anonymous padding fields, which /// Rust doesn't support yet (see `unnamed_fields`), so we define our own /// struct with explicit padding, with bidirectional `From` impls. #[cfg(fix_y2038)] #[repr(C)] #[derive(Debug, Clone)] pub(crate) struct LibcTimespec { pub(crate) tv_sec: Secs, #[cfg(target_endian = "big")] padding: core::mem::MaybeUninit, pub(crate) tv_nsec: i32, #[cfg(target_endian = "little")] padding: core::mem::MaybeUninit, } #[cfg(fix_y2038)] impl From for Timespec { #[inline] fn from(t: LibcTimespec) -> Self { Self { tv_sec: t.tv_sec, tv_nsec: t.tv_nsec as _, } } } #[cfg(fix_y2038)] impl From for LibcTimespec { #[inline] fn from(t: Timespec) -> Self { Self { tv_sec: t.tv_sec, tv_nsec: t.tv_nsec as _, padding: core::mem::MaybeUninit::uninit(), } } } #[cfg(not(fix_y2038))] pub(crate) fn as_libc_timespec_ptr(timespec: &Timespec) -> *const c::timespec { #[cfg(test)] { assert_eq_size!(Timespec, c::timespec); } crate::utils::as_ptr(timespec).cast::() } #[cfg(not(fix_y2038))] pub(crate) fn as_libc_timespec_mut_ptr( timespec: &mut core::mem::MaybeUninit, ) -> *mut c::timespec { #[cfg(test)] { assert_eq_size!(Timespec, c::timespec); } timespec.as_mut_ptr().cast::() } #[cfg(not(fix_y2038))] pub(crate) fn option_as_libc_timespec_ptr(timespec: Option<&Timespec>) -> *const c::timespec { match timespec { None => null(), Some(timespec) => as_libc_timespec_ptr(timespec), } } /// As described [here], Apple platforms may return a negative nanoseconds /// value in some cases; adjust it so that nanoseconds is always in /// `0..1_000_000_000`. /// /// [here]: https://github.com/rust-lang/rust/issues/108277#issuecomment-1787057158 #[cfg(apple)] #[inline] pub(crate) fn fix_negative_nsecs( mut secs: c::time_t, mut nsecs: c::c_long, ) -> (c::time_t, c::c_long) { #[cold] fn adjust(secs: &mut c::time_t, nsecs: c::c_long) -> c::c_long { assert!(nsecs >= -1_000_000_000); assert!(*secs < 0); assert!(*secs > c::time_t::MIN); *secs -= 1; nsecs + 1_000_000_000 } if nsecs < 0 { nsecs = adjust(&mut secs, nsecs); } (secs, nsecs) } #[cfg(test)] mod tests { use super::*; #[cfg(apple)] #[test] fn test_negative_timestamps() { let mut secs = -59; let mut nsecs = -900_000_000; (secs, nsecs) = fix_negative_nsecs(secs, nsecs); assert_eq!(secs, -60); assert_eq!(nsecs, 100_000_000); (secs, nsecs) = fix_negative_nsecs(secs, nsecs); assert_eq!(secs, -60); assert_eq!(nsecs, 100_000_000); } #[test] fn test_sizes() { assert_eq_size!(Secs, u64); const_assert!(core::mem::size_of::() >= core::mem::size_of::<(u64, u32)>()); const_assert!(core::mem::size_of::() >= 4); let mut t = Timespec { tv_sec: 0, tv_nsec: 0, }; // `tv_nsec` needs to be able to hold nanoseconds up to a second. t.tv_nsec = 999_999_999_u32 as _; assert_eq!(t.tv_nsec as u64, 999_999_999_u64); // `tv_sec` needs to be able to hold more than 32-bits of seconds. t.tv_sec = 0x1_0000_0000_u64 as _; assert_eq!(t.tv_sec as u64, 0x1_0000_0000_u64); } // Test that our workarounds are needed. #[cfg(fix_y2038)] #[test] #[allow(deprecated)] fn test_fix_y2038() { assert_eq_size!(libc::time_t, u32); } // Test that our workarounds are not needed. #[cfg(not(fix_y2038))] #[test] fn timespec_layouts() { use crate::backend::c; check_renamed_struct!(Timespec, timespec, tv_sec, tv_nsec); } // Test that `Timespec` matches Linux's `__kernel_timespec`. #[cfg(linux_kernel)] #[test] fn test_against_kernel_timespec() { assert_eq_size!(Timespec, linux_raw_sys::general::__kernel_timespec); assert_eq_align!(Timespec, linux_raw_sys::general::__kernel_timespec); assert_eq!( memoffset::span_of!(Timespec, tv_sec), memoffset::span_of!(linux_raw_sys::general::__kernel_timespec, tv_sec) ); assert_eq!( memoffset::span_of!(Timespec, tv_nsec), memoffset::span_of!(linux_raw_sys::general::__kernel_timespec, tv_nsec) ); } }