Skip to content

Commit 07ccdcf

Browse files
authored
Rollup merge of rust-lang#65094 - oxalica:linux-statx, r=alexcrichton
Prefer statx on linux if available This PR make `metadata`-related functions try to invoke `statx` first on Linux if available, making `std::fs::Metadata::created` work on Linux with `statx` supported. It follows the discussion in rust-lang#61386 , and will fix rust-lang#59743 The implementation of this PR is simply converting `struct statx` into `struct stat64` with extra fields for `btime` if `statx` succeeds, since other fields are not currently used. --- I also did a separated benchmark for `fs::metadata`, `stat64`, `statx`, and `statx` with conversion to `stat64`. It shows that `statx` with conversion is even more faster than pure `statx`. I think it's due to `sizeof stat64 == 114` but `sizeof statx == 256`. Anyway, the bare implementation of `statx` with conversion is only about 0.2% slower than the original impl (`stat64`-family). With heap-allocation counted (~8.5% of total cost), the difference between `stat` and `statx` (with or without conversion) is just nothing. Therefore, I think it is not urgent to use bare `struct statx` as underlying representation now. There is no need to break `std::os::linux::fs::MetadataExt::as_raw_stat` (rust-lang#61386 (comment)) [Separated bare benchmarks](https://gist.github.com/oxalica/c4073ecb202c599fe41b7f15f86dc79c): ``` metadata_ok time: [529.41 ns 529.77 ns 530.19 ns] metadata_err time: [538.71 ns 539.39 ns 540.35 ns] stat64_ok time: [484.32 ns 484.53 ns 484.75 ns] stat64_err time: [481.77 ns 482.00 ns 482.24 ns] statx_ok time: [488.07 ns 488.35 ns 488.62 ns] statx_err time: [487.74 ns 488.00 ns 488.27 ns] statx_cvt_ok time: [485.05 ns 485.28 ns 485.53 ns] statx_cvt_err time: [485.23 ns 485.45 ns 485.67 ns] ``` r? @alexcrichton
2 parents 237d54f + e3b7f3d commit 07ccdcf

File tree

2 files changed

+232
-13
lines changed

2 files changed

+232
-13
lines changed

src/libstd/fs.rs

Lines changed: 17 additions & 3 deletions
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
1093+
/// The returned value corresponds to the `btime` field of `statx` on
1094+
/// Linux not prior to 4.11, the `birthtime` field of `stat` on other
10941095
/// 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

Lines changed: 215 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,136 @@ pub use crate::sys_common::fs::remove_dir_all;
4141

4242
pub struct File(FileDesc);
4343

44-
#[derive(Clone)]
45-
pub struct FileAttr {
46-
stat: stat64,
44+
// FIXME: This should be available on Linux with all `target_arch` and `target_env`.
45+
// https://github.com/rust-lang/libc/issues/1545
46+
macro_rules! cfg_has_statx {
47+
({ $($then_tt:tt)* } else { $($else_tt:tt)* }) => {
48+
cfg_if::cfg_if! {
49+
if #[cfg(all(target_os = "linux", target_env = "gnu", any(
50+
target_arch = "x86",
51+
target_arch = "arm",
52+
// target_arch = "mips",
53+
target_arch = "powerpc",
54+
target_arch = "x86_64",
55+
// target_arch = "aarch64",
56+
target_arch = "powerpc64",
57+
// target_arch = "mips64",
58+
// target_arch = "s390x",
59+
target_arch = "sparc64",
60+
)))] {
61+
$($then_tt)*
62+
} else {
63+
$($else_tt)*
64+
}
65+
}
66+
};
67+
($($block_inner:tt)*) => {
68+
#[cfg(all(target_os = "linux", target_env = "gnu", any(
69+
target_arch = "x86",
70+
target_arch = "arm",
71+
// target_arch = "mips",
72+
target_arch = "powerpc",
73+
target_arch = "x86_64",
74+
// target_arch = "aarch64",
75+
target_arch = "powerpc64",
76+
// target_arch = "mips64",
77+
// target_arch = "s390x",
78+
target_arch = "sparc64",
79+
)))]
80+
{
81+
$($block_inner)*
82+
}
83+
};
4784
}
4885

