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.
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 noos.statxon any host despite it being a feature in the regular build.python-build-standalone/docs/running.rst
Lines 78 to 84 in 63249f9
python-build-standalone/pythonbuild/downloads.json
Line 269 in 5e46737
From some exploration I was able to confirm that that
struct statxarrived in Linux 4.11, with commit a528d35e8bfcThe 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.16but #1163 added the necessary linux uapi headers.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 definestruct statxthemselves. Heres the full compat matrix to explain<linux/stat.h>itselfredefinition of 'statx_timestamp'struct statxstatx()prototype andAT_STATX_*struct statxstruct statxredefinition of 'statx_timestamp'struct statxin<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/#definefallbacks. The values (0x6000mask,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)
Since then
sysdeps/unix/sysv/linux/bits/statx.hincludes"linux/stat.h"when__has_includefinds it and checksSTATX_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
To explain why this seems to work
HAVE_STATXcomes fromAC_CHECK_FUNCS([statx])on Linux, a link test against the build libc. glibc < 2.28 does not exportstatx, 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_STATXalone is not enough. Withac_cv_func_statx=yesand the unpatched source, PBS-style compilation fails atposixmodule.c:425withuse 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_STATSis defined by every libc that declaresstatx()io/bits/stax.hio/bits/statx-geneirc.hor technically alsostatx.hvia inclusion I suppose for backcompat maybe ?musl1.2.5 in<sys/stat.h>So then the negation there should guarantee no clashes, roughly similar logic for the
AT_STATX_*constants, on glibc >= 2.28 andmuslthe 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 theos.statxfix to work fully we obviously want to also make sure it can be passed all its flags properly. Of note is that theAT_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).
The additional issue
The high level idea for a potential solution is that (roughly similar to some of the existing patches)
Note: the one edge case, if you can call it that, is that if you were to then build dynamically against >=
musl1.2.5 and then run on an older statx-lacking musl system instead of os.statx being dropped gracefully the interpreter would poop itself withThough 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.
The result of this fix would then be as far as I can tell
os.statxbeing 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
/pythonbuild/disttests/__init__.py#L416-L468testingos.statxI 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.