Skip to content

Commit

Permalink
Work around a negative timestamps bug on macOS.
Browse files Browse the repository at this point in the history
As described [here], macOS can sometimes return `timespec` values
with negative `tv_nsecs` values. Adjust `timespec` values as needed
to ensure that `tv_nsec` is in 0..1_000_000_000.

[here]: rust-lang/rust#108277 (comment)
  • Loading branch information
sunfishcode committed Feb 1, 2025
1 parent 40733be commit e24dc76
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 7 deletions.
29 changes: 25 additions & 4 deletions src/backend/libc/fs/syscalls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -613,7 +613,10 @@ pub(crate) fn stat(path: &CStr) -> io::Result<Stat> {
unsafe {
let mut stat = MaybeUninit::<Stat>::uninit();
ret(c::stat(c_str(path), stat.as_mut_ptr()))?;
Ok(stat.assume_init())
let stat = stat.assume_init();
#[cfg(apple)]
let stat = fix_negative_stat_nsecs(stat);
Ok(stat)
}
}

Expand Down Expand Up @@ -653,7 +656,10 @@ pub(crate) fn lstat(path: &CStr) -> io::Result<Stat> {
unsafe {
let mut stat = MaybeUninit::<Stat>::uninit();
ret(c::lstat(c_str(path), stat.as_mut_ptr()))?;
Ok(stat.assume_init())
let stat = stat.assume_init();
#[cfg(apple)]
let stat = fix_negative_stat_nsecs(stat);
Ok(stat)
}
}

Expand Down Expand Up @@ -694,7 +700,10 @@ pub(crate) fn statat(dirfd: BorrowedFd<'_>, path: &CStr, flags: AtFlags) -> io::
stat.as_mut_ptr(),
bitflags_bits!(flags),
))?;
Ok(stat.assume_init())
let stat = stat.assume_init();
#[cfg(apple)]
let stat = fix_negative_stat_nsecs(stat);
Ok(stat)
}
}

Expand Down Expand Up @@ -1445,7 +1454,10 @@ pub(crate) fn fstat(fd: BorrowedFd<'_>) -> io::Result<Stat> {
unsafe {
let mut stat = MaybeUninit::<Stat>::uninit();
ret(c::fstat(borrowed_fd(fd), stat.as_mut_ptr()))?;
Ok(stat.assume_init())
let stat = stat.assume_init();
#[cfg(apple)]
let stat = fix_negative_stat_nsecs(stat);
Ok(stat)
}
}

Expand Down Expand Up @@ -2553,6 +2565,15 @@ pub(crate) fn fremovexattr(fd: BorrowedFd<'_>, name: &CStr) -> io::Result<()> {
}
}

/// See [`crate::timespec::fix_negative_nsec`] for details.
#[cfg(apple)]
fn fix_negative_stat_nsecs(mut stat: Stat) -> Stat {
crate::timespec::fix_negative_nsecs(&mut stat.st_atime, &mut stat.st_atime_nsec);
crate::timespec::fix_negative_nsecs(&mut stat.st_mtime, &mut stat.st_mtime_nsec);
crate::timespec::fix_negative_nsecs(&mut stat.st_ctime, &mut stat.st_ctime_nsec);
stat
}

#[test]
fn test_sizes() {
#[cfg(linux_kernel)]
Expand Down
18 changes: 15 additions & 3 deletions src/backend/libc/time/syscalls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,10 @@ pub(crate) fn clock_gettime(id: ClockId) -> Timespec {
as_libc_timespec_mut_ptr(&mut timespec),
))
.unwrap();
timespec.assume_init()
let timespec = timespec.assume_init();
#[cfg(apple)]
let timespec = fix_negative_timespec_nsecs(timespec);
timespec
}
}

Expand Down Expand Up @@ -220,8 +223,10 @@ pub(crate) fn clock_gettime_dynamic(id: DynamicClockId<'_>) -> io::Result<Timesp
id as c::clockid_t,
as_libc_timespec_mut_ptr(&mut timespec),
))?;

Ok(timespec.assume_init())
let timespec = timespec.assume_init();
#[cfg(apple)]
let timespec = fix_negative_timespec_nsecs(timespec);
Ok(timespec)
}
}

