Coverage Report

Created: 2026-09-28 06:08

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/rustix-1.1.5/src/timespec.rs
Line
Count
Source
1
//! `Timespec` and related types, which are used by multiple public API
2
//! modules.
3
4
#![allow(dead_code)]
5
6
use core::num::TryFromIntError;
7
use core::ops::{Add, AddAssign, Neg, Sub, SubAssign};
8
use core::time::Duration;
9
10
use crate::backend::c;
11
#[allow(unused)]
12
use crate::ffi;
13
#[cfg(not(fix_y2038))]
14
use 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)]
19
pub 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`].
35
pub 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
))]
43
pub 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
))]
51
pub type Nsecs = ffi::c_long;
52
53
impl 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
0
    pub const fn checked_add(self, rhs: Self) -> Option<Self> {
107
0
        if let Some(mut tv_sec) = self.tv_sec.checked_add(rhs.tv_sec) {
108
0
            let mut tv_nsec = self.tv_nsec + rhs.tv_nsec;
109
0
            if tv_nsec >= 1_000_000_000 {
110
0
                tv_nsec -= 1_000_000_000;
111
0
                if let Some(carried_sec) = tv_sec.checked_add(1) {
112
0
                    tv_sec = carried_sec;
113
0
                } else {
114
0
                    return None;
115
                }
116
0
            }
117
0
            Some(Self { tv_sec, tv_nsec })
118
        } else {
119
0
            None
120
        }
121
0
    }
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
0
    pub const fn checked_sub(self, rhs: Self) -> Option<Self> {
176
0
        if let Some(mut tv_sec) = self.tv_sec.checked_sub(rhs.tv_sec) {
177
0
            let mut tv_nsec = self.tv_nsec - rhs.tv_nsec;
178
0
            if tv_nsec < 0 {
179
0
                tv_nsec += 1_000_000_000;
180
0
                if let Some(borrowed_sec) = tv_sec.checked_sub(1) {
181
0
                    tv_sec = borrowed_sec;
182
0
                } else {
183
0
                    return None;
184
                }
185
0
            }
186
0
            Some(Self { tv_sec, tv_nsec })
187
        } else {
188
0
            None
189
        }
190
0
    }
191
192
    /// Convert from `Timespec` to `c::c_int` milliseconds, rounded up.
193
0
    pub(crate) fn as_c_int_millis(&self) -> Option<c::c_int> {
194
0
        let secs = self.tv_sec;
195
0
        if secs < 0 {
196
0
            return None;
197
0
        }
198
0
        secs.checked_mul(1000)
199
0
            .and_then(|millis| {
200
                // Add the nanoseconds, converted to milliseconds, rounding up.
201
                // With Rust 1.73.0 this can use `div_ceil`.
202
0
                millis.checked_add((i64::from(self.tv_nsec) + 999_999) / 1_000_000)
203
0
            })
204
0
            .and_then(|millis| c::c_int::try_from(millis).ok())
205
0
    }
206
207
    /// Convert `Timespec` to seconds and microseconds, rounding up fractional
208
    /// microseconds and carrying any overflow into seconds.
209
    #[inline]
210
0
    pub(crate) fn to_sec_usec(&self) -> Option<(Secs, u32)> {
211
0
        let mut sec = self.tv_sec;
212
0
        let mut usec = (self.tv_nsec + 999) / 1000;
213
0
        if usec >= 1_000_000 {
214
0
            sec = sec.checked_add(1)?;
215
0
            usec -= 1_000_000;
216
0
        }
217
0
        Some((sec, usec as u32))
218
0
    }
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
243
impl TryFrom<Timespec> for Duration {
244
    type Error = TryFromIntError;
245
246
0
    fn try_from(ts: Timespec) -> Result<Self, Self::Error> {
247
0
        Ok(Self::new(ts.tv_sec.try_into()?, ts.tv_nsec as _))
248
0
    }
249
}
250
251
impl TryFrom<Duration> for Timespec {
252
    type Error = TryFromIntError;
253
254
0
    fn try_from(dur: Duration) -> Result<Self, Self::Error> {
255
        Ok(Self {
256
0
            tv_sec: dur.as_secs().try_into()?,
257
0
            tv_nsec: dur.subsec_nanos() as _,
258
        })
259
0
    }
260
}
261
262
impl Add for Timespec {
263
    type Output = Self;
264
265
0
    fn add(self, rhs: Self) -> Self {
266
0
        self.checked_add(rhs)
267
0
            .expect("overflow when adding timespecs")
268
0
    }
269
}
270
271
impl AddAssign for Timespec {
272
0
    fn add_assign(&mut self, rhs: Self) {
273
0
        *self = *self + rhs;
274
0
    }
275
}
276
277
impl Sub for Timespec {
278
    type Output = Self;
279
280
0
    fn sub(self, rhs: Self) -> Self {
281
0
        self.checked_sub(rhs)
282
0
            .expect("overflow when subtracting timespecs")
283
0
    }
284
}
285
286
impl SubAssign for Timespec {
287
0
    fn sub_assign(&mut self, rhs: Self) {
288
0
        *self = *self - rhs;
289
0
    }
290
}
291
292
impl Neg for Timespec {
293
    type Output = Self;
294
295
0
    fn neg(self) -> Self {
296
0
        Self::default() - self
297
0
    }
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)]
306
pub(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)]
319
impl 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)]
330
impl 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))]
342
0
pub(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
0
    crate::utils::as_ptr(timespec).cast::<c::timespec>()
348
0
}
349
350
#[cfg(not(fix_y2038))]
351
0
pub(crate) fn as_libc_timespec_mut_ptr(
352
0
    timespec: &mut core::mem::MaybeUninit<Timespec>,
353
0
) -> *mut c::timespec {
354
    #[cfg(test)]
355
    {
356
        static_assertions::assert_eq_size!(Timespec, c::timespec);
357
    }
358
0
    timespec.as_mut_ptr().cast::<c::timespec>()
359
0
}
360
361
#[cfg(not(fix_y2038))]
362
0
pub(crate) fn option_as_libc_timespec_ptr(timespec: Option<&Timespec>) -> *const c::timespec {
363
0
    match timespec {
364
0
        None => null(),
365
0
        Some(timespec) => as_libc_timespec_ptr(timespec),
366
    }
367
0
}
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]
376
pub(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)]
396
mod 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
}