Skip to content

Commit 24a0eec

Browse files
committed
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
2 parents f9b2e3c + f967518 commit 24a0eec

File tree

5 files changed

+205
-168
lines changed

5 files changed

+205
-168
lines changed

library/std/src/os/linux/fs.rs

+18-3
Original file line numberDiff line numberDiff line change
@@ -356,19 +356,34 @@ impl MetadataExt for Metadata {
356356
self.as_inner().as_inner().st_size as u64
357357
}
358358
fn st_atime(&self) -> i64 {
359-
self.as_inner().as_inner().st_atime as i64
359+
let file_attr = self.as_inner();
360+
#[cfg(all(target_env = "gnu", target_pointer_width = "32"))]
361+
if let Some(atime) = file_attr.stx_atime() {
362+
return atime.tv_sec;
363+
}
364+
file_attr.as_inner().st_atime as i64
360365
}
361366
fn st_atime_nsec(&self) -> i64 {
362367
self.as_inner().as_inner().st_atime_nsec as i64
363368
}
364369
fn st_mtime(&self) -> i64 {
365-
self.as_inner().as_inner().st_mtime as i64
370+
let file_attr = self.as_inner();
371+
#[cfg(all(target_env = "gnu", target_pointer_width = "32"))]
372+
if let Some(mtime) = file_attr.stx_mtime() {
373+
return mtime.tv_sec;
374+
}
375+
file_attr.as_inner().st_mtime as i64
366376
}
367377
fn st_mtime_nsec(&self) -> i64 {
368378
self.as_inner().as_inner().st_mtime_nsec as i64
369379
}
370380
fn st_ctime(&self) -> i64 {
371-
self.as_inner().as_inner().st_ctime as i64
381+
let file_attr = self.as_inner();
382+
#[cfg(all(target_env = "gnu", target_pointer_width = "32"))]
383+
if let Some(ctime) = file_attr.stx_ctime() {
384+
return ctime.tv_sec;
385+
}
386+
file_attr.as_inner().st_ctime as i64
372387
}
373388
fn st_ctime_nsec(&self) -> i64 {
374389
self.as_inner().as_inner().st_ctime_nsec as i64

library/std/src/sys/unix/fs.rs

+71-38
Original file line numberDiff line numberDiff line change
@@ -113,10 +113,19 @@ cfg_has_statx! {{
113113
// This is needed to check if btime is supported by the filesystem.
114114
stx_mask: u32,
115115
stx_btime: libc::statx_timestamp,
116+
// With statx, we can overcome 32-bit `time_t` too.
117+
#[cfg(target_pointer_width = "32")]
118+
stx_atime: libc::statx_timestamp,
119+
#[cfg(target_pointer_width = "32")]
120+
stx_ctime: libc::statx_timestamp,
121+
#[cfg(target_pointer_width = "32")]
122+
stx_mtime: libc::statx_timestamp,
123+
116124
}
117125

118-
// We prefer `statx` on Linux if available, which contains file creation time.
119-
// Default `stat64` contains no creation time.
126+
// We prefer `statx` on Linux if available, which contains file creation time,
127+
// as well as 64-bit timestamps of all kinds.
128+
// Default `stat64` contains no creation time and may have 32-bit `time_t`.
120129
unsafe fn try_statx(
121130
fd: c_int,
122131
path: *const c_char,
@@ -192,6 +201,13 @@ cfg_has_statx! {{
192201
let extra = StatxExtraFields {
193202
stx_mask: buf.stx_mask,
194203
stx_btime: buf.stx_btime,
204+
// Store full times to avoid 32-bit `time_t` truncation.
205+
#[cfg(target_pointer_width = "32")]
206+
stx_atime: buf.stx_atime,
207+
#[cfg(target_pointer_width = "32")]
208+
stx_ctime: buf.stx_ctime,
209+
#[cfg(target_pointer_width = "32")]
210+
stx_mtime: buf.stx_mtime,
195211
};
196212

197213
Some(Ok(FileAttr { stat, statx_extra_fields: Some(extra) }))
@@ -310,6 +326,36 @@ cfg_has_statx! {{
310326
fn from_stat64(stat: stat64) -> Self {
311327
Self { stat, statx_extra_fields: None }
312328
}
329+
330+
#[cfg(target_pointer_width = "32")]
331+
pub fn stx_mtime(&self) -> Option<&libc::statx_timestamp> {
332+
if let Some(ext) = &self.statx_extra_fields {
333+
if (ext.stx_mask & libc::STATX_MTIME) != 0 {
334+
return Some(&ext.stx_mtime);
335+
}
336+
}
337+
None
338+
}
339+
340+
#[cfg(target_pointer_width = "32")]
341+
pub fn stx_atime(&self) -> Option<&libc::statx_timestamp> {
342+
if let Some(ext) = &self.statx_extra_fields {
343+
if (ext.stx_mask & libc::STATX_ATIME) != 0 {
344+
return Some(&ext.stx_atime);
345+
}
346+
}
347+
None
348+
}
349+
350+
#[cfg(target_pointer_width = "32")]
351+
pub fn stx_ctime(&self) -> Option<&libc::statx_timestamp> {
352+
if let Some(ext) = &self.statx_extra_fields {
353+
if (ext.stx_mask & libc::STATX_CTIME) != 0 {
354+
return Some(&ext.stx_ctime);
355+
}
356+
}
357+
None
358+
}
313359
}
314360
} else {
315361
impl FileAttr {
@@ -335,59 +381,52 @@ impl FileAttr {
335381
#[cfg(target_os = "netbsd")]
336382
impl FileAttr {
337383
pub fn modified(&self) -> io::Result<SystemTime> {
338-
Ok(SystemTime::from(libc::timespec {
339-
tv_sec: self.stat.st_mtime as libc::time_t,
340-
tv_nsec: self.stat.st_mtimensec as libc::c_long,
341-
}))
384+
Ok(SystemTime::new(self.stat.st_mtime as i64, self.stat.st_mtimensec as i64))
342385
}
343386

344387
pub fn accessed(&self) -> io::Result<SystemTime> {
345-
Ok(SystemTime::from(libc::timespec {
346-
tv_sec: self.stat.st_atime as libc::time_t,
347-
tv_nsec: self.stat.st_atimensec as libc::c_long,
348-
}))
388+
Ok(SystemTime::new(self.stat.st_atime as i64, self.stat.st_atimensec as i64))
349389
}
350390

351391
pub fn created(&self) -> io::Result<SystemTime> {
352-
Ok(SystemTime::from(libc::timespec {
353-
tv_sec: self.stat.st_birthtime as libc::time_t,
354-
tv_nsec: self.stat.st_birthtimensec as libc::c_long,
355-
}))
392+
Ok(SystemTime::new(self.stat.st_birthtime as i64, self.stat.st_birthtimensec as i64))
356393
}
357394
}
358395

359396
#[cfg(not(target_os = "netbsd"))]
360397
impl FileAttr {
361398
#[cfg(all(not(target_os = "vxworks"), not(target_os = "espidf")))]
362399
pub fn modified(&self) -> io::Result<SystemTime> {
363-
Ok(SystemTime::from(libc::timespec {
364-
tv_sec: self.stat.st_mtime as libc::time_t,
365-
tv_nsec: self.stat.st_mtime_nsec as _,
366-
}))
400+
#[cfg(target_pointer_width = "32")]
401+
cfg_has_statx! {
402+
if let Some(mtime) = self.stx_mtime() {
403+
return Ok(SystemTime::new(mtime.tv_sec, mtime.tv_nsec as i64));
404+
}
405+
}
406+
407+
Ok(SystemTime::new(self.stat.st_mtime as i64, self.stat.st_mtime_nsec as i64))
367408
}
368409

369410
#[cfg(any(target_os = "vxworks", target_os = "espidf"))]
370411
pub fn modified(&self) -> io::Result<SystemTime> {
371-
Ok(SystemTime::from(libc::timespec {
372-
tv_sec: self.stat.st_mtime as libc::time_t,
373-
tv_nsec: 0,
374-
}))
412+
Ok(SystemTime::new(self.stat.st_mtime as i64, 0))
375413
}
376414

377415
#[cfg(all(not(target_os = "vxworks"), not(target_os = "espidf")))]
378416
pub fn accessed(&self) -> io::Result<SystemTime> {
379-
Ok(SystemTime::from(libc::timespec {
380-
tv_sec: self.stat.st_atime as libc::time_t,
381-
tv_nsec: self.stat.st_atime_nsec as _,
382-
}))
417+
#[cfg(target_pointer_width = "32")]
418+
cfg_has_statx! {
419+
if let Some(atime) = self.stx_atime() {
420+
return Ok(SystemTime::new(atime.tv_sec, atime.tv_nsec as i64));
421+
}
422+
}
423+
424+
Ok(SystemTime::new(self.stat.st_atime as i64, self.stat.st_atime_nsec as i64))
383425
}
384426

385427
#[cfg(any(target_os = "vxworks", target_os = "espidf"))]
386428
pub fn accessed(&self) -> io::Result<SystemTime> {
387-
Ok(SystemTime::from(libc::timespec {
388-
tv_sec: self.stat.st_atime as libc::time_t,
389-
tv_nsec: 0,
390-
}))
429+
Ok(SystemTime::new(self.stat.st_atime as i64, 0))
391430
}
392431

393432
#[cfg(any(
@@ -397,10 +436,7 @@ impl FileAttr {
397436
target_os = "ios"
398437
))]
399438
pub fn created(&self) -> io::Result<SystemTime> {
400-
Ok(SystemTime::from(libc::timespec {
401-
tv_sec: self.stat.st_birthtime as libc::time_t,
402-
tv_nsec: self.stat.st_birthtime_nsec as libc::c_long,
403-
}))
439+
Ok(SystemTime::new(self.stat.st_birthtime as i64, self.stat.st_birthtime_nsec as i64))
404440
}
405441

406442
#[cfg(not(any(
@@ -413,10 +449,7 @@ impl FileAttr {
413449
cfg_has_statx! {
414450
if let Some(ext) = &self.statx_extra_fields {
415451
return if (ext.stx_mask & libc::STATX_BTIME) != 0 {
416-
Ok(SystemTime::from(libc::timespec {
417-
tv_sec: ext.stx_btime.tv_sec as libc::time_t,
418-
tv_nsec: ext.stx_btime.tv_nsec as _,
419-
}))
452+
Ok(SystemTime::new(ext.stx_btime.tv_sec, ext.stx_btime.tv_nsec as i64))
420453
} else {
421454
Err(io::const_io_error!(
422455
io::ErrorKind::Uncategorized,

library/std/src/sys/unix/futex.rs

+5-4
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@ pub fn futex_wait(futex: &AtomicU32, expected: u32, timeout: Option<Duration>) -
2424
// Calculate the timeout as an absolute timespec.
2525
//
2626
// Overflows are rounded up to an infinite timeout (None).
27-
let timespec =
28-
timeout.and_then(|d| Some(Timespec::now(libc::CLOCK_MONOTONIC).checked_add_duration(&d)?));
27+
let timespec = timeout
28+
.and_then(|d| Some(Timespec::now(libc::CLOCK_MONOTONIC).checked_add_duration(&d)?))
29+
.and_then(|t| t.to_timespec());
2930

3031
loop {
3132
// No need to wait if the value already changed.
@@ -41,7 +42,7 @@ pub fn futex_wait(futex: &AtomicU32, expected: u32, timeout: Option<Duration>) -
4142
// identical. It supports absolute timeouts through a flag
4243
// in the _umtx_time struct.
4344
let umtx_timeout = timespec.map(|t| libc::_umtx_time {
44-
_timeout: t.t,
45+
_timeout: t,
4546
_flags: libc::UMTX_ABSTIME,
4647
_clockid: libc::CLOCK_MONOTONIC as u32,
4748
});
@@ -62,7 +63,7 @@ pub fn futex_wait(futex: &AtomicU32, expected: u32, timeout: Option<Duration>) -
6263
futex as *const AtomicU32,
6364
libc::FUTEX_WAIT_BITSET | libc::FUTEX_PRIVATE_FLAG,
6465
expected,
65-
timespec.as_ref().map_or(null(), |t| &t.t as *const libc::timespec),
66+
timespec.as_ref().map_or(null(), |t| t as *const libc::timespec),
6667
null::<u32>(), // This argument is unused for FUTEX_WAIT_BITSET.
6768
!0u32, // A full bitmask, to make it behave like a regular FUTEX_WAIT.
6869
)

library/std/src/sys/unix/thread_parker.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@ unsafe fn wait_timeout(
7979
(Timespec::now(libc::CLOCK_MONOTONIC), dur)
8080
};
8181

82-
let timeout = now.checked_add_duration(&dur).map(|t| t.t).unwrap_or(TIMESPEC_MAX);
82+
let timeout =
83+
now.checked_add_duration(&dur).and_then(|t| t.to_timespec()).unwrap_or(TIMESPEC_MAX);
8384
let r = libc::pthread_cond_timedwait(cond, lock, &timeout);
8485
debug_assert!(r == libc::ETIMEDOUT || r == 0);
8586
}

0 commit comments

Comments
 (0)