Skip to content

libc: Make sure we don't depend on glibc > 2.34#42100

Open
daandemeyer wants to merge 15 commits into
systemd:mainfrom
daandemeyer:push-upurpuwxkkto
Open

libc: Make sure we don't depend on glibc > 2.34#42100
daandemeyer wants to merge 15 commits into
systemd:mainfrom
daandemeyer:push-upurpuwxkkto

Conversation

@daandemeyer
Copy link
Copy Markdown
Collaborator

@daandemeyer daandemeyer commented May 14, 2026

No description provided.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 14, 2026

Claude review of PR #42100 (e8c6460)

Must fix

  • Wrong librt sonametest/test-link-abi.py:32librt.so.2 should be librt.so.1; glibc ships soversion 1
  • C++ test not excluded from link-abi checktest/meson.build:133 — resolved by adding libstdc++.so.6 to GCC_LIBS
  • Arch-specific .symver version tagssrc/include/glibc/math.h:11 — replaced with dlsym-based shims
  • Test checks errno but stub doesn't set itsrc/test/test-bpf-token.c:24 — stub now returns -ENOSYS and test checks return value directly
  • xiszero undefinedsrc/basic/math-util.c:6 — assertion rewritten to use manual absolute value instead of xiszero
  • color-util.c missing math-util.h includesrc/shared/color-util.c:60 — include now present; compiles correctly
  • dlopen_libpam skips libpam_misc on subsequent callssrc/shared/pam-util.c:408 — fixed; function now loads both libraries together
  • TPM2 modules break NEEDED allowlisttest/test-link-abi.py:86cryptsetup-token-systemd-tpm2 links libtss2 libraries not in any allowlist; test fails with TPM2 enabled
  • ABS on doubles causes UB in xfmod assertionsrc/basic/math-util.c:7 — fixed by _Generic ABS macro dispatching doubles to __builtin_fabs
  • hsv_to_rgb assertion fails with h=360.0src/shared/color-util.c:54 — fixed; assertion now uses h <= 360 and the function handles h=360 correctly via % 6
  • xisinf undefined in test-math-util.csrc/test/test-math-util.c:177 — fixed; uses isinf correctly

