From cdcbd56d4eacba3b3ee4d8b0c38d6509a307a2b7 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 14 Jun 2022 09:07:00 +0900 Subject: [PATCH] locale-setup: merge locale handling in PID1 and localed Related: #2087652 --- src/basic/locale-util.c | 11 ++ src/basic/locale-util.h | 1 + src/core/locale-setup.c | 95 -------------- src/core/locale-setup.h | 4 - src/core/meson.build | 2 - src/locale/keymap-util.c | 102 +-------------- src/locale/keymap-util.h | 7 +- src/locale/localectl.c | 47 +++---- src/locale/localed.c | 87 +++---------- src/shared/locale-setup.c | 256 ++++++++++++++++++++++++++++++++++++++ src/shared/locale-setup.h | 28 +++++ src/shared/meson.build | 2 + 12 files changed, 333 insertions(+), 309 deletions(-) delete mode 100644 src/core/locale-setup.c delete mode 100644 src/core/locale-setup.h create mode 100644 src/shared/locale-setup.c create mode 100644 src/shared/locale-setup.h diff --git a/src/basic/locale-util.c b/src/basic/locale-util.c index 8098369db5..21f0982bb5 100644 --- a/src/basic/locale-util.c +++ b/src/basic/locale-util.c @@ -335,6 +335,17 @@ void locale_variables_free(char *l[_VARIABLE_LC_MAX]) { l[i] = mfree(l[i]); } +void locale_variables_simplify(char *l[_VARIABLE_LC_MAX]) { + assert(l); + + for (LocaleVariable p = 0; p < _VARIABLE_LC_MAX; p++) { + if (p == VARIABLE_LANG) + continue; + if (isempty(l[p]) || streq_ptr(l[VARIABLE_LANG], l[p])) + l[p] = mfree(l[p]); + } +} + static const char * const locale_variable_table[_VARIABLE_LC_MAX] = { [VARIABLE_LANG] = "LANG", [VARIABLE_LANGUAGE] = "LANGUAGE", diff --git a/src/basic/locale-util.h b/src/basic/locale-util.h index bab927146b..8990cb6a75 100644 --- a/src/basic/locale-util.h +++ b/src/basic/locale-util.h @@ -53,3 +53,4 @@ void locale_variables_free(char* l[_VARIABLE_LC_MAX]); static inline void locale_variables_freep(char*(*l)[_VARIABLE_LC_MAX]) { locale_variables_free(*l); } +void locale_variables_simplify(char *l[_VARIABLE_LC_MAX]); diff --git a/src/core/locale-setup.c b/src/core/locale-setup.c deleted file mode 100644 index 716febbefa..0000000000 --- a/src/core/locale-setup.c +++ /dev/null @@ -1,95 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include -#include - -#include "env-file.h" -#include "env-util.h" -#include "locale-setup.h" -#include "locale-util.h" -#include "proc-cmdline.h" -#include "string-util.h" -#include "strv.h" -#include "util.h" -#include "virt.h" - -int locale_setup(char ***environment) { - _cleanup_(locale_variables_freep) char *variables[_VARIABLE_LC_MAX] = {}; - _cleanup_strv_free_ char **add = NULL; - int r; - - r = proc_cmdline_get_key_many(PROC_CMDLINE_STRIP_RD_PREFIX, - "locale.LANG", &variables[VARIABLE_LANG], - "locale.LANGUAGE", &variables[VARIABLE_LANGUAGE], - "locale.LC_CTYPE", &variables[VARIABLE_LC_CTYPE], - "locale.LC_NUMERIC", &variables[VARIABLE_LC_NUMERIC], - "locale.LC_TIME", &variables[VARIABLE_LC_TIME], - "locale.LC_COLLATE", &variables[VARIABLE_LC_COLLATE], - "locale.LC_MONETARY", &variables[VARIABLE_LC_MONETARY], - "locale.LC_MESSAGES", &variables[VARIABLE_LC_MESSAGES], - "locale.LC_PAPER", &variables[VARIABLE_LC_PAPER], - "locale.LC_NAME", &variables[VARIABLE_LC_NAME], - "locale.LC_ADDRESS", &variables[VARIABLE_LC_ADDRESS], - "locale.LC_TELEPHONE", &variables[VARIABLE_LC_TELEPHONE], - "locale.LC_MEASUREMENT", &variables[VARIABLE_LC_MEASUREMENT], - "locale.LC_IDENTIFICATION", &variables[VARIABLE_LC_IDENTIFICATION]); - if (r < 0 && r != -ENOENT) - log_warning_errno(r, "Failed to read /proc/cmdline: %m"); - - /* Hmm, nothing set on the kernel cmd line? Then let's try /etc/locale.conf */ - if (r <= 0) { - r = parse_env_file(NULL, "/etc/locale.conf", - "LANG", &variables[VARIABLE_LANG], - "LANGUAGE", &variables[VARIABLE_LANGUAGE], - "LC_CTYPE", &variables[VARIABLE_LC_CTYPE], - "LC_NUMERIC", &variables[VARIABLE_LC_NUMERIC], - "LC_TIME", &variables[VARIABLE_LC_TIME], - "LC_COLLATE", &variables[VARIABLE_LC_COLLATE], - "LC_MONETARY", &variables[VARIABLE_LC_MONETARY], - "LC_MESSAGES", &variables[VARIABLE_LC_MESSAGES], - "LC_PAPER", &variables[VARIABLE_LC_PAPER], - "LC_NAME", &variables[VARIABLE_LC_NAME], - "LC_ADDRESS", &variables[VARIABLE_LC_ADDRESS], - "LC_TELEPHONE", &variables[VARIABLE_LC_TELEPHONE], - "LC_MEASUREMENT", &variables[VARIABLE_LC_MEASUREMENT], - "LC_IDENTIFICATION", &variables[VARIABLE_LC_IDENTIFICATION]); - if (r < 0 && r != -ENOENT) - log_warning_errno(r, "Failed to read /etc/locale.conf: %m"); - } - - for (LocaleVariable i = 0; i < _VARIABLE_LC_MAX; i++) { - char *s; - - if (!variables[i]) - continue; - - s = strjoin(locale_variable_to_string(i), "=", variables[i]); - if (!s) - return -ENOMEM; - - if (strv_consume(&add, s) < 0) - return -ENOMEM; - } - - if (strv_isempty(add)) { - /* If no locale is configured then default to compile-time default. */ - - add = strv_new("LANG=" SYSTEMD_DEFAULT_LOCALE); - if (!add) - return -ENOMEM; - } - - if (strv_isempty(*environment)) - strv_free_and_replace(*environment, add); - else { - char **merged; - - merged = strv_env_merge(*environment, add); - if (!merged) - return -ENOMEM; - - strv_free_and_replace(*environment, merged); - } - - return 0; -} diff --git a/src/core/locale-setup.h b/src/core/locale-setup.h deleted file mode 100644 index d554ad3060..0000000000 --- a/src/core/locale-setup.h +++ /dev/null @@ -1,4 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -int locale_setup(char ***environment); diff --git a/src/core/meson.build b/src/core/meson.build index 97ac431763..7704478d43 100644 --- a/src/core/meson.build +++ b/src/core/meson.build @@ -83,8 +83,6 @@ libcore_sources = ''' load-dropin.h load-fragment.c load-fragment.h - locale-setup.c - locale-setup.h manager-dump.c manager-dump.h manager-serialize.c diff --git a/src/locale/keymap-util.c b/src/locale/keymap-util.c index 10d2ed7aec..a3af396ebe 100644 --- a/src/locale/keymap-util.c +++ b/src/locale/keymap-util.c @@ -65,13 +65,8 @@ static void context_free_vconsole(Context *c) { c->vc_keymap_toggle = mfree(c->vc_keymap_toggle); } -static void context_free_locale(Context *c) { - for (LocaleVariable p = 0; p < _VARIABLE_LC_MAX; p++) - c->locale[p] = mfree(c->locale[p]); -} - void context_clear(Context *c) { - context_free_locale(c); + locale_context_clear(&c->locale_context); context_free_x11(c); context_free_vconsole(c); @@ -82,15 +77,8 @@ void context_clear(Context *c) { bus_verify_polkit_async_registry_free(c->polkit_registry); }; -void locale_simplify(char *locale[_VARIABLE_LC_MAX]) { - for (LocaleVariable p = VARIABLE_LANG+1; p < _VARIABLE_LC_MAX; p++) - if (isempty(locale[p]) || streq_ptr(locale[VARIABLE_LANG], locale[p])) - locale[p] = mfree(locale[p]); -} - int locale_read_data(Context *c, sd_bus_message *m) { - struct stat st; - int r; + assert(c); /* Do not try to re-read the file within single bus operation. */ if (m) { @@ -101,57 +89,7 @@ int locale_read_data(Context *c, sd_bus_message *m) { c->locale_cache = sd_bus_message_ref(m); } - r = stat("/etc/locale.conf", &st); - if (r < 0 && errno != ENOENT) - return -errno; - - if (r >= 0) { - usec_t t; - - /* If mtime is not changed, then we do not need to re-read the file. */ - t = timespec_load(&st.st_mtim); - if (c->locale_mtime != USEC_INFINITY && t == c->locale_mtime) - return 0; - - c->locale_mtime = t; - context_free_locale(c); - - r = parse_env_file(NULL, "/etc/locale.conf", - "LANG", &c->locale[VARIABLE_LANG], - "LANGUAGE", &c->locale[VARIABLE_LANGUAGE], - "LC_CTYPE", &c->locale[VARIABLE_LC_CTYPE], - "LC_NUMERIC", &c->locale[VARIABLE_LC_NUMERIC], - "LC_TIME", &c->locale[VARIABLE_LC_TIME], - "LC_COLLATE", &c->locale[VARIABLE_LC_COLLATE], - "LC_MONETARY", &c->locale[VARIABLE_LC_MONETARY], - "LC_MESSAGES", &c->locale[VARIABLE_LC_MESSAGES], - "LC_PAPER", &c->locale[VARIABLE_LC_PAPER], - "LC_NAME", &c->locale[VARIABLE_LC_NAME], - "LC_ADDRESS", &c->locale[VARIABLE_LC_ADDRESS], - "LC_TELEPHONE", &c->locale[VARIABLE_LC_TELEPHONE], - "LC_MEASUREMENT", &c->locale[VARIABLE_LC_MEASUREMENT], - "LC_IDENTIFICATION", &c->locale[VARIABLE_LC_IDENTIFICATION]); - if (r < 0) - return r; - } else { - c->locale_mtime = USEC_INFINITY; - context_free_locale(c); - - /* Fill in what we got passed from systemd. */ - for (LocaleVariable p = 0; p < _VARIABLE_LC_MAX; p++) { - const char *name; - - name = locale_variable_to_string(p); - assert(name); - - r = free_and_strdup(&c->locale[p], empty_to_null(getenv(name))); - if (r < 0) - return r; - } - } - - locale_simplify(c->locale); - return 0; + return locale_context_load(&c->locale_context, LOCALE_LOAD_LOCALE_CONF | LOCALE_LOAD_ENVIRONMENT | LOCALE_LOAD_SIMPLIFY); } int vconsole_read_data(Context *c, sd_bus_message *m) { @@ -285,40 +223,6 @@ int x11_read_data(Context *c, sd_bus_message *m) { return 0; } -int locale_write_data(Context *c, char ***settings) { - _cleanup_strv_free_ char **l = NULL; - struct stat st; - int r; - - /* Set values will be returned as strv in *settings on success. */ - - for (LocaleVariable p = 0; p < _VARIABLE_LC_MAX; p++) - if (!isempty(c->locale[p])) { - r = strv_env_assign(&l, locale_variable_to_string(p), c->locale[p]); - if (r < 0) - return r; - } - - if (strv_isempty(l)) { - if (unlink("/etc/locale.conf") < 0) - return errno == ENOENT ? 0 : -errno; - - c->locale_mtime = USEC_INFINITY; - return 0; - } - - r = write_env_file_label("/etc/locale.conf", l); - if (r < 0) - return r; - - *settings = TAKE_PTR(l); - - if (stat("/etc/locale.conf", &st) >= 0) - c->locale_mtime = timespec_load(&st.st_mtim); - - return 0; -} - int vconsole_write_data(Context *c) { _cleanup_strv_free_ char **l = NULL; struct stat st; diff --git a/src/locale/keymap-util.h b/src/locale/keymap-util.h index c087dbcbbe..5470d1bb9b 100644 --- a/src/locale/keymap-util.h +++ b/src/locale/keymap-util.h @@ -4,13 +4,12 @@ #include "sd-bus.h" #include "hashmap.h" -#include "locale-util.h" +#include "locale-setup.h" #include "time-util.h" typedef struct Context { sd_bus_message *locale_cache; - usec_t locale_mtime; - char *locale[_VARIABLE_LC_MAX]; + LocaleContext locale_context; sd_bus_message *x11_cache; usec_t x11_mtime; @@ -40,8 +39,6 @@ int vconsole_convert_to_x11(Context *c); int vconsole_write_data(Context *c); int x11_convert_to_vconsole(Context *c); int x11_write_data(Context *c); -void locale_simplify(char *locale[_VARIABLE_LC_MAX]); -int locale_write_data(Context *c, char ***settings); bool locale_gen_check_available(void); int locale_gen_enable_locale(const char *locale); diff --git a/src/locale/localectl.c b/src/locale/localectl.c index 661d54c27d..6bfb564f97 100644 --- a/src/locale/localectl.c +++ b/src/locale/localectl.c @@ -12,7 +12,7 @@ #include "fd-util.h" #include "fileio.h" #include "kbd-util.h" -#include "locale-util.h" +#include "locale-setup.h" #include "main-func.h" #include "memory-util.h" #include "pager.h" @@ -52,44 +52,25 @@ static void status_info_clear(StatusInfo *info) { } static void print_overridden_variables(void) { - _cleanup_(locale_variables_freep) char *variables[_VARIABLE_LC_MAX] = {}; - bool print_warning = true; + _cleanup_(locale_context_clear) LocaleContext c = { .mtime = USEC_INFINITY }; + _cleanup_strv_free_ char **env = NULL; int r; if (arg_transport != BUS_TRANSPORT_LOCAL) return; - r = proc_cmdline_get_key_many( - PROC_CMDLINE_STRIP_RD_PREFIX, - "locale.LANG", &variables[VARIABLE_LANG], - "locale.LANGUAGE", &variables[VARIABLE_LANGUAGE], - "locale.LC_CTYPE", &variables[VARIABLE_LC_CTYPE], - "locale.LC_NUMERIC", &variables[VARIABLE_LC_NUMERIC], - "locale.LC_TIME", &variables[VARIABLE_LC_TIME], - "locale.LC_COLLATE", &variables[VARIABLE_LC_COLLATE], - "locale.LC_MONETARY", &variables[VARIABLE_LC_MONETARY], - "locale.LC_MESSAGES", &variables[VARIABLE_LC_MESSAGES], - "locale.LC_PAPER", &variables[VARIABLE_LC_PAPER], - "locale.LC_NAME", &variables[VARIABLE_LC_NAME], - "locale.LC_ADDRESS", &variables[VARIABLE_LC_ADDRESS], - "locale.LC_TELEPHONE", &variables[VARIABLE_LC_TELEPHONE], - "locale.LC_MEASUREMENT", &variables[VARIABLE_LC_MEASUREMENT], - "locale.LC_IDENTIFICATION", &variables[VARIABLE_LC_IDENTIFICATION]); - if (r < 0 && r != -ENOENT) { - log_warning_errno(r, "Failed to read /proc/cmdline: %m"); - return; - } - - for (LocaleVariable j = 0; j < _VARIABLE_LC_MAX; j++) - if (variables[j]) { - if (print_warning) { - log_warning("Warning: Settings on kernel command line override system locale settings in /etc/locale.conf.\n" - " Command Line: %s=%s", locale_variable_to_string(j), variables[j]); + (void) locale_context_load(&c, LOCALE_LOAD_PROC_CMDLINE); - print_warning = false; - } else - log_warning(" %s=%s", locale_variable_to_string(j), variables[j]); - } + r = locale_context_build_env(&c, &env, NULL); + if (r < 0) + return (void) log_warning_errno(r, "Failed to build locale settings from kernel command line, ignoring: %m"); + + STRV_FOREACH(p, env) + if (p == env) + log_warning("Warning: Settings on kernel command line override system locale settings in /etc/locale.conf.\n" + " Command Line: %s", *p); + else + log_warning(" %s", *p); } static void print_status_info(StatusInfo *i) { diff --git a/src/locale/localed.c b/src/locale/localed.c index 89bf9c6fba..9718c5b95f 100644 --- a/src/locale/localed.c +++ b/src/locale/localed.c @@ -21,7 +21,6 @@ #include "dlfcn-util.h" #include "kbd-util.h" #include "keymap-util.h" -#include "locale-util.h" #include "macro.h" #include "main-func.h" #include "missing_capability.h" @@ -33,44 +32,13 @@ #include "strv.h" #include "user-util.h" -static int locale_update_system_manager(Context *c, sd_bus *bus) { - _cleanup_free_ char **l_unset = NULL; - _cleanup_strv_free_ char **l_set = NULL; +static int locale_update_system_manager(sd_bus *bus, char **l_set, char **l_unset) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - size_t c_set = 0, c_unset = 0; int r; assert(bus); - l_unset = new0(char*, _VARIABLE_LC_MAX); - if (!l_unset) - return log_oom(); - - l_set = new0(char*, _VARIABLE_LC_MAX); - if (!l_set) - return log_oom(); - - for (LocaleVariable p = 0; p < _VARIABLE_LC_MAX; p++) { - const char *name; - - name = locale_variable_to_string(p); - assert(name); - - if (isempty(c->locale[p])) - l_unset[c_set++] = (char*) name; - else { - char *s; - - s = strjoin(name, "=", c->locale[p]); - if (!s) - return log_oom(); - - l_set[c_unset++] = s; - } - } - - assert(c_set + c_unset == _VARIABLE_LC_MAX); r = sd_bus_message_new_method_call(bus, &m, "org.freedesktop.systemd1", "/org/freedesktop/systemd1", @@ -188,21 +156,9 @@ static int property_get_locale( if (!l) return -ENOMEM; - for (LocaleVariable p = 0, q = 0; p < _VARIABLE_LC_MAX; p++) { - char *t; - const char *name; - - name = locale_variable_to_string(p); - assert(name); - - if (isempty(c->locale[p])) - continue; - - if (asprintf(&t, "%s=%s", name, c->locale[p]) < 0) - return -ENOMEM; - - l[q++] = t; - } + r = locale_context_build_env(&c->locale_context, &l, NULL); + if (r < 0) + return r; return sd_bus_message_append_strv(reply, l); } @@ -342,9 +298,8 @@ static int locale_gen_process_locale(char *new_locale[static _VARIABLE_LC_MAX], static int method_set_locale(sd_bus_message *m, void *userdata, sd_bus_error *error) { _cleanup_(locale_variables_freep) char *new_locale[_VARIABLE_LC_MAX] = {}; - _cleanup_strv_free_ char **settings = NULL, **l = NULL; + _cleanup_strv_free_ char **l = NULL, **l_set = NULL, **l_unset = NULL; Context *c = userdata; - bool modified = false; int interactive, r; bool use_localegen; @@ -402,22 +357,13 @@ static int method_set_locale(sd_bus_message *m, void *userdata, sd_bus_error *er } /* Merge with the current settings */ - for (LocaleVariable p = 0; p < _VARIABLE_LC_MAX; p++) - if (!isempty(c->locale[p]) && isempty(new_locale[p])) { - new_locale[p] = strdup(c->locale[p]); - if (!new_locale[p]) - return -ENOMEM; - } - - locale_simplify(new_locale); + r = locale_context_merge(&c->locale_context, new_locale); + if (r < 0) + return r; - for (LocaleVariable p = 0; p < _VARIABLE_LC_MAX; p++) - if (!streq_ptr(c->locale[p], new_locale[p])) { - modified = true; - break; - } + locale_variables_simplify(new_locale); - if (!modified) { + if (locale_context_equal(&c->locale_context, new_locale)) { log_debug("Locale settings were not modified."); return sd_bus_reply_method_return(m, NULL); } @@ -443,22 +389,21 @@ static int method_set_locale(sd_bus_message *m, void *userdata, sd_bus_error *er return r; } - for (LocaleVariable p = 0; p < _VARIABLE_LC_MAX; p++) - free_and_replace(c->locale[p], new_locale[p]); + locale_context_take(&c->locale_context, new_locale); /* Write locale configuration */ - r = locale_write_data(c, &settings); + r = locale_context_save(&c->locale_context, &l_set, &l_unset); if (r < 0) { log_error_errno(r, "Failed to set locale: %m"); return sd_bus_error_set_errnof(error, r, "Failed to set locale: %m"); } - (void) locale_update_system_manager(c, sd_bus_message_get_bus(m)); + (void) locale_update_system_manager(sd_bus_message_get_bus(m), l_set, l_unset); - if (settings) { + if (!strv_isempty(l_set)) { _cleanup_free_ char *line = NULL; - line = strv_join(settings, ", "); + line = strv_join(l_set, ", "); log_info("Changed locale to %s.", strnull(line)); } else log_info("Changed locale to unset."); @@ -827,7 +772,7 @@ static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) { static int run(int argc, char *argv[]) { _cleanup_(context_clear) Context context = { - .locale_mtime = USEC_INFINITY, + .locale_context.mtime = USEC_INFINITY, .vc_mtime = USEC_INFINITY, .x11_mtime = USEC_INFINITY, }; diff --git a/src/shared/locale-setup.c b/src/shared/locale-setup.c new file mode 100644 index 0000000000..b8c6647e7c --- /dev/null +++ b/src/shared/locale-setup.c @@ -0,0 +1,256 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#include "env-file-label.h" +#include "env-file.h" +#include "env-util.h" +#include "locale-setup.h" +#include "proc-cmdline.h" +#include "strv.h" + +void locale_context_clear(LocaleContext *c) { + assert(c); + + c->mtime = USEC_INFINITY; + + for (LocaleVariable i = 0; i < _VARIABLE_LC_MAX; i++) + c->locale[i] = mfree(c->locale[i]); +} + +int locale_context_load(LocaleContext *c, LocaleLoadFlag flag) { + int r; + + assert(c); + + if (FLAGS_SET(flag, LOCALE_LOAD_PROC_CMDLINE)) { + locale_context_clear(c); + + r = proc_cmdline_get_key_many(PROC_CMDLINE_STRIP_RD_PREFIX, + "locale.LANG", &c->locale[VARIABLE_LANG], + "locale.LANGUAGE", &c->locale[VARIABLE_LANGUAGE], + "locale.LC_CTYPE", &c->locale[VARIABLE_LC_CTYPE], + "locale.LC_NUMERIC", &c->locale[VARIABLE_LC_NUMERIC], + "locale.LC_TIME", &c->locale[VARIABLE_LC_TIME], + "locale.LC_COLLATE", &c->locale[VARIABLE_LC_COLLATE], + "locale.LC_MONETARY", &c->locale[VARIABLE_LC_MONETARY], + "locale.LC_MESSAGES", &c->locale[VARIABLE_LC_MESSAGES], + "locale.LC_PAPER", &c->locale[VARIABLE_LC_PAPER], + "locale.LC_NAME", &c->locale[VARIABLE_LC_NAME], + "locale.LC_ADDRESS", &c->locale[VARIABLE_LC_ADDRESS], + "locale.LC_TELEPHONE", &c->locale[VARIABLE_LC_TELEPHONE], + "locale.LC_MEASUREMENT", &c->locale[VARIABLE_LC_MEASUREMENT], + "locale.LC_IDENTIFICATION", &c->locale[VARIABLE_LC_IDENTIFICATION]); + if (r < 0 && r != -ENOENT) + log_debug_errno(r, "Failed to read /proc/cmdline, ignoring: %m"); + if (r > 0) + goto finalize; + } + + if (FLAGS_SET(flag, LOCALE_LOAD_LOCALE_CONF)) { + struct stat st; + usec_t t; + + r = stat("/etc/locale.conf", &st); + if (r < 0 && errno != ENOENT) + return log_debug_errno(errno, "Failed to stat /etc/locale.conf: %m"); + + if (r >= 0) { + /* If mtime is not changed, then we do not need to re-read the file. */ + t = timespec_load(&st.st_mtim); + if (c->mtime != USEC_INFINITY && t == c->mtime) + return 0; + + locale_context_clear(c); + c->mtime = t; + + r = parse_env_file(NULL, "/etc/locale.conf", + "LANG", &c->locale[VARIABLE_LANG], + "LANGUAGE", &c->locale[VARIABLE_LANGUAGE], + "LC_CTYPE", &c->locale[VARIABLE_LC_CTYPE], + "LC_NUMERIC", &c->locale[VARIABLE_LC_NUMERIC], + "LC_TIME", &c->locale[VARIABLE_LC_TIME], + "LC_COLLATE", &c->locale[VARIABLE_LC_COLLATE], + "LC_MONETARY", &c->locale[VARIABLE_LC_MONETARY], + "LC_MESSAGES", &c->locale[VARIABLE_LC_MESSAGES], + "LC_PAPER", &c->locale[VARIABLE_LC_PAPER], + "LC_NAME", &c->locale[VARIABLE_LC_NAME], + "LC_ADDRESS", &c->locale[VARIABLE_LC_ADDRESS], + "LC_TELEPHONE", &c->locale[VARIABLE_LC_TELEPHONE], + "LC_MEASUREMENT", &c->locale[VARIABLE_LC_MEASUREMENT], + "LC_IDENTIFICATION", &c->locale[VARIABLE_LC_IDENTIFICATION]); + if (r < 0) + return log_debug_errno(r, "Failed to read /etc/locale.conf: %m"); + + goto finalize; + } + } + + if (FLAGS_SET(flag, LOCALE_LOAD_ENVIRONMENT)) { + locale_context_clear(c); + + /* Fill in what we got passed from systemd. */ + for (LocaleVariable p = 0; p < _VARIABLE_LC_MAX; p++) { + const char *name = ASSERT_PTR(locale_variable_to_string(p)); + + r = free_and_strdup(&c->locale[p], empty_to_null(getenv(name))); + if (r < 0) + return log_oom_debug(); + } + + goto finalize; + } + + /* Nothing loaded. */ + locale_context_clear(c); + return 0; + +finalize: + if (FLAGS_SET(flag, LOCALE_LOAD_SIMPLIFY)) + locale_variables_simplify(c->locale); + + return 0; +} + +int locale_context_build_env(const LocaleContext *c, char ***ret_set, char ***ret_unset) { + _cleanup_strv_free_ char **set = NULL, **unset = NULL; + int r; + + assert(c); + + if (!ret_set && !ret_unset) + return 0; + + for (LocaleVariable p = 0; p < _VARIABLE_LC_MAX; p++) { + const char *name = ASSERT_PTR(locale_variable_to_string(p)); + + if (isempty(c->locale[p])) { + if (!ret_unset) + continue; + r = strv_extend(&unset, name); + } else { + if (!ret_set) + continue; + r = strv_env_assign(&set, name, c->locale[p]); + } + if (r < 0) + return r; + } + + if (ret_set) + *ret_set = TAKE_PTR(set); + if (ret_unset) + *ret_unset = TAKE_PTR(unset); + return 0; +} + +int locale_context_save(LocaleContext *c, char ***ret_set, char ***ret_unset) { + _cleanup_strv_free_ char **set = NULL, **unset = NULL; + struct stat st; + int r; + + assert(c); + + /* Set values will be returned as strv in *ret on success. */ + + r = locale_context_build_env(c, &set, ret_unset ? &unset : NULL); + if (r < 0) + return r; + + if (strv_isempty(set)) { + if (unlink("/etc/locale.conf") < 0) + return errno == ENOENT ? 0 : -errno; + + c->mtime = USEC_INFINITY; + if (ret_set) + *ret_set = NULL; + if (ret_unset) + *ret_unset = NULL; + return 0; + } + + r = write_env_file_label("/etc/locale.conf", set); + if (r < 0) + return r; + + if (stat("/etc/locale.conf", &st) >= 0) + c->mtime = timespec_load(&st.st_mtim); + + if (ret_set) + *ret_set = TAKE_PTR(set); + if (ret_unset) + *ret_unset = TAKE_PTR(unset); + return 0; +} + +int locale_context_merge(const LocaleContext *c, char *l[_VARIABLE_LC_MAX]) { + assert(c); + assert(l); + + for (LocaleVariable p = 0; p < _VARIABLE_LC_MAX; p++) + if (!isempty(c->locale[p]) && isempty(l[p])) { + l[p] = strdup(c->locale[p]); + if (!l[p]) + return -ENOMEM; + } + + return 0; +} + +void locale_context_take(LocaleContext *c, char *l[_VARIABLE_LC_MAX]) { + assert(c); + assert(l); + + for (LocaleVariable p = 0; p < _VARIABLE_LC_MAX; p++) + free_and_replace(c->locale[p], l[p]); +} + +bool locale_context_equal(const LocaleContext *c, char *l[_VARIABLE_LC_MAX]) { + assert(c); + assert(l); + + for (LocaleVariable p = 0; p < _VARIABLE_LC_MAX; p++) + if (!streq_ptr(c->locale[p], l[p])) + return false; + + return true; +} + +int locale_setup(char ***environment) { + _cleanup_(locale_context_clear) LocaleContext c = { .mtime = USEC_INFINITY }; + _cleanup_strv_free_ char **add = NULL; + int r; + + assert(environment); + + r = locale_context_load(&c, LOCALE_LOAD_PROC_CMDLINE | LOCALE_LOAD_LOCALE_CONF); + if (r < 0) + return r; + + r = locale_context_build_env(&c, &add, NULL); + if (r < 0) + return r; + + if (strv_isempty(add)) { + /* If no locale is configured then default to compile-time default. */ + + add = strv_new("LANG=" SYSTEMD_DEFAULT_LOCALE); + if (!add) + return -ENOMEM; + } + + if (strv_isempty(*environment)) + strv_free_and_replace(*environment, add); + else { + char **merged; + + merged = strv_env_merge(*environment, add); + if (!merged) + return -ENOMEM; + + strv_free_and_replace(*environment, merged); + } + + return 0; +} diff --git a/src/shared/locale-setup.h b/src/shared/locale-setup.h new file mode 100644 index 0000000000..ec3fc8c364 --- /dev/null +++ b/src/shared/locale-setup.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "locale-util.h" +#include "time-util.h" + +typedef struct LocaleContext { + usec_t mtime; + char *locale[_VARIABLE_LC_MAX]; +} LocaleContext; + +typedef enum LocaleLoadFlag { + LOCALE_LOAD_PROC_CMDLINE = 1 << 0, + LOCALE_LOAD_LOCALE_CONF = 1 << 1, + LOCALE_LOAD_ENVIRONMENT = 1 << 2, + LOCALE_LOAD_SIMPLIFY = 1 << 3, +} LocaleLoadFlag; + +void locale_context_clear(LocaleContext *c); +int locale_context_load(LocaleContext *c, LocaleLoadFlag flag); +int locale_context_build_env(const LocaleContext *c, char ***ret_set, char ***ret_unset); +int locale_context_save(LocaleContext *c, char ***ret_set, char ***ret_unset); + +int locale_context_merge(const LocaleContext *c, char *l[_VARIABLE_LC_MAX]); +void locale_context_take(LocaleContext *c, char *l[_VARIABLE_LC_MAX]); +bool locale_context_equal(const LocaleContext *c, char *l[_VARIABLE_LC_MAX]); + +int locale_setup(char ***environment); diff --git a/src/shared/meson.build b/src/shared/meson.build index 006310a917..62365682cf 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -195,6 +195,8 @@ shared_sources = files(''' linux/ethtool.h local-addresses.c local-addresses.h + locale-setup.c + locale-setup.h lockfile-util.c lockfile-util.h log-link.h