Skip to content

Commit de73972

Browse files
authored
Fix statx created times when unavailable (#268)
* Fix statx created times when unavailable This previously returned `Some(UNIX_EPOCH)` instead of `None` when `statx` did not return a created time. * Loosen fs_additional test assertion for created times This makes it OK to return file created times even if std doesn't, which is currently the case for musl builds. * Check `statx` mask to see if `mtime` and `atime` were returned, too `std` does these checks, so cap-std should too. `std` only does the checks for 32-bit targets, but they don't seem harmful to enable for all targets.
1 parent 21382e5 commit de73972

File tree

2 files changed

+56
-16
lines changed

2 files changed

+56
-16
lines changed

cap-primitives/src/rustix/fs/metadata_ext.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
use crate::fs::{FileTypeExt, Metadata, PermissionsExt};
44
use crate::time::{Duration, SystemClock, SystemTime};
55
#[cfg(any(target_os = "android", target_os = "linux"))]
6-
use rustix::fs::{makedev, Statx};
6+
use rustix::fs::{makedev, Statx, StatxFlags};
77
use rustix::fs::{RawMode, Stat};
88
use std::convert::{TryFrom, TryInto};
99
use std::{fs, io};
@@ -226,9 +226,21 @@ impl MetadataExt {
226226
file_type: FileTypeExt::from_raw_mode(RawMode::from(statx.stx_mode)),
227227
len: u64::try_from(statx.stx_size).unwrap(),
228228
permissions: PermissionsExt::from_raw_mode(RawMode::from(statx.stx_mode)),
229-
modified: system_time_from_rustix(statx.stx_mtime.tv_sec, statx.stx_mtime.tv_nsec as _),
230-
accessed: system_time_from_rustix(statx.stx_atime.tv_sec, statx.stx_atime.tv_nsec as _),
231-
created: system_time_from_rustix(statx.stx_btime.tv_sec, statx.stx_btime.tv_nsec as _),
229+
modified: if statx.stx_mask & StatxFlags::MTIME.bits() != 0 {
230+
system_time_from_rustix(statx.stx_mtime.tv_sec, statx.stx_mtime.tv_nsec as _)
231+
} else {
232+
None
233+
},
234+
accessed: if statx.stx_mask & StatxFlags::ATIME.bits() != 0 {
235+
system_time_from_rustix(statx.stx_atime.tv_sec, statx.stx_atime.tv_nsec as _)
236+
} else {
237+
None
238+
},
239+
created: if statx.stx_mask & StatxFlags::BTIME.bits() != 0 {
240+
system_time_from_rustix(statx.stx_btime.tv_sec, statx.stx_btime.tv_nsec as _)
241+
} else {
242+
None
243+
},
232244

233245
ext: Self {
234246
dev: makedev(statx.stx_dev_major, statx.stx_dev_minor),

tests/fs_additional.rs

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
mod sys_common;
77

88
use cap_std::fs::{DirBuilder, OpenOptions};
9+
use cap_std::time::SystemClock;
910
use std::io::{self, Read, Write};
1011
use std::path::Path;
1112
use std::str;
@@ -936,24 +937,51 @@ fn check_metadata(std: &std::fs::Metadata, cap: &cap_std::fs::Metadata) {
936937

937938
// If the standard library supports file modified/accessed/created times,
938939
// then cap-std should too.
939-
if let Ok(expected) = std.modified() {
940-
assert_eq!(expected, check!(cap.modified()).into_std());
940+
match std.modified() {
941+
Ok(expected) => assert_eq!(expected, check!(cap.modified()).into_std()),
942+
Err(e) => assert!(
943+
cap.modified().is_err(),
944+
"modified time should be error ({}), got {:#?}",
945+
e,
946+
cap.modified()
947+
),
941948
}
942949
// The access times might be a little different due to either our own
943950
// or concurrent accesses.
944951
const ACCESS_TOLERANCE_SEC: u32 = 60;
945-
if let Ok(expected) = std.accessed() {
946-
let access_tolerance = std::time::Duration::from_secs(ACCESS_TOLERANCE_SEC.into());
947-
assert!(
948-
((expected - access_tolerance)..(expected + access_tolerance))
949-
.contains(&check!(cap.accessed()).into_std()),
950-
"std accessed {:#?}, cap accessed {:#?}",
951-
expected,
952+
match std.accessed() {
953+
Ok(expected) => {
954+
let access_tolerance = std::time::Duration::from_secs(ACCESS_TOLERANCE_SEC.into());
955+
assert!(
956+
((expected - access_tolerance)..(expected + access_tolerance))
957+
.contains(&check!(cap.accessed()).into_std()),
958+
"std accessed {:#?}, cap accessed {:#?}",
959+
expected,
960+
cap.accessed()
961+
);
962+
}
963+
Err(e) => assert!(
964+
cap.accessed().is_err(),
965+
"accessed time should be error ({}), got {:#?}",
966+
e,
952967
cap.accessed()
953-
);
968+
),
954969
}
955-
if let Ok(expected) = std.created() {
956-
assert_eq!(expected, check!(cap.created()).into_std());
970+
match std.created() {
971+
Ok(expected) => assert_eq!(expected, check!(cap.created()).into_std()),
972+
Err(e) => {
973+
// An earlier bug returned the Unix epoch instead of `None` when
974+
// created times were unavailable. This tries to catch such errors,
975+
// while also allowing some targets to return valid created times
976+
// even when std doesn't.
977+
if let Ok(actual) = cap.created() {
978+
println!(
979+
"std returned error for created time ({}) but got {:#?}",
980+
e, actual
981+
);
982+
assert_ne!(actual, SystemClock::UNIX_EPOCH);
983+
}
984+
}
957985
}
958986

959987
#[cfg(unix)]

0 commit comments

Comments
 (0)