Skip to content

Commit 43f398b

Browse files
committed
Prefer statx on linux if available
1 parent fa0f7d0 commit 43f398b

File tree

2 files changed

+182
-11
lines changed

2 files changed

+182
-11
lines changed

src/libstd/fs.rs

+18-4
Original file line numberDiff line numberDiff line change
@@ -1090,13 +1090,14 @@ impl Metadata {
10901090

10911091
/// Returns the creation time listed in this metadata.
10921092
///
1093-
/// The returned value corresponds to the `birthtime` field of `stat` on
1094-
/// Unix platforms and the `ftCreationTime` field on Windows platforms.
1093+
/// The returned value corresponds to the `btime` field of `statx` on
1094+
/// Linux kernel starting from to 4.11, the `birthtime` field of `stat` on other
1095+
/// Unix platforms, and the `ftCreationTime` field on Windows platforms.
10951096
///
10961097
/// # Errors
10971098
///
10981099
/// This field may not be available on all platforms, and will return an
1099-
/// `Err` on platforms where it is not available.
1100+
/// `Err` on platforms or filesystems where it is not available.
11001101
///
11011102
/// # Examples
11021103
///
@@ -1109,7 +1110,7 @@ impl Metadata {
11091110
/// if let Ok(time) = metadata.created() {
11101111
/// println!("{:?}", time);
11111112
/// } else {
1112-
/// println!("Not supported on this platform");
1113+
/// println!("Not supported on this platform or filesystem");
11131114
/// }
11141115
/// Ok(())
11151116
/// }
@@ -3443,5 +3444,18 @@ mod tests {
34433444
check!(a.created());
34443445
check!(b.created());
34453446
}
3447+
3448+
if cfg!(target_os = "linux") {
3449+
// Not always available
3450+
match (a.created(), b.created()) {
3451+
(Ok(t1), Ok(t2)) => assert!(t1 <= t2),
3452+
(Err(e1), Err(e2)) if e1.kind() == ErrorKind::Other &&
3453+
e2.kind() == ErrorKind::Other => {}
3454+
(a, b) => panic!(
3455+
"creation time must be always supported or not supported: {:?} {:?}",
3456+
a, b,
3457+
),
3458+
}
3459+
}
34463460
}
34473461
}

src/libstd/sys/unix/fs.rs

