Skip to content
Open
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
96 changes: 96 additions & 0 deletions library/std/src/os/linux/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,29 @@

#![stable(feature = "metadata_ext", since = "1.1.0")]

use core::mem;

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, StatxExtraFields};
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;
Comment thread
asder8215 marked this conversation as resolved.

/// OS-specific extensions to [`fs::Metadata`].
///
Expand Down Expand Up @@ -41,6 +60,50 @@ 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.
Comment on lines +65 to +66
Copy link
Copy Markdown
Member

@Darksonn Darksonn May 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was planning to wait for #154981 before implementing this to avoid this clause.

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got you. I think the order doesn't matter right now since this function would automatically have musl support once #154981 lands as the PR updates the cfg_has_statx macro. The only thing that needs to be updated is the documentation for this function, which I'm down to keep my eyes on #154981 to see it get merged in.

///
/// # 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`].
///
/// [`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::<statx>::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<statx> = 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
Expand Down Expand Up @@ -337,6 +400,39 @@ 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 {
let buf = statxbuf as *const libc::statx;

// 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 extra = StatxExtraFields::from_statx(statxbuf);

Metadata::from_inner(FileAttr::from_statx(stat, Some(extra)))
}} 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
}
Expand Down
4 changes: 4 additions & 0 deletions library/std/src/sys/fs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ 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, StatxExtraFields
};
}
target_os = "windows" => {
mod windows;
Expand Down
28 changes: 27 additions & 1 deletion library/std/src/sys/fs/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ use crate::fmt::{self, Write as _};
use crate::fs::TryLockError;
use crate::io::{self, BorrowedCursor, Error, IoSlice, IoSliceMut, SeekFrom};
use crate::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd};
cfg_has_statx! {{
use crate::os::raw::c_void;
} else {}}
#[cfg(target_family = "unix")]
use crate::os::unix::prelude::*;
#[cfg(target_os = "wasi")]
Expand Down Expand Up @@ -125,6 +128,8 @@ macro_rules! cfg_has_statx {
};
}

pub(crate) use cfg_has_statx;

cfg_has_statx! {{
#[derive(Clone)]
pub struct FileAttr {
Expand All @@ -133,7 +138,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,
Expand Down Expand Up @@ -525,11 +530,32 @@ pub struct DirBuilder {
struct Mode(mode_t);

cfg_has_statx! {{
impl StatxExtraFields {
pub unsafe fn from_statx(statxbuf: *const c_void) -> Self {
let buf = statxbuf as *const libc::statx;

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 }
}

pub fn from_statx(stat: stat64, extra: Option<StatxExtraFields>) -> Self {
Self {stat, statx_extra_fields: extra}
}

#[cfg(target_pointer_width = "32")]
pub fn stx_mtime(&self) -> Option<&libc::statx_timestamp> {
if let Some(ext) = &self.statx_extra_fields {
Expand Down
Loading