]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
locale-setup: merge locale handling in PID1 and localed
authorYu Watanabe <watanabe.yu+github@gmail.com>
Tue, 14 Jun 2022 00:07:00 +0000 (09:07 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Tue, 14 Jun 2022 11:56:23 +0000 (20:56 +0900)
12 files changed:
src/basic/locale-util.c
src/basic/locale-util.h
src/core/locale-setup.c [deleted file]
src/core/locale-setup.h [deleted file]
src/core/meson.build
src/locale/keymap-util.c
src/locale/keymap-util.h
src/locale/localectl.c
src/locale/localed.c
src/shared/locale-setup.c [new file with mode: 0644]
src/shared/locale-setup.h [new file with mode: 0644]
src/shared/meson.build

index 667b0452262f0fbb931391ad70453938d07ee123..d8518ec06aad50b6fae7729a03e490a8910d5b6b 100644 (file)
@@ -339,6 +339,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",
index bab927146b96c23282a7d05f364bbb2c8b80e2bf..8990cb6a75a4a25e01e30066797ef6df65bc736c 100644 (file)
@@ -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 (file)
index 716febb..0000000
+++ /dev/null
@@ -1,95 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-
-#include <errno.h>
-#include <stdlib.h>
-
-#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 (file)
index d554ad3..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-#pragma once
-
-int locale_setup(char ***environment);
index ac4b8d374c2db7ea5d755527830745e7175c59b4..6fc90895830e19b1b355180066b397ff8a623511 100644 (file)
@@ -85,8 +85,6 @@ libcore_sources = files(
         'load-dropin.h',
         'load-fragment.c',
         'load-fragment.h',
-        'locale-setup.c',
-        'locale-setup.h',
         'manager-dump.c',
         'manager-dump.h',
         'manager-serialize.c',
index 9759f461631e8c6114e440a18d8ffca1b804dcc3..0717697e5b0955e8620dacc5e6318168b5a17a8c 100644 (file)
@@ -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) {
@@ -280,40 +218,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;
index c087dbcbbe260447280f5a636f8b5784da42955f..5470d1bb9b63264a3cc8e09da35ad5b3c40a9056 100644 (file)
@@ -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);
index 661d54c27dd054dd14c15c400b533477037857a2..6bfb564f9708e577eda917935092a1136dd4a975 100644 (file)
@@ -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) {
index 89bf9c6fbad5d7c1dd8eaae64ca1c93be5212380..9718c5b95fa022c074158c428ce7bc139176f25b 100644 (file)
@@ -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"
 #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 (file)
index 0000000..b8c6647
--- /dev/null
@@ -0,0 +1,256 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <errno.h>
+#include <sys/stat.h>
+
+#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 (file)
index 0000000..ec3fc8c
--- /dev/null
@@ -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);
index 72457d3da37a0cd882a637a869941060f540e220..598a64593b533fe122442bfd373d47085ef91cc5 100644 (file)
@@ -199,6 +199,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',