From 1bf22c11decd1a209dc69f67fac136de043d67f7 Mon Sep 17 00:00:00 2001 From: Leon White Date: Mon, 13 Apr 2026 10:25:09 +0200 Subject: [PATCH 1/3] Make statx available on all Linux targets Since statx is currently invoked through the syscall directly anyways, it makes no sense to gate it to glibc targets, since none of this is glibc specific at the moment. As long as the kernel is recent enough, it supports statx. --- library/std/src/sys/fs/unix.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index cadbf1a21aa8f..eb1d63324e933 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -6,7 +6,7 @@ #[cfg(test)] mod tests; -#[cfg(all(target_os = "linux", target_env = "gnu"))] +#[cfg(target_os = "linux")] use libc::c_char; #[cfg(any( all(target_os = "linux", not(target_env = "musl")), @@ -95,7 +95,7 @@ use crate::sys::fd::FileDesc; pub use crate::sys::fs::common::exists; use crate::sys::helpers::run_path_with_cstr; use crate::sys::time::SystemTime; -#[cfg(all(target_os = "linux", target_env = "gnu"))] +#[cfg(target_os = "linux")] use crate::sys::weak::syscall; #[cfg(target_os = "android")] use crate::sys::weak::weak; @@ -126,14 +126,14 @@ mod runtime_symbols { pub struct File(FileDesc); -// FIXME: This should be available on Linux with all `target_env`. -// But currently only glibc exposes `statx` fn and structs. -// We don't want to import unverified raw C structs here directly. +// statx is available on Linux when: +// - target_env = "gnu": added in glibc >= 2.28 +// - target_env = "musl": added in musl >= 1.2.5 // https://github.com/rust-lang/rust/pull/67774 macro_rules! cfg_has_statx { ({ $($then_tt:tt)* } else { $($else_tt:tt)* }) => { cfg_select! { - all(target_os = "linux", target_env = "gnu") => { + target_os = "linux" => { $($then_tt)* } _ => { @@ -142,7 +142,7 @@ macro_rules! cfg_has_statx { } }; ($($block_inner:tt)*) => { - #[cfg(all(target_os = "linux", target_env = "gnu"))] + #[cfg(target_os = "linux")] { $($block_inner)* } From bd844a54121d26cbd0714d23ac0e88e5a4b8d29c Mon Sep 17 00:00:00 2001 From: Aelin Reidel Date: Sun, 19 Jul 2026 02:18:37 +0200 Subject: [PATCH 2/3] bootstrap: Set RUST_LIBC_UNSTABLE_MUSL_V1_2_3=1 This is a requirement for using libc::statx in std and the musl baseline in the Rust compiler is currently 1.2.5. --- src/bootstrap/src/core/builder/cargo.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/bootstrap/src/core/builder/cargo.rs b/src/bootstrap/src/core/builder/cargo.rs index 6de70c7d70cde..38878355096d5 100644 --- a/src/bootstrap/src/core/builder/cargo.rs +++ b/src/bootstrap/src/core/builder/cargo.rs @@ -1305,6 +1305,13 @@ impl Builder<'_> { // The libc build script emits check-cfg flags only when this environment variable is set, // so this line allows the use of custom libcs. cargo.env("LIBC_CHECK_CFG", "1"); + // The libc crate has a musl version baseline that is much lower than that of the musl + // targets in the Rust compiler at the moment. This unfortunately means that features only + // supported in newer musl releases like time64 and, for example statx, are not available by + // default unless opted into via an environment variable. The version baseline of the Rust + // compiler targets is currently 1.2.5, so we can set the variable to get access to statx in + // std, amongst other things. + cargo.env("RUST_LIBC_UNSTABLE_MUSL_V1_2_3", "1"); let mut lint_flags = Vec::new(); From 5c51ad51f3ca466ffed2fc19c2b39bb6c343a831 Mon Sep 17 00:00:00 2001 From: Aelin Reidel Date: Sun, 19 Jul 2026 02:20:13 +0200 Subject: [PATCH 3/3] std: Unconditionally use statx on musl targets On musl targets, we can assume that musl has support for statx and falls back to fstatat internally, so there's no need to do detection and caching of statx in std. --- library/std/src/sys/fs/unix.rs | 112 ++++++++++++++++++--------------- 1 file changed, 63 insertions(+), 49 deletions(-) diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index eb1d63324e933..2ec065ee6c983 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -95,7 +95,7 @@ use crate::sys::fd::FileDesc; pub use crate::sys::fs::common::exists; use crate::sys::helpers::run_path_with_cstr; use crate::sys::time::SystemTime; -#[cfg(target_os = "linux")] +#[cfg(all(target_os = "linux", not(target_env = "musl")))] use crate::sys::weak::syscall; #[cfg(target_os = "android")] use crate::sys::weak::weak; @@ -174,67 +174,81 @@ cfg_has_statx! {{ // We prefer `statx` on Linux if available, which contains file creation time, // as well as 64-bit timestamps of all kinds. // Default `stat64` contains no creation time and may have 32-bit `time_t`. + // + // We can unconditionally assume that musl targets have `statx` because musl + // takes care of falling back to fstatat and our musl version baseline is + // 1.2.5, which is the first release with statx support. unsafe fn try_statx( fd: c_int, path: *const c_char, flags: i32, mask: u32, ) -> Option> { - use crate::sync::atomic::{Atomic, AtomicU8, Ordering}; - - // Linux kernel prior to 4.11 or glibc prior to glibc 2.28 don't support `statx`. - // We check for it on first failure and remember availability to avoid having to - // do it again. - #[repr(u8)] - enum STATX_STATE{ Unknown = 0, Present, Unavailable } - static STATX_SAVED_STATE: Atomic = AtomicU8::new(STATX_STATE::Unknown as u8); - - syscall!( - fn statx( - fd: c_int, - pathname: *const c_char, - flags: c_int, - mask: libc::c_uint, - statxbuf: *mut libc::statx, - ) -> c_int; - ); - - let statx_availability = STATX_SAVED_STATE.load(Ordering::Relaxed); - if statx_availability == STATX_STATE::Unavailable as u8 { - return None; - } - let mut buf: libc::statx = mem::zeroed(); - if let Err(err) = cvt(statx(fd, path, flags, mask, &mut buf)) { - if STATX_SAVED_STATE.load(Ordering::Relaxed) == STATX_STATE::Present as u8 { + + #[cfg(target_env = "musl")] + { + if let Err(err) = cvt(libc::statx(fd, path, flags, mask, &mut buf)) { return Some(Err(err)); } + } + #[cfg(not(target_env = "musl"))] + { + use crate::sync::atomic::{Atomic, AtomicU8, Ordering}; + + // Linux kernel prior to 4.11 or glibc prior to glibc 2.28 don't support `statx`. + // We check for it on first failure and remember availability to avoid having to + // do it again. + #[repr(u8)] + enum STATX_STATE{ Unknown = 0, Present, Unavailable } + static STATX_SAVED_STATE: Atomic = AtomicU8::new(STATX_STATE::Unknown as u8); + + syscall!( + fn statx( + fd: c_int, + pathname: *const c_char, + flags: c_int, + mask: libc::c_uint, + statxbuf: *mut libc::statx, + ) -> c_int; + ); + + let statx_availability = STATX_SAVED_STATE.load(Ordering::Relaxed); + if statx_availability == STATX_STATE::Unavailable as u8 { + return None; + } - // We're not yet entirely sure whether `statx` is usable on this kernel - // or not. Syscalls can return errors from things other than the kernel - // per se, e.g. `EPERM` can be returned if seccomp is used to block the - // syscall, or `ENOSYS` might be returned from a faulty FUSE driver. - // - // Availability is checked by performing a call which expects `EFAULT` - // if the syscall is usable. - // - // See: https://github.com/rust-lang/rust/issues/65662 - // - // FIXME what about transient conditions like `ENOMEM`? - let err2 = cvt(statx(0, ptr::null(), 0, libc::STATX_BASIC_STATS | libc::STATX_BTIME, ptr::null_mut())) - .err() - .and_then(|e| e.raw_os_error()); - if err2 == Some(libc::EFAULT) { + if let Err(err) = cvt(statx(fd, path, flags, mask, &mut buf)) { + if STATX_SAVED_STATE.load(Ordering::Relaxed) == STATX_STATE::Present as u8 { + return Some(Err(err)); + } + + // We're not yet entirely sure whether `statx` is usable on this kernel + // or not. Syscalls can return errors from things other than the kernel + // per se, e.g. `EPERM` can be returned if seccomp is used to block the + // syscall, or `ENOSYS` might be returned from a faulty FUSE driver. + // + // Availability is checked by performing a call which expects `EFAULT` + // if the syscall is usable. + // + // See: https://github.com/rust-lang/rust/issues/65662 + // + // FIXME what about transient conditions like `ENOMEM`? + let err2 = cvt(statx(0, ptr::null(), 0, libc::STATX_BASIC_STATS | libc::STATX_BTIME, ptr::null_mut())) + .err() + .and_then(|e| e.raw_os_error()); + if err2 == Some(libc::EFAULT) { + STATX_SAVED_STATE.store(STATX_STATE::Present as u8, Ordering::Relaxed); + return Some(Err(err)); + } else { + STATX_SAVED_STATE.store(STATX_STATE::Unavailable as u8, Ordering::Relaxed); + return None; + } + } + if statx_availability == STATX_STATE::Unknown as u8 { STATX_SAVED_STATE.store(STATX_STATE::Present as u8, Ordering::Relaxed); - return Some(Err(err)); - } else { - STATX_SAVED_STATE.store(STATX_STATE::Unavailable as u8, Ordering::Relaxed); - return None; } } - if statx_availability == STATX_STATE::Unknown as u8 { - 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();