Suggestions

  • Missing scanf format attributessrc/include/override/stdio.h:13sscanf_shim and fscanf_shim declarations lack __attribute__((format(scanf, ...))), suppressing format mismatch warnings
  • No NULL check on dlsym resultsrc/libc/glibc/stdio.c:29fn pointer from resolve() is used without checking for NULL
  • Dead version filter branchtest/test-link-abi.py:101 — The parenthesized version check never matches real readelf output format
  • GCC_LIBS missing libstdc++test/test-link-abi.py:42 — C++ runtime libstdc++.so.6 now added to the compiler runtime allowlist
  • DEFINE_LIBC_PURE_SHIM lacks NULL checksrc/libc/libc-shim.h:114 — Unlike the other SHIM macros, this one unconditionally dereferences the dlsym result without a NULL guard
  • Math overrides apply on musl unnecessarilysrc/include/override/math.h:8fmod/exp10 shims are in the shared override/ directory rather than glibc-specific, adding dlsym overhead on musl
  • test-link-abi silently passes when zero binaries checkedtest/test-link-abi.py:183 — If all categories are empty or all paths are non-ELF, the test reports success; could mask build misconfiguration
  • readelf symbol filter should check UNDtest/test-link-abi.py:101readelf --dyn-syms shows both defined and undefined symbols; filtering for UND would prevent theoretical false positives
  • libnsl.so.1 in GLIBC_LIBStest/test-link-abi.py:35libnsl.so.1 was removed from glibc in 2.32, before the 2.34 baseline; allows dependency on standalone libnsl2 package
  • readelf failure aborts entire test (dismissed) — test/test-link-abi.py:94 — author confirmed this should be fatal
  • pidref_wait_for_terminate_full requires valid pidfdsrc/basic/lock-util.c:238 — if pidref_safe_fork falls back to no pidfd (fd exhaustion), the timeout path returns -ENOMEDIUM which callers don't handle
  • assert() should be assert_se() in xfmodsrc/basic/math-util.c:8 — precondition guard against int64_t overflow can be compiled out with NDEBUG
  • Hardcoded sanitizer soname versionstest/test-link-abi.py:96-100libasan.so.8 and libubsan.so.8 are pinned to GCC 14; other compiler versions will fail the test
  • dlopen_libpam loads libpam_misc unconditionallysrc/shared/pam-util.c:417 — all callers (including PID1 exec-invoke.c which doesn't use pam_misc) are forced to load libpam_misc.so.0; fails unnecessarily on systems that package it separately
  • opensuse initrd uses wrong cryptsetup package namemkosi/mkosi.initrd.conf/mkosi.conf.d/opensuse.conf:11libcryptsetup12 now added in locale-util commit
  • Only one of three fmod calls convertedsrc/shared/color-util.c:60 — all three fmod calls now use xfmod via math-util.c
  • glibc-internal strtol macros fragilesrc/include/glibc/stdio.h:4 — relies on __GLIBC_USE_C2X_STRTOL/__GLIBC_USE_C23_STRTOL which are not public API and have already been renamed once
  • Missing SD_ELF_NOTE_DLOPEN for dlopen_libintlsrc/basic/locale-util.c:30 — other dlopen_* functions carry ELF note annotations for tooling discovery of runtime deps; this one is missing
  • Transitive symbol loading via gio handle (dismissed) — src/libsystemd/sd-bus/test-bus-marshal.c:47 — author confirmed transitive dep visibility is intentional
  • NEEDED allowlist has no per-target escape hatchtest/test-link-abi.py:49 — modules that legitimately link third-party libs have no way to allowlist additional NEEDED prefixes
  • Stale SIGALRM comment in lock-util.csrc/basic/lock-util.c:206 — comment still describes removed SIGALRM mechanism; the code now uses parent-side pidref_wait_for_terminate_full with timeout + SIGKILL
  • format_arg(2) on function pointer variablesrc/basic/locale-util.h:13 — relies on undocumented compiler behaviour for pointer variables; could silently break format-string propagation
  • #pragma once fragile with #include_nextsrc/include/glibc/stdio.h:2 — traditional include guard would be more robust in wrapper headers that use #include_next
  • Signal numbers not portable on MIPS/Alphatools/test-crash-trace.sh:25 — SIGBUS is 10 (exit code 138) on MIPS/Alpha, not 7 (135); crash detection misses SIGBUS on those architectures
  • Missing NULL check on g_dbus_message_new_from_blobsrc/libsystemd/sd-bus/test-bus-marshal.c:364 — return value not checked before use; deserialization failure will crash
  • dlopen_libintl ASSERT_OK unconditional on muslsrc/test/test-dlopen-so.c:69 — unlike other ASSERT_DLOPEN entries, this fails if libintl.so.8 is not installed at test time
  • Missing libclang_rt.ubsan in allowlisttest/test-link-abi.py:38 — only libclang_rt.asan.so is allowed; Clang UBSan builds will produce false positives
  • debuginfod may hang in CItools/test-crash-trace.sh:87set debuginfod enabled on causes gdb to download symbols from the network with no timeout; CI could stall
  • SIGQUIT missing from crash tracetools/test-crash-trace.sh:77 — exit code 131 (128+SIGQUIT) is not in the case pattern despite systemd treating SIGQUIT as a crash signal
  • Missing inttypes.h/wchar.h wrapperssrc/include/glibc/stdlib.h:9strtoimax/wcstol family also redirected by __GLIBC_USE(C2X_STRTOL); not covered by current wrappers
  • gdb replay has no timeouttools/test-crash-trace.sh:90 — re-running the crashing test under gdb has no timeout; long-running tests could hang CI indefinitely on replay
  • xfmod precision loss for large quotientssrc/basic/math-util.c:7 — assert allows ratios up to INT64_MAX but IEEE-754 double loses integer precision above 2^53; the (int64_t) truncation may produce incorrect remainders in the 2^53..INT64_MAX window
  • ASAN workaround removed without verificationsrc/libsystemd/sd-bus/test-bus-marshal.c:360 — the #if !HAS_FEATURE_ADDRESS_SANITIZER guard was removed; if glib loads under ASAN the original conflict (c8d980a) could re-manifest
  • libc-shim cache variable could benefit from attribute((used))src/libc/libc-shim.h:62 — currently relies solely on asm volatile barrier to prevent DCE under LTO; adding used would be defense-in-depth
  • PAM_SESSION_ERR inconsistent with other entry pointssrc/home/pam_systemd_home.c:913pam_sm_close_session returns PAM_SESSION_ERR for dlopen_libpam failure while all other entry points return PAM_SERVICE_ERR; same issue in src/login/pam_systemd.c:1850
  • exe_wrapper adds overhead unconditionallymeson.build:19 — every test invocation forks an extra bash process; consider making it conditional on a meson option or documenting --setup=plain bypass
  • Crash replay may not reproducetools/test-crash-trace.sh:40 — non-deterministic crashes won't reproduce under gdb; silently showing an unrelated backtrace is misleading
  • Legacy libdl/librt/libpthread sonames may cause false positivestest/test-link-abi.py:24 — older toolchains may still emit NEEDED entries for these stub libraries despite glibc 2.34 baseline
  • Missing bpf dlopen failure cachesrc/shared/bpf-util.c:80 — dlopen_bpf lacks negative-result caching; failed attempts re-try dlopen+dlsym on every call unlike old code
  • Crash wrapper may generate spurious core dumptools/test-crash-trace.sh:48 — re-raising crash signal in wrapper can produce a second core file confusing CI triage

Nits

  • Extra leading spacesrc/libc/stdio.c:32 — Indentation has an extra space before r
  • Trailing whitespacesrc/libc/stdio.c:40 — Line has trailing whitespace
  • bpf stub return value conventionsrc/shared/bpf-dlopen.c:54 — Stub returns -1 but libbpf's actual contract returns the negative errno value directly
  • bpf test skip message conflates failure causessrc/test/test-bpf-token.c:25 — ENOSYS could mean libbpf < 1.5 OR kernel lacks BPF_TOKEN_CREATE; message only mentions the former
  • Sentinel value should be a named constantsrc/libc/libc-shim.h:55(void *) -1 used in three macros as "not yet resolved"; a #define SHIM_UNRESOLVED would be self-documenting
  • xfmod precedence relies on cast bindingsrc/basic/math-util.c:8x - (double)(int64_t)(x / y) * y would be clearer with explicit parentheses around the cast result
  • Duplicated strtol-compat overridessrc/include/glibc/stdio.h:13 — identical macro logic in both stdio.h and stdlib.h; could factor into shared header
  • Sanitizer libs miscategorized as LIBCtest/test-link-abi.py:29 — libasan/libubsan are compiler runtimes (GCC/LLVM), not C library components; belong in GCC_LIB_PREFIXES
  • test-json.c assertion reorderingsrc/libsystemd/sd-json/test-json.c:1083 — isinf(data.x3) moved above x1/x2 checks, breaking natural ordering and separating from its companion data.x3 > 0 check
  • test-json.c iszero_safe guard separationsrc/libsystemd/sd-json/test-json.c:694 — iszero_safe guards grouped together rather than interleaved with their corresponding ABS checks
  • Missing O_CLOEXEC in test-bpf-token.csrc/test/test-bpf-token.c:18 — O_CLOEXEC now added
  • Redundant pam_template dependencies overridesrc/home/meson.build:110'dependencies' : libpam_cflags duplicates what pam_template already provides
  • Missing O_DIRECTORY in test-bpf-token.csrc/test/test-bpf-token.c:18 — opening a known directory without O_DIRECTORY misses early ENOTDIR detection
  • ABS _Generic missing unsigned long long casesrc/fundamental/macro-fundamental.h:190unsigned long long values fall through to default which casts to (long long), silently truncating values above LLONG_MAX
  • Redundant shared-forward.h includesrc/cryptsetup/cryptsetup-tokens/cryptsetup-token-util.h:6 — already included transitively via cryptsetup-util.h on the line above
  • Inconsistent dlopen check pattern in fido2 tokensrc/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-fido2.c:103 — uses bare if (dlopen_cryptsetup(...) < 0) return without assigning to r, unlike other entry points
  • dlopen_libintl always returns 1 on glibcsrc/basic/locale-util.c:38 — other dlopen_* return 0 on subsequent calls; this always returns 1
  • Data race in _() macro on non-glibcsrc/basic/locale-util.h:45 — sym_dgettext read without memory ordering; benign for current callers but fragile

Workflow run

@github-actions github-actions Bot added the please-review PR is ready for (re-)review by a maintainer label May 14, 2026
@daandemeyer daandemeyer changed the title push upurpuwxkkto libc: Make sure we don't depend on glibc > 2.34 May 14, 2026
Comment thread test/test-link-abi.py Outdated
Comment thread src/include/override/stdio.h Outdated
Comment thread src/libc/glibc/stdio.c Outdated
Comment thread test/test-link-abi.py Outdated
Comment thread src/libc/stdio.c Outdated
Comment thread src/libc/stdio.c Outdated
@bluca bluca added ci-fails/needs-rework 🔥 Please rework this, the CI noticed an issue with the PR and removed please-review PR is ready for (re-)review by a maintainer labels May 14, 2026
@poettering
Copy link
Copy Markdown
Member

not a fan of the dlsym approach for this.

can't __asm__(".symver memcpy, memcpy@GLIBC_2.2.5") work to just pin the old implementations for good?

@daandemeyer
Copy link
Copy Markdown
Collaborator Author

not a fan of the dlsym approach for this.

can't __asm__(".symver memcpy, memcpy@GLIBC_2.2.5") work to just pin the old implementations for good?

Not for symbols that don't exist in the older glibc. So we need dlsym for those anyway. We could use it for strtol() exp10(), fmod() and such but I prefer sticking to one mechanism for all, hence dlsym().

@github-actions github-actions Bot added please-review PR is ready for (re-)review by a maintainer and removed ci-fails/needs-rework 🔥 Please rework this, the CI noticed an issue with the PR labels May 14, 2026
Comment thread test/meson.build Outdated
Comment thread test/test-link-abi.py Outdated
Comment thread src/libc/libc-shim.h Outdated
Comment thread src/include/override/math.h Outdated
Comment thread src/shared/bpf-dlopen.c Outdated
Comment thread src/include/glibc/stdio.h Outdated
Comment thread src/include/glibc/stdlib.h Outdated
@yuwata
Copy link
Copy Markdown
Member

yuwata commented May 15, 2026

BTW, what is the motivation of this?? Do you want to downgrade glibc without rebuilding systemd??

@daandemeyer
Copy link
Copy Markdown
Collaborator Author

BTW, what is the motivation of this?? Do you want to downgrade glibc without rebuilding systemd??

I want to be able to upgrade systemd without having to pull in new glibc (or any other library). One of the previous issues with dlopen is that if we build on a build system with newer headers available, it won't run on systems with an older version of the library, and since rpm or other package managers can't detect the newer required versions of the library yet, hence we'd fail to dlopen the library because we try to dlopen some newer symbol that isn't available. That was already fixed by my previous PR. For glibc, when we build on a system with newer glibc and it happens to have a newer symbol like openat2(), wherever we install the new systemd we will be forced to upgrade glibc as well to make sure openat2() is available. This is suboptimal if you ask me, a systemd upgrade shouldn't force a glibc upgrade just to make sure the openat2() symbol is available. Rather we should pick up the openat2() symbol if it is available, but gracefully degrade to the system call if it isn't. And this should be a runtime decision, not a build time one.

Renames src/shared/bpf-dlopen.{c,h} to src/shared/bpf-util.{c,h} and
folds the former src/shared/bpf-compat.h (struct forward decl and
compat_bpf_map_create() helper) into the new header.

Aligns dlopen_bpf() with the standard wrapper pattern: drops the
manual dlopen_safe()/dlsym_many_or_warn()/TAKE_PTR(dl) plumbing and
the bespoke 'cached' int in favor of dlopen_many_sym_or_warn() inside
a FOREACH_STRING() soname-fallback loop.

Unifies declaration of the version-specific symbols (bpf_create_map,
bpf_map_create, bpf_object__next_map, bpf_token_create) into a single
DISABLE_WARNING_REDUNDANT_DECLS block in the header, and alphabetically
merges the DLSYM_PROTOTYPE list. DLSYM_OPTIONAL is used to load each
one — call sites already handle NULL (compat_bpf_map_create() and the
sym_bpf_object__next_map guard in userns-restrict.c). bpf_token_create
additionally defaults to a missing_bpf_token_create() stub returning
-ENOSYS, so callers can branch on the errno instead of NULL-checking
the pointer.

Updates test-bpf-token to match: drops the compile-time
LIBBPF_MAJOR_VERSION ≥ 1.5 gate and the direct <bpf/bpf.h> include in
favor of dlopen_bpf() + sym_bpf_token_create(), and treats -ENOSYS as
the test-skip path (covering both 'libbpf too old' and 'kernel lacks
BPF_TOKEN_CREATE support').
Copy link
Copy Markdown
Member

@brauner brauner left a comment

Choose a reason for hiding this comment

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

Looks good to me now!

Comment thread src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-pkcs11.c
Comment thread src/basic/math-util.c
Comment on lines +5 to +42
double xexp10i(int n) {
/* Powers of 10 up to 10^22 are exact in IEEE-754 binary64. */
static const double table[] = {
1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11,
1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22,
};
bool negative = n < 0;

/* Why not just __builtin_powi(10.0, n)? At runtime it resolves to libgcc's __powidf2, which
* computes 10^n by repeatedly squaring: 10^16 → 10^32 → 10^64 → … . That's fast, but a double
* can only store 10^k exactly for k ≤ 22 — beyond that, every squaring has to round to fit in
* 53 mantissa bits, and the errors compound across squarings. By 10^308 (close to the largest
* finite double) the answer is off by a few of the smallest possible double-steps. That sounds
* tiny, but at the edge it's decisive: parsing DBL_MAX back from its JSON representation does
* (mantissa × 10^308), and if 10^308 is even slightly too big the product overflows to +Inf and
* the round-trip fails.
*
* So this does it the slower-but-safer way — a 23-entry table for 10^0..10^22 (all exact) plus
* a multiply-by-10 loop for larger exponents. Each result beyond 10^22 picks up at most one
* rounding instead of a whole chain. */

/* Cast before negation so n == INT_MIN doesn't invoke signed-overflow UB. Unsigned negation
* wraps to the magnitude we want. */
unsigned k = negative ? -(unsigned) n : (unsigned) n;
double r;

if (k < ELEMENTSOF(table))
r = table[k];
else {
/* 10^309 already overflows binary64 to +Inf; anything beyond just stays there. */
k = MIN(k, 309u);
r = table[ELEMENTSOF(table) - 1];
for (unsigned i = ELEMENTSOF(table) - 1; i < k; i++)
r *= 10.0;
}

return negative ? 1.0 / r : r;
}
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.

I'm not super happy with open-coding this but sure.

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.

yeah I'd prefer to avoid open coding this stuff, is it really necessary? Can't we just keep the dependency for these symbols? Same with the other vendored math macros

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

All the other options were a bigger pain, doing asm symver to pick and older symbol version didn't work because the older symbol version differs depending on the architecture. Using dlsym() didn't work because with one of my local followups we get rid of the rest of the libm dependencies and then dlsym() stops working and we have to do dlopen first as it's not linked in by default anymore.

I guess we could do dlopen() for libm but we hardly need anything from it, just this function and one other one coming in an upcoming PR, so I'm inclined to opencode the two functions we need so we can drop the dependency entirely.

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.

I'd prefer to just dlopen it, and let the libc people worry about doing math right tbh

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I guess, will change to dlopen

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Actually after trying it I still like open coding more. By dlopening it every function that does math can suddenly fail whereas previously it can't. And it means we also need to take care of dlopening libm sufficently early when we fork something off that runs in different namespaces and such, it's generally just a pain to deal with.

The open coded version works, has a bunch of tests, CI is green, and it's really not that complex, I'd lean towards keeping it like this.

But we should probably let @poettering be the final arbiter, I sense he's gonna have an opinion on this as well.

Comment thread src/libc/libc-shim.h
@brauner brauner added the good-to-merge/waiting-for-ci 👍 PR is good to merge, but CI hasn't passed at time of review. Please merge if you see CI has passed label May 18, 2026
This avoids having to subpackage the tokens separately. If they link directly
against libcryptsetup, package manager will automatically add a dependency on
libcryptsetup to the package containing the tokens. With this change, the tokens
can ship in the main systemd package without necessarily pulling in libcryptsetup.

It also makes things more consistent. Once we also do the same for pam, any direct
linking will be limited to just libc, which for example simplifies writing tests for
ensuring we don't link unnecessarily as we don't have to add exceptions for the
cryptsetup tokens.

This actually drops the dependency on cryptsetup-libs for the fedora/centos/opensuse
systemd-udev package so install it explicitly in the initrd now to keep the tests
working.
Same reasoning as for cryptsetup tokens. It means we can include
the pam plugins in the main systemd package without the package 
manager introducing a dependency on libpam. It also makes things
more consistent and makes writing the upcoming linking test script
a lot simpler.

At the same time, we get rid of the libpam_misc dependency as the
one symbol we were using from it is trivial to reimplement ourselves.
The test only uses 9 symbols (5 from glib, 4 from libdbus) for its
interop checks; dlopen them at runtime so the binary no longer carries a
hard link-time dependency on either library. Headers are still pulled in
through the *_cflags partial dependencies for the type declarations.

While we're at it, drop the compat glue for glib 2.36 which is long obsolete
at this point.
We can just put a timeout on the parent process completing 
rather than doing it inside the subprocess.
In hsv_to_rgb, restructure the conversion around the sector index
k = (int)(h/60) and fractional offset f = h/60 - k. The auxiliary
x value becomes c * (k & 1 ? 1.0 - f : f) and the six branches turn
into a switch on k. This drops the two xfmod() calls that were doing
the modulo work, in exchange for a single assert(h >= 0 && h < 360) —
all in-tree callers satisfy this and never relied on the wrap.

In rgb_to_hsv, the two xfmod() calls were no-ops (their arguments
were always within the divisor's magnitude). The trailing
xfmod(*ret_h, 360) appeared to be wrapping negative hues from the
r-max branch back into [0, 360), but fmod is sign-preserving so it
never did. Drop the no-ops and add an explicit +360 wrap so magenta
(1, 0, 1) now yields h ≈ 300 instead of -60.

Extend the tests to cover all six primary/secondary colors at sector
boundaries, all six sector midpoints (to catch any future inversion
of the ramp direction), the h-near-360 edge of the last sector, and
the rgb_to_hsv negative-wrap path via magenta. Switch the new and
existing integer-channel checks to ASSERT_EQ from tests.h; the
double-typed h/s/v range checks stay on ASSERT_TRUE since the
ASSERT_* comparison macros only support integer types.

Co-developed-by: Claude Opus 4.7 <noreply@anthropic.com>
@daandemeyer
Copy link
Copy Markdown
Collaborator Author

daandemeyer commented May 18, 2026

Moved the ABS() changes to the right commit

Comment thread src/fundamental/macro-fundamental.h
Comment thread src/fundamental/macro-fundamental.h
To make this work, ABS() is made generic so it also works on
floats and doubles.

While at it, fold the __ABS_INTEGER indirection and the
assert_cc(sizeof(long long) == sizeof(intmax_t)) away. The previous
form switched between __builtin_llabs (clang) and __builtin_imaxabs
(gcc), with the assert keeping the two paths behaviorally identical
on every platform we build for. imaxabs was originally chosen because
intmax_t is conceptually the widest signed integer type the platform
exposes, but the _Generic ABS already casts to (long long) before the
call, so the extra width imaxabs could in theory carry was being
narrowed away immediately anyway. With both paths collapsed to
__builtin_llabs((long long) (a)), the size relationship between
long long and intmax_t is no longer relevant.

Also add explicit unsigned long long / unsigned long / unsigned int
cases that pass the argument through unchanged. The previous default
branch cast unsigned values to (long long); for values above LLONG_MAX
this reinterprets them as negative, and __builtin_llabs(LLONG_MIN) is
UB. Unsigned values are already non-negative, so passing them through
is both correct and avoids the narrowing. Smaller unsigned types
(unsigned char, unsigned short) still go through the default branch
but promote to int first and fit in long long losslessly.

Co-developed-by: Claude Opus 4.7 <noreply@anthropic.com>
exp10() has a symbol version > 2.34 on latest glibc. To allow 
dropping our baseline required glibc runtime version to <= 2.34, 
let's add our own version to prevent pulling in the newer symbol 
from glibc.
dgettext() lives in libc on glibc and in libintl.so.8 on musl with
gettext. Resolve it via dlsym() so neither configuration produces a
hard link-time dependency on libintl: try libintl.so.8 first and fall
back to RTLD_DEFAULT (which finds dgettext in libc on glibc).

The _() macro now expands to a runtime check that returns the
untranslated string if dlopen_libintl() has not run successfully, so
callers don't have to gate every translatable message on a runtime
check. pam_systemd_home — currently the only consumer of _() — calls
dlopen_libintl() best-effort from each PAM entry point.

The meson find_library('intl') dance is replaced with a has_header()
check; the only thing we need at build time is the prototype.
And drop the libm dependency.
Our baseline glibc is 2.34, which merged libdl, libpthread (the
dependency('threads') target), and librt into libc. Empty .so/.a stubs
remain for backward compatibility with old binaries, but new builds
resolve dl_*, pthread_*, mq_*, timer_*, etc. directly from libc.
On musl the same libraries are likewise empty stubs.

Drop the libdl, threads, and librt entries from every meson.build, and
remove the now-stale 'Libs.private: -lrt -pthread' from libudev.pc.in
since both flags resolve to empty link-time stubs on glibc 2.34+ and
musl.

Verified with readelf -d that libsystemd.so, libudev.so, and systemd no
longer carry DT_NEEDED entries for libdl/libpthread/librt.
Weak symbols still introduce a version requirement on a newer libc.
Resolve each libc symbol via dlsym(RTLD_DEFAULT) from a per-shim
constructor and cache the result in a file-scope static instead. This
avoids the version requirement, keeps the call path free of atomics
(constructors run single-threaded before main() and before any signal
handler can fire), and keeps dlsym() out of contexts where it is not
async-signal-safe.
When _GNU_SOURCE is defined, glibc will always use c23 versions
of strtol(), sscanf() and friends if available (introduced after
glibc 2.34). Which means that any binaries built with headers
from newer glibc won't load on glibc < 2.38. To work around this,
redefine the appropriate constants to zero make sure the c99 
versions are used instead.
@daandemeyer
Copy link
Copy Markdown
Collaborator Author

Pushed updated commit message for the ABS()/MAX()/MIN() commit

Comment thread src/shared/bpf-util.c
Comment thread tools/test-crash-trace.sh
Comment thread src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-fido2.c
Comment thread src/basic/locale-util.c

int dlopen_libintl(int log_level) {
#ifdef __GLIBC__
return 1;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Claude: nit: On glibc, dlopen_libintl unconditionally returns 1 on every call. Other dlopen_* functions in the project return 0 on subsequent calls ("already loaded") vs 1 on the first successful load via dlopen_many_sym_or_warn. While callers currently only check for >= 0, the inconsistent semantics may confuse readers and break if a caller ever distinguishes first-load from already-loaded.

Comment thread src/basic/locale-util.h
For every built executable, internal shared library, and plugin module,
verify two link-time properties via readelf:

1. No imported GLIBC symbol's version is newer than 2.34.
2. The dynamic section's NEEDED entries reference only glibc, the
   runtime linker, our own libraries.
Copy link
Copy Markdown
Member

@yuwata yuwata left a comment

Choose a reason for hiding this comment

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

LGTM.

It is fine to do that later, but could you address this?
#42100 (comment)

@yuwata yuwata added the rc-blocker 🚧 PRs and Issues tagged this way are blocking the upcoming rc release! label May 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

build-system claude-review good-to-merge/waiting-for-ci 👍 PR is good to merge, but CI hasn't passed at time of review. Please merge if you see CI has passed meson please-review PR is ready for (re-)review by a maintainer rc-blocker 🚧 PRs and Issues tagged this way are blocking the upcoming rc release! tests util-lib

Development

Successfully merging this pull request may close these issues.

6 participants