+164-7
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,84 @@ pub struct File(FileDesc);
4444
#[derive(Clone)]
4545
pub struct FileAttr {
4646
stat: stat64,
47+
#[cfg(target_os = "linux")]
48+
statx_extra_fields: Option<StatxExtraFields>,
49+
}
50+
51+
#[cfg(target_os = "linux")]
52+
#[derive(Clone)]
53+
struct StatxExtraFields {
54+
// This is needed to check if btime is supported by the filesystem.
55+
stx_mask: u32,
56+
stx_btime: libc::statx_timestamp,
57+
}
58+
59+
// We prefer `statx` on Linux if available, which contains file creation time.
60+
// Default `stat64` contains no creation time.
61+
#[cfg(target_os = "linux")]
62+
unsafe fn try_statx(
63+
fd: c_int,
64+
path: *const libc::c_char,
65+
flags: i32,
66+
mask: u32,
67+
) -> Option<io::Result<FileAttr>> {
68+
use crate::sync::atomic::{AtomicBool, Ordering};
69+
70+
// Linux kernel prior to 4.11 or glibc prior to glibc 2.28 don't support `statx`
71+
// We store the availability in a global to avoid unnecessary syscalls
72+
static HAS_STATX: AtomicBool = AtomicBool::new(true);
73+
syscall! {
74+
fn statx(
75+
fd: c_int,
76+
pathname: *const libc::c_char,
77+
flags: c_int,
78+
mask: libc::c_uint,
79+
statxbuf: *mut libc::statx
80+
) -> c_int
81+
}
82+
83+
if !HAS_STATX.load(Ordering::Relaxed) {
84+
return None;
85+
}
86+
87+
let mut buf: libc::statx = mem::zeroed();
88+
let ret = cvt(statx(fd, path, flags, mask, &mut buf));
89+
match ret {
90+
Err(err) => match err.raw_os_error() {
91+
Some(libc::ENOSYS) => {
92+
HAS_STATX.store(false, Ordering::Relaxed);
93+
return None;
94+
}
95+
_ => return Some(Err(err)),
96+
}
97+
Ok(_) => {
98+
// We cannot fill `stat64` exhaustively because of private padding fields.
99+
let mut stat: stat64 = mem::zeroed();
100+
stat.st_dev = libc::makedev(buf.stx_dev_major, buf.stx_dev_minor);
101+
stat.st_ino = buf.stx_ino as libc::ino64_t;
102+
stat.st_nlink = buf.stx_nlink as libc::nlink_t;
103+
stat.st_mode = buf.stx_mode as libc::mode_t;
104+
stat.st_uid = buf.stx_uid as libc::uid_t;
105+
stat.st_gid = buf.stx_gid as libc::gid_t;
106+
stat.st_rdev = libc::makedev(buf.stx_rdev_major, buf.stx_rdev_minor);
107+
stat.st_size = buf.stx_size as off64_t;
108+
stat.st_blksize = buf.stx_blksize as libc::blksize_t;
109+
stat.st_blocks = buf.stx_blocks as libc::blkcnt64_t;
110+
stat.st_atime = buf.stx_atime.tv_sec as libc::time_t;
111+
stat.st_atime_nsec = buf.stx_atime.tv_nsec as libc::c_long;
112+
stat.st_mtime = buf.stx_mtime.tv_sec as libc::time_t;
113+
stat.st_mtime_nsec = buf.stx_mtime.tv_nsec as libc::c_long;
114+
stat.st_ctime = buf.stx_ctime.tv_sec as libc::time_t;
115+
stat.st_ctime_nsec = buf.stx_ctime.tv_nsec as libc::c_long;
116+
117+
let extra = StatxExtraFields {
118+
stx_mask: buf.stx_mask,
119+
stx_btime: buf.stx_btime,
120+
};
121+
122+
Some(Ok(FileAttr { stat, statx_extra_fields: Some(extra) }))
123+
}
124+
}
47125
}
48126

49127
// all DirEntry's will have a reference to this struct
@@ -98,6 +176,14 @@ pub struct FileType { mode: mode_t }
98176
pub struct DirBuilder { mode: mode_t }
99177

