mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-02 15:32:06 +00:00
Rollup merge of #117451 - Byron:issue-108277-apple-fix, r=joshtriplett
Add support for pre-unix-epoch file dates on Apple platforms (#108277) Please note that even though the assertion being hit is the same on MacOS and thus similar to what's described in #108277, on MacOS it's possible to convert the numbers such that they are valid, don't hit the assertion and are round-trippable. Doing so effectively fixes the issue on Apple platforms. This PR does not attempt to harden other platforms against negative nanoseconds, which can happen for many reasons including mild filesystem corruption. ---- Time in UNIX system calls counts from the epoch, 1970-01-01. The timespec struct used in various system calls represents this as a number of seconds and a number of nanoseconds. Nanoseconds are required to be between 0 and 999_999_999, because the portion outside that range should be represented in the seconds field; if nanoseconds were larger than 999_999_999, the seconds field should go up instead. Suppose you ask for the time 1969-12-31, what time is that? On UNIX systems that support times before the epoch, that's seconds=-86400, one day before the epoch. But now, suppose you ask for the time 1969-12-31 23:59:00.1. In other words, a tenth of a second after one minute before the epoch. On most UNIX systems, that's represented as seconds=-60, nanoseconds=100_000_000. The macOS bug is that it returns seconds=-59, nanoseconds=-900_000_000. While that's in some sense an accurate description of the time (59.9 seconds before the epoch), that violates the invariant of the timespec data structure: nanoseconds must be between 0 and 999999999. This causes this assertion in the Rust standard library. So, on macOS, if we get a Timespec value with seconds less than or equal to zero, and nanoseconds between -999_999_999 and -1 (inclusive), we can add 1_000_000_000 to the nanoseconds and subtract 1 from the seconds, and then convert. The resulting timespec value is still accepted by macOS, and when fed back into the OS, produces the same results. (If you set a file's mtime with that timestamp, then read it back, you get back the one with negative nanoseconds again.) Co-authored-by: Josh Triplett <josh@joshtriplett.org>
This commit is contained in:
commit
d06200b988
@ -1708,6 +1708,48 @@ fn test_file_times() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "watchos"))]
|
||||
fn test_file_times_pre_epoch_with_nanos() {
|
||||
#[cfg(target_os = "ios")]
|
||||
use crate::os::ios::fs::FileTimesExt;
|
||||
#[cfg(target_os = "macos")]
|
||||
use crate::os::macos::fs::FileTimesExt;
|
||||
#[cfg(target_os = "tvos")]
|
||||
use crate::os::tvos::fs::FileTimesExt;
|
||||
#[cfg(target_os = "watchos")]
|
||||
use crate::os::watchos::fs::FileTimesExt;
|
||||
|
||||
let tmp = tmpdir();
|
||||
let file = File::create(tmp.join("foo")).unwrap();
|
||||
|
||||
for (accessed, modified, created) in [
|
||||
// The first round is to set filetimes to something we know works, but this time
|
||||
// it's validated with nanoseconds as well which probe the numeric boundary.
|
||||
(
|
||||
SystemTime::UNIX_EPOCH + Duration::new(12345, 1),
|
||||
SystemTime::UNIX_EPOCH + Duration::new(54321, 100_000_000),
|
||||
SystemTime::UNIX_EPOCH + Duration::new(32123, 999_999_999),
|
||||
),
|
||||
// The second rounds uses pre-epoch dates along with nanoseconds that probe
|
||||
// the numeric boundary.
|
||||
(
|
||||
SystemTime::UNIX_EPOCH - Duration::new(1, 1),
|
||||
SystemTime::UNIX_EPOCH - Duration::new(60, 100_000_000),
|
||||
SystemTime::UNIX_EPOCH - Duration::new(3600, 999_999_999),
|
||||
),
|
||||
] {
|
||||
let mut times = FileTimes::new();
|
||||
times = times.set_accessed(accessed).set_modified(modified).set_created(created);
|
||||
file.set_times(times).unwrap();
|
||||
|
||||
let metadata = file.metadata().unwrap();
|
||||
assert_eq!(metadata.accessed().unwrap(), accessed);
|
||||
assert_eq!(metadata.modified().unwrap(), modified);
|
||||
assert_eq!(metadata.created().unwrap(), created);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(windows)]
|
||||
fn windows_unix_socket_exists() {
|
||||
|
@ -76,6 +76,30 @@ impl Timespec {
|
||||
}
|
||||
|
||||
const fn new(tv_sec: i64, tv_nsec: i64) -> Timespec {
|
||||
// On Apple OS, dates before epoch are represented differently than on other
|
||||
// Unix platforms: e.g. 1/10th of a second before epoch is represented as `seconds=-1`
|
||||
// and `nanoseconds=100_000_000` on other platforms, but is `seconds=0` and
|
||||
// `nanoseconds=-900_000_000` on Apple OS.
|
||||
//
|
||||
// To compensate, we first detect this special case by checking if both
|
||||
// seconds and nanoseconds are in range, and then correct the value for seconds
|
||||
// and nanoseconds to match the common unix representation.
|
||||
//
|
||||
// Please note that Apple OS nonetheless accepts the standard unix format when
|
||||
// setting file times, which makes this compensation round-trippable and generally
|
||||
// transparent.
|
||||
#[cfg(any(
|
||||
target_os = "macos",
|
||||
target_os = "ios",
|
||||
target_os = "tvos",
|
||||
target_os = "watchos"
|
||||
))]
|
||||
let (tv_sec, tv_nsec) =
|
||||
if (tv_sec <= 0 && tv_sec > i64::MIN) && (tv_nsec < 0 && tv_nsec > -1_000_000_000) {
|
||||
(tv_sec - 1, tv_nsec + 1_000_000_000)
|
||||
} else {
|
||||
(tv_sec, tv_nsec)
|
||||
};
|
||||
assert!(tv_nsec >= 0 && tv_nsec < NSEC_PER_SEC as i64);
|
||||
// SAFETY: The assert above checks tv_nsec is within the valid range
|
||||
Timespec { tv_sec, tv_nsec: unsafe { Nanoseconds(tv_nsec as u32) } }
|
||||
|
Loading…
Reference in New Issue
Block a user