libc,shared: detect newer library symbols at runtime

For libc syscall wrappers (pidfd_open, fsopen, openat2, etc.) we previously
gated the calls behind build-time HAVE_* checks. Replace these with shim
functions in src/libc/ that fall back to the raw syscall at runtime when the
loaded glibc lacks the symbol. The infrastructure lives in src/libc/libc-shim.h:
DEFINE_SYSCALL_SHIM falls back to a direct syscall, DEFINE_LIBC_SHIM returns
ENOSYS (for posix_spawn-family helpers that have no corresponding syscall), and
DEFINE_LIBC_ERRNO_SHIM sets errno=ENOSYS and returns -1 (for read/write-style
helpers). The weak reference to the libc symbol is bound via __asm__(\"name\")
rename so the bare libc identifier never appears as a C token — this avoids
both #undef boilerplate against override-header redirects and the resulting
-Wredundant-decls warning. Drop the corresponding cc.has_function() loop from
meson.build.

For optional libraries (libcryptsetup, libdw, libarchive), drop the per-symbol
HAVE_* checks. Always declare the prototypes, suppressing the redundant-decl
warnings via DISABLE_WARNING_REDUNDANT_DECLS and NOLINT, and resolve the symbols
after the main dlopen via a new DLSYM_OPTIONAL() helper that only assigns on
success. libcryptsetup's crypt_set_keyring_to_link / crypt_token_set_external_path
and libarchive's *_is_set wrappers use fallback functions as their pointer
initializers (returning -ENOSYS and 0 respectively), so call sites can invoke
the symbol unconditionally and just check for -ENOSYS where the \"not supported\"
distinction matters.

The same shim treatment applies to pidfd_spawn / posix_spawnattr_setcgroup_np
(src/libc/spawn.c) and epoll_pwait2 (src/libc/epoll.c), with corresponding
override headers in src/include/override/spawn.h and
src/include/override/sys/epoll.h. posix_spawn_wrapper() in process-util.c and
epoll_wait_usec() in sd-event.c now detect ENOSYS in the return value instead
of checking the function pointer, falling back to plain posix_spawn() and
epoll_wait() respectively. coredump-config and coredump-submit get a
dlopen_dw_has_dwfl_set_sysroot() helper. The kexec arch gate now uses
defined(__NR_kexec_file_load) directly; pidfd.h uses __has_include_next() to
decide whether to pull in glibc's header.

This lets binaries built against newer glibc / libcryptsetup / libdw /
libarchive headers still load and run on older targets where these symbols are
absent.
This commit is contained in:
Daan De Meyer
2026-05-13 10:29:50 +02:00
parent 5d60cd8539
commit f795d54591
51 changed files with 504 additions and 475 deletions
+6 -7
View File
@@ -1,11 +1,10 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <sys/bpf.h>
#include <sys/syscall.h>
#include <unistd.h>
#if !HAVE_BPF
int missing_bpf(int cmd, union bpf_attr *attr, size_t size) {
return syscall(__NR_bpf, cmd, attr, size);
}
#endif
#include "libc-shim.h"
DEFINE_SYSCALL_SHIM(bpf, int,
int, cmd,
union bpf_attr *, attr,
size_t, size)
+12
View File
@@ -0,0 +1,12 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <sys/epoll.h>
#include "libc-shim.h"
DEFINE_LIBC_ERRNO_SHIM(epoll_pwait2, int,
int, fd,
struct epoll_event *, events,
int, maxevents,
const struct timespec *, timeout,
const sigset_t *, sigmask)
+7 -7
View File
@@ -1,11 +1,11 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <fcntl.h>
#include <sys/syscall.h>
#include <unistd.h>
#if !HAVE_OPENAT2
int missing_openat2(int dfd, const char *filename, const struct open_how *how, size_t usize) {
return syscall(__NR_openat2, dfd, filename, how, usize);
}
#endif
#include "libc-shim.h"
DEFINE_SYSCALL_SHIM(openat2, int,
int, dfd,
const char *, filename,
const struct open_how *, how,
size_t, usize)
+9 -12
View File
@@ -1,17 +1,14 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <sys/ioprio.h>
#include <sys/syscall.h>
#include <unistd.h>
#if !HAVE_IOPRIO_GET
int missing_ioprio_get(int which, int who) {
return syscall(__NR_ioprio_get, which, who);
}
#endif
#include "libc-shim.h"
#if !HAVE_IOPRIO_SET
int missing_ioprio_set(int which, int who, int ioprio) {
return syscall(__NR_ioprio_set, which, who, ioprio);
}
#endif
DEFINE_SYSCALL_SHIM(ioprio_get, int,
int, which,
int, who)
DEFINE_SYSCALL_SHIM(ioprio_set, int,
int, which,
int, who,
int, ioprio)
+8 -7
View File
@@ -1,11 +1,12 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <sys/kcmp.h>
#include <sys/syscall.h>
#include <unistd.h>
#if !HAVE_KCMP
int missing_kcmp(pid_t pid1, pid_t pid2, int type, unsigned long idx1, unsigned long idx2) {
return syscall(__NR_kcmp, pid1, pid2, type, idx1, idx2);
}
#endif
#include "libc-shim.h"
DEFINE_SYSCALL_SHIM(kcmp, int,
pid_t, pid1,
pid_t, pid2,
int, type,
unsigned long, idx1,
unsigned long, idx2)
+9 -6
View File
@@ -1,11 +1,14 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <sys/kexec.h>
#include <sys/syscall.h>
#include <unistd.h>
#if !HAVE_KEXEC_FILE_LOAD && defined __NR_kexec_file_load
int missing_kexec_file_load(int kernel_fd, int initrd_fd, unsigned long cmdline_len, const char *cmdline, unsigned long flags) {
return syscall(__NR_kexec_file_load, kernel_fd, initrd_fd, cmdline_len, cmdline, flags);
}
#include "libc-shim.h"
#ifdef __NR_kexec_file_load
DEFINE_SYSCALL_SHIM(kexec_file_load, int,
int, kernel_fd,
int, initrd_fd,
unsigned long, cmdline_len,
const char *, cmdline,
unsigned long, flags)
#endif
+19 -17
View File
@@ -1,23 +1,25 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <sys/keyctl.h>
#include <sys/syscall.h>
#include <unistd.h>
#if !HAVE_KEYCTL
long missing_keyctl(int cmd, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5) {
return syscall(__NR_keyctl, cmd, arg2, arg3, arg4, arg5);
}
#endif
#include "libc-shim.h"
#if !HAVE_ADD_KEY
key_serial_t missing_add_key(const char *type, const char *description, const void *payload, size_t plen, key_serial_t ringid) {
return syscall(__NR_add_key, type, description, payload, plen, ringid);
}
#endif
DEFINE_SYSCALL_SHIM(keyctl, long,
int, cmd,
unsigned long, arg2,
unsigned long, arg3,
unsigned long, arg4,
unsigned long, arg5)
#if !HAVE_REQUEST_KEY
key_serial_t missing_request_key(const char *type, const char *description, const char *callout_info, key_serial_t destringid) {
return syscall(__NR_request_key, type, description, callout_info, destringid);
}
#endif
DEFINE_SYSCALL_SHIM(add_key, key_serial_t,
const char *, type,
const char *, description,
const void *, payload,
size_t, plen,
key_serial_t, ringid)
DEFINE_SYSCALL_SHIM(request_key, key_serial_t,
const char *, type,
const char *, description,
const char *, callout_info,
key_serial_t, destringid)
+76
View File
@@ -0,0 +1,76 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <errno.h>
#include <sys/syscall.h>
#include <unistd.h>
/* Each parameter is passed as a flat (type, name) pair: type1, name1, type2, name2, ... The
* _SHIM_DECL/_SHIM_NAME macros consume two args at a time and emit either "type name" pairs (for
* the function declarator) or just the names (for forwarding). _SHIM_PAIRS counts the number of
* pairs by indexing into a table that increments every two positions. */
#define _SHIM_CAT(a, b) _SHIM_CAT_(a, b)
#define _SHIM_CAT_(a, b) a##b
#define _SHIM_NTH(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, N, ...) N
#define _SHIM_PAIRS(...) _SHIM_NTH(__VA_ARGS__, 8, 8, 7, 7, 6, 6, 5, 5, 4, 4, 3, 3, 2, 2, 1, 1, 0)
#define _SHIM_DECL_1(t, n) t n
#define _SHIM_DECL_2(t, n, ...) t n, _SHIM_DECL_1(__VA_ARGS__)
#define _SHIM_DECL_3(t, n, ...) t n, _SHIM_DECL_2(__VA_ARGS__)
#define _SHIM_DECL_4(t, n, ...) t n, _SHIM_DECL_3(__VA_ARGS__)
#define _SHIM_DECL_5(t, n, ...) t n, _SHIM_DECL_4(__VA_ARGS__)
#define _SHIM_DECL_6(t, n, ...) t n, _SHIM_DECL_5(__VA_ARGS__)
#define _SHIM_DECL_7(t, n, ...) t n, _SHIM_DECL_6(__VA_ARGS__)
#define _SHIM_DECL_8(t, n, ...) t n, _SHIM_DECL_7(__VA_ARGS__)
#define _SHIM_DECL(...) _SHIM_CAT(_SHIM_DECL_, _SHIM_PAIRS(__VA_ARGS__))(__VA_ARGS__)
#define _SHIM_NAME_1(t, n) n
#define _SHIM_NAME_2(t, n, ...) n, _SHIM_NAME_1(__VA_ARGS__)
#define _SHIM_NAME_3(t, n, ...) n, _SHIM_NAME_2(__VA_ARGS__)
#define _SHIM_NAME_4(t, n, ...) n, _SHIM_NAME_3(__VA_ARGS__)
#define _SHIM_NAME_5(t, n, ...) n, _SHIM_NAME_4(__VA_ARGS__)
#define _SHIM_NAME_6(t, n, ...) n, _SHIM_NAME_5(__VA_ARGS__)
#define _SHIM_NAME_7(t, n, ...) n, _SHIM_NAME_6(__VA_ARGS__)
#define _SHIM_NAME_8(t, n, ...) n, _SHIM_NAME_7(__VA_ARGS__)
#define _SHIM_NAME(...) _SHIM_CAT(_SHIM_NAME_, _SHIM_PAIRS(__VA_ARGS__))(__VA_ARGS__)
/* Defines a wrapper that calls the libc symbol if available at runtime, or falls back to the
* corresponding direct syscall otherwise. The libc symbol is redeclared as a weak reference so the
* binary still loads on libc versions that don't provide it. Each parameter is passed as type,
* name pairs (flat).
*
* The weak reference is bound to the libc symbol via an __asm__("label") rename so that the bare
* libc identifier never appears as a C token. This matters because override/musl headers
* sometimes #define the libc name to redirect it to the _shim variant — without the rename the
* caller would have to #undef each name before invoking the macro. # and ## operators don't
* macro-expand their operands, so the parameter "func" stays a literal token everywhere. */
#define DEFINE_SYSCALL_SHIM(func, ret, ...) \
extern typeof(func##_shim) func##_libc_weak __asm__(#func) __attribute__((__weak__)); \
ret func##_shim(_SHIM_DECL(__VA_ARGS__)) { \
if (func##_libc_weak) \
return func##_libc_weak(_SHIM_NAME(__VA_ARGS__)); \
return syscall(__NR_##func, _SHIM_NAME(__VA_ARGS__)); \
}
/* Like DEFINE_SYSCALL_SHIM but for libc helpers that have no corresponding syscall and report errors
* by returning the positive errno value directly (posix_spawn-family convention). If the libc symbol
* is missing at runtime, ENOSYS is returned. */
#define DEFINE_LIBC_SHIM(func, ret, ...) \
extern typeof(func##_shim) func##_libc_weak __asm__(#func) __attribute__((__weak__)); \
ret func##_shim(_SHIM_DECL(__VA_ARGS__)) { \
if (func##_libc_weak) \
return func##_libc_weak(_SHIM_NAME(__VA_ARGS__)); \
return ENOSYS; \
}
/* Like DEFINE_LIBC_SHIM but for libc helpers that report errors via errno + -1 return value. If the
* libc symbol is missing at runtime, errno is set to ENOSYS and -1 is returned. */
#define DEFINE_LIBC_ERRNO_SHIM(func, ret, ...) \
extern typeof(func##_shim) func##_libc_weak __asm__(#func) __attribute__((__weak__)); \
ret func##_shim(_SHIM_DECL(__VA_ARGS__)) { \
if (func##_libc_weak) \
return func##_libc_weak(_SHIM_NAME(__VA_ARGS__)); \
errno = ENOSYS; \
return -1; \
}
+12 -12
View File
@@ -1,17 +1,17 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <sys/mempolicy.h>
#include <sys/syscall.h>
#include <unistd.h>
#if !HAVE_SET_MEMPOLICY
int missing_set_mempolicy(int mode, const unsigned long *nodemask, unsigned long maxnode) {
return syscall(__NR_set_mempolicy, mode, nodemask, maxnode);
}
#endif
#include "libc-shim.h"
#if !HAVE_GET_MEMPOLICY
int missing_get_mempolicy(int *mode, unsigned long *nodemask, unsigned long maxnode, void *addr, unsigned long flags) {
return syscall(__NR_get_mempolicy, mode, nodemask, maxnode, addr, flags);
}
#endif
DEFINE_SYSCALL_SHIM(set_mempolicy, int,
int, mode,
const unsigned long *, nodemask,
unsigned long, maxnode)
DEFINE_SYSCALL_SHIM(get_mempolicy, int,
int *, mode,
unsigned long *, nodemask,
unsigned long, maxnode,
void *, addr,
unsigned long, flags)
+2
View File
@@ -2,6 +2,7 @@
libc_wrapper_sources = files(
'bpf.c',
'epoll.c',
'fcntl.c',
'ioprio.c',
'kcmp.c',
@@ -13,6 +14,7 @@ libc_wrapper_sources = files(
'quota.c',
'sched.c',
'signal.c',
'spawn.c',
'stat.c',
'unistd.c',
'xattr.c',
+37 -37
View File
@@ -1,47 +1,47 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <sys/mount.h>
#include <sys/syscall.h>
#include <unistd.h>
#if !HAVE_FSOPEN
int missing_fsopen(const char *fsname, unsigned flags) {
return syscall(__NR_fsopen, fsname, flags);
}
#endif
#include "libc-shim.h"
#if !HAVE_FSMOUNT
int missing_fsmount(int fd, unsigned flags, unsigned ms_flags) {
return syscall(__NR_fsmount, fd, flags, ms_flags);
}
#endif
DEFINE_SYSCALL_SHIM(fsopen, int,
const char *, fsname,
unsigned, flags)
#if !HAVE_MOVE_MOUNT
int missing_move_mount(int from_dfd, const char *from_pathname, int to_dfd, const char *to_pathname, unsigned flags) {
return syscall(__NR_move_mount, from_dfd, from_pathname, to_dfd, to_pathname, flags);
}
#endif
DEFINE_SYSCALL_SHIM(fsmount, int,
int, fd,
unsigned, flags,
unsigned, ms_flags)
#if !HAVE_FSCONFIG
int missing_fsconfig(int fd, unsigned cmd, const char *key, const void *value, int aux) {
return syscall(__NR_fsconfig, fd, cmd, key, value, aux);
}
#endif
DEFINE_SYSCALL_SHIM(move_mount, int,
int, from_dfd,
const char *, from_pathname,
int, to_dfd,
const char *, to_pathname,
unsigned, flags)
#if !HAVE_OPEN_TREE
int missing_open_tree(int dfd, const char *filename, unsigned flags) {
return syscall(__NR_open_tree, dfd, filename, flags);
}
#endif
DEFINE_SYSCALL_SHIM(fsconfig, int,
int, fd,
unsigned, cmd,
const char *, key,
const void *, value,
int, aux)
#if !HAVE_MOUNT_SETATTR
int missing_mount_setattr(int dfd, const char *path, unsigned flags, struct mount_attr *attr, size_t size) {
return syscall(__NR_mount_setattr, dfd, path, flags, attr, size);
}
#endif
DEFINE_SYSCALL_SHIM(open_tree, int,
int, dfd,
const char *, filename,
unsigned, flags)
#if !HAVE_OPEN_TREE_ATTR
int missing_open_tree_attr(int dfd, const char *filename, unsigned int flags, struct mount_attr *attr, size_t size) {
return syscall(__NR_open_tree_attr, dfd, filename, flags, attr, size);
}
#endif
DEFINE_SYSCALL_SHIM(mount_setattr, int,
int, dfd,
const char *, path,
unsigned, flags,
struct mount_attr *, attr,
size_t, size)
DEFINE_SYSCALL_SHIM(open_tree_attr, int,
int, dfd,
const char *, filename,
unsigned int, flags,
struct mount_attr *, attr,
size_t, size)
+8 -7
View File
@@ -3,14 +3,15 @@
#include <errno.h>
#include <stdio.h>
#include <stdio_ext.h>
#include <sys/syscall.h>
#include <unistd.h>
#if !HAVE_RENAMEAT2
int missing_renameat2(int __oldfd, const char *__old, int __newfd, const char *__new, unsigned __flags) {
return syscall(__NR_renameat2, __oldfd, __old, __newfd, __new, __flags);
}
#endif
#include "../libc-shim.h"
DEFINE_SYSCALL_SHIM(renameat2, int,
int, __oldfd,
const char *, __old,
int, __newfd,
const char *, __new,
unsigned, __flags)
#define DEFINE_PUT(func) \
int func##_check_writable(int c, FILE *stream) { \
+10 -12
View File
@@ -1,17 +1,15 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <sys/pidfd.h>
#include <sys/syscall.h>
#include <unistd.h>
#if !HAVE_PIDFD_OPEN
int missing_pidfd_open(pid_t pid, unsigned flags) {
return syscall(__NR_pidfd_open, pid, flags);
}
#endif
#include "libc-shim.h"
#if !HAVE_PIDFD_SEND_SIGNAL
int missing_pidfd_send_signal(int fd, int sig, siginfo_t *info, unsigned flags) {
return syscall(__NR_pidfd_send_signal, fd, sig, info, flags);
}
#endif
DEFINE_SYSCALL_SHIM(pidfd_open, int,
pid_t, pid,
unsigned, flags)
DEFINE_SYSCALL_SHIM(pidfd_send_signal, int,
int, fd,
int, sig,
siginfo_t *, info,
unsigned, flags)
+7 -7
View File
@@ -1,11 +1,11 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <sys/quota.h>
#include <sys/syscall.h>
#include <unistd.h>
#if !HAVE_QUOTACTL_FD
int missing_quotactl_fd(int fd, int cmd, int id, void *addr) {
return syscall(__NR_quotactl_fd, fd, cmd, id, addr);
}
#endif
#include "libc-shim.h"
DEFINE_SYSCALL_SHIM(quotactl_fd, int,
int, fd,
int, cmd,
int, id,
void *, addr)
+6 -7
View File
@@ -1,11 +1,10 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <sched.h>
#include <sys/syscall.h>
#include <unistd.h>
#if !HAVE_SCHED_SETATTR
int missing_sched_setattr(pid_t pid, struct sched_attr *attr, unsigned flags) {
return syscall(__NR_sched_setattr, pid, attr, flags);
}
#endif
#include "libc-shim.h"
DEFINE_SYSCALL_SHIM(sched_setattr, int,
pid_t, pid,
struct sched_attr *, attr,
unsigned, flags)
+7 -7
View File
@@ -1,11 +1,11 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <signal.h>
#include <sys/syscall.h>
#include <unistd.h>
#if !HAVE_RT_TGSIGQUEUEINFO
int missing_rt_tgsigqueueinfo(pid_t tgid, pid_t tid, int sig, siginfo_t *info) {
return syscall(__NR_rt_tgsigqueueinfo, tgid, tid, sig, info);
}
#endif
#include "libc-shim.h"
DEFINE_SYSCALL_SHIM(rt_tgsigqueueinfo, int,
pid_t, tgid,
pid_t, tid,
int, sig,
siginfo_t *, info)
+17
View File
@@ -0,0 +1,17 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <spawn.h>
#include "libc-shim.h"
DEFINE_LIBC_SHIM(pidfd_spawn, int,
pid_t *restrict, pidfd,
const char *restrict, path,
const posix_spawn_file_actions_t *restrict, file_actions,
const posix_spawnattr_t *restrict, attrp,
char *const *restrict, argv,
char *const *restrict, envp)
DEFINE_LIBC_SHIM(posix_spawnattr_setcgroup_np, int,
posix_spawnattr_t *, attr,
int, cgroup)
+7 -7
View File
@@ -1,11 +1,11 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <sys/stat.h>
#include <sys/syscall.h>
#include <unistd.h>
#if !HAVE_FCHMODAT2
int missing_fchmodat2(int dirfd, const char *path, mode_t mode, int flags) {
return syscall(__NR_fchmodat2, dirfd, path, mode, flags);
}
#endif
#include "libc-shim.h"
DEFINE_SYSCALL_SHIM(fchmodat2, int,
int, dirfd,
const char *, path,
mode_t, mode,
int, flags)
+4 -7
View File
@@ -1,10 +1,7 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <sys/syscall.h>
#include <unistd.h>
#include "libc-shim.h"
#if !HAVE_PIVOT_ROOT
int missing_pivot_root(const char *new_root, const char *put_old) {
return syscall(__NR_pivot_root, new_root, put_old);
}
#endif
DEFINE_SYSCALL_SHIM(pivot_root, int,
const char *, new_root,
const char *, put_old)
+14 -12
View File
@@ -1,17 +1,19 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <sys/syscall.h>
#include <sys/xattr.h>
#include <unistd.h>
#if !HAVE_SETXATTRAT
int missing_setxattrat(int fd, const char *path, int at_flags, const char *name, const struct xattr_args *args, size_t size) {
return syscall(__NR_setxattrat, fd, path, at_flags, name, args, size);
}
#endif
#include "libc-shim.h"
#if !HAVE_REMOVEXATTRAT
int missing_removexattrat(int fd, const char *path, int at_flags, const char *name) {
return syscall(__NR_removexattrat, fd, path, at_flags, name);
}
#endif
DEFINE_SYSCALL_SHIM(setxattrat, int,
int, fd,
const char *, path,
int, at_flags,
const char *, name,
const struct xattr_args *, args,
size_t, size)
DEFINE_SYSCALL_SHIM(removexattrat, int,
int, fd,
const char *, path,
int, at_flags,
const char *, name)