100178
impl FileAttr {
179+
fn from_stat64(stat: stat64) -> Self {
180+
Self {
181+
stat,
182+
#[cfg(target_os = "linux")]
183+
statx_extra_fields: None,
184+
}
185+
}
186+
101187
pub fn size(&self) -> u64 { self.stat.st_size as u64 }
102188
pub fn perm(&self) -> FilePermissions {
103189
FilePermissions { mode: (self.stat.st_mode as mode_t) }
@@ -164,6 +250,23 @@ impl FileAttr {
164250
target_os = "macos",
165251
target_os = "ios")))]
166252
pub fn created(&self) -> io::Result<SystemTime> {
253+
#[cfg(target_os = "linux")]
254+
{
255+
if let Some(ext) = &self.statx_extra_fields {
256+
return if (ext.stx_mask & libc::STATX_BTIME) != 0 {
257+
Ok(SystemTime::from(libc::timespec {
258+
tv_sec: ext.stx_btime.tv_sec as libc::time_t,
259+
tv_nsec: ext.stx_btime.tv_nsec as libc::c_long,
260+
}))
261+
} else {
262+
Err(io::Error::new(
263+
io::ErrorKind::Other,
264+
"creation time is not available for the filesystem",
265+
))
266+
};
267+
}
268+
}
269+
167270
Err(io::Error::new(io::ErrorKind::Other,
168271
"creation time is not available on this platform \
169272
currently"))
@@ -306,12 +409,26 @@ impl DirEntry {
306409

307410
#[cfg(any(target_os = "linux", target_os = "emscripten", target_os = "android"))]
308411
pub fn metadata(&self) -> io::Result<FileAttr> {
309-
let fd = cvt(unsafe {dirfd(self.dir.inner.dirp.0)})?;
412+
let fd = cvt(unsafe { dirfd(self.dir.inner.dirp.0) })?;
413+
let name = self.entry.d_name.as_ptr();
414+
415+
#[cfg(target_os = "linux")]
416+
{
417+
if let Some(ret) = unsafe { try_statx(
418+
fd,
419+
name,
420+
libc::AT_SYMLINK_NOFOLLOW | libc::AT_STATX_SYNC_AS_STAT,
421+
libc::STATX_ALL,
422+
) } {
423+
return ret;
424+
}
425+
}
426+
310427
let mut stat: stat64 = unsafe { mem::zeroed() };
311428
cvt(unsafe {
312-
fstatat64(fd, self.entry.d_name.as_ptr(), &mut stat, libc::AT_SYMLINK_NOFOLLOW)
429+
fstatat64(fd, name, &mut stat, libc::AT_SYMLINK_NOFOLLOW)
313430
})?;
314-
Ok(FileAttr { stat })
431+
Ok(FileAttr::from_stat64(stat))
315432
}
316433

317434
#[cfg(not(any(target_os = "linux", target_os = "emscripten", target_os = "android")))]
@@ -517,11 +634,25 @@ impl File {
517634
}
518635

519636
pub fn file_attr(&self) -> io::Result<FileAttr> {
637+
let fd = self.0.raw();
638+
639+
#[cfg(target_os = "linux")]
640+
{
641+
if let Some(ret) = unsafe { try_statx(
642+
fd,
643+
b"\0" as *const _ as *const libc::c_char,
644+
libc::AT_EMPTY_PATH | libc::AT_STATX_SYNC_AS_STAT,
645+
libc::STATX_ALL,
646+
) } {
647+
return ret;
648+
}
649+
}
650+
520651
let mut stat: stat64 = unsafe { mem::zeroed() };
521652
cvt(unsafe {
522-
fstat64(self.0.raw(), &mut stat)
653+
fstat64(fd, &mut stat)
523654
})?;
524-
Ok(FileAttr { stat })
655+
Ok(FileAttr::from_stat64(stat))
525656
}
526657

527658
pub fn fsync(&self) -> io::Result<()> {
@@ -798,20 +929,46 @@ pub fn link(src: &Path, dst: &Path) -> io::Result<()> {
798929

799930
pub fn stat(p: &Path) -> io::Result<FileAttr> {
800931
let p = cstr(p)?;
932+
933+
#[cfg(target_os = "linux")]
934+
{
935+
if let Some(ret) = unsafe { try_statx(
936+
libc::AT_FDCWD,
937+
p.as_ptr(),
938+
libc::AT_STATX_SYNC_AS_STAT,
939+
libc::STATX_ALL,
940+
) } {
941+
return ret;
942+
}
943+
}
944+
801945
let mut stat: stat64 = unsafe { mem::zeroed() };
802946
cvt(unsafe {
803947
stat64(p.as_ptr(), &mut stat)
804948
})?;
805-
Ok(FileAttr { stat })
949+
Ok(FileAttr::from_stat64(stat))
806950
}
807951

808952
pub fn lstat(p: &Path) -> io::Result<FileAttr> {
809953
let p = cstr(p)?;
954+
955+
#[cfg(target_os = "linux")]
956+
{
957+
if let Some(ret) = unsafe { try_statx(
958+
libc::AT_FDCWD,
959+
p.as_ptr(),
960+
libc::AT_SYMLINK_NOFOLLOW | libc::AT_STATX_SYNC_AS_STAT,
961+
libc::STATX_ALL,
962+
) } {
963+
return ret;
964+
}
965+
}
966+
810967
let mut stat: stat64 = unsafe { mem::zeroed() };
811968
cvt(unsafe {
812969
lstat64(p.as_ptr(), &mut stat)
813970
})?;
814-
Ok(FileAttr { stat })
971+
Ok(FileAttr::from_stat64(stat))
815972
}
816973

817974
pub fn canonicalize(p: &Path) -> io::Result<PathBuf> {

0 commit comments

Comments
 (0)