Auto merge of #96657 - cuviper:time64, r=joshtriplett

Use 64-bit time on 32-bit linux-gnu

The standard library suffered the [Year 2038 problem][Y2038] in two main places on targets with 32-bit `time_t`:

- In `std::time::SystemTime`, we stored a `timespec` that has `time_t` seconds. This is now changed to directly store 64-bit seconds and nanoseconds, and on 32-bit linux-gnu we try to use `__clock_gettime64` (glibc 2.34+) to get the larger timestamp.

- In `std::fs::Metadata`, we store a `stat64`, which has 64-bit `off_t` but still 32-bit `time_t`, and unfortunately that is baked in the API by the (deprecated) `MetadataExt::as_raw_stat()`. However, we can use `statx` for 64-bit `statx_timestamp` to store in addition to the `stat64`, as we already do to support creation time, and the rest of the `MetadataExt` methods can return those full values. Note that some filesystems may still be limited in their actual timestamp support, but that's not something Rust can change.

There remain a few places that need `timespec` for system call timeouts -- I leave that to future work.

[Y2038]: https://en.wikipedia.org/wiki/Year_2038_problem
This commit is contained in:
bors 2022-05-07 17:53:59 +00:00
commit 24a0eecf03
5 changed files with 207 additions and 170 deletions

View file

