Coverage Report

Created: 2025-11-16 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/rustix-1.1.2/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
208
impl TryFrom<Timespec> for Duration {
209
    type Error = TryFromIntError;
210
211
0
    fn try_from(ts: Timespec) -> Result<Self, Self::Error> {
212
0
        Ok(Self::new(ts.tv_sec.try_into()?, ts.tv_nsec as _))
213
0
    }
214
}
215
216
impl TryFrom<Duration> for Timespec {
217
    type Error = TryFromIntError;
218
219
0
    fn try_from(dur: Duration) -> Result<Self, Self::Error> {
220
        Ok(Self {
221
0
            tv_sec: dur.as_secs().try_into()?,
222
0
            tv_nsec: dur.subsec_nanos() as _,
223
        })
224
0
    }
225
}
226
227
impl Add for Timespec {
228
    type Output = Self;
229
230
0
    fn add(self, rhs: Self) -> Self {
231
0
        self.checked_add(rhs)
232
0
            .expect("overflow when adding timespecs")
233
0
    }
234
}
235
236
impl AddAssign for Timespec {
237
0
    fn add_assign(&mut self, rhs: Self) {
238
0
        *self = *self + rhs;
239
0
    }
240
}
241
242
impl Sub for Timespec {
243
    type Output = Self;
244
245
0
    fn sub(self, rhs: Self) -> Self {
246
0
        self.checked_sub(rhs)
247
0
            .expect("overflow when subtracting timespecs")
248
0
    }
249
}
250
251
impl SubAssign for Timespec {
252
0
    fn sub_assign(&mut self, rhs: Self) {
253
0
        *self = *self - rhs;
254
0
    }
255
}
256
257
impl Neg for Timespec {
258
    type Output = Self;
259
260
0
    fn neg(self) -> Self {
261
0
        Self::default() - self
262
0
    }
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)]
271
pub(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)]
284
impl 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)]
295
impl 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))]
307
0
pub(crate) fn as_libc_timespec_ptr(timespec: &Timespec) -> *const c::timespec {
308
    #[cfg(test)]
309
    {
310
        assert_eq_size!(Timespec, c::timespec);
311
    }
312
0
    crate::utils::as_ptr(timespec).cast::<c::timespec>()
313
0
}
314
315
#[cfg(not(fix_y2038))]
316
0
pub(crate) fn as_libc_timespec_mut_ptr(
317
0
    timespec: &mut core::mem::MaybeUninit<Timespec>,
318
0
) -> *mut c::timespec {
319
    #[cfg(test)]
320
    {
321
        assert_eq_size!(Timespec, c::timespec);
322
    }
323
0
    timespec.as_mut_ptr().cast::<c::timespec>()
324
0
}
325
326
#[cfg(not(fix_y2038))]
327
0
pub(crate) fn option_as_libc_timespec_ptr(timespec: Option<&Timespec>) -> *const c::timespec {
328
0
    match timespec {
329
0
        None => null(),
330
0
        Some(timespec) => as_libc_timespec_ptr(timespec),
331
    }
332
0
}
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]
341
pub(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)]
361
mod 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_raw_dep)]
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
}