From: Daan De Meyer Date: Fri, 15 May 2026 18:33:43 +0000 (+0000) Subject: locale-util: dlopen() libintl instead of linking against it X-Git-Tag: v261-rc1~104^2~4 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=e6e65dc26157207a6b720fccc49632ac77236384;p=thirdparty%2Fsystemd.git locale-util: dlopen() libintl instead of linking against it 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. --- diff --git a/meson.build b/meson.build index 05739e517b2..b26ef7979c9 100644 --- a/meson.build +++ b/meson.build @@ -983,20 +983,11 @@ librt = cc.find_library('rt') libm = cc.find_library('m') libdl = cc.find_library('dl') -# On some distributions that use musl (e.g. Alpine), libintl.h may be provided by gettext rather than musl. -# In that case, we need to explicitly link with libintl.so. -if cc.has_function('dgettext', - prefix : '''#include ''', - args : '-D_GNU_SOURCE') - libintl = [] -else - libintl = cc.find_library('intl') - if not cc.has_function('dgettext', - prefix : '''#include ''', - args : '-D_GNU_SOURCE', - dependencies : libintl) - error('dgettext() not found') - endif +# Header presence check only — dgettext itself is resolved via dlopen_libintl() at runtime, so we never +# link against libintl. On glibc dgettext lives in libc; on musl gettext-dev provides libintl.h alongside +# libintl.so.8 which we dlopen() if present. +if not cc.has_header('libintl.h') + error('libintl.h not found (install gettext / gettext-dev)') endif # On some architectures, libatomic is required. But on some installations, diff --git a/src/basic/locale-util.c b/src/basic/locale-util.c index 3b869d70f97..6d4493c0bb4 100644 --- a/src/basic/locale-util.c +++ b/src/basic/locale-util.c @@ -7,7 +7,12 @@ #include #include +#ifndef __GLIBC__ +#include "sd-dlopen.h" +#endif + #include "dirent-util.h" +#include "dlfcn-util.h" #include "env-util.h" #include "fd-util.h" #include "fileio.h" @@ -22,6 +27,32 @@ #include "strv.h" #include "utf8.h" +#ifdef __GLIBC__ +DLSYM_PROTOTYPE(dgettext) = dgettext; +#else +DLSYM_PROTOTYPE(dgettext) = NULL; +#endif + +int dlopen_libintl(int log_level) { +#ifdef __GLIBC__ + return 1; +#else + static void *libintl_dl = NULL; + + SD_ELF_NOTE_DLOPEN( + "intl", + "Support for message translation via gettext", + SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libintl.so.8"); + + return dlopen_many_sym_or_warn( + &libintl_dl, + "libintl.so.8", + log_level, + DLSYM_ARG(dgettext)); +#endif +} + static char* normalize_locale(const char *name) { const char *e; diff --git a/src/basic/locale-util.h b/src/basic/locale-util.h index bf5cbcb4392..fb22953f16f 100644 --- a/src/basic/locale-util.h +++ b/src/basic/locale-util.h @@ -1,9 +1,18 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include /* IWYU pragma: export */ #include /* IWYU pragma: export */ #include "basic-forward.h" +#include "dlfcn-util.h" + +/* format_arg(2) propagates the format-string nature of the second argument to the return value, so that + * printf(_("Hello %s"), name) still gets checked. It survives both DLSYM_PROTOTYPE's typeof() and the + * ternary in _() below — verified on gcc and clang. */ +extern DLSYM_PROTOTYPE(dgettext) __attribute__((format_arg(2))); + +int dlopen_libintl(int log_level); typedef enum LocaleVariable { /* We don't list LC_ALL here on purpose. People should be @@ -31,7 +40,9 @@ int get_locales(char ***ret); bool locale_is_valid(const char *name); int locale_is_installed(const char *name); -#define _(String) dgettext(GETTEXT_PACKAGE, String) +/* Falls back to the untranslated string if dlopen_libintl() hasn't run or has failed, so callers don't have + * to gate every translatable message on a runtime check. */ +#define _(String) (sym_dgettext ? sym_dgettext(GETTEXT_PACKAGE, (String)) : (String)) #define N_(String) String bool is_locale_utf8(void); diff --git a/src/home/meson.build b/src/home/meson.build index 631eeb13aa8..0b27001c5a0 100644 --- a/src/home/meson.build +++ b/src/home/meson.build @@ -115,7 +115,6 @@ modules += [ 'conditions' : ['HAVE_PAM'], 'sources' : pam_systemd_home_sources, 'dependencies' : [ - libintl, libpam_cflags, threads, ], diff --git a/src/home/pam_systemd_home.c b/src/home/pam_systemd_home.c index 5fe6dcbec20..6e24924e91c 100644 --- a/src/home/pam_systemd_home.c +++ b/src/home/pam_systemd_home.c @@ -1,7 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "sd-bus.h" #include "alloc-util.h" @@ -792,6 +790,8 @@ _public_ PAM_EXTERN int pam_sm_authenticate( if (r < 0) return PAM_SERVICE_ERR; + (void) dlopen_libintl(LOG_DEBUG); /* best-effort: messages won't be translated if this fails */ + pam_log_setup(); if (parse_env(pamh, &flags) < 0) @@ -857,6 +857,8 @@ _public_ PAM_EXTERN int pam_sm_open_session( if (r < 0) return PAM_SERVICE_ERR; + (void) dlopen_libintl(LOG_DEBUG); /* best-effort: messages won't be translated if this fails */ + pam_log_setup(); if (parse_env(pamh, &flags) < 0) @@ -914,6 +916,8 @@ _public_ PAM_EXTERN int pam_sm_close_session( if (r < 0) return PAM_SERVICE_ERR; + (void) dlopen_libintl(LOG_DEBUG); /* best-effort: messages won't be translated if this fails */ + pam_log_setup(); if (parse_argv(pamh, @@ -979,6 +983,8 @@ _public_ PAM_EXTERN int pam_sm_acct_mgmt( if (r < 0) return PAM_SERVICE_ERR; + (void) dlopen_libintl(LOG_DEBUG); /* best-effort: messages won't be translated if this fails */ + pam_log_setup(); if (parse_env(pamh, &flags) < 0) @@ -1098,6 +1104,8 @@ _public_ PAM_EXTERN int pam_sm_chauthtok( if (r < 0) return PAM_SERVICE_ERR; + (void) dlopen_libintl(LOG_DEBUG); /* best-effort: messages won't be translated if this fails */ + pam_log_setup(); if (parse_argv(pamh, diff --git a/src/test/test-dlopen-so.c b/src/test/test-dlopen-so.c index 0eca1356c03..de1d7671392 100644 --- a/src/test/test-dlopen-so.c +++ b/src/test/test-dlopen-so.c @@ -18,6 +18,7 @@ #include "libcrypt-util.h" #include "libfido2-util.h" #include "libmount-util.h" +#include "locale-util.h" #include "main-func.h" #include "microhttpd-util.h" #include "module-util.h" @@ -65,6 +66,7 @@ static int run(int argc, char **argv) { ASSERT_DLOPEN(dlopen_libblkid, HAVE_BLKID); ASSERT_DLOPEN(dlopen_libcrypt, HAVE_LIBCRYPT); ASSERT_DLOPEN(dlopen_libfido2, HAVE_LIBFIDO2); + ASSERT_OK(dlopen_libintl(LOG_DEBUG)); /* Required to be available at build time. */ ASSERT_DLOPEN(dlopen_libkmod, HAVE_KMOD); ASSERT_DLOPEN(dlopen_libmount, HAVE_LIBMOUNT); ASSERT_DLOPEN(dlopen_libpam, HAVE_PAM);