@ -356,19 +356,34 @@ impl MetadataExt for Metadata {
self.as_inner().as_inner().st_size as u64
}
fn st_atime(&self) -> i64 {
self.as_inner().as_inner().st_atime as i64
let file_attr = self.as_inner();
#[cfg(all(target_env = "gnu", target_pointer_width = "32"))]
if let Some(atime) = file_attr.stx_atime() {
return atime.tv_sec;
}
file_attr.as_inner().st_atime as i64
}
fn st_atime_nsec(&self) -> i64 {
self.as_inner().as_inner().st_atime_nsec as i64
}
fn st_mtime(&self) -> i64 {
self.as_inner().as_inner().st_mtime as i64
let file_attr = self.as_inner();
#[cfg(all(target_env = "gnu", target_pointer_width = "32"))]
if let Some(mtime) = file_attr.stx_mtime() {
return mtime.tv_sec;
}
file_attr.as_inner().st_mtime as i64
}
fn st_mtime_nsec(&self) -> i64 {
self.as_inner().as_inner().st_mtime_nsec as i64
}
fn st_ctime(&self) -> i64 {
self.as_inner().as_inner().st_ctime as i64
let file_attr = self.as_inner();
#[cfg(all(target_env = "gnu", target_pointer_width = "32"))]
if let Some(ctime) = file_attr.stx_ctime() {
return ctime.tv_sec;
}
file_attr.as_inner().st_ctime as i64
}
fn st_ctime_nsec(&self) -> i64 {
self.as_inner().as_inner().st_ctime_nsec as i64

View file

@ -113,10 +113,19 @@ cfg_has_statx! {{
// This is needed to check if btime is supported by the filesystem.
stx_mask: u32,
stx_btime: libc::statx_timestamp,
// With statx, we can overcome 32-bit `time_t` too.
#[cfg(target_pointer_width = "32")]
stx_atime: libc::statx_timestamp,
#[cfg(target_pointer_width = "32")]
stx_ctime: libc::statx_timestamp,
#[cfg(target_pointer_width = "32")]
stx_mtime: libc::statx_timestamp,
}
// We prefer `statx` on Linux if available, which contains file creation time.
// Default `stat64` contains no creation time.
// We prefer `statx` on Linux if available, which contains file creation time,
// as well as 64-bit timestamps of all kinds.
// Default `stat64` contains no creation time and may have 32-bit `time_t`.
unsafe fn try_statx(
fd: c_int,
path: *const c_char,
@ -192,6 +201,13 @@ cfg_has_statx! {{
let extra = StatxExtraFields {
stx_mask: buf.stx_mask,
stx_btime: buf.stx_btime,
// Store full times to avoid 32-bit `time_t` truncation.
#[cfg(target_pointer_width = "32")]
stx_atime: buf.stx_atime,
#[cfg(target_pointer_width = "32")]
stx_ctime: buf.stx_ctime,
#[cfg(target_pointer_width = "32")]
stx_mtime: buf.stx_mtime,
};
Some(Ok(FileAttr { stat, statx_extra_fields: Some(extra) }))
@ -310,6 +326,36 @@ cfg_has_statx! {{
fn from_stat64(stat: stat64) -> Self {
Self { stat, statx_extra_fields: None }
}
#[cfg(target_pointer_width = "32")]
pub fn stx_mtime(&self) -> Option<&libc::statx_timestamp> {
if let Some(ext) = &self.statx_extra_fields {
if (ext.stx_mask & libc::STATX_MTIME) != 0 {
return Some(&ext.stx_mtime);
}
}
None
}
#[cfg(target_pointer_width = "32")]
pub fn stx_atime(&self) -> Option<&libc::statx_timestamp> {
if let Some(ext) = &self.statx_extra_fields {
if (ext.stx_mask & libc::STATX_ATIME) != 0 {
return Some(&ext.stx_atime);
}
}
None
}
#[cfg(target_pointer_width = "32")]
pub fn stx_ctime(&self) -> Option<&libc::statx_timestamp> {
if let Some(ext) = &self.statx_extra_fields {
if (ext.stx_mask & libc::STATX_CTIME) != 0 {
return Some(&ext.stx_ctime);
}
}
None
}
}
} else {
impl FileAttr {
@ -335,24 +381,15 @@ impl FileAttr {
#[cfg(target_os = "netbsd")]
impl FileAttr {
pub fn modified(&self) -> io::Result<SystemTime> {
Ok(SystemTime::from(libc::timespec {
tv_sec: self.stat.st_mtime as libc::time_t,
tv_nsec: self.stat.st_mtimensec as libc::c_long,
}))
Ok(SystemTime::new(self.stat.st_mtime as i64, self.stat.st_mtimensec as i64))
}
pub fn accessed(&self) -> io::Result<SystemTime> {
Ok(SystemTime::from(libc::timespec {
tv_sec: self.stat.st_atime as libc::time_t,
tv_nsec: self.stat.st_atimensec as libc::c_long,
}))
Ok(SystemTime::new(self.stat.st_atime as i64, self.stat.st_atimensec as i64))
}
pub fn created(&self) -> io::Result<SystemTime> {
Ok(SystemTime::from(libc::timespec {
tv_sec: self.stat.st_birthtime as libc::time_t,
tv_nsec: self.stat.st_birthtimensec as libc::c_long,
}))
Ok(SystemTime::new(self.stat.st_birthtime as i64, self.stat.st_birthtimensec as i64))
}
}
@ -360,34 +397,36 @@ impl FileAttr {
impl FileAttr {
#[cfg(all(not(target_os = "vxworks"), not(target_os = "espidf")))]
pub fn modified(&self) -> io::Result<SystemTime> {
Ok(SystemTime::from(libc::timespec {
tv_sec: self.stat.st_mtime as libc::time_t,
tv_nsec: self.stat.st_mtime_nsec as _,
}))
#[cfg(target_pointer_width = "32")]
cfg_has_statx! {
if let Some(mtime) = self.stx_mtime() {
return Ok(SystemTime::new(mtime.tv_sec, mtime.tv_nsec as i64));
}
}
Ok(SystemTime::new(self.stat.st_mtime as i64, self.stat.st_mtime_nsec as i64))
}
#[cfg(any(target_os = "vxworks", target_os = "espidf"))]
pub fn modified(&self) -> io::Result<SystemTime> {
Ok(SystemTime::from(libc::timespec {
tv_sec: self.stat.st_mtime as libc::time_t,
tv_nsec: 0,
}))
Ok(SystemTime::new(self.stat.st_mtime as i64, 0))
}
#[cfg(all(not(target_os = "vxworks"), not(target_os = "espidf")))]
pub fn accessed(&self) -> io::Result<SystemTime> {
Ok(SystemTime::from(libc::timespec {
tv_sec: self.stat.st_atime as libc::time_t,
tv_nsec: self.stat.st_atime_nsec as _,
}))
#[cfg(target_pointer_width = "32")]
cfg_has_statx! {
if let Some(atime) = self.stx_atime() {
return Ok(SystemTime::new(atime.tv_sec, atime.tv_nsec as i64));
}
}
Ok(SystemTime::new(self.stat.st_atime as i64, self.stat.st_atime_nsec as i64))
}
#[cfg(any(target_os = "vxworks", target_os = "espidf"))]
pub fn accessed(&self) -> io::Result<SystemTime> {
Ok(SystemTime::from(libc::timespec {
tv_sec: self.stat.st_atime as libc::time_t,
tv_nsec: 0,
}))
Ok(SystemTime::new(self.stat.st_atime as i64, 0))
}
#[cfg(any(
@ -397,10 +436,7 @@ impl FileAttr {
target_os = "ios"
))]
pub fn created(&self) -> io::Result<SystemTime> {
Ok(SystemTime::from(libc::timespec {
tv_sec: self.stat.st_birthtime as libc::time_t,
tv_nsec: self.stat.st_birthtime_nsec as libc::c_long,
}))
Ok(SystemTime::new(self.stat.st_birthtime as i64, self.stat.st_birthtime_nsec as i64))
}
#[cfg(not(any(
@ -413,10 +449,7 @@ impl FileAttr {
cfg_has_statx! {
if let Some(ext) = &self.statx_extra_fields {
return if (ext.stx_mask & libc::STATX_BTIME) != 0 {
Ok(SystemTime::from(libc::timespec {
tv_sec: ext.stx_btime.tv_sec as libc::time_t,
tv_nsec: ext.stx_btime.tv_nsec as _,
}))
Ok(SystemTime::new(ext.stx_btime.tv_sec, ext.stx_btime.tv_nsec as i64))
} else {
Err(io::const_io_error!(
io::ErrorKind::Uncategorized,

View file

@ -24,8 +24,9 @@ pub fn futex_wait(futex: &AtomicU32, expected: u32, timeout: Option<Duration>) -
// Calculate the timeout as an absolute timespec.
//
// Overflows are rounded up to an infinite timeout (None).
let timespec =
timeout.and_then(|d| Some(Timespec::now(libc::CLOCK_MONOTONIC).checked_add_duration(&d)?));
let timespec = timeout
.and_then(|d| Some(Timespec::now(libc::CLOCK_MONOTONIC).checked_add_duration(&d)?))
.and_then(|t| t.to_timespec());
loop {
// No need to wait if the value already changed.
@ -41,7 +42,7 @@ pub fn futex_wait(futex: &AtomicU32, expected: u32, timeout: Option<Duration>) -
// identical. It supports absolute timeouts through a flag
// in the _umtx_time struct.
let umtx_timeout = timespec.map(|t| libc::_umtx_time {
_timeout: t.t,
_timeout: t,
_flags: libc::UMTX_ABSTIME,
_clockid: libc::CLOCK_MONOTONIC as u32,
});
@ -62,7 +63,7 @@ pub fn futex_wait(futex: &AtomicU32, expected: u32, timeout: Option<Duration>) -
futex as *const AtomicU32,
libc::FUTEX_WAIT_BITSET | libc::FUTEX_PRIVATE_FLAG,
expected,
timespec.as_ref().map_or(null(), |t| &t.t as *const libc::timespec),
timespec.as_ref().map_or(null(), |t| t as *const libc::timespec),
null::<u32>(), // This argument is unused for FUTEX_WAIT_BITSET.
!0u32, // A full bitmask, to make it behave like a regular FUTEX_WAIT.
)

View file

@ -79,7 +79,8 @@ unsafe fn wait_timeout(
(Timespec::now(libc::CLOCK_MONOTONIC), dur)
};
let timeout = now.checked_add_duration(&dur).map(|t| t.t).unwrap_or(TIMESPEC_MAX);
let timeout =
now.checked_add_duration(&dur).and_then(|t| t.to_timespec()).unwrap_or(TIMESPEC_MAX);
let r = libc::pthread_cond_timedwait(cond, lock, &timeout);
debug_assert!(r == libc::ETIMEDOUT || r == 0);
}

View file

@ -1,21 +1,63 @@
use crate::cmp::Ordering;
use crate::fmt;
use crate::time::Duration;
use core::hash::{Hash, Hasher};
pub use self::inner::{Instant, SystemTime, UNIX_EPOCH};
pub use self::inner::Instant;
use crate::convert::TryInto;
const NSEC_PER_SEC: u64 = 1_000_000_000;
pub const UNIX_EPOCH: SystemTime = SystemTime { t: Timespec::zero() };
#[derive(Copy, Clone)]
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SystemTime {
pub(in crate::sys::unix) t: Timespec,
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(in crate::sys::unix) struct Timespec {
pub t: libc::timespec,
tv_sec: i64,
tv_nsec: i64,
}
impl SystemTime {
pub fn new(tv_sec: i64, tv_nsec: i64) -> SystemTime {
SystemTime { t: Timespec::new(tv_sec, tv_nsec) }
}
pub fn sub_time(&self, other: &SystemTime) -> Result<Duration, Duration> {
self.t.sub_timespec(&other.t)
}
pub fn checked_add_duration(&self, other: &Duration) -> Option<SystemTime> {
Some(SystemTime { t: self.t.checked_add_duration(other)? })
}
pub fn checked_sub_duration(&self, other: &Duration) -> Option<SystemTime> {
Some(SystemTime { t: self.t.checked_sub_duration(other)? })
}
}
impl From<libc::timespec> for SystemTime {
fn from(t: libc::timespec) -> SystemTime {
SystemTime { t: Timespec::from(t) }
}
}
impl fmt::Debug for SystemTime {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SystemTime")
.field("tv_sec", &self.t.tv_sec)
.field("tv_nsec", &self.t.tv_nsec)
.finish()
}
}
impl Timespec {
const fn zero() -> Timespec {
Timespec { t: libc::timespec { tv_sec: 0, tv_nsec: 0 } }
Timespec { tv_sec: 0, tv_nsec: 0 }
}
fn new(tv_sec: i64, tv_nsec: i64) -> Timespec {
Timespec { tv_sec, tv_nsec }
}
pub fn sub_timespec(&self, other: &Timespec) -> Result<Duration, Duration> {
@ -23,22 +65,22 @@ impl Timespec {
// NOTE(eddyb) two aspects of this `if`-`else` are required for LLVM
// to optimize it into a branchless form (see also #75545):
//
// 1. `self.t.tv_sec - other.t.tv_sec` shows up as a common expression
// 1. `self.tv_sec - other.tv_sec` shows up as a common expression
// in both branches, i.e. the `else` must have its `- 1`
// subtraction after the common one, not interleaved with it
// (it used to be `self.t.tv_sec - 1 - other.t.tv_sec`)
// (it used to be `self.tv_sec - 1 - other.tv_sec`)
//
// 2. the `Duration::new` call (or any other additional complexity)
// is outside of the `if`-`else`, not duplicated in both branches
//
// Ideally this code could be rearranged such that it more
// directly expresses the lower-cost behavior we want from it.
let (secs, nsec) = if self.t.tv_nsec >= other.t.tv_nsec {
((self.t.tv_sec - other.t.tv_sec) as u64, (self.t.tv_nsec - other.t.tv_nsec) as u32)
let (secs, nsec) = if self.tv_nsec >= other.tv_nsec {
((self.tv_sec - other.tv_sec) as u64, (self.tv_nsec - other.tv_nsec) as u32)
} else {
(
(self.t.tv_sec - other.t.tv_sec - 1) as u64,
self.t.tv_nsec as u32 + (NSEC_PER_SEC as u32) - other.t.tv_nsec as u32,
(self.tv_sec - other.tv_sec - 1) as u64,
self.tv_nsec as u32 + (NSEC_PER_SEC as u32) - other.tv_nsec as u32,
)
};
@ -54,89 +96,65 @@ impl Timespec {
pub fn checked_add_duration(&self, other: &Duration) -> Option<Timespec> {
let mut secs = other
.as_secs()
.try_into() // <- target type would be `libc::time_t`
.try_into() // <- target type would be `i64`
.ok()
.and_then(|secs| self.t.tv_sec.checked_add(secs))?;
.and_then(|secs| self.tv_sec.checked_add(secs))?;
// Nano calculations can't overflow because nanos are <1B which fit
// in a u32.
let mut nsec = other.subsec_nanos() + self.t.tv_nsec as u32;
let mut nsec = other.subsec_nanos() + self.tv_nsec as u32;
if nsec >= NSEC_PER_SEC as u32 {
nsec -= NSEC_PER_SEC as u32;
secs = secs.checked_add(1)?;
}
Some(Timespec { t: libc::timespec { tv_sec: secs, tv_nsec: nsec as _ } })
Some(Timespec::new(secs, nsec as i64))
}
pub fn checked_sub_duration(&self, other: &Duration) -> Option<Timespec> {
let mut secs = other
.as_secs()
.try_into() // <- target type would be `libc::time_t`
.try_into() // <- target type would be `i64`
.ok()
.and_then(|secs| self.t.tv_sec.checked_sub(secs))?;
.and_then(|secs| self.tv_sec.checked_sub(secs))?;
// Similar to above, nanos can't overflow.
let mut nsec = self.t.tv_nsec as i32 - other.subsec_nanos() as i32;
let mut nsec = self.tv_nsec as i32 - other.subsec_nanos() as i32;
if nsec < 0 {
nsec += NSEC_PER_SEC as i32;
secs = secs.checked_sub(1)?;
}
Some(Timespec { t: libc::timespec { tv_sec: secs, tv_nsec: nsec as _ } })
Some(Timespec::new(secs, nsec as i64))
}
pub fn to_timespec(&self) -> Option<libc::timespec> {
use crate::convert::TryInto;
Some(libc::timespec {
tv_sec: self.tv_sec.try_into().ok()?,
tv_nsec: self.tv_nsec.try_into().ok()?,
})
}
}
impl PartialEq for Timespec {
fn eq(&self, other: &Timespec) -> bool {
self.t.tv_sec == other.t.tv_sec && self.t.tv_nsec == other.t.tv_nsec
}
}
impl Eq for Timespec {}
impl PartialOrd for Timespec {
fn partial_cmp(&self, other: &Timespec) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Timespec {
fn cmp(&self, other: &Timespec) -> Ordering {
let me = (self.t.tv_sec, self.t.tv_nsec);
let other = (other.t.tv_sec, other.t.tv_nsec);
me.cmp(&other)
}
}
impl Hash for Timespec {
fn hash<H: Hasher>(&self, state: &mut H) {
self.t.tv_sec.hash(state);
self.t.tv_nsec.hash(state);
impl From<libc::timespec> for Timespec {
fn from(t: libc::timespec) -> Timespec {
Timespec::new(t.tv_sec as i64, t.tv_nsec as i64)
}
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
mod inner {
use crate::fmt;
use crate::sync::atomic::{AtomicU64, Ordering};
use crate::sys::cvt;
use crate::sys_common::mul_div_u64;
use crate::time::Duration;
use super::Timespec;
use super::NSEC_PER_SEC;
use super::{SystemTime, Timespec, NSEC_PER_SEC};
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
pub struct Instant {
t: u64,
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SystemTime {
pub(in crate::sys::unix) t: Timespec,
}
pub const UNIX_EPOCH: SystemTime = SystemTime { t: Timespec::zero() };
#[repr(C)]
#[derive(Copy, Clone)]
struct mach_timebase_info {
@ -178,41 +196,17 @@ mod inner {
cvt(unsafe { libc::gettimeofday(&mut s, ptr::null_mut()) }).unwrap();
return SystemTime::from(s);
}
}
pub fn sub_time(&self, other: &SystemTime) -> Result<Duration, Duration> {
self.t.sub_timespec(&other.t)
}
pub fn checked_add_duration(&self, other: &Duration) -> Option<SystemTime> {
Some(SystemTime { t: self.t.checked_add_duration(other)? })
}
pub fn checked_sub_duration(&self, other: &Duration) -> Option<SystemTime> {
Some(SystemTime { t: self.t.checked_sub_duration(other)? })
impl From<libc::timeval> for Timespec {
fn from(t: libc::timeval) -> Timespec {
Timespec::new(t.tv_sec as i64, 1000 * t.tv_usec as i64)
}
}
impl From<libc::timeval> for SystemTime {
fn from(t: libc::timeval) -> SystemTime {
SystemTime::from(libc::timespec {
tv_sec: t.tv_sec,
tv_nsec: (t.tv_usec * 1000) as libc::c_long,
})
}
}
impl From<libc::timespec> for SystemTime {
fn from(t: libc::timespec) -> SystemTime {
SystemTime { t: Timespec { t } }
}
}
impl fmt::Debug for SystemTime {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SystemTime")
.field("tv_sec", &self.t.t.tv_sec)
.field("tv_nsec", &self.t.t.tv_nsec)
.finish()
SystemTime { t: Timespec::from(t) }
}
}
@ -270,20 +264,13 @@ mod inner {
use crate::sys::cvt;
use crate::time::Duration;
use super::Timespec;
use super::{SystemTime, Timespec};
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Instant {
t: Timespec,
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SystemTime {
pub(in crate::sys::unix) t: Timespec,
}
pub const UNIX_EPOCH: SystemTime = SystemTime { t: Timespec::zero() };
impl Instant {
pub fn now() -> Instant {
Instant { t: Timespec::now(libc::CLOCK_MONOTONIC) }
@ -305,8 +292,8 @@ mod inner {
impl fmt::Debug for Instant {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Instant")
.field("tv_sec", &self.t.t.tv_sec)
.field("tv_nsec", &self.t.t.tv_nsec)
.field("tv_sec", &self.t.tv_sec)
.field("tv_nsec", &self.t.tv_nsec)
.finish()
}
}
@ -315,33 +302,6 @@ mod inner {
pub fn now() -> SystemTime {
SystemTime { t: Timespec::now(libc::CLOCK_REALTIME) }
}
pub fn sub_time(&self, other: &SystemTime) -> Result<Duration, Duration> {
self.t.sub_timespec(&other.t)
}
pub fn checked_add_duration(&self, other: &Duration) -> Option<SystemTime> {
Some(SystemTime { t: self.t.checked_add_duration(other)? })
}
pub fn checked_sub_duration(&self, other: &Duration) -> Option<SystemTime> {
Some(SystemTime { t: self.t.checked_sub_duration(other)? })
}
}
impl From<libc::timespec> for SystemTime {
fn from(t: libc::timespec) -> SystemTime {
SystemTime { t: Timespec { t } }
}
}
impl fmt::Debug for SystemTime {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SystemTime")
.field("tv_sec", &self.t.t.tv_sec)
.field("tv_nsec", &self.t.t.tv_nsec)
.finish()
}
}
#[cfg(not(any(target_os = "dragonfly", target_os = "espidf")))]
@ -351,9 +311,36 @@ mod inner {
impl Timespec {
pub fn now(clock: clock_t) -> Timespec {
// Try to use 64-bit time in preparation for Y2038.
#[cfg(all(target_os = "linux", target_env = "gnu", target_pointer_width = "32"))]
{
use crate::sys::weak::weak;
// __clock_gettime64 was added to 32-bit arches in glibc 2.34,
// and it handles both vDSO calls and ENOSYS fallbacks itself.
weak!(fn __clock_gettime64(libc::clockid_t, *mut __timespec64) -> libc::c_int);
#[repr(C)]
struct __timespec64 {
tv_sec: i64,
#[cfg(target_endian = "big")]
_padding: i32,
tv_nsec: i32,
#[cfg(target_endian = "little")]
_padding: i32,
}
if let Some(clock_gettime64) = __clock_gettime64.get() {
let mut t = MaybeUninit::uninit();
cvt(unsafe { clock_gettime64(clock, t.as_mut_ptr()) }).unwrap();
let t = unsafe { t.assume_init() };
return Timespec { tv_sec: t.tv_sec, tv_nsec: t.tv_nsec as i64 };
}
}
let mut t = MaybeUninit::uninit();
cvt(unsafe { libc::clock_gettime(clock, t.as_mut_ptr()) }).unwrap();
Timespec { t: unsafe { t.assume_init() } }
Timespec::from(unsafe { t.assume_init() })
}
}
}