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 <libintl.h>''',
- args : '-D_GNU_SOURCE')
- libintl = []
-else
- libintl = cc.find_library('intl')
- if not cc.has_function('dgettext',
- prefix : '''#include <libintl.h>''',
- 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,
#include <sys/stat.h>
#include <unistd.h>
+#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"
#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;
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
+#include <libintl.h> /* IWYU pragma: export */
#include <locale.h> /* 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
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);
'conditions' : ['HAVE_PAM'],
'sources' : pam_systemd_home_sources,
'dependencies' : [
- libintl,
libpam_cflags,
threads,
],
/* SPDX-License-Identifier: LGPL-2.1-or-later */
-#include <libintl.h>
-
#include "sd-bus.h"
#include "alloc-util.h"
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)
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)
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,
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)
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,
#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"
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);