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
124 changes: 69 additions & 55 deletions library/std/src/sys/fs/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")),
Expand Down Expand Up @@ -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(all(target_os = "linux", not(target_env = "musl")))]
use crate::sys::weak::syscall;
#[cfg(target_os = "android")]
use crate::sys::weak::weak;
Expand Down Expand Up @@ -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)*
}
_ => {
Expand All @@ -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)*
}
Expand Down Expand Up @@ -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<io::Result<FileAttr>> {
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<u8> = 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<u8> = 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;
}

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) {
// 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();
Expand Down
7 changes: 7 additions & 0 deletions src/bootstrap/src/core/builder/cargo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");

@bjorn3 bjorn3 Jul 19, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This would break with build-std, right? Cargo wouldn't set this env var.

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.

That's a good point, right. Not sure how to go about that. I presume we'd want to modify cargo to set the variable for rustc processes while compiling std? That sounds fairly complicated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we make this a regular cargo feature prefixed with unstable or internal-do-not-use or something like that and then enable it here?

@Gelbpunkt Gelbpunkt Jul 26, 2026

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.

More recent libc versions require us to specify --cfg=libc_unstable_musl_v1_2_3 as of rust-lang/libc@452750f, but in that case just setting it here still wouldn't be enough. Having to set this here and in cargo feels a bit hacky. It might make sense to make the rustc-dep-of-std feature imply libc_unstable_musl_v1_2_3. libc already suppresses a few warnings for musl version compatibility if that feature is enabled.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Would we actually be able to lift the musl_v1_2_3 gate on statx? I don't remember why I originally asked for the gate but I guess maybe I was thinking of struct stat which has time_t fields (and thus would have an incorrect definition without musl_v1_2_3), whereas struct statx brings its own time types https://github.com/rust-lang/libc/blob/d0c9b0f14ef624576cfcb0a1285b2b29d54f4d47/src/unix/linux_like/mod.rs#L283-L333.

If we can do that then it avoids the build-std issue. At some point, however, we should probably figure out a way to set the cfgs for libc to enable 64-bit time_t.

@Gelbpunkt Gelbpunkt Aug 6, 2026

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.

statx is supported in musl only since 1.2.5, so if libc were to unconditionally expose it, I presume you'd see linker errors with older musl releases when calling it. I really think the easiest path forward here would be making rustc-dep-of-std imply libc_unstable_musl_v1_2_3 and 64-bit time_t, that'd also fix the build-std issue.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't think that's a problem since we have lots of API for all platforms that isn't available until more recently. Won't most users be getting musl via the bundled version anyway, which is 1.2.5?

Regardless I think what you mentioned with rustc-dep-of-std is reasonable, though it would be best to test it by first setting the cfg in rust-lang/rust and running tests to see if anything else needs changing (to avoid some back-and-forth)


let mut lint_flags = Vec::new();

Expand Down
Loading