Skip to content

Commit e24dc76

Browse files
committed
Work around a negative timestamps bug on macOS.
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)
1 parent 40733be commit e24dc76

File tree

5 files changed

+128
-7
lines changed

5 files changed

+128
-7
lines changed

src/backend/libc/fs/syscalls.rs

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -613,7 +613,10 @@ pub(crate) fn stat(path: &CStr) -> io::Result<Stat> {
613613
unsafe {
614614
let mut stat = MaybeUninit::<Stat>::uninit();
615615
ret(c::stat(c_str(path), stat.as_mut_ptr()))?;
616-
Ok(stat.assume_init())
616+
let stat = stat.assume_init();
617+
#[cfg(apple)]
618+
let stat = fix_negative_stat_nsecs(stat);
619+
Ok(stat)
617620
}
618621
}
619622

@@ -653,7 +656,10 @@ pub(crate) fn lstat(path: &CStr) -> io::Result<Stat> {
653656
unsafe {
654657
let mut stat = MaybeUninit::<Stat>::uninit();
655658
ret(c::lstat(c_str(path), stat.as_mut_ptr()))?;
656-
Ok(stat.assume_init())
659+
let stat = stat.assume_init();
660+
#[cfg(apple)]
661+
let stat = fix_negative_stat_nsecs(stat);
662+
Ok(stat)
657663
}
658664
}
659665

@@ -694,7 +700,10 @@ pub(crate) fn statat(dirfd: BorrowedFd<'_>, path: &CStr, flags: AtFlags) -> io::
694700
stat.as_mut_ptr(),
695701
bitflags_bits!(flags),
696702
))?;
697-
Ok(stat.assume_init())
703+
let stat = stat.assume_init();
704+
#[cfg(apple)]
705+
let stat = fix_negative_stat_nsecs(stat);
706+
Ok(stat)
698707
}
699708
}
700709

@@ -1445,7 +1454,10 @@ pub(crate) fn fstat(fd: BorrowedFd<'_>) -> io::Result<Stat> {
14451454
unsafe {
14461455
let mut stat = MaybeUninit::<Stat>::uninit();
14471456
ret(c::fstat(borrowed_fd(fd), stat.as_mut_ptr()))?;
1448-
Ok(stat.assume_init())
1457+
let stat = stat.assume_init();
1458+
#[cfg(apple)]
1459+
let stat = fix_negative_stat_nsecs(stat);
1460+
Ok(stat)
14491461
}
14501462
}
14511463

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

2568+
/// See [`crate::timespec::fix_negative_nsec`] for details.
2569+
#[cfg(apple)]
2570+
fn fix_negative_stat_nsecs(mut stat: Stat) -> Stat {
2571+
crate::timespec::fix_negative_nsecs(&mut stat.st_atime, &mut stat.st_atime_nsec);
2572+
crate::timespec::fix_negative_nsecs(&mut stat.st_mtime, &mut stat.st_mtime_nsec);
2573+
crate::timespec::fix_negative_nsecs(&mut stat.st_ctime, &mut stat.st_ctime_nsec);
2574+
stat
2575+
}
2576+
25562577
#[test]
25572578
fn test_sizes() {
25582579
#[cfg(linux_kernel)]

src/backend/libc/time/syscalls.rs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,10 @@ pub(crate) fn clock_gettime(id: ClockId) -> Timespec {
130130
as_libc_timespec_mut_ptr(&mut timespec),
131131
))
132132
.unwrap();
133-
timespec.assume_init()
133+
let timespec = timespec.assume_init();
134+
#[cfg(apple)]
135+
let timespec = fix_negative_timespec_nsecs(timespec);
136+
timespec
134137
}
135138
}
136139

@@ -220,8 +223,10 @@ pub(crate) fn clock_gettime_dynamic(id: DynamicClockId<'_>) -> io::Result<Timesp
220223
id as c::clockid_t,
221224
as_libc_timespec_mut_ptr(&mut timespec),
222225
))?;
223-
224-
Ok(timespec.assume_init())
226+
let timespec = timespec.assume_init();
227+
#[cfg(apple)]
228+
let timespec = fix_negative_timespec_nsecs(timespec);
229+
Ok(timespec)
225230
}
226231
}
227232

@@ -476,3 +481,10 @@ fn timerfd_gettime_old(fd: BorrowedFd<'_>) -> io::Result<Itimerspec> {
476481
},
477482
})
478483
}
484+
485+
/// See [`crate::timespec::fix_negative_nsecs`] for details.
486+
#[cfg(apple)]
487+
fn fix_negative_timespec_nsecs(mut ts: Timespec) -> Timespec {
488+
crate::timespec::fix_negative_nsecs(&mut ts.tv_sec, &mut ts.tv_nsec);
489+
ts
490+
}

src/timespec.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,41 @@ pub(crate) fn option_as_libc_timespec_ptr(timespec: Option<&Timespec>) -> *const
109109
}
110110
}
111111

112+
/// As described [here], Apple platforms may return a negative nanoseconds
113+
/// value in some cases; adjust it so that nanoseconds is always in
114+
/// `0..1_000_000_000`.
115+
///
116+
/// [here]: https://github.com/rust-lang/rust/issues/108277#issuecomment-1787057158
117+
#[cfg(apple)]
118+
#[inline]
119+
pub(crate) fn fix_negative_nsecs(secs: &mut i64, nsecs: &mut i64) {
120+
if *nsecs < 0 {
121+
adjust(secs, nsecs);
122+
}
123+
124+
#[cold]
125+
fn adjust(secs: &mut i64, nsecs: &mut i64) {
126+
assert!(*nsecs >= -1_000_000_000);
127+
assert!(*secs < 0);
128+
assert!(*secs > i64::MIN);
129+
*nsecs += 1_000_000_000;
130+
*secs -= 1;
131+
}
132+
}
133+
134+
#[cfg(apple)]
135+
#[test]
136+
fn test_negative_timestamps() {
137+
let mut secs = -59;
138+
let mut nsecs = -900_000_000;
139+
fix_negative_nsecs(&mut secs, &mut nsecs);
140+
assert_eq!(secs, -60);
141+
assert_eq!(nsecs, 100_000_000);
142+
fix_negative_nsecs(&mut secs, &mut nsecs);
143+
assert_eq!(secs, -60);
144+
assert_eq!(nsecs, 100_000_000);
145+
}
146+
112147
#[test]
113148
fn test_sizes() {
114149
assert_eq_size!(Secs, u64);

tests/fs/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ mod long_paths;
3131
mod makedev;
3232
mod mkdirat;
3333
mod mknodat;
34+
mod negative_timestamp;
3435
#[cfg(linux_kernel)]
3536
mod openat;
3637
#[cfg(linux_kernel)]

tests/fs/negative_timestamp.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#[test]
2+
fn negative_file_timetamp() {
3+
use rustix::fs::{
4+
fstat, futimens, lstat, open, stat, statat, AtFlags, Mode, OFlags, Timespec, Timestamps,
5+
CWD,
6+
};
7+
8+
let tmp = tempfile::tempdir().unwrap();
9+
10+
let file = open(
11+
tmp.path().join("foo"),
12+
OFlags::CREATE | OFlags::WRONLY,
13+
Mode::RWXU,
14+
)
15+
.unwrap();
16+
17+
let stamps = Timestamps {
18+
last_modification: Timespec {
19+
tv_sec: -20,
20+
tv_nsec: 12,
21+
},
22+
last_access: Timespec {
23+
tv_sec: -23,
24+
tv_nsec: 14,
25+
},
26+
};
27+
futimens(&file, &stamps).unwrap();
28+
29+
let st = fstat(file).unwrap();
30+
assert_eq!(st.st_mtime, -20);
31+
assert_eq!(st.st_mtime_nsec, 12);
32+
assert_eq!(st.st_atime, -23);
33+
assert_eq!(st.st_atime_nsec, 14);
34+
35+
let st = stat(tmp.path().join("foo")).unwrap();
36+
assert_eq!(st.st_mtime, -20);
37+
assert_eq!(st.st_mtime_nsec, 12);
38+
assert_eq!(st.st_atime, -23);
39+
assert_eq!(st.st_atime_nsec, 14);
40+
41+
let st = lstat(tmp.path().join("foo")).unwrap();
42+
assert_eq!(st.st_mtime, -20);
43+
assert_eq!(st.st_mtime_nsec, 12);
44+
assert_eq!(st.st_atime, -23);
45+
assert_eq!(st.st_atime_nsec, 14);
46+
47+
let st = statat(CWD, tmp.path().join("foo"), AtFlags::empty()).unwrap();
48+
assert_eq!(st.st_mtime, -20);
49+
assert_eq!(st.st_mtime_nsec, 12);
50+
assert_eq!(st.st_atime, -23);
51+
assert_eq!(st.st_atime_nsec, 14);
52+
}

0 commit comments

Comments
 (0)