Skip to main content

rustix/
timespec.rs

1//! `Timespec` and related types, which are used by multiple public API
2//! modules.
3
4#![allow(dead_code)]
5
6use core::num::TryFromIntError;
7use core::ops::{Add, AddAssign, Neg, Sub, SubAssign};
8use core::time::Duration;
9
10use crate::backend::c;
11#[allow(unused)]
12use crate::ffi;
13#[cfg(not(fix_y2038))]
14use core::ptr::null;
15
16/// `struct timespec`—A quantity of time in seconds plus nanoseconds.
17#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
18#[repr(C)]
19pub struct Timespec {
20    /// Seconds.
21    pub tv_sec: Secs,
22
23    /// Nanoseconds. Must be less than 1_000_000_000.
24    ///
25    /// When passed to [`rustix::fs::utimensat`], this field may instead be
26    /// assigned the values [`UTIME_NOW`] or [`UTIME_OMIT`].
27    ///
28    /// [`UTIME_NOW`]: crate::fs::UTIME_NOW
29    /// [`UTIME_OMIT`]: crate::fs::UTIME_OMIT
30    /// [`rustix::fs::utimensat`]: crate::fs::utimensat
31    pub tv_nsec: Nsecs,
32}
33
34/// A type for the `tv_sec` field of [`Timespec`].
35pub type Secs = i64;
36
37/// A type for the `tv_nsec` field of [`Timespec`].
38#[cfg(any(
39    fix_y2038,
40    linux_raw,
41    all(libc, target_arch = "x86_64", target_pointer_width = "32")
42))]
43pub type Nsecs = i64;
44
45/// A type for the `tv_nsec` field of [`Timespec`].
46#[cfg(all(
47    not(fix_y2038),
48    libc,
49    not(all(target_arch = "x86_64", target_pointer_width = "32"))
50))]
51pub type Nsecs = ffi::c_long;
52
53impl Timespec {
54    /// Checked `Timespec` addition. Returns `None` if overflow occurred.
55    ///
56    /// # Panics
57    ///
58    /// If `0 <= .tv_nsec < 1_000_000_000` doesn't hold, this function may
59    /// panic or return unexpected results.
60    ///
61    /// # Example
62    ///
63    /// ```
64    /// use rustix::event::Timespec;
65    ///
66    /// assert_eq!(
67    ///     Timespec {
68    ///         tv_sec: 1,
69    ///         tv_nsec: 2
70    ///     }
71    ///     .checked_add(Timespec {
72    ///         tv_sec: 30,
73    ///         tv_nsec: 40
74    ///     }),
75    ///     Some(Timespec {
76    ///         tv_sec: 31,
77    ///         tv_nsec: 42
78    ///     })
79    /// );
80    /// assert_eq!(
81    ///     Timespec {
82    ///         tv_sec: 0,
83    ///         tv_nsec: 999_999_999
84    ///     }
85    ///     .checked_add(Timespec {
86    ///         tv_sec: 0,
87    ///         tv_nsec: 2
88    ///     }),
89    ///     Some(Timespec {
90    ///         tv_sec: 1,
91    ///         tv_nsec: 1
92    ///     })
93    /// );
94    /// assert_eq!(
95    ///     Timespec {
96    ///         tv_sec: i64::MAX,
97    ///         tv_nsec: 999_999_999
98    ///     }
99    ///     .checked_add(Timespec {
100    ///         tv_sec: 0,
101    ///         tv_nsec: 1
102    ///     }),
103    ///     None
104    /// );
105    /// ```
106    pub const fn checked_add(self, rhs: Self) -> Option<Self> {
107        if let Some(mut tv_sec) = self.tv_sec.checked_add(rhs.tv_sec) {
108            let mut tv_nsec = self.tv_nsec + rhs.tv_nsec;
109            if tv_nsec >= 1_000_000_000 {
110                tv_nsec -= 1_000_000_000;
111                if let Some(carried_sec) = tv_sec.checked_add(1) {
112                    tv_sec = carried_sec;
113                } else {
114                    return None;
115                }
116            }
117            Some(Self { tv_sec, tv_nsec })
118        } else {
119            None
120        }
121    }
122
123    /// Checked `Timespec` subtraction. Returns `None` if overflow occurred.
124    ///
125    /// # Panics
126    ///
127    /// If `0 <= .tv_nsec < 1_000_000_000` doesn't hold, this function may
128    /// panic or return unexpected results.
129    ///
130    /// # Example
131    ///
132    /// ```
133    /// use rustix::event::Timespec;
134    ///
135    /// assert_eq!(
136    ///     Timespec {
137    ///         tv_sec: 31,
138    ///         tv_nsec: 42
139    ///     }
140    ///     .checked_sub(Timespec {
141    ///         tv_sec: 30,
142    ///         tv_nsec: 40
143    ///     }),
144    ///     Some(Timespec {
145    ///         tv_sec: 1,
146    ///         tv_nsec: 2
147    ///     })
148    /// );
149    /// assert_eq!(
150    ///     Timespec {
151    ///         tv_sec: 1,
152    ///         tv_nsec: 1
153    ///     }
154    ///     .checked_sub(Timespec {
155    ///         tv_sec: 0,
156    ///         tv_nsec: 2
157    ///     }),
158    ///     Some(Timespec {
159    ///         tv_sec: 0,
160    ///         tv_nsec: 999_999_999
161    ///     })
162    /// );
163    /// assert_eq!(
164    ///     Timespec {
165    ///         tv_sec: i64::MIN,
166    ///         tv_nsec: 0
167    ///     }
168    ///     .checked_sub(Timespec {
169    ///         tv_sec: 0,
170    ///         tv_nsec: 1
171    ///     }),
172    ///     None
173    /// );
174    /// ```
175    pub const fn checked_sub(self, rhs: Self) -> Option<Self> {
176        if let Some(mut tv_sec) = self.tv_sec.checked_sub(rhs.tv_sec) {
177            let mut tv_nsec = self.tv_nsec - rhs.tv_nsec;
178            if tv_nsec < 0 {
179                tv_nsec += 1_000_000_000;
180                if let Some(borrowed_sec) = tv_sec.checked_sub(1) {
181                    tv_sec = borrowed_sec;
182                } else {
183                    return None;
184                }
185            }
186            Some(Self { tv_sec, tv_nsec })
187        } else {
188            None
189        }
190    }
191
192    /// Convert from `Timespec` to `c::c_int` milliseconds, rounded up.
193    pub(crate) fn as_c_int_millis(&self) -> Option<c::c_int> {
194        let secs = self.tv_sec;
195        if secs < 0 {
196            return None;
197        }
198        secs.checked_mul(1000)
199            .and_then(|millis| {
200                // Add the nanoseconds, converted to milliseconds, rounding up.
201                // With Rust 1.73.0 this can use `div_ceil`.
202                millis.checked_add((i64::from(self.tv_nsec) + 999_999) / 1_000_000)
203            })
204            .and_then(|millis| c::c_int::try_from(millis).ok())
205    }
206
207    /// Convert `Timespec` to seconds and microseconds, rounding up fractional
208    /// microseconds and carrying any overflow into seconds.
209    #[inline]
210    pub(crate) fn to_sec_usec(&self) -> Option<(Secs, u32)> {
211        let mut sec = self.tv_sec;
212        let mut usec = (self.tv_nsec + 999) / 1000;
213        if usec >= 1_000_000 {
214            sec = sec.checked_add(1)?;
215            usec -= 1_000_000;
216        }
217        Some((sec, usec as u32))
218    }
219
220    /// Convert from `Timespec` to `c::timeval`, rounding up fractional
221    /// microseconds and carrying any overflow into seconds.
222    #[cfg(all(any(libc, target_os = "wasi"), not(windows)))]
223    pub(crate) fn to_timeval(&self) -> crate::io::Result<c::timeval> {
224        let (sec, usec) = self.to_sec_usec().ok_or(crate::io::Errno::INVAL)?;
225        Ok(c::timeval {
226            tv_sec: sec.try_into().map_err(|_| crate::io::Errno::INVAL)?,
227            tv_usec: usec as _,
228        })
229    }
230
231    /// Convert from `Timespec` to `c::TIMEVAL`, rounding up fractional
232    /// microseconds and carrying any overflow into seconds.
233    #[cfg(windows)]
234    pub(crate) fn to_timeval(&self) -> crate::io::Result<c::TIMEVAL> {
235        let (sec, usec) = self.to_sec_usec().ok_or(crate::io::Errno::OPNOTSUPP)?;
236        Ok(c::TIMEVAL {
237            tv_sec: sec.try_into().map_err(|_| crate::io::Errno::OPNOTSUPP)?,
238            tv_usec: usec as _,
239        })
240    }
241}
242
243impl TryFrom<Timespec> for Duration {
244    type Error = TryFromIntError;
245
246    fn try_from(ts: Timespec) -> Result<Self, Self::Error> {
247        Ok(Self::new(ts.tv_sec.try_into()?, ts.tv_nsec as _))
248    }
249}
250
251impl TryFrom<Duration> for Timespec {
252    type Error = TryFromIntError;
253
254    fn try_from(dur: Duration) -> Result<Self, Self::Error> {
255        Ok(Self {
256            tv_sec: dur.as_secs().try_into()?,
257            tv_nsec: dur.subsec_nanos() as _,
258        })
259    }
260}
261
262impl Add for Timespec {
263    type Output = Self;
264
265    fn add(self, rhs: Self) -> Self {
266        self.checked_add(rhs)
267            .expect("overflow when adding timespecs")
268    }
269}
270
271impl AddAssign for Timespec {
272    fn add_assign(&mut self, rhs: Self) {
273        *self = *self + rhs;
274    }
275}
276
277impl Sub for Timespec {
278    type Output = Self;
279
280    fn sub(self, rhs: Self) -> Self {
281        self.checked_sub(rhs)
282            .expect("overflow when subtracting timespecs")
283    }
284}
285
286impl SubAssign for Timespec {
287    fn sub_assign(&mut self, rhs: Self) {
288        *self = *self - rhs;
289    }
290}
291
292impl Neg for Timespec {
293    type Output = Self;
294
295    fn neg(self) -> Self {
296        Self::default() - self
297    }
298}
299
300/// On 32-bit glibc platforms, `timespec` has anonymous padding fields, which
301/// Rust doesn't support yet (see `unnamed_fields`), so we define our own
302/// struct with explicit padding, with bidirectional `From` impls.
303#[cfg(fix_y2038)]
304#[repr(C)]
305#[derive(Debug, Clone)]
306pub(crate) struct LibcTimespec {
307    pub(crate) tv_sec: Secs,
308
309    #[cfg(target_endian = "big")]
310    padding: core::mem::MaybeUninit<u32>,
311
312    pub(crate) tv_nsec: i32,
313
314    #[cfg(target_endian = "little")]
315    padding: core::mem::MaybeUninit<u32>,
316}
317
318#[cfg(fix_y2038)]
319impl From<LibcTimespec> for Timespec {
320    #[inline]
321    fn from(t: LibcTimespec) -> Self {
322        Self {
323            tv_sec: t.tv_sec,
324            tv_nsec: t.tv_nsec as _,
325        }
326    }
327}
328
329#[cfg(fix_y2038)]
330impl From<Timespec> for LibcTimespec {
331    #[inline]
332    fn from(t: Timespec) -> Self {
333        Self {
334            tv_sec: t.tv_sec,
335            tv_nsec: t.tv_nsec as _,
336            padding: core::mem::MaybeUninit::uninit(),
337        }
338    }
339}
340
341#[cfg(not(fix_y2038))]
342pub(crate) fn as_libc_timespec_ptr(timespec: &Timespec) -> *const c::timespec {
343    #[cfg(test)]
344    {
345        static_assertions::assert_eq_size!(Timespec, c::timespec);
346    }
347    crate::utils::as_ptr(timespec).cast::<c::timespec>()
348}
349
350#[cfg(not(fix_y2038))]
351pub(crate) fn as_libc_timespec_mut_ptr(
352    timespec: &mut core::mem::MaybeUninit<Timespec>,
353) -> *mut c::timespec {
354    #[cfg(test)]
355    {
356        static_assertions::assert_eq_size!(Timespec, c::timespec);
357    }
358    timespec.as_mut_ptr().cast::<c::timespec>()
359}
360
361#[cfg(not(fix_y2038))]
362pub(crate) fn option_as_libc_timespec_ptr(timespec: Option<&Timespec>) -> *const c::timespec {
363    match timespec {
364        None => null(),
365        Some(timespec) => as_libc_timespec_ptr(timespec),
366    }
367}
368
369/// As described [here], Apple platforms may return a negative nanoseconds
370/// value in some cases; adjust it so that nanoseconds is always in
371/// `0..1_000_000_000`.
372///
373/// [here]: https://github.com/rust-lang/rust/issues/108277#issuecomment-1787057158
374#[cfg(apple)]
375#[inline]
376pub(crate) fn fix_negative_nsecs(
377    mut secs: c::time_t,
378    mut nsecs: c::c_long,
379) -> (c::time_t, c::c_long) {
380    #[cold]
381    fn adjust(secs: &mut c::time_t, nsecs: c::c_long) -> c::c_long {
382        assert!(nsecs >= -1_000_000_000);
383        assert!(*secs < 0);
384        assert!(*secs > c::time_t::MIN);
385        *secs -= 1;
386        nsecs + 1_000_000_000
387    }
388
389    if nsecs < 0 {
390        nsecs = adjust(&mut secs, nsecs);
391    }
392    (secs, nsecs)
393}
394
395#[cfg(test)]
396mod tests {
397    use super::*;
398
399    #[cfg(apple)]
400    #[test]
401    fn test_negative_timestamps() {
402        let mut secs = -59;
403        let mut nsecs = -900_000_000;
404        (secs, nsecs) = fix_negative_nsecs(secs, nsecs);
405        assert_eq!(secs, -60);
406        assert_eq!(nsecs, 100_000_000);
407        (secs, nsecs) = fix_negative_nsecs(secs, nsecs);
408        assert_eq!(secs, -60);
409        assert_eq!(nsecs, 100_000_000);
410    }
411
412    #[test]
413    fn test_sizes() {
414        static_assertions::assert_eq_size!(Secs, u64);
415        static_assertions::const_assert!(
416            core::mem::size_of::<Timespec>() >= core::mem::size_of::<(u64, u32)>()
417        );
418        static_assertions::const_assert!(core::mem::size_of::<Nsecs>() >= 4);
419
420        let mut t = Timespec {
421            tv_sec: 0,
422            tv_nsec: 0,
423        };
424
425        // `tv_nsec` needs to be able to hold nanoseconds up to a second.
426        t.tv_nsec = 999_999_999_u32 as _;
427        assert_eq!(t.tv_nsec as u64, 999_999_999_u64);
428
429        // `tv_sec` needs to be able to hold more than 32-bits of seconds.
430        t.tv_sec = 0x1_0000_0000_u64 as _;
431        assert_eq!(t.tv_sec as u64, 0x1_0000_0000_u64);
432    }
433
434    #[cfg(any(libc, target_os = "wasi", windows))]
435    #[test]
436    fn test_to_timeval() {
437        let ts = Timespec {
438            tv_sec: 4,
439            tv_nsec: 999_999_500,
440        };
441        let tv = ts.to_timeval().unwrap();
442        assert_eq!(tv.tv_sec, 5);
443        assert_eq!(tv.tv_usec, 0);
444
445        let ts2 = Timespec {
446            tv_sec: 2,
447            tv_nsec: 500_000_000,
448        };
449        let tv2 = ts2.to_timeval().unwrap();
450        assert_eq!(tv2.tv_sec, 2);
451        assert_eq!(tv2.tv_usec, 500_000);
452    }
453
454    // Test that our workarounds are needed.
455    #[cfg(fix_y2038)]
456    #[test]
457    #[allow(deprecated)]
458    fn test_fix_y2038() {
459        static_assertions::assert_eq_size!(libc::time_t, u32);
460    }
461
462    // Test that our workarounds are not needed.
463    #[cfg(not(fix_y2038))]
464    #[test]
465    fn timespec_layouts() {
466        use crate::backend::c;
467        check_renamed_struct!(Timespec, timespec, tv_sec, tv_nsec);
468    }
469
470    // Test that `Timespec` matches Linux's `__kernel_timespec`.
471    #[cfg(linux_raw_dep)]
472    #[test]
473    fn test_against_kernel_timespec() {
474        static_assertions::assert_eq_size!(Timespec, linux_raw_sys::general::__kernel_timespec);
475        static_assertions::assert_eq_align!(Timespec, linux_raw_sys::general::__kernel_timespec);
476        assert_eq!(
477            memoffset::span_of!(Timespec, tv_sec),
478            memoffset::span_of!(linux_raw_sys::general::__kernel_timespec, tv_sec)
479        );
480        assert_eq!(
481            memoffset::span_of!(Timespec, tv_nsec),
482            memoffset::span_of!(linux_raw_sys::general::__kernel_timespec, tv_nsec)
483        );
484    }
485}