From 3c6923027b19e232bd7d3864e882f3899bfe9c90 Mon Sep 17 00:00:00 2001 From: Mahdi Ali-Raihan Date: Thu, 7 May 2026 02:36:26 -0400 Subject: [PATCH] Added Metadata::from_statx for Linux MetadataExt trait Added STATX_MASK constant and accompanying documentation for reference on what Metadata::from_statx will accept Refactored shared code from try_statx and Metadata::from_statx into FileAttr::from_statx With libc bumped to version 0.2.191, statx, STATX_BASIC_STATS, STATX_BTIME are supported across all musl arches, so now we can note in documentation that Metadata::from_statx is supported on all Linux platforms --- library/std/src/os/linux/fs.rs | 75 +++++++++++++++++++++++++++ library/std/src/sys/fs/mod.rs | 2 + library/std/src/sys/fs/unix.rs | 94 +++++++++++++++++++++------------- 3 files changed, 136 insertions(+), 35 deletions(-) diff --git a/library/std/src/os/linux/fs.rs b/library/std/src/os/linux/fs.rs index 0e8fa0a15da09..a7fd414d73438 100644 --- a/library/std/src/os/linux/fs.rs +++ b/library/std/src/os/linux/fs.rs @@ -7,7 +7,25 @@ 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")] +#[cfg(any(target_env = "gnu", target_env = "musl", target_os = "android", target_os = "l4re"))] +pub const STATX_MASK: c_uint = libc::STATX_BASIC_STATS | libc::STATX_BTIME; /// OS-specific extensions to [`fs::Metadata`]. /// @@ -42,6 +60,52 @@ 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. + /// + /// # 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 @@ -354,6 +418,17 @@ 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 e15841b363d18..6ca85d7fc9fbf 100644 --- a/library/std/src/sys/fs/mod.rs +++ b/library/std/src/sys/fs/mod.rs @@ -11,6 +11,8 @@ cfg_select! { use unix as imp; #[cfg(any(target_os = "linux", target_os = "android"))] pub(super) use unix::CachedFileMetadata; + #[cfg(target_os = "linux")] + pub(crate) use unix::cfg_has_statx; #[cfg(not(any(target_os = "fuchsia", target_os = "wasi")))] pub use unix::chroot; #[cfg(not(target_os = "wasi"))] diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index d0cbabbdd4955..641ef50fe1158 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -123,6 +123,9 @@ macro_rules! cfg_has_statx { }; } +#[cfg(target_os = "linux")] +pub(crate) use cfg_has_statx; + cfg_has_statx! {{ #[derive(Clone)] pub struct FileAttr { @@ -131,7 +134,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, @@ -210,40 +213,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 { @@ -480,11 +450,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 {