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 | 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 | |
208 | impl 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 | |
216 | impl 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 | |
227 | impl 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 | |
236 | impl AddAssign for Timespec { |
237 | fn add_assign(&mut self, rhs: Self) { |
238 | *self = *self + rhs; |
239 | } |
240 | } |
241 | |
242 | impl 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 | |
251 | impl SubAssign for Timespec { |
252 | fn sub_assign(&mut self, rhs: Self) { |
253 | *self = *self - rhs; |
254 | } |
255 | } |
256 | |
257 | impl 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)] |
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 | 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 | crate::utils::as_ptr(timespec).cast::<c::timespec>() |
313 | } |
314 | |
315 | #[cfg (not(fix_y2038))] |
316 | pub(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))] |
327 | pub(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 ] |
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_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 | |