Skip to content

os.statx (new in 3.15) and all its constants are missing from the gnu builds (also the +static musl 1.2.5 builds that have statx) #1316

Description

@KaiErikNiermann

A short preamble, I didn't run into this issue personally in the sense of like it effected me, I'm just raising this because I assume it could very reasonably effect people who want to use os.statx or something it depends on under the assumption the code both works and for the dependents actually calls the intended thing as opposed to some fallback path. Also this isn't technically a PBS issue-ish the real problem is ofc cpython's (sort-of) I guess but from what I can tell the precident specifically for backcompat related things seems to quite often be initiated from here via patches which get sent upstream. And I assume this issue would just be drowned out by the bagillion other issues in cpython and I mean its also arguably just more pertinent to be raised here I'd say.

heyooo again,

this issue seems to be a lot more annoying to fix, the tldr is actually rather simple os.statx (new in 3.15, gh-83714, PR #139178) is compiled out whenever the build's glibc predates 2.28, because configure gates it on a link test against the build libc. python-build-standalone (PBS), and therefore uv, builds against glibc 2.19 headers, so its 3.15 builds have no os.statx on any host despite it being a feature in the regular build.

Update: while writing this I discovered an additional problem, for static musl builds where musl actually has statx the current cpython code would mean linkers ignore the actually present statx bc it only declares it as a weak reference.

Tested, all against glibc 2.44's libc.a (same would hold for musl)

bfd weak: &statx=(nil)
gold weak: &statx=(nil)
ld.lld weak: &statx=(nil)
weak + a strong ref elsewhere in the program: &statx=0x41ae90

So this is a related bug currently present in the 3.15 +static release using musl 1.2.5

``x86_64-unknown-linux-musl``
Linux 64-bit Intel/AMD CPUs linking against musl libc.
Distributions are provided that dynamically link musl or are fully static
(``+static``). Dynamically linked distributions require musl to be installed
on the host. Fully static distributions have no shared library dependencies,
but cannot load Python ``.so`` extensions.

"url": "https://musl.libc.org/releases/musl-1.2.5.tar.gz",

From some exploration I was able to confirm that that struct statx arrived in Linux 4.11, with commit a528d35e8bfc

"statx: Add a system call to make enhanced file info available" (David Howells, 2017-01-31).

The natural question for a patch is then first of all what kernel headers do y'all use the long story short seems to be glibc 2.19 + Linux 3.16 but #1163 added the necessary linux uapi headers.

"python-build-standalone installs these header from Linux 7.0.12 and places them before the sysroot headers in the search path."

The main issue is that you can't just naively include the linux stat headers the tldr here is that <linux/stat.h> coexists with glibc 2.17 and 2.19 <sys/stat.h>, but clashes with glibc 2.28-2.29 and musl 1.2.5, which define struct statx themselves. Heres the full compat matrix to explain

libc + kernel headers naive include gated include note
glibc 2.44 + Linux 7.2 (this host) ok ok glibc >= 2.30 includes <linux/stat.h> itself
glibc 2.28 + Linux 7.0.12 redefinition of 'statx_timestamp' ok glibc's own copy of struct statx
glibc 2.19 + Linux 7.0.12 (PBS) ok ok needs only a statx() prototype and AT_STATX_*
glibc 2.19 + Linux 3.16 no struct statx ok only with local fallback Jessie alone
glibc 2.17 + Linux 3.10 no struct statx ok only with local fallback CentOS 7 / manylinux2014
musl 1.2.2 + Linux 7.0.12 ok ok needs a prototype
musl 1.2.5 + Linux 7.0.12 redefinition of 'statx_timestamp' ok musl defines struct statx in <sys/stat.h>

AT_STATX_* (which are flags that statx takes) are a separate problem. The kernel defines them in <linux/fcntl.h>, and that header clashes with glibc's <fcntl.h> (struct flock). On glibc < 2.28 they have to be #ifndef/#define fallbacks. The values (0x6000 mask, 0x0000, 0x2000, 0x4000) are fixed since Linux 4.11. musl 1.2.2 already has them in <fcntl.h>.

glibc documents the clash itself. Commit 5dad6ffbb2b7 (Florian Weimer, 2019-06-12, first in glibc 2.30)

"<sys/stat.h>: Use Linux UAPI header for statx if available and useful ... This will automatically import new STATX_* constants. It also avoids a conflict between <sys/stat.h> and <linux/stat.h>."

Since then sysdeps/unix/sysv/linux/bits/statx.h includes "linux/stat.h" when __has_include finds it and checks STATX_TYPE, the same test the gate here uses. glibc 2.28 and 2.29 ship their own definition only, hence the clash in the table.

The following patch does appear to fix the clashes (and the underlying issue) as far as I could tell at least

/* after <sys/stat.h> */
#if defined(HAVE_STATX) && !defined(STATX_BASIC_STATS)
#  include <linux/stat.h>
extern int statx(int, const char *, int, unsigned int, struct statx *);
#endif

/* after <fcntl.h> */
#if defined(HAVE_STATX) && !defined(AT_STATX_SYNC_TYPE)
#  define AT_STATX_SYNC_TYPE    0x6000
#  define AT_STATX_SYNC_AS_STAT 0x0000
#  define AT_STATX_FORCE_SYNC   0x2000
#  define AT_STATX_DONT_SYNC    0x4000
#endif

To explain why this seems to work HAVE_STATX comes from AC_CHECK_FUNCS([statx]) on Linux, a link test against the build libc. glibc < 2.28 does not export statx, so the test fails and the preprocessor removes all of the above. The struct member probes only run when that test passed, and they include only <sys/stat.h>.

Forcing HAVE_STATX alone is not enough. With ac_cv_func_statx=yes and the unpatched source, PBS-style compilation fails at posixmodule.c:425 with use of undeclared identifier 'STATX_BASIC_STATS', because glibc 2.19's <sys/stat.h> has no statx definitions and nothing includes the kernel header. (it defines the stat struct but not the function)

STATX_BASIC_STATS is defined by every libc that declares statx()

So then the negation there should guarantee no clashes, roughly similar logic for the AT_STATX_* constants, on glibc >= 2.28 and musl the libc's <fcntl.h> defines the flags as a group so we use just the first to avoid the conflict there. The motivation here again being that PSB's glibc does not have these flags so for the os.statx fix to work fully we obviously want to also make sure it can be passed all its flags properly. Of note is that the AT_STAX_* approach appears to be similar to what was done here so there seems to be some precident for hardcoded kernel ABI values.

There is one related pedantic edge with un-versioned binding if glibc ever re-versioned statx for an incompatible change, an unversioned caller would get the new behaviour while compiled for the old one. For statx that is probably very unlikely, since the struct is frozen kernel ABI and glibc's wrapper only passes it through. sem_clockwait's re-versioning kept the same implementation (both versions are aliases at the same address).

Also this doesnt seem to be the general practice for cpython anyways from what I can tell so I assume it doesn't matter.

The additional issue

The high level idea for a potential solution is that (roughly similar to some of the existing patches)

  • we have statx be weak only when the build libc lacks stax, this is just the current situation and would fix the existing issue detailed above
  • we have statx be a plain strong reference when the build does have it so that the linker knows to pull it in for the static compilation case

Note: the one edge case, if you can call it that, is that if you were to then build dynamically against >= musl 1.2.5 and then run on an older statx-lacking musl system instead of os.statx being dropped gracefully the interpreter would poop itself with

Error relocating /usr/bin/python3: statx: symbol not found

Though of note is that this already happens for preadv2/pwritev2 anyways, so I mean this wouldn't really break anything for that case that isn't arguably already broken.

Sidenote: os.preadv/os.pwritev raise NotImplementedError for any flags value, and the RWF_* constants are missing, also relatedly os.posix_spawn(..., setsid=True) raises NotImplementedError on every glibc, since POSIX_SPAWN_SETSID is not in the 2.17 headers, I'd be happy to raise proper separate issues for these btw these seem to be smaller fixes similar to the existing patches, I assume sep issues might just be noise, in any case just wanted to add this

The result of this fix would then be as far as I can tell os.statx being correctly available for all "normal" cases where it intuitively should be available. I did prototype this into a full thing and I think it works as described but I might be missing certain nuances here so I can share my approach if it would help developing a patch but I just didn't want to send an unprompted PR and my prototype here is still a bit unrefined so I thought I'd just describe it roughly and then I guess on request I can share wat I did.

Some final notes which might be helpful

  • From what I could tell the static libc link issue wasn't caught because because cpython just doesn't appear to test it ? I haven't checked yet if this has broader reaching implications or is just relevant here. The main test I could see that did exercise statx working used a strong reference which meant that statx was properly pulled in by the linker hence no issue.
  • Interestingly y'all cover related functions here /pythonbuild/disttests/__init__.py#L416-L468 testing os.statx I would assume would fail, and to be pedantic to catch both issue's y'all would have to also include the static musl build as well in the like decorator thingimabob which I'd assume goes through all the different build options.

Activity

  1. changed the title [-]`os.statx` (new in 3.15) and all its constants are missing from the gnu builds, since glibc 2.17 has no statx and CPython's `#pragma weak statx` sits under `#ifdef HAVE_STATX`[/-] [+]`os.statx` (new in 3.15) and all its constants are missing from the gnu builds, since glibc 2.17 has no statx and CPython's `#pragma weak statx` sits under `#ifdef HAVE_STATX` (also the +static musl 1.2.5 builds that have statx)[/+] on Oct 3, 2026
  2. changed the title [-]`os.statx` (new in 3.15) and all its constants are missing from the gnu builds, since glibc 2.17 has no statx and CPython's `#pragma weak statx` sits under `#ifdef HAVE_STATX` (also the +static musl 1.2.5 builds that have statx)[/-] [+]`os.statx` (new in 3.15) and all its constants are missing from the gnu builds (also the +static musl 1.2.5 builds that have statx)[/+] on Oct 3, 2026
  3. jjhelmus commented on Oct 7, 2026

    @jjhelmus
    Contributor

    We have been using weak symbols and building with newer kernel headers to work around similar issue to allow functionality that depends on a minimum glibc or kernel version to be detected at runtime rather than build time.

    When I looked at os.statx I though something similar was done upstream. Looking at it more detail I see that the build time glibc version is checked before the weak symbol is introduced..

    Falling back to the kernel provided statx syscall, __NR_statx may be an option here, similar to python/cpython#155762 but other changes are needed since the the current function uses the libc wrapper and requires various other macros. I'll look into this further later this week.

  4. jjhelmus commented on Oct 8, 2026

    @jjhelmus
    Contributor

    Looking at this further the pattern used in python/cpython#155520 can likely be adapted for statx. That PR added a purely syscall fallback which is used when the libc wrappers is unavailable at build time.

  5. jjhelmus commented on Oct 8, 2026

    @jjhelmus
    Contributor

    I put together python/cpython#159043 which adds a syscall fallback for statx which makes the function available when CPython is built with an older glibc but kernel headers that include the statx syscall (the case in PBS). I'm not sure how this effects that static musl builds, that needs further investigation.

  6. KaiErikNiermann commented on Oct 9, 2026

    @KaiErikNiermann
    Author

    I believe the 1.2.5 musl case should work bc it would just fallback to the syscall. In that the weak sym pragma still means statx is null for the build but the fallback just takes over so it works (of note if u remove the weak pragma the linker should take the wrapper path instead, i think, though in either case it would work). What I did notice though is that the 1.2.2 musl builds don't seem to include the linux-uapi headers, which is mostly* fine because it seems to have the syscalls for most of the stuff that the various glibc patches addressed but it seems to be missing struct statx (despite having the syscall) so I'd assume y'all would either need to include the headers there as well or move dynamic builds to 1.2.5 so that people using those builds also get the fix.

    *mostly fine in the sense that there are some other missing things like os.getrandom, os.P_PIDFD, flags for os.preadv

    Edit: I just realized after a bit of testing, if we keep the weak sym ref but stop gating it on the builds libc having statx then it should i think force the build on the hosts machine to ref its libc's impl of statx which minimizes the syscall path to only the case where the hosts build actually doesn't have the wrapper. The (debatable) benefit of this is that the libc wrappers around statx have the fallback handling themselves so if the hosts libc has the call the downstreamer user gets automatic fallbacks which the syscall ofc doesnt have. So like the tldr is u get glibc emulation + LD_PRELOAD interposition when the func is actually on the host machine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions