From 862816a1fcdc1f3455caa519ab436d15af608164 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 19 May 2026 15:50:20 +0200 Subject: [PATCH] locale-setup: pick up platform lang from firmware --- src/shared/locale-setup.c | 76 +++++++++++++++++++++++++++++++++++++++ src/shared/locale-setup.h | 7 ++++ 2 files changed, 83 insertions(+) diff --git a/src/shared/locale-setup.c b/src/shared/locale-setup.c index 6b66c15443..294963640e 100644 --- a/src/shared/locale-setup.c +++ b/src/shared/locale-setup.c @@ -5,10 +5,12 @@ #include #include "alloc-util.h" +#include "efivars.h" #include "env-file.h" #include "env-util.h" #include "errno-util.h" #include "fd-util.h" +#include "iovec-util.h" #include "locale-setup.h" #include "log.h" #include "proc-cmdline.h" @@ -318,3 +320,77 @@ const char* etc_vconsole_conf(void) { return cached; } + +int locale_lang_from_efi(char **ret, LocaleLangFromEfiFlags flags) { + int r; + + assert(ret); + + if (!is_efi_boot()) { + *ret = NULL; + return 0; + } + + /* NB: unlike most other UEFI variables, PlatformLang is actually in 7bit ASCII! Hence we are not + * using efi_get_variable_string() here */ + _cleanup_(iovec_done) struct iovec iov = {}; + r = efi_get_variable(EFI_GLOBAL_VARIABLE_STR("PlatformLang"), /* ret_attribute= */ NULL, &iov.iov_base, &iov.iov_len); + if (r == -ENOENT) { + *ret = NULL; + return 0; + } + if (r < 0) + return log_debug_errno(r, "Failed to read PlatformLang EFI variable: %m"); + + _cleanup_free_ char *tag = NULL; + r = make_cstring(iov.iov_base, iov.iov_len, MAKE_CSTRING_ALLOW_TRAILING_NUL, &tag); + if (r < 0) + return log_debug_errno(r, "Failed to convert PlatformLang EFI variable to C string: %m"); + + /* Convert the UEFI BCP47 language tag into a glibc tag. We'll not bother with the complexity of the + * whole spec, but just convert "xx-XX" into "xx_XX", with some flexibility on the case + * sensitivity. This is a best-effort thing anyway. */ + + if (strlen(tag) != 5 || + !strchr(LETTERS, tag[0]) || + !strchr(LETTERS, tag[1]) || + tag[2] != '-' || + !strchr(LETTERS, tag[3]) || + !strchr(LETTERS, tag[4])) { + log_debug("PlatformLang variable does not have the form 'xx-XX', ignoring: %s", tag); + *ret = NULL; + return 0; + } + + tag[0] = ascii_tolower(tag[0]); + tag[1] = ascii_tolower(tag[1]); + tag[2] = '_'; + tag[3] = ascii_toupper(tag[3]); + tag[4] = ascii_toupper(tag[4]); + + /* Let's optionally suppress en_US locale, since that's almost certainly just the built-in default + * locale of the firmware. Since we typically prefer C.UTF-8 over en_US.UTF-8 as default, let's hence + * suppress it. */ + if (FLAGS_SET(flags, LOCALE_SUPPRESS_EN_US) && streq(tag, "en_US")) { + log_debug("Firmware language is en_US, suppressing because likely just the firmware default."); + *ret = NULL; + return 0; + } + + if (!strextend(&tag, ".UTF-8")) + return -ENOMEM; + + if (FLAGS_SET(flags, LOCALE_REQUIRE_INSTALLED)) { + r = locale_is_installed(tag); + if (r < 0) + return r; + if (r == 0) { + log_debug("Determined locale '%s' from PlatformLang, but it isn't installed, ignoring.", tag); + *ret = NULL; + return 0; + } + } + + *ret = TAKE_PTR(tag); + return 1; +} diff --git a/src/shared/locale-setup.h b/src/shared/locale-setup.h index 8b71064cad..d720a53adb 100644 --- a/src/shared/locale-setup.h +++ b/src/shared/locale-setup.h @@ -31,3 +31,10 @@ int locale_setup(char ***environment); const char* etc_locale_conf(void); const char* etc_vconsole_conf(void); + +typedef enum LocaleLangFromEfiFlags { + LOCALE_REQUIRE_INSTALLED = 1 << 0, + LOCALE_SUPPRESS_EN_US = 1 << 1, +} LocaleLangFromEfiFlags; + +int locale_lang_from_efi(char **ret, LocaleLangFromEfiFlags flags);