Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for pre-unix-epoch file dates on Apple platforms (#108277) #117451

Merged
merged 1 commit into from Nov 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
42 changes: 42 additions & 0 deletions library/std/src/fs/tests.rs
Expand Up @@ -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() {
Expand Down
24 changes: 24 additions & 0 deletions library/std/src/sys/unix/time.rs
Expand Up @@ -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(
joshtriplett marked this conversation as resolved.
Show resolved Hide resolved
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)
};
joshtriplett marked this conversation as resolved.
Show resolved Hide resolved
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) } }
Expand Down