Skip to content

Fix building against glibc older than 2.29 - #104

Open
bernardladenthin wants to merge 2 commits into
sheredom:mainfrom
bernardladenthin:fix/addchdir-np-old-glibc
Open

Fix building against glibc older than 2.29#104
bernardladenthin wants to merge 2 commits into
sheredom:mainfrom
bernardladenthin:fix/addchdir-np-old-glibc

Conversation

@bernardladenthin

@bernardladenthin bernardladenthin commented Aug 1, 2026

Copy link
Copy Markdown

Problem

subprocess.h does not compile on glibc older than 2.29 once subprocess_create_ex is
used. posix_spawn_file_actions_addchdir_np (added by #83, macOS branch added by #99)
only exists from glibc 2.29 onwards, and the POSIX path calls it unconditionally.

On manylinux2014 (CentOS 7, glibc 2.17, devtoolset-10 / GCC 10.2) the failure appears in
three shapes depending on the language mode:

C++    subprocess.h:1215: error: 'posix_spawn_file_actions_addchdir_np' was not declared in this scope
C11    subprocess.h:1215: error: implicit declaration of function ... [-Werror=implicit-function-declaration]
gnu89  ld: undefined reference to `posix_spawn_file_actions_addchdir_np'

The third one matters: in gnu89 the implicit declaration is only a warning, so the
compiler is happy and the linker fails instead. The symbol is genuinely absent from
libc.so. This is therefore not the _GNU_SOURCE visibility issue that
test/CMakeLists.txt already works around — no feature-test macro can conjure a symbol
that does not exist.

Affected in practice: manylinux2014 (glibc 2.17) and manylinux_2_28 (glibc 2.28), i.e. the
standard build environments for redistributable Linux binaries. llama.cpp vendors this
header, so its manylinux jobs hit this too.

Fix

Add a SUBPROCESS_HAVE_CWD compile-time probe next to the POSIX includes, and report a
requested process_cwd as ENOSYS where the platform cannot honour it. The cwd test is
compiled only where the capability exists.

Four deliberate choices:

  • Named after the capability, not the glibc symbol. Users care whether they may pass a
    cwd, not how it is implemented.
  • Defined on every platform, not only under #if !defined(_WIN32). An undefined macro
    evaluates to 0 inside #if, which would have silently dropped the test on Windows.
  • Nested #if, not #if defined(__GLIBC__) && !__GLIBC_PREREQ(2, 29). The
    preprocessor replaces unknown identifiers with 0 before evaluating, so the flat form
    becomes 0 (2, 29) — a syntax error on macOS, musl and Windows. && does not help,
    because this fails at parse time, not evaluation time.
  • Overridable. The detection is skipped when the macro is already defined, so platforms
    it cannot recognise are not stuck with the wrong answer. See the limitation below.

Behaviour on glibc >= 2.29 and on every non-glibc platform is byte-for-byte unchanged.
The macro is documented in README.md next to the process_cwd argument.

Testing

Full test suite in Docker, one container per row, sanitizer probes pinned off for
reproducibility. Windows was built natively with MSVC.

Environment libc Compiler SUBPROCESS_HAVE_CWD Test cases Result
manylinux2014 glibc 2.17 GCC 10.2 0 423 405 pass, 18 fail (see below)
AlmaLinux 8 glibc 2.28 clang 21.1 0 423 423 pass
Fedora 30 glibc 2.29 GCC 9.3 1 header only builds
Debian 11 glibc 2.31 GCC 10.2 1 432 432 pass
Ubuntu 24.04 glibc 2.39 GCC 13.3 1 432 432 pass
Ubuntu 24.04 glibc 2.39 clang 18.1 1 432 432 pass
Ubuntu 24.04 glibc 2.39 tcc 0.9.27 1 header only builds
Alpine 3.20 musl 1.2 GCC 13.2 1 432 432 pass
Alpine 3.10 musl 1.1.22 GCC 8.3 1 header only link error (see below)
Windows (cross) mingw-w64 GCC 13 1 compile-time assert holds
Windows (native) UCRT MSVC 19.44 1 387 387 pass

Before this change, the first two rows do not build at all.

The 2.29 boundary is hit exactly: 2.28 → 0, 2.29 → 1, 2.31 → 1. The 9 cwd test cases are
excluded only where the function is missing — all 432 are still compiled and green
everywhere else, including musl, where the #else branch applies. On Windows all 9 are
present and pass; the total of 387 there is 432 minus the 45 cases (5 tests x 9 language
modes) that test_shared.h already excludes under #if !defined(_MSC_VER).

macOS was not tested locally — CI covers it.

Limitation: musl older than 1.1.24

musl gained posix_spawn_file_actions_addchdir_np in 1.1.24, so the #else branch is
wrong for releases before that: Alpine 3.10 (musl 1.1.22) still fails to link. musl
deliberately exposes no version macro, so this cannot be detected. That is what the
override is for — -DSUBPROCESS_HAVE_CWD=0 builds cleanly there, verified.

Pre-existing failures on glibc 2.17, unrelated to this change

The 18 remaining failures on manylinux2014 are create_ex_subprocess_create_failure_preserves_error
and create_ex_subprocess_create_failure_does_not_leak_resources across the 9 language
modes. They expect subprocess_error_not_found and get 0.

The cause is in glibc, not in this library. A standalone probe on the same image:

posix_spawn  -> rc=0   child exited 127
posix_spawnp -> rc=0   child exited 127

Old posix_spawn does not report a failed exec back to the caller; glibc reworked this
in 2.24 — the same probe returns rc=0 on 2.17 and 2.23, and rc=2 on 2.24 and 2.28.
AlmaLinux 8 (glibc 2.28) shows the same SUBPROCESS_HAVE_CWD=0 code path with
zero failures, which isolates it cleanly. These failures were simply never observable
before, because the library did not build there at all. Not addressed here.

Also noticed

test/CMakeLists.txt passes -std=c++20, which GCC 8 (the RHEL 8 system compiler) rejects
in favour of -std=c++2a — the suite cannot be built with it, though the library itself
compiles fine. Not touched here; #105 fixes it separately.

posix_spawn_file_actions_addchdir_np arrived in glibc 2.29, so
subprocess_create_ex fails to compile and link on older systems such as
manylinux2014. Add a SUBPROCESS_HAVE_CWD probe, report a requested cwd
as ENOSYS where the call is unavailable, and skip the cwd test there.
musl gained posix_spawn_file_actions_addchdir_np in 1.1.24 but exposes no
version macro, so the detection cannot cover older releases. Skip it when the
macro is already defined, letting those users set it themselves.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant