diff --git a/src/libstd/fs.rs b/src/libstd/fs.rs index fc26dcb3211..6595f54162f 100644 --- a/src/libstd/fs.rs +++ b/src/libstd/fs.rs @@ -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 /// @@ -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(()) /// } @@ -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, + ), + } + } } } diff --git a/src/libstd/sys/unix/fs.rs b/src/libstd/sys/unix/fs.rs index 3b1eb86b84f..8113fe38165 100644 --- a/src/libstd/sys/unix/fs.rs +++ b/src/libstd/sys/unix/fs.rs @@ -44,6 +44,84 @@ pub struct File(FileDesc); #[derive(Clone)] pub struct FileAttr { stat: stat64, + #[cfg(target_os = "linux")] + statx_extra_fields: Option, +} + +#[cfg(target_os = "linux")] +#[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. +#[cfg(target_os = "linux")] +unsafe fn try_statx( + fd: c_int, + path: *const libc::c_char, + flags: i32, + mask: u32, +) -> Option> { + 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(); + stat.st_dev = libc::makedev(buf.stx_dev_major, buf.stx_dev_minor); + 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); + 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; + stat.st_atime_nsec = buf.stx_atime.tv_nsec as libc::c_long; + stat.st_mtime = buf.stx_mtime.tv_sec as libc::time_t; + stat.st_mtime_nsec = buf.stx_mtime.tv_nsec as libc::c_long; + stat.st_ctime = buf.stx_ctime.tv_sec as libc::time_t; + stat.st_ctime_nsec = buf.stx_ctime.tv_nsec as libc::c_long; + + let extra = StatxExtraFields { + stx_mask: buf.stx_mask, + stx_btime: buf.stx_btime, + }; + + Some(Ok(FileAttr { stat, statx_extra_fields: Some(extra) })) + } + } } // all DirEntry's will have a reference to this struct @@ -98,6 +176,14 @@ pub struct FileType { mode: mode_t } pub struct DirBuilder { mode: mode_t } impl FileAttr { + fn from_stat64(stat: stat64) -> Self { + Self { + stat, + #[cfg(target_os = "linux")] + statx_extra_fields: None, + } + } + pub fn size(&self) -> u64 { self.stat.st_size as u64 } pub fn perm(&self) -> FilePermissions { FilePermissions { mode: (self.stat.st_mode as mode_t) } @@ -164,6 +250,23 @@ impl FileAttr { target_os = "macos", target_os = "ios")))] pub fn created(&self) -> io::Result { + #[cfg(target_os = "linux")] + { + 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 libc::c_long, + })) + } 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")) @@ -306,12 +409,26 @@ impl DirEntry { #[cfg(any(target_os = "linux", target_os = "emscripten", target_os = "android"))] pub fn metadata(&self) -> io::Result { - 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(target_os = "linux")] + { + 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")))] @@ -517,11 +634,25 @@ impl File { } pub fn file_attr(&self) -> io::Result { + let fd = self.0.raw(); + + #[cfg(target_os = "linux")] + { + 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<()> { @@ -798,20 +929,46 @@ pub fn link(src: &Path, dst: &Path) -> io::Result<()> { pub fn stat(p: &Path) -> io::Result { let p = cstr(p)?; + + #[cfg(target_os = "linux")] + { + 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 { let p = cstr(p)?; + + #[cfg(target_os = "linux")] + { + 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 {