From 5a417e253530581d9cb82d4e9bef76008044b7dd Mon Sep 17 00:00:00 2001 From: Mahdi Ali-Raihan Date: Sun, 28 Jun 2026 21:19:35 +0000 Subject: [PATCH] Implement `Metadata:from_statx` for Linux `MetadataExt` trait * Implement Metadata:from_statx for Linux MetadataExt trait * Added STATX_MASK constant and included documentation for it * Refactored shared code from try_statx and Metadata::from_statx into a function with FileAttr::from_statx --- library/std/src/os/linux/fs.rs | 72 ++++++++++++++++++++++++++ library/std/src/sys/fs/mod.rs | 2 + library/std/src/sys/fs/unix.rs | 94 +++++++++++++++++++++------------- 3 files changed, 133 insertions(+), 35 deletions(-) diff --git a/library/std/src/os/linux/fs.rs b/library/std/src/os/linux/fs.rs index e52a63bc798ea..00c57794abb69 100644 --- a/library/std/src/os/linux/fs.rs +++ b/library/std/src/os/linux/fs.rs @@ -7,7 +7,24 @@ use crate::fs::Metadata; #[allow(deprecated)] use crate::os::linux::raw; +use crate::os::raw::{c_uint, c_void}; use crate::sys::AsInner; +use crate::sys::fs::cfg_has_statx; +cfg_has_statx! {{ + use crate::sys::fs::FileAttr; + use crate::sys::FromInner; +} else { + use crate::sys::unsupported; +}} + +/// This is the [`statx`] mask expected by [`Metadata::from_statx`], which sets both +/// `STATX_BASIC_STATS` and `STATX_BTIME`. See the [Linux man page] for statx for more +/// details. +/// +/// [`statx`]: https://docs.rs/libc/latest/libc/struct.statx.html +/// [Linux man page]: https://man7.org/linux/man-pages/man2/statx.2.html +#[unstable(feature = "metadata_statx", issue = "156268")] +pub const STATX_MASK: c_uint = libc::STATX_BASIC_STATS | libc::STATX_BTIME; /// OS-specific extensions to [`fs::Metadata`]. /// @@ -41,6 +58,53 @@ pub trait MetadataExt { #[allow(deprecated)] fn as_raw_stat(&self) -> &raw::stat; + /// Creates a [`Metadata`] from a const void pointer populated by the [`statx`] syscall. + /// + /// Currently [`Metadata::from_statx`] is only supported on Linux platforms with a target + /// environment of GNU. + /// + /// # Safety + /// + /// The caller must take care to provide a valid const void pointer containing information + /// populated by the [`statx`] syscall. In particular, the provided pointer should contain + /// statx information pertaining to the mask [`STATX_MASK`], so that there will be no + /// uninitialized data encountered in constructing [`Metadata`]. + /// + /// Note that the relevant information is copied out of the structure and the pointer is + /// not retained past the call. + /// + /// [`Metadata`]: crate::fs::Metadata + /// [`statx`]: https://docs.rs/libc/latest/libc/struct.statx.html + /// + /// ```no_run + /// #![feature(metadata_statx)] + /// use libc::statx; + /// use std::ffi::c_void; + /// use std::fs::{write, Metadata}; + /// use std::io; + /// use std::os::linux::fs::{MetadataExt, STATX_MASK}; + /// + /// fn main() -> io::Result<()> { + /// write("hello.txt", "Hello World!")?; + /// let mut buf = Box::::new_uninit(); + /// unsafe { + /// libc::statx( + /// libc::AT_FDCWD, + /// "hello.txt".as_ptr().cast(), + /// libc::AT_STATX_SYNC_AS_STAT, + /// STATX_MASK, + /// buf.as_mut_ptr().cast() + /// ); + /// } + /// let statxbuf: Box = unsafe { buf.assume_init() }; + /// let metadata = unsafe { Metadata::from_statx(&*statxbuf as *const statx as *const c_void) }; + /// assert_eq!(metadata.len(), 12); // "Hello World!" is 12 bytes + /// Ok(()) + /// } + /// ``` + #[unstable(feature = "metadata_statx", issue = "156268")] + unsafe fn from_statx(statxbuf: *const c_void) -> Self; + /// Returns the device ID on which this file resides. /// /// # Examples @@ -337,6 +401,14 @@ impl MetadataExt for Metadata { &*(self.as_inner().as_inner() as *const libc::stat64 as *const raw::stat) } } + cfg_has_statx! {{ + unsafe fn from_statx(statxbuf: *const c_void) -> Metadata { + Metadata::from_inner(FileAttr::from_statx(*(statxbuf as *const libc::statx))) + }} else { + unsafe fn from_statx(statxbuf: *const c_void) -> Self { + unsupported(); + } + }} fn st_dev(&self) -> u64 { self.as_inner().as_inner().st_dev as u64 } diff --git a/library/std/src/sys/fs/mod.rs b/library/std/src/sys/fs/mod.rs index 0c297c5766b82..789d40299bcaa 100644 --- a/library/std/src/sys/fs/mod.rs +++ b/library/std/src/sys/fs/mod.rs @@ -18,6 +18,8 @@ cfg_select! { #[cfg(any(target_os = "linux", target_os = "android"))] pub(super) use unix::CachedFileMetadata; use crate::sys::helpers::run_path_with_cstr as with_native_path; + #[cfg(all(target_os = "linux", target_env = "gnu"))] + pub(crate) use unix::cfg_has_statx; } target_os = "windows" => { mod windows; diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index 3152a22534f6c..2fd6203b7021d 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -125,6 +125,9 @@ macro_rules! cfg_has_statx { }; } +#[cfg(all(target_os = "linux", target_env = "gnu"))] +pub(crate) use cfg_has_statx; + cfg_has_statx! {{ #[derive(Clone)] pub struct FileAttr { @@ -133,7 +136,7 @@ cfg_has_statx! {{ } #[derive(Clone)] - struct StatxExtraFields { + pub struct StatxExtraFields { // This is needed to check if btime is supported by the filesystem. stx_mask: u32, stx_btime: libc::statx_timestamp, @@ -212,40 +215,7 @@ cfg_has_statx! {{ STATX_SAVED_STATE.store(STATX_STATE::Present as u8, Ordering::Relaxed); } - // 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, - // Store full times to avoid 32-bit `time_t` truncation. - #[cfg(target_pointer_width = "32")] - stx_atime: buf.stx_atime, - #[cfg(target_pointer_width = "32")] - stx_ctime: buf.stx_ctime, - #[cfg(target_pointer_width = "32")] - stx_mtime: buf.stx_mtime, - }; - - Some(Ok(FileAttr { stat, statx_extra_fields: Some(extra) })) + Some(Ok(FileAttr::from_statx(buf))) } } else { @@ -525,11 +495,65 @@ pub struct DirBuilder { struct Mode(mode_t); cfg_has_statx! {{ + impl StatxExtraFields { + /// Constructs `StatxExtraFields` from a given `libc::statx` struct + /// + /// SAFETY: + /// The caller must take care to provide a `libc::statx` buffer that is + /// populated by the statx syscall with the flags `STATX_BASIC_STATS` + /// and `STATX_BTIME` enabled + pub unsafe fn from_statx(buf: libc::statx) -> Self { + StatxExtraFields { + stx_mask: buf.stx_mask, + stx_btime: buf.stx_btime, + // Store full times to avoid 32-bit `time_t` truncation. + #[cfg(target_pointer_width = "32")] + stx_atime: buf.stx_atime, + #[cfg(target_pointer_width = "32")] + stx_ctime: buf.stx_ctime, + #[cfg(target_pointer_width = "32")] + stx_mtime: buf.stx_mtime, + } + } + } impl FileAttr { fn from_stat64(stat: stat64) -> Self { Self { stat, statx_extra_fields: None } } + /// Constructs `FileAttr` from a given `libc::statx` struct + /// + /// SAFETY: + /// The caller must take care to provide a `libc::statx` buffer that is + /// populated by the statx syscall with the flags `STATX_BASIC_STATS` + /// and `STATX_BTIME` enabled + pub unsafe fn from_statx(buf: libc::statx) -> Self { + // We cannot fill `stat64` exhaustively because of private padding fields. + let mut stat: libc::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 libc::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 statx_extra_fields = Some(StatxExtraFields::from_statx(buf)); + + Self {stat, statx_extra_fields} + } + #[cfg(target_pointer_width = "32")] pub fn stx_mtime(&self) -> Option<&libc::statx_timestamp> { if let Some(ext) = &self.statx_extra_fields {