Skip to content

Commit 309b956

Browse files
committed
Prefer statx on linux if available
1 parent cfb6d84 commit 309b956

File tree

2 files changed

+182
-2
lines changed

2 files changed

+182
-2
lines changed

src/libstd/fs.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3441,5 +3441,18 @@ mod tests {
34413441
check!(a.created());
34423442
check!(b.created());
34433443
}
3444+
3445+
if cfg!(target_os = "linux") {
3446+
// Not always available
3447+
match (a.created(), b.created()) {
3448+
(Ok(t1), Ok(t2)) => assert!(t1 <= t2),
3449+
(Err(e1), Err(e2)) if e1.kind() == ErrorKind::Other &&
3450+
e2.kind() == ErrorKind::Other => {}
3451+
(a, b) => panic!(
3452+
"creation time must be always supported or not supported: {:?} {:?}",
3453+
a, b,
3454+
),
3455+
}
3456+
}
34443457
}
34453458
}

src/libstd/sys/unix/fs.rs

Lines changed: 169 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ use libc::{stat64, fstat64, lstat64, off64_t, ftruncate64, lseek64, dirent64, re
2020
use libc::fstatat64;
2121
#[cfg(any(target_os = "linux", target_os = "emscripten", target_os = "android"))]
2222
use libc::dirfd;
23+
// We only use struct `statx`, not the function `statx`.
24+
// Instead, use `syscall` to check if it is available at runtime.
25+
#[cfg(target_os = "linux")]
26+
use libc::{statx, makedev};
2327
#[cfg(target_os = "android")]
2428
use libc::{stat as stat64, fstat as fstat64, fstatat as fstatat64, lstat as lstat64, lseek64,
2529
dirent as dirent64, open as open64};
@@ -44,6 +48,82 @@ pub struct File(FileDesc);
4448
#[derive(Clone)]
4549
pub struct FileAttr {
4650
stat: stat64,
51+
#[cfg(target_os = "linux")]
52+
statx_extra_fields: Option<StatxExtraFields>,
53+
}
54+
55+
#[derive(Clone)]
56+
struct StatxExtraFields {
57+
// This is needed to check if btime is supported by the filesystem.
58+
stx_mask: u32,
59+
stx_btime: libc::statx_timestamp,
60+
}
61+
62+
// We prefer `statx` if available, which contains file creation time.
63+
#[cfg(target_os = "linux")]
64+
unsafe fn try_statx(
65+
fd: c_int,
66+
path: *const libc::c_char,
67+
flags: i32,
68+
mask: u32,
69+
) -> Option<io::Result<FileAttr>> {
70+
use crate::sync::atomic::{AtomicBool, Ordering};
71+
72+
// Linux kernel prior to 4.11 or glibc prior to glibc 2.28 don't support `statx`
73+
// We store the availability in a global to avoid unnecessary syscalls
74+
static HAS_STATX: AtomicBool = AtomicBool::new(true);
75+
syscall! {
76+
fn statx(
77+
fd: c_int,
78+
pathname: *const libc::c_char,
79+
flags: c_int,
80+
mask: libc::c_uint,
81+
statxbuf: *mut statx
82+
) -> c_int
83+
}
84+
85+
if !HAS_STATX.load(Ordering::Relaxed) {
86+
return None;
87+
}
88+
89+
let mut buf: statx = mem::zeroed();
90+
let ret = cvt(statx(fd, path, flags, mask, &mut buf));
91+
match ret {
92+
Err(err) => match err.raw_os_error() {
93+
Some(libc::ENOSYS) => {
94+
HAS_STATX.store(false, Ordering::Relaxed);
95+
return None;
96+
}
97+
_ => return Some(Err(err)),
98+
}
99+
Ok(_) => {
100+
// We cannot fill `stat64` exhaustively because of private padding fields.
101+
let mut stat: stat64 = mem::zeroed();
102+
stat.st_dev = makedev(buf.stx_dev_major, buf.stx_dev_minor);
103+
stat.st_ino = buf.stx_ino;
104+
stat.st_nlink = buf.stx_nlink as u64;
105+
stat.st_mode = buf.stx_mode as u32;
106+
stat.st_uid = buf.stx_uid;
107+
stat.st_gid = buf.stx_gid;
108+
stat.st_rdev = makedev(buf.stx_rdev_major, buf.stx_rdev_minor);
109+
stat.st_size = buf.stx_size as i64;
110+
stat.st_blksize = buf.stx_blksize as i64;
111+
stat.st_blocks = buf.stx_blocks as i64;
112+
stat.st_atime = buf.stx_atime.tv_sec;
113+
stat.st_atime_nsec = buf.stx_atime.tv_nsec as i64;
114+
stat.st_mtime = buf.stx_mtime.tv_sec;
115+
stat.st_mtime_nsec = buf.stx_mtime.tv_nsec as i64;
116+
stat.st_ctime = buf.stx_ctime.tv_sec;
117+
stat.st_ctime_nsec = buf.stx_ctime.tv_nsec as i64;
118+
119+
let extra = StatxExtraFields {
120+
stx_mask: buf.stx_mask,
121+
stx_btime: buf.stx_btime,
122+
};
123+
124+
Some(Ok(FileAttr { stat, statx_extra_fields: Some(extra) }))
125+
}
126+
}
47127
}
48128

49129
// all DirEntry's will have a reference to this struct
@@ -148,6 +228,26 @@ impl FileAttr {
148228
}))
149229
}
150230

