Auto merge of #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 #61386 , and will fix #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` (https://github.com/rust-lang/rust/pull/61386#issuecomment-538074018) [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
This commit is contained in:
commit
22eec92882
@ -1090,13 +1090,14 @@ impl Metadata {
|
|||||||
|
|
||||||
/// Returns the creation time listed in this metadata.
|
/// Returns the creation time listed in this metadata.
|
||||||
///
|
///
|
||||||
/// The returned value corresponds to the `birthtime` field of `stat` on
|
/// The returned value corresponds to the `btime` field of `statx` on
|
||||||
/// Unix platforms and the `ftCreationTime` field on Windows platforms.
|
/// Linux kernel starting from to 4.11, the `birthtime` field of `stat` on other
|
||||||
|
/// Unix platforms, and the `ftCreationTime` field on Windows platforms.
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// This field may not be available on all platforms, and will return an
|
/// This field may not be available on all platforms, and will return an
|
||||||
/// `Err` on platforms where it is not available.
|
/// `Err` on platforms or filesystems where it is not available.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
@ -1109,7 +1110,7 @@ impl Metadata {
|
|||||||
/// if let Ok(time) = metadata.created() {
|
/// if let Ok(time) = metadata.created() {
|
||||||
/// println!("{:?}", time);
|
/// println!("{:?}", time);
|
||||||
/// } else {
|
/// } else {
|
||||||
/// println!("Not supported on this platform");
|
/// println!("Not supported on this platform or filesystem");
|
||||||
/// }
|
/// }
|
||||||
/// Ok(())
|
/// Ok(())
|
||||||
/// }
|
/// }
|
||||||
@ -3443,5 +3444,18 @@ mod tests {
|
|||||||
check!(a.created());
|
check!(a.created());
|
||||||
check!(b.created());
|
check!(b.created());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cfg!(target_os = "linux") {
|
||||||
|
// Not always available
|
||||||
|
match (a.created(), b.created()) {
|
||||||
|
(Ok(t1), Ok(t2)) => assert!(t1 <= t2),
|
||||||
|
(Err(e1), Err(e2)) if e1.kind() == ErrorKind::Other &&
|
||||||
|
e2.kind() == ErrorKind::Other => {}
|
||||||
|
(a, b) => panic!(
|
||||||
|
"creation time must be always supported or not supported: {:?} {:?}",
|
||||||
|
a, b,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,11 +41,138 @@ pub use crate::sys_common::fs::remove_dir_all;
|
|||||||
|
|
||||||
pub struct File(FileDesc);
|
pub struct File(FileDesc);
|
||||||
|
|
||||||
#[derive(Clone)]
|
// FIXME: This should be available on Linux with all `target_arch` and `target_env`.
|
||||||
pub struct FileAttr {
|
// https://github.com/rust-lang/libc/issues/1545
|
||||||
stat: stat64,
|
macro_rules! cfg_has_statx {
|
||||||
|
({ $($then_tt:tt)* } else { $($else_tt:tt)* }) => {
|
||||||
|
cfg_if::cfg_if! {
|
||||||
|
if #[cfg(all(target_os = "linux", target_env = "gnu", any(
|
||||||
|
target_arch = "x86",
|
||||||
|
target_arch = "arm",
|
||||||
|
// target_arch = "mips",
|
||||||
|
target_arch = "powerpc",
|
||||||
|
target_arch = "x86_64",
|
||||||
|
// target_arch = "aarch64",
|
||||||
|
target_arch = "powerpc64",
|
||||||
|
// target_arch = "mips64",
|
||||||
|
// target_arch = "s390x",
|
||||||
|
target_arch = "sparc64",
|
||||||
|
)))] {
|
||||||
|
$($then_tt)*
|
||||||
|
} else {
|
||||||
|
$($else_tt)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
($($block_inner:tt)*) => {
|
||||||
|
#[cfg(all(target_os = "linux", target_env = "gnu", any(
|
||||||
|
target_arch = "x86",
|
||||||
|
target_arch = "arm",
|
||||||
|
// target_arch = "mips",
|
||||||
|
target_arch = "powerpc",
|
||||||
|
target_arch = "x86_64",
|
||||||
|
// target_arch = "aarch64",
|
||||||
|
target_arch = "powerpc64",
|
||||||
|
// target_arch = "mips64",
|
||||||
|
// target_arch = "s390x",
|
||||||
|
target_arch = "sparc64",
|
||||||
|
)))]
|
||||||
|
{
|
||||||
|
$($block_inner)*
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cfg_has_statx! {{
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct FileAttr {
|
||||||
|
stat: stat64,
|
||||||
|
statx_extra_fields: Option<StatxExtraFields>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct StatxExtraFields {
|
||||||
|
// This is needed to check if btime is supported by the filesystem.
|
||||||
|
stx_mask: u32,
|
||||||
|
stx_btime: libc::statx_timestamp,
|
||||||
|
}
|
||||||
|
|
||||||
|
// We prefer `statx` on Linux if available, which contains file creation time.
|
||||||
|
// Default `stat64` contains no creation time.
|
||||||
|
unsafe fn try_statx(
|
||||||
|
fd: c_int,
|
||||||
|
path: *const libc::c_char,
|
||||||
|
flags: i32,
|
||||||
|
mask: u32,
|
||||||
|
) -> Option<io::Result<FileAttr>> {
|
||||||
|
use crate::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
||||||
|
// Linux kernel prior to 4.11 or glibc prior to glibc 2.28 don't support `statx`
|
||||||
|
// We store the availability in a global to avoid unnecessary syscalls
|
||||||
|
static HAS_STATX: AtomicBool = AtomicBool::new(true);
|
||||||
|
syscall! {
|
||||||
|
fn statx(
|
||||||
|
fd: c_int,
|
||||||
|
pathname: *const libc::c_char,
|
||||||
|
flags: c_int,
|
||||||
|
mask: libc::c_uint,
|
||||||
|
statxbuf: *mut libc::statx
|
||||||
|
) -> c_int
|
||||||
|
}
|
||||||
|
|
||||||
|
if !HAS_STATX.load(Ordering::Relaxed) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut buf: libc::statx = mem::zeroed();
|
||||||
|
let ret = cvt(statx(fd, path, flags, mask, &mut buf));
|
||||||
|
match ret {
|
||||||
|
Err(err) => match err.raw_os_error() {
|
||||||
|
Some(libc::ENOSYS) => {
|
||||||
|
HAS_STATX.store(false, Ordering::Relaxed);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
_ => return Some(Err(err)),
|
||||||
|
}
|
||||||
|
Ok(_) => {
|
||||||
|
// We cannot fill `stat64` exhaustively because of private padding fields.
|
||||||
|
let mut stat: stat64 = mem::zeroed();
|
||||||
|
// `c_ulong` on gnu-mips, `dev_t` otherwise
|
||||||
|
stat.st_dev = libc::makedev(buf.stx_dev_major, buf.stx_dev_minor) as _;
|
||||||
|
stat.st_ino = buf.stx_ino as libc::ino64_t;
|
||||||
|
stat.st_nlink = buf.stx_nlink as libc::nlink_t;
|
||||||
|
stat.st_mode = buf.stx_mode as libc::mode_t;
|
||||||
|
stat.st_uid = buf.stx_uid as libc::uid_t;
|
||||||
|
stat.st_gid = buf.stx_gid as libc::gid_t;
|
||||||
|
stat.st_rdev = libc::makedev(buf.stx_rdev_major, buf.stx_rdev_minor) as _;
|
||||||
|
stat.st_size = buf.stx_size as off64_t;
|
||||||
|
stat.st_blksize = buf.stx_blksize as libc::blksize_t;
|
||||||
|
stat.st_blocks = buf.stx_blocks as libc::blkcnt64_t;
|
||||||
|
stat.st_atime = buf.stx_atime.tv_sec as libc::time_t;
|
||||||
|
// `i64` on gnu-x86_64-x32, `c_ulong` otherwise.
|
||||||
|
stat.st_atime_nsec = buf.stx_atime.tv_nsec as _;
|
||||||
|
stat.st_mtime = buf.stx_mtime.tv_sec as libc::time_t;
|
||||||
|
stat.st_mtime_nsec = buf.stx_mtime.tv_nsec as _;
|
||||||
|
stat.st_ctime = buf.stx_ctime.tv_sec as libc::time_t;
|
||||||
|
stat.st_ctime_nsec = buf.stx_ctime.tv_nsec as _;
|
||||||
|
|
||||||
|
let extra = StatxExtraFields {
|
||||||
|
stx_mask: buf.stx_mask,
|
||||||
|
stx_btime: buf.stx_btime,
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(Ok(FileAttr { stat, statx_extra_fields: Some(extra) }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct FileAttr {
|
||||||
|
stat: stat64,
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
|
||||||
// all DirEntry's will have a reference to this struct
|
// all DirEntry's will have a reference to this struct
|
||||||
struct InnerReadDir {
|
struct InnerReadDir {
|
||||||
dirp: Dir,
|
dirp: Dir,
|
||||||
@ -97,6 +224,20 @@ pub struct FileType { mode: mode_t }
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct DirBuilder { mode: mode_t }
|
pub struct DirBuilder { mode: mode_t }
|
||||||
|
|
||||||
|
cfg_has_statx! {{
|
||||||
|
impl FileAttr {
|
||||||
|
fn from_stat64(stat: stat64) -> Self {
|
||||||
|
Self { stat, statx_extra_fields: None }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
impl FileAttr {
|
||||||
|
fn from_stat64(stat: stat64) -> Self {
|
||||||
|
Self { stat }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
|
||||||
impl FileAttr {
|
impl FileAttr {
|
||||||
pub fn size(&self) -> u64 { self.stat.st_size as u64 }
|
pub fn size(&self) -> u64 { self.stat.st_size as u64 }
|
||||||
pub fn perm(&self) -> FilePermissions {
|
pub fn perm(&self) -> FilePermissions {
|
||||||
@ -164,6 +305,22 @@ impl FileAttr {
|
|||||||
target_os = "macos",
|
target_os = "macos",
|
||||||
target_os = "ios")))]
|
target_os = "ios")))]
|
||||||
pub fn created(&self) -> io::Result<SystemTime> {
|
pub fn created(&self) -> io::Result<SystemTime> {
|
||||||
|
cfg_has_statx! {
|
||||||
|
if let Some(ext) = &self.statx_extra_fields {
|
||||||
|
return if (ext.stx_mask & libc::STATX_BTIME) != 0 {
|
||||||
|
Ok(SystemTime::from(libc::timespec {
|
||||||
|
tv_sec: ext.stx_btime.tv_sec as libc::time_t,
|
||||||
|
tv_nsec: ext.stx_btime.tv_nsec as _,
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
Err(io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
"creation time is not available for the filesystem",
|
||||||
|
))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Err(io::Error::new(io::ErrorKind::Other,
|
Err(io::Error::new(io::ErrorKind::Other,
|
||||||
"creation time is not available on this platform \
|
"creation time is not available on this platform \
|
||||||
currently"))
|
currently"))
|
||||||
@ -306,12 +463,25 @@ impl DirEntry {
|
|||||||
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "emscripten", target_os = "android"))]
|
#[cfg(any(target_os = "linux", target_os = "emscripten", target_os = "android"))]
|
||||||
pub fn metadata(&self) -> io::Result<FileAttr> {
|
pub fn metadata(&self) -> io::Result<FileAttr> {
|
||||||
let fd = cvt(unsafe {dirfd(self.dir.inner.dirp.0)})?;
|
let fd = cvt(unsafe { dirfd(self.dir.inner.dirp.0) })?;
|
||||||
|
let name = self.entry.d_name.as_ptr();
|
||||||
|
|
||||||
|
cfg_has_statx! {
|
||||||
|
if let Some(ret) = unsafe { try_statx(
|
||||||
|
fd,
|
||||||
|
name,
|
||||||
|
libc::AT_SYMLINK_NOFOLLOW | libc::AT_STATX_SYNC_AS_STAT,
|
||||||
|
libc::STATX_ALL,
|
||||||
|
) } {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut stat: stat64 = unsafe { mem::zeroed() };
|
let mut stat: stat64 = unsafe { mem::zeroed() };
|
||||||
cvt(unsafe {
|
cvt(unsafe {
|
||||||
fstatat64(fd, self.entry.d_name.as_ptr(), &mut stat, libc::AT_SYMLINK_NOFOLLOW)
|
fstatat64(fd, name, &mut stat, libc::AT_SYMLINK_NOFOLLOW)
|
||||||
})?;
|
})?;
|
||||||
Ok(FileAttr { stat })
|
Ok(FileAttr::from_stat64(stat))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(any(target_os = "linux", target_os = "emscripten", target_os = "android")))]
|
#[cfg(not(any(target_os = "linux", target_os = "emscripten", target_os = "android")))]
|
||||||
@ -517,11 +687,24 @@ impl File {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn file_attr(&self) -> io::Result<FileAttr> {
|
pub fn file_attr(&self) -> io::Result<FileAttr> {
|
||||||
|
let fd = self.0.raw();
|
||||||
|
|
||||||
|
cfg_has_statx! {
|
||||||
|
if let Some(ret) = unsafe { try_statx(
|
||||||
|
fd,
|
||||||
|
b"\0" as *const _ as *const libc::c_char,
|
||||||
|
libc::AT_EMPTY_PATH | libc::AT_STATX_SYNC_AS_STAT,
|
||||||
|
libc::STATX_ALL,
|
||||||
|
) } {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut stat: stat64 = unsafe { mem::zeroed() };
|
let mut stat: stat64 = unsafe { mem::zeroed() };
|
||||||
cvt(unsafe {
|
cvt(unsafe {
|
||||||
fstat64(self.0.raw(), &mut stat)
|
fstat64(fd, &mut stat)
|
||||||
})?;
|
})?;
|
||||||
Ok(FileAttr { stat })
|
Ok(FileAttr::from_stat64(stat))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fsync(&self) -> io::Result<()> {
|
pub fn fsync(&self) -> io::Result<()> {
|
||||||
@ -798,20 +981,44 @@ pub fn link(src: &Path, dst: &Path) -> io::Result<()> {
|
|||||||
|
|
||||||
pub fn stat(p: &Path) -> io::Result<FileAttr> {
|
pub fn stat(p: &Path) -> io::Result<FileAttr> {
|
||||||
let p = cstr(p)?;
|
let p = cstr(p)?;
|
||||||
|
|
||||||
|
cfg_has_statx! {
|
||||||
|
if let Some(ret) = unsafe { try_statx(
|
||||||
|
libc::AT_FDCWD,
|
||||||
|
p.as_ptr(),
|
||||||
|
libc::AT_STATX_SYNC_AS_STAT,
|
||||||
|
libc::STATX_ALL,
|
||||||
|
) } {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut stat: stat64 = unsafe { mem::zeroed() };
|
let mut stat: stat64 = unsafe { mem::zeroed() };
|
||||||
cvt(unsafe {
|
cvt(unsafe {
|
||||||
stat64(p.as_ptr(), &mut stat)
|
stat64(p.as_ptr(), &mut stat)
|
||||||
})?;
|
})?;
|
||||||
Ok(FileAttr { stat })
|
Ok(FileAttr::from_stat64(stat))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn lstat(p: &Path) -> io::Result<FileAttr> {
|
pub fn lstat(p: &Path) -> io::Result<FileAttr> {
|
||||||
let p = cstr(p)?;
|
let p = cstr(p)?;
|
||||||
|
|
||||||
|
cfg_has_statx! {
|
||||||
|
if let Some(ret) = unsafe { try_statx(
|
||||||
|
libc::AT_FDCWD,
|
||||||
|
p.as_ptr(),
|
||||||
|
libc::AT_SYMLINK_NOFOLLOW | libc::AT_STATX_SYNC_AS_STAT,
|
||||||
|
libc::STATX_ALL,
|
||||||
|
) } {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut stat: stat64 = unsafe { mem::zeroed() };
|
let mut stat: stat64 = unsafe { mem::zeroed() };
|
||||||
cvt(unsafe {
|
cvt(unsafe {
|
||||||
lstat64(p.as_ptr(), &mut stat)
|
lstat64(p.as_ptr(), &mut stat)
|
||||||
})?;
|
})?;
|
||||||
Ok(FileAttr { stat })
|
Ok(FileAttr::from_stat64(stat))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn canonicalize(p: &Path) -> io::Result<PathBuf> {
|
pub fn canonicalize(p: &Path) -> io::Result<PathBuf> {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user