| 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 | |