231+
#[cfg(target_os = "linux")]
232+
pub fn created(&self) -> io::Result<SystemTime> {
233+
match &self.statx_extra_fields {
234+
Some(ext) if (ext.stx_mask & libc::STATX_BTIME) != 0 => {
235+
Ok(SystemTime::from(libc::timespec {
236+
tv_sec: ext.stx_btime.tv_sec as libc::time_t,
237+
tv_nsec: ext.stx_btime.tv_nsec as libc::c_long,
238+
}))
239+
}
240+
Some(_) => Err(io::Error::new(
241+
io::ErrorKind::Other,
242+
"creation time is not available for the filesystam",
243+
)),
244+
None => Err(io::Error::new(
245+
io::ErrorKind::Other,
246+
"creation time is not available on this platform currently",
247+
)),
248+
}
249+
}
250+
151251
#[cfg(any(target_os = "freebsd",
152252
target_os = "openbsd",
153253
target_os = "macos",
@@ -159,7 +259,8 @@ impl FileAttr {
159259
}))
160260
}
161261

162-
#[cfg(not(any(target_os = "freebsd",
262+
#[cfg(not(any(target_os = "linux",
263+
target_os = "freebsd",
163264
target_os = "openbsd",
164265
target_os = "macos",
165266
target_os = "ios")))]
@@ -304,7 +405,23 @@ impl DirEntry {
304405
OsStr::from_bytes(self.name_bytes()).to_os_string()
305406
}
306407

307-
#[cfg(any(target_os = "linux", target_os = "emscripten", target_os = "android"))]
408+
#[cfg(target_os = "linux")]
409+
pub fn metadata(&self) -> io::Result<FileAttr> {
410+
let dir_fd = cvt(unsafe { dirfd(self.dir.inner.dirp.0) })?;
411+
let pathname = self.entry.d_name.as_ptr();
412+
unsafe { try_statx(
413+
dir_fd,
414+
pathname,
415+
libc::AT_SYMLINK_NOFOLLOW | libc::AT_STATX_SYNC_AS_STAT,
416+
libc::STATX_ALL,
417+
) }.unwrap_or_else(|| {
418+
let mut stat = unsafe { mem::zeroed() };
419+
cvt(unsafe { fstatat64(dir_fd, pathname, &mut stat, libc::AT_SYMLINK_NOFOLLOW) })?;
420+
Ok(FileAttr { stat, statx_extra_fields: None })
421+
})
422+
}
423+
424+
#[cfg(any(target_os = "emscripten", target_os = "android"))]
308425
pub fn metadata(&self) -> io::Result<FileAttr> {
309426
let fd = cvt(unsafe {dirfd(self.dir.inner.dirp.0)})?;
310427
let mut stat: stat64 = unsafe { mem::zeroed() };
@@ -516,6 +633,22 @@ impl File {
516633
Ok(File(fd))
517634
}
518635

636+
#[cfg(target_os = "linux")]
637+
pub fn file_attr(&self) -> io::Result<FileAttr> {
638+
let fd = self.0.raw();
639+
unsafe { try_statx(
640+
fd,
641+
b"\0" as *const _ as *const libc::c_char,
642+
libc::AT_EMPTY_PATH | libc::AT_STATX_SYNC_AS_STAT,
643+
libc::STATX_ALL,
644+
) }.unwrap_or_else(|| {
645+
let mut stat = unsafe { mem::zeroed() };
646+
cvt(unsafe { fstat64(fd, &mut stat) })?;
647+
Ok(FileAttr { stat, statx_extra_fields: None })
648+
})
649+
}
650+
651+
#[cfg(not(target_os = "linux"))]
519652
pub fn file_attr(&self) -> io::Result<FileAttr> {
520653
let mut stat: stat64 = unsafe { mem::zeroed() };
521654
cvt(unsafe {
@@ -796,6 +929,23 @@ pub fn link(src: &Path, dst: &Path) -> io::Result<()> {
796929
Ok(())
797930
}
798931

932+
#[cfg(target_os = "linux")]
933+
pub fn stat(p: &Path) -> io::Result<FileAttr> {
934+
let p = cstr(p)?;
935+
let p = p.as_ptr();
936+
unsafe { try_statx(
937+
libc::AT_FDCWD,
938+
p,
939+
libc::AT_STATX_SYNC_AS_STAT,
940+
libc::STATX_ALL,
941+
) }.unwrap_or_else(|| {
942+
let mut stat = unsafe { mem::zeroed() };
943+
cvt(unsafe { stat64(p, &mut stat) })?;
944+
Ok(FileAttr { stat, statx_extra_fields: None })
945+
})
946+
}
947+
948+
#[cfg(not(target_os = "linux"))]
799949
pub fn stat(p: &Path) -> io::Result<FileAttr> {
800950
let p = cstr(p)?;
801951
let mut stat: stat64 = unsafe { mem::zeroed() };
@@ -805,6 +955,23 @@ pub fn stat(p: &Path) -> io::Result<FileAttr> {
805955
Ok(FileAttr { stat })
806956
}
807957

958+
#[cfg(target_os = "linux")]
959+
pub fn lstat(p: &Path) -> io::Result<FileAttr> {
960+
let p = cstr(p)?;
961+
let p = p.as_ptr();
962+
unsafe { try_statx(
963+
libc::AT_FDCWD,
964+
p,
965+
libc::AT_SYMLINK_NOFOLLOW | libc::AT_STATX_SYNC_AS_STAT,
966+
libc::STATX_ALL,
967+
) }.unwrap_or_else(|| {
968+
let mut stat = unsafe { mem::zeroed() };
969+
cvt(unsafe { lstat64(p, &mut stat) })?;
970+
Ok(FileAttr { stat, statx_extra_fields: None })
971+
})
972+
}
973+
974+
#[cfg(not(target_os = "linux"))]
808975
pub fn lstat(p: &Path) -> io::Result<FileAttr> {
809976
let p = cstr(p)?;
810977
let mut stat: stat64 = unsafe { mem::zeroed() };

0 commit comments

Comments
 (0)