86+
cfg_has_statx! {{
87+
#[derive(Clone)]
88+
pub struct FileAttr {
89+
stat: stat64,
90+
statx_extra_fields: Option<StatxExtraFields>,
91+
}
92+
93+
#[derive(Clone)]
94+
struct StatxExtraFields {
95+
// This is needed to check if btime is supported by the filesystem.
96+
stx_mask: u32,
97+
stx_btime: libc::statx_timestamp,
98+
}
99+
100+
// We prefer `statx` on Linux if available, which contains file creation time.
101+
// Default `stat64` contains no creation time.
102+
unsafe fn try_statx(
103+
fd: c_int,
104+
path: *const libc::c_char,
105+
flags: i32,
106+
mask: u32,
107+
) -> Option<io::Result<FileAttr>> {
108+
use crate::sync::atomic::{AtomicBool, Ordering};
109+
110+
// Linux kernel prior to 4.11 or glibc prior to glibc 2.28 don't support `statx`
111+
// We store the availability in a global to avoid unnecessary syscalls
112+
static HAS_STATX: AtomicBool = AtomicBool::new(true);
113+
syscall! {
114+
fn statx(
115+
fd: c_int,
116+
pathname: *const libc::c_char,
117+
flags: c_int,
118+
mask: libc::c_uint,
119+
statxbuf: *mut libc::statx
120+
) -> c_int
121+
}
122+
123+
if !HAS_STATX.load(Ordering::Relaxed) {
124+
return None;
125+
}
126+
127+
let mut buf: libc::statx = mem::zeroed();
128+
let ret = cvt(statx(fd, path, flags, mask, &mut buf));
129+
match ret {
130+
Err(err) => match err.raw_os_error() {
131+
Some(libc::ENOSYS) => {
132+
HAS_STATX.store(false, Ordering::Relaxed);
133+
return None;
134+
}
135+
_ => return Some(Err(err)),
136+
}
137+
Ok(_) => {
138+
// We cannot fill `stat64` exhaustively because of private padding fields.
139+
let mut stat: stat64 = mem::zeroed();
140+
stat.st_dev = libc::makedev(buf.stx_dev_major, buf.stx_dev_minor);
141+
stat.st_ino = buf.stx_ino as libc::ino64_t;
142+
stat.st_nlink = buf.stx_nlink as libc::nlink_t;
143+
stat.st_mode = buf.stx_mode as libc::mode_t;
144+
stat.st_uid = buf.stx_uid as libc::uid_t;
145+
stat.st_gid = buf.stx_gid as libc::gid_t;
146+
stat.st_rdev = libc::makedev(buf.stx_rdev_major, buf.stx_rdev_minor);
147+
stat.st_size = buf.stx_size as off64_t;
148+
stat.st_blksize = buf.stx_blksize as libc::blksize_t;
149+
stat.st_blocks = buf.stx_blocks as libc::blkcnt64_t;
150+
stat.st_atime = buf.stx_atime.tv_sec as libc::time_t;
151+
stat.st_atime_nsec = buf.stx_atime.tv_nsec as libc::c_long;
152+
stat.st_mtime = buf.stx_mtime.tv_sec as libc::time_t;
153+
stat.st_mtime_nsec = buf.stx_mtime.tv_nsec as libc::c_long;
154+
stat.st_ctime = buf.stx_ctime.tv_sec as libc::time_t;
155+
stat.st_ctime_nsec = buf.stx_ctime.tv_nsec as libc::c_long;
156+
157+
let extra = StatxExtraFields {
158+
stx_mask: buf.stx_mask,
159+
stx_btime: buf.stx_btime,
160+
};
161+
162+
Some(Ok(FileAttr { stat, statx_extra_fields: Some(extra) }))
163+
}
164+
}
165+
}
166+
167+
} else {
168+
#[derive(Clone)]
169+
pub struct FileAttr {
170+
stat: stat64,
171+
}
172+
}}
173+
49174
// all DirEntry's will have a reference to this struct
50175
struct InnerReadDir {
51176
dirp: Dir,
@@ -97,6 +222,20 @@ pub struct FileType { mode: mode_t }
97222
#[derive(Debug)]
98223
pub struct DirBuilder { mode: mode_t }
99224

225+
cfg_has_statx! {{
226+
impl FileAttr {
227+
fn from_stat64(stat: stat64) -> Self {
228+
Self { stat, statx_extra_fields: None }
229+
}
230+
}
231+
} else {
232+
impl FileAttr {
233+
fn from_stat64(stat: stat64) -> Self {
234+
Self { stat }
235+
}
236+
}
237+
}}
238+
100239
impl FileAttr {
101240
pub fn size(&self) -> u64 { self.stat.st_size as u64 }
102241
pub fn perm(&self) -> FilePermissions {
@@ -164,6 +303,22 @@ impl FileAttr {
164303
target_os = "macos",
165304
target_os = "ios")))]
166305
pub fn created(&self) -> io::Result<SystemTime> {
306+
cfg_has_statx! {
307+
if let Some(ext) = &self.statx_extra_fields {
308+
return if (ext.stx_mask & libc::STATX_BTIME) != 0 {
309+
Ok(SystemTime::from(libc::timespec {
310+
tv_sec: ext.stx_btime.tv_sec as libc::time_t,
311+
tv_nsec: ext.stx_btime.tv_nsec as libc::c_long,
312+
}))
313+
} else {
314+
Err(io::Error::new(
315+
io::ErrorKind::Other,
316+
"creation time is not available for the filesystem",
317+
))
318+
};
319+
}
320+
}
321+
167322
Err(io::Error::new(io::ErrorKind::Other,
168323
"creation time is not available on this platform \
169324
currently"))
@@ -306,12 +461,25 @@ impl DirEntry {
306461

307462
#[cfg(any(target_os = "linux", target_os = "emscripten", target_os = "android"))]
308463
pub fn metadata(&self) -> io::Result<FileAttr> {
309-
let fd = cvt(unsafe {dirfd(self.dir.inner.dirp.0)})?;
464+
let fd = cvt(unsafe { dirfd(self.dir.inner.dirp.0) })?;
465+
let name = self.entry.d_name.as_ptr();
466+
467+
cfg_has_statx! {
468+
if let Some(ret) = unsafe { try_statx(
469+
fd,
470+
name,
471+
libc::AT_SYMLINK_NOFOLLOW | libc::AT_STATX_SYNC_AS_STAT,
472+
libc::STATX_ALL,
473+
) } {
474+
return ret;
475+
}
476+
}
477+
310478
let mut stat: stat64 = unsafe { mem::zeroed() };
311479
cvt(unsafe {
312-
fstatat64(fd, self.entry.d_name.as_ptr(), &mut stat, libc::AT_SYMLINK_NOFOLLOW)
480+
fstatat64(fd, name, &mut stat, libc::AT_SYMLINK_NOFOLLOW)
313481
})?;
314-
Ok(FileAttr { stat })
482+
Ok(FileAttr::from_stat64(stat))
315483
}
316484

317485
#[cfg(not(any(target_os = "linux", target_os = "emscripten", target_os = "android")))]
@@ -517,11 +685,24 @@ impl File {
517685
}
518686

519687
pub fn file_attr(&self) -> io::Result<FileAttr> {
688+
let fd = self.0.raw();
689+
690+
cfg_has_statx! {
691+
if let Some(ret) = unsafe { try_statx(
692+
fd,
693+
b"\0" as *const _ as *const libc::c_char,
694+
libc::AT_EMPTY_PATH | libc::AT_STATX_SYNC_AS_STAT,
695+
libc::STATX_ALL,
696+
) } {
697+
return ret;
698+
}
699+
}
700+
520701
let mut stat: stat64 = unsafe { mem::zeroed() };
521702
cvt(unsafe {
522-
fstat64(self.0.raw(), &mut stat)
703+
fstat64(fd, &mut stat)
523704
})?;
524-
Ok(FileAttr { stat })
705+
Ok(FileAttr::from_stat64(stat))
525706
}
526707

527708
pub fn fsync(&self) -> io::Result<()> {
@@ -798,20 +979,44 @@ pub fn link(src: &Path, dst: &Path) -> io::Result<()> {
798979

799980
pub fn stat(p: &Path) -> io::Result<FileAttr> {
800981
let p = cstr(p)?;
982+
983+
cfg_has_statx! {
984+
if let Some(ret) = unsafe { try_statx(
985+
libc::AT_FDCWD,
986+
p.as_ptr(),
987+
libc::AT_STATX_SYNC_AS_STAT,
988+
libc::STATX_ALL,
989+
) } {
990+
return ret;
991+
}
992+
}
993+
801994
let mut stat: stat64 = unsafe { mem::zeroed() };
802995
cvt(unsafe {
803996
stat64(p.as_ptr(), &mut stat)
804997
})?;
805-
Ok(FileAttr { stat })
998+
Ok(FileAttr::from_stat64(stat))
806999
}
8071000

8081001
pub fn lstat(p: &Path) -> io::Result<FileAttr> {
8091002
let p = cstr(p)?;
1003+
1004+
cfg_has_statx! {
1005+
if let Some(ret) = unsafe { try_statx(
1006+
libc::AT_FDCWD,
1007+
p.as_ptr(),
1008+
libc::AT_SYMLINK_NOFOLLOW | libc::AT_STATX_SYNC_AS_STAT,
1009+
libc::STATX_ALL,
1010+
) } {
1011+
return ret;
1012+
}
1013+
}
1014+
8101015
let mut stat: stat64 = unsafe { mem::zeroed() };
8111016
cvt(unsafe {
8121017
lstat64(p.as_ptr(), &mut stat)
8131018
})?;
814-
Ok(FileAttr { stat })
1019+
Ok(FileAttr::from_stat64(stat))
8151020
}
8161021

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

0 commit comments

Comments
 (0)