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
208impl TryFrom<Timespec> for Duration {
209 type Error = TryFromIntError;
210
211 fn try_from(ts: Timespec) -> Result<Self, Self::Error> {
212 Ok(Self::new(secs:ts.tv_sec.try_into()?, nanos:ts.tv_nsec as _))
213 }
214}
215
216impl TryFrom<Duration> for Timespec {
217 type Error = TryFromIntError;
218
219 fn try_from(dur: Duration) -> Result<Self, Self::Error> {
220 Ok(Self {
221 tv_sec: dur.as_secs().try_into()?,
222 tv_nsec: dur.subsec_nanos() as _,
223 })
224 }
225}
226
227impl Add for Timespec {
228 type Output = Self;
229
230 fn add(self, rhs: Self) -> Self {
231 self.checked_add(rhs)
232 .expect(msg:"overflow when adding timespecs")
233 }
234}
235
236impl AddAssign for Timespec {
237 fn add_assign(&mut self, rhs: Self) {
238 *self = *self + rhs;
239 }
240}
241
242impl Sub for Timespec {
243 type Output = Self;
244
245 fn sub(self, rhs: Self) -> Self {
246 self.checked_sub(rhs)
247 .expect(msg:"overflow when subtracting timespecs")
248 }
249}
250
251impl SubAssign for Timespec {
252 fn sub_assign(&mut self, rhs: Self) {
253 *self = *self - rhs;
254 }
255}
256
257impl Neg for Timespec {
258 type Output = Self;
259
260 fn neg(self) -> Self {
261 Self::default() - self
262 }
263}
264
265/// On 32-bit glibc platforms, `timespec` has anonymous padding fields, which
266/// Rust doesn't support yet (see `unnamed_fields`), so we define our own
267/// struct with explicit padding, with bidirectional `From` impls.
268#[cfg(fix_y2038)]
269#[repr(C)]
270#[derive(Debug, Clone)]
271pub(crate) struct LibcTimespec {
272 pub(crate) tv_sec: Secs,
273
274 #[cfg(target_endian = "big")]
275 padding: core::mem::MaybeUninit<u32>,
276
277 pub(crate) tv_nsec: i32,
278
279 #[cfg(target_endian = "little")]
280 padding: core::mem::MaybeUninit<u32>,
281}
282
283#[cfg(fix_y2038)]
284impl From<LibcTimespec> for Timespec {
285 #[inline]
286 fn from(t: LibcTimespec) -> Self {
287 Self {
288 tv_sec: t.tv_sec,
289 tv_nsec: t.tv_nsec as _,
290 }
291 }
292}
293
294#[cfg(fix_y2038)]
295impl From<Timespec> for LibcTimespec {
296 #[inline]
297 fn from(t: Timespec) -> Self {
298 Self {
299 tv_sec: t.tv_sec,
300 tv_nsec: t.tv_nsec as _,
301 padding: core::mem::MaybeUninit::uninit(),
302 }
303 }
304}
305
306#[cfg(not(fix_y2038))]
307pub(crate) fn as_libc_timespec_ptr(timespec: &Timespec) -> *const c::timespec {
308 #[cfg(test)]
309 {
310 assert_eq_size!(Timespec, c::timespec);
311 }
312 crate::utils::as_ptr(timespec).cast::<c::timespec>()
313}
314
315#[cfg(not(fix_y2038))]
316pub(crate) fn as_libc_timespec_mut_ptr(
317 timespec: &mut core::mem::MaybeUninit<Timespec>,
318) -> *mut c::timespec {
319 #[cfg(test)]
320 {
321 assert_eq_size!(Timespec, c::timespec);
322 }
323 timespec.as_mut_ptr().cast::<c::timespec>()
324}
325
326#[cfg(not(fix_y2038))]
327pub(crate) fn option_as_libc_timespec_ptr(timespec: Option<&Timespec>) -> *const c::timespec {
328 match timespec {
329 None => null(),
330 Some(timespec: &Timespec) => as_libc_timespec_ptr(timespec),
331 }
332}
333
334/// As described [here], Apple platforms may return a negative nanoseconds
335/// value in some cases; adjust it so that nanoseconds is always in
336/// `0..1_000_000_000`.
337///
338/// [here]: https://github.com/rust-lang/rust/issues/108277#issuecomment-1787057158
339#[cfg(apple)]
340#[inline]
341pub(crate) fn fix_negative_nsecs(
342 mut secs: c::time_t,
343 mut nsecs: c::c_long,
344) -> (c::time_t, c::c_long) {
345 #[cold]
346 fn adjust(secs: &mut c::time_t, nsecs: c::c_long) -> c::c_long {
347 assert!(nsecs >= -1_000_000_000);
348 assert!(*secs < 0);
349 assert!(*secs > c::time_t::MIN);
350 *secs -= 1;
351 nsecs + 1_000_000_000
352 }
353
354 if nsecs < 0 {
355 nsecs = adjust(&mut secs, nsecs);
356 }
357 (secs, nsecs)
358}
359
360#[cfg(test)]
361mod tests {
362 use super::*;
363
364 #[cfg(apple)]
365 #[test]
366 fn test_negative_timestamps() {
367 let mut secs = -59;
368 let mut nsecs = -900_000_000;
369 (secs, nsecs) = fix_negative_nsecs(secs, nsecs);
370 assert_eq!(secs, -60);
371 assert_eq!(nsecs, 100_000_000);
372 (secs, nsecs) = fix_negative_nsecs(secs, nsecs);
373 assert_eq!(secs, -60);
374 assert_eq!(nsecs, 100_000_000);
375 }
376
377 #[test]
378 fn test_sizes() {
379 assert_eq_size!(Secs, u64);
380 const_assert!(core::mem::size_of::<Timespec>() >= core::mem::size_of::<(u64, u32)>());
381 const_assert!(core::mem::size_of::<Nsecs>() >= 4);
382
383 let mut t = Timespec {
384 tv_sec: 0,
385 tv_nsec: 0,
386 };
387
388 // `tv_nsec` needs to be able to hold nanoseconds up to a second.
389 t.tv_nsec = 999_999_999_u32 as _;
390 assert_eq!(t.tv_nsec as u64, 999_999_999_u64);
391
392 // `tv_sec` needs to be able to hold more than 32-bits of seconds.
393 t.tv_sec = 0x1_0000_0000_u64 as _;
394 assert_eq!(t.tv_sec as u64, 0x1_0000_0000_u64);
395 }
396
397 // Test that our workarounds are needed.
398 #[cfg(fix_y2038)]
399 #[test]
400 #[allow(deprecated)]
401 fn test_fix_y2038() {
402 assert_eq_size!(libc::time_t, u32);
403 }
404
405 // Test that our workarounds are not needed.
406 #[cfg(not(fix_y2038))]
407 #[test]
408 fn timespec_layouts() {
409 use crate::backend::c;
410 check_renamed_struct!(Timespec, timespec, tv_sec, tv_nsec);
411 }
412
413 // Test that `Timespec` matches Linux's `__kernel_timespec`.
414 #[cfg(linux_kernel)]
415 #[test]
416 fn test_against_kernel_timespec() {
417 assert_eq_size!(Timespec, linux_raw_sys::general::__kernel_timespec);
418 assert_eq_align!(Timespec, linux_raw_sys::general::__kernel_timespec);
419 assert_eq!(
420 memoffset::span_of!(Timespec, tv_sec),
421 memoffset::span_of!(linux_raw_sys::general::__kernel_timespec, tv_sec)
422 );
423 assert_eq!(
424 memoffset::span_of!(Timespec, tv_nsec),
425 memoffset::span_of!(linux_raw_sys::general::__kernel_timespec, tv_nsec)
426 );
427 }
428}
429