Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prefer statx on linux if available #65094

Merged
merged 3 commits into from
Oct 20, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions src/libstd/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1090,13 +1090,14 @@ impl Metadata {

/// Returns the creation time listed in this metadata.
///
/// The returned value corresponds to the `birthtime` field of `stat` on
/// Unix platforms and the `ftCreationTime` field on Windows platforms.
/// The returned value corresponds to the `btime` field of `statx` on
/// Linux kernel starting from to 4.11, the `birthtime` field of `stat` on other
/// Unix platforms, and the `ftCreationTime` field on Windows platforms.
///
/// # Errors
///
/// 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
///
Expand All @@ -1109,7 +1110,7 @@ impl Metadata {
/// if let Ok(time) = metadata.created() {
/// println!("{:?}", time);
/// } else {
/// println!("Not supported on this platform");
/// println!("Not supported on this platform or filesystem");
/// }
/// Ok(())
/// }
Expand Down Expand Up @@ -3443,5 +3444,18 @@ mod tests {
check!(a.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,
),
}
}
}
}
227 changes: 217 additions & 10 deletions src/libstd/sys/unix/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,138 @@ pub use crate::sys_common::fs::remove_dir_all;

pub struct File(FileDesc);

#[derive(Clone)]
pub struct FileAttr {
stat: stat64,
// FIXME: This should be available on Linux with all `target_arch` and `target_env`.
// https://github.com/rust-lang/libc/issues/1545
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
struct InnerReadDir {
dirp: Dir,
Expand Down Expand Up @@ -97,6 +224,20 @@ pub struct FileType { mode: mode_t }
#[derive(Debug)]
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 {
pub fn size(&self) -> u64 { self.stat.st_size as u64 }
pub fn perm(&self) -> FilePermissions {
Expand Down Expand Up @@ -164,6 +305,22 @@ impl FileAttr {
target_os = "macos",
target_os = "ios")))]
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,
"creation time is not available on this platform \
currently"))
Expand Down Expand Up @@ -306,12 +463,25 @@ impl DirEntry {

#[cfg(any(target_os = "linux", target_os = "emscripten", target_os = "android"))]
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() };
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")))]
Expand Down Expand Up @@ -517,11 +687,24 @@ impl File {
}

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() };
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<()> {
Expand Down Expand Up @@ -798,20 +981,44 @@ pub fn link(src: &Path, dst: &Path) -> io::Result<()> {

pub fn stat(p: &Path) -> io::Result<FileAttr> {
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() };
cvt(unsafe {
stat64(p.as_ptr(), &mut stat)
})?;
Ok(FileAttr { stat })
Ok(FileAttr::from_stat64(stat))
}

pub fn lstat(p: &Path) -> io::Result<FileAttr> {
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() };
cvt(unsafe {
lstat64(p.as_ptr(), &mut stat)
})?;
Ok(FileAttr { stat })
Ok(FileAttr::from_stat64(stat))
}

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