about summary refs log tree commit diff
path: root/src/libstd
diff options
context:
space:
mode:
authoroxalica <oxalicc@pm.me>2019-10-04 16:17:23 +0800
committeroxalica <oxalicc@pm.me>2019-10-18 10:32:27 +0800
commit43f398be6d0faaf119150c950133ba4aa0ff42b3 (patch)
treefef8247c46d5ac6191e1a1606014330ef8e97c5d /src/libstd
parentfa0f7d0080d8e7e9eb20aa9cbf8013f96c81287f (diff)
downloadrust-43f398be6d0faaf119150c950133ba4aa0ff42b3.tar.gz
rust-43f398be6d0faaf119150c950133ba4aa0ff42b3.zip
Prefer statx on linux if available
Diffstat (limited to 'src/libstd')
-rw-r--r--src/libstd/fs.rs22
-rw-r--r--src/libstd/sys/unix/fs.rs171
2 files changed, 182 insertions, 11 deletions
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<StatxExtraFields>,
+}
+
+#[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<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();
+            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<SystemTime> {
+        #[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<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(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<FileAttr> {
+        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<FileAttr> {
     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<FileAttr> {
     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<PathBuf> {