Expand Down Expand Up @@ -476,3 +481,10 @@ fn timerfd_gettime_old(fd: BorrowedFd<'_>) -> io::Result<Itimerspec> {
},
})
}

/// See [`crate::timespec::fix_negative_nsecs`] for details.
#[cfg(apple)]
fn fix_negative_timespec_nsecs(mut ts: Timespec) -> Timespec {
crate::timespec::fix_negative_nsecs(&mut ts.tv_sec, &mut ts.tv_nsec);
ts
}
35 changes: 35 additions & 0 deletions src/timespec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,41 @@ pub(crate) fn option_as_libc_timespec_ptr(timespec: Option<&Timespec>) -> *const
}
}

/// As described [here], Apple platforms may return a negative nanoseconds
/// value in some cases; adjust it so that nanoseconds is always in
/// `0..1_000_000_000`.
///
/// [here]: https://github.com/rust-lang/rust/issues/108277#issuecomment-1787057158
#[cfg(apple)]
#[inline]
pub(crate) fn fix_negative_nsecs(secs: &mut i64, nsecs: &mut i64) {
if *nsecs < 0 {
adjust(secs, nsecs);
}

#[cold]
fn adjust(secs: &mut i64, nsecs: &mut i64) {
assert!(*nsecs >= -1_000_000_000);
assert!(*secs < 0);
assert!(*secs > i64::MIN);
*nsecs += 1_000_000_000;
*secs -= 1;
}
}

#[cfg(apple)]
#[test]
fn test_negative_timestamps() {
let mut secs = -59;
let mut nsecs = -900_000_000;
fix_negative_nsecs(&mut secs, &mut nsecs);
assert_eq!(secs, -60);
assert_eq!(nsecs, 100_000_000);
fix_negative_nsecs(&mut secs, &mut nsecs);
assert_eq!(secs, -60);
assert_eq!(nsecs, 100_000_000);
}

#[test]
fn test_sizes() {
assert_eq_size!(Secs, u64);
Expand Down
1 change: 1 addition & 0 deletions tests/fs/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ mod long_paths;
mod makedev;
mod mkdirat;
mod mknodat;
mod negative_timestamp;
#[cfg(linux_kernel)]
mod openat;
#[cfg(linux_kernel)]
Expand Down
52 changes: 52 additions & 0 deletions tests/fs/negative_timestamp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#[test]
fn negative_file_timetamp() {
use rustix::fs::{
fstat, futimens, lstat, open, stat, statat, AtFlags, Mode, OFlags, Timespec, Timestamps,
CWD,
};

let tmp = tempfile::tempdir().unwrap();

let file = open(
tmp.path().join("foo"),
OFlags::CREATE | OFlags::WRONLY,
Mode::RWXU,
)
.unwrap();

let stamps = Timestamps {
last_modification: Timespec {
tv_sec: -20,
tv_nsec: 12,
},
last_access: Timespec {
tv_sec: -23,
tv_nsec: 14,
},
};
futimens(&file, &stamps).unwrap();

let st = fstat(file).unwrap();
assert_eq!(st.st_mtime, -20);
assert_eq!(st.st_mtime_nsec, 12);
assert_eq!(st.st_atime, -23);
assert_eq!(st.st_atime_nsec, 14);

let st = stat(tmp.path().join("foo")).unwrap();
assert_eq!(st.st_mtime, -20);
assert_eq!(st.st_mtime_nsec, 12);
assert_eq!(st.st_atime, -23);
assert_eq!(st.st_atime_nsec, 14);

let st = lstat(tmp.path().join("foo")).unwrap();
assert_eq!(st.st_mtime, -20);
assert_eq!(st.st_mtime_nsec, 12);
assert_eq!(st.st_atime, -23);
assert_eq!(st.st_atime_nsec, 14);

let st = statat(CWD, tmp.path().join("foo"), AtFlags::empty()).unwrap();
assert_eq!(st.st_mtime, -20);
assert_eq!(st.st_mtime_nsec, 12);
assert_eq!(st.st_atime, -23);
assert_eq!(st.st_atime_nsec, 14);
}

0 comments on commit e24dc76

Please sign in to comment.