diff --git a/src/basic/locale-util.c b/src/basic/locale-util.c index cb99641263..d8518ec06a 100644 --- a/src/basic/locale-util.c +++ b/src/basic/locale-util.c @@ -160,7 +160,7 @@ static int add_locales_from_archive(Set *locales) { return r; } -static int add_locales_from_libdir (Set *locales) { +static int add_locales_from_libdir(Set *locales) { _cleanup_closedir_ DIR *dir = NULL; int r; @@ -187,7 +187,7 @@ static int add_locales_from_libdir (Set *locales) { } int get_locales(char ***ret) { - _cleanup_set_free_ Set *locales = NULL; + _cleanup_set_free_free_ Set *locales = NULL; _cleanup_strv_free_ char **l = NULL; int r; @@ -203,10 +203,22 @@ int get_locales(char ***ret) { if (r < 0) return r; + char *locale; + SET_FOREACH(locale, locales) { + r = locale_is_installed(locale); + if (r < 0) + return r; + if (r == 0) + free(set_remove(locales, locale)); + } + l = set_get_strv(locales); if (!l) return -ENOMEM; + /* Now, all elements are owned by strv 'l'. Hence, do not call set_free_free(). */ + locales = set_free(locales); + r = getenv_bool("SYSTEMD_LIST_NON_UTF8_LOCALES"); if (r == -ENXIO || r == 0) { char **a, **b; @@ -327,20 +339,31 @@ 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", - [VARIABLE_LC_CTYPE] = "LC_CTYPE", - [VARIABLE_LC_NUMERIC] = "LC_NUMERIC", - [VARIABLE_LC_TIME] = "LC_TIME", - [VARIABLE_LC_COLLATE] = "LC_COLLATE", - [VARIABLE_LC_MONETARY] = "LC_MONETARY", - [VARIABLE_LC_MESSAGES] = "LC_MESSAGES", - [VARIABLE_LC_PAPER] = "LC_PAPER", - [VARIABLE_LC_NAME] = "LC_NAME", - [VARIABLE_LC_ADDRESS] = "LC_ADDRESS", - [VARIABLE_LC_TELEPHONE] = "LC_TELEPHONE", - [VARIABLE_LC_MEASUREMENT] = "LC_MEASUREMENT", + [VARIABLE_LANG] = "LANG", + [VARIABLE_LANGUAGE] = "LANGUAGE", + [VARIABLE_LC_CTYPE] = "LC_CTYPE", + [VARIABLE_LC_NUMERIC] = "LC_NUMERIC", + [VARIABLE_LC_TIME] = "LC_TIME", + [VARIABLE_LC_COLLATE] = "LC_COLLATE", + [VARIABLE_LC_MONETARY] = "LC_MONETARY", + [VARIABLE_LC_MESSAGES] = "LC_MESSAGES", + [VARIABLE_LC_PAPER] = "LC_PAPER", + [VARIABLE_LC_NAME] = "LC_NAME", + [VARIABLE_LC_ADDRESS] = "LC_ADDRESS", + [VARIABLE_LC_TELEPHONE] = "LC_TELEPHONE", + [VARIABLE_LC_MEASUREMENT] = "LC_MEASUREMENT", [VARIABLE_LC_IDENTIFICATION] = "LC_IDENTIFICATION" }; 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 ac4b8d374c..6fc9089583 100644 --- a/src/core/meson.build +++ b/src/core/meson.build @@ -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', diff --git a/src/locale/localectl.c b/src/locale/localectl.c index 661d54c27d..55db3c9ed8 100644 --- a/src/locale/localectl.c +++ b/src/locale/localectl.c @@ -2,7 +2,6 @@ #include #include -#include #include "sd-bus.h" @@ -11,8 +10,9 @@ #include "bus-map-properties.h" #include "fd-util.h" #include "fileio.h" +#include "format-table.h" #include "kbd-util.h" -#include "locale-util.h" +#include "locale-setup.h" #include "main-func.h" #include "memory-util.h" #include "pager.h" @@ -51,69 +51,102 @@ 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; +static int print_status_info(StatusInfo *i) { + _cleanup_strv_free_ char **kernel_locale = NULL; + _cleanup_(table_unrefp) Table *table = NULL; + TableCell *cell; 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]); - - print_warning = false; - } else - log_warning(" %s=%s", locale_variable_to_string(j), variables[j]); - } -} - -static void print_status_info(StatusInfo *i) { assert(i); - if (strv_isempty(i->locale)) - puts(" System Locale: n/a"); - else { - printf(" System Locale: %s\n", i->locale[0]); - STRV_FOREACH(j, i->locale + 1) - printf(" %s\n", *j); + if (arg_transport == BUS_TRANSPORT_LOCAL) { + _cleanup_(locale_context_clear) LocaleContext c = { .mtime = USEC_INFINITY }; + + r = locale_context_load(&c, LOCALE_LOAD_PROC_CMDLINE); + if (r < 0) + return log_error_errno(r, "Failed to read /proc/cmdline: %m"); + + r = locale_context_build_env(&c, &kernel_locale, NULL); + if (r < 0) + return log_error_errno(r, "Failed to build locale settings from kernel command line: %m"); } - printf(" VC Keymap: %s\n", strna(i->vconsole_keymap)); - if (!isempty(i->vconsole_keymap_toggle)) - printf("VC Toggle Keymap: %s\n", i->vconsole_keymap_toggle); + table = table_new("key", "value"); + if (!table) + return log_oom(); - printf(" X11 Layout: %s\n", strna(i->x11_layout)); - if (!isempty(i->x11_model)) - printf(" X11 Model: %s\n", i->x11_model); - if (!isempty(i->x11_variant)) - printf(" X11 Variant: %s\n", i->x11_variant); - if (!isempty(i->x11_options)) - printf(" X11 Options: %s\n", i->x11_options); + assert_se(cell = table_get_cell(table, 0, 0)); + (void) table_set_ellipsize_percent(table, cell, 100); + (void) table_set_align_percent(table, cell, 100); + + table_set_header(table, false); + + r = table_set_empty_string(table, "n/a"); + if (r < 0) + return log_oom(); + + if (!strv_isempty(kernel_locale)) { + log_warning("Warning: Settings on kernel command line override system locale settings in /etc/locale.conf."); + r = table_add_many(table, + TABLE_STRING, "Command Line:", + TABLE_SET_COLOR, ansi_highlight_yellow(), + TABLE_STRV, kernel_locale, + TABLE_SET_COLOR, ansi_highlight_yellow()); + if (r < 0) + return table_log_add_error(r); + } + + r = table_add_many(table, + TABLE_STRING, "System Locale:", + TABLE_STRV, i->locale, + TABLE_STRING, "VC Keymap:", + TABLE_STRING, i->vconsole_keymap); + if (r < 0) + return table_log_add_error(r); + + if (!isempty(i->vconsole_keymap_toggle)) { + r = table_add_many(table, + TABLE_STRING, "VC Toggle Keymap:", + TABLE_STRING, i->vconsole_keymap_toggle); + if (r < 0) + return table_log_add_error(r); + } + + r = table_add_many(table, + TABLE_STRING, "X11 Layout:", + TABLE_STRING, i->x11_layout); + if (r < 0) + return table_log_add_error(r); + + if (!isempty(i->x11_model)) { + r = table_add_many(table, + TABLE_STRING, "X11 Model:", + TABLE_STRING, i->x11_model); + if (r < 0) + return table_log_add_error(r); + } + + if (!isempty(i->x11_variant)) { + r = table_add_many(table, + TABLE_STRING, "X11 Variant:", + TABLE_STRING, i->x11_variant); + if (r < 0) + return table_log_add_error(r); + } + + if (!isempty(i->x11_options)) { + r = table_add_many(table, + TABLE_STRING, "X11 Options:", + TABLE_STRING, i->x11_options); + if (r < 0) + return table_log_add_error(r); + } + + r = table_print(table, NULL); + if (r < 0) + return table_log_print_error(r); + + return 0; } static int show_status(int argc, char **argv, void *userdata) { @@ -147,10 +180,7 @@ static int show_status(int argc, char **argv, void *userdata) { if (r < 0) return log_error_errno(r, "Could not get properties: %s", bus_error_message(&error, r)); - print_overridden_variables(); - print_status_info(&info); - - return r; + return print_status_info(&info); } static int set_locale(int argc, char **argv, void *userdata) { diff --git a/src/locale/keymap-util.c b/src/locale/localed-util.c similarity index 87% rename from src/locale/keymap-util.c rename to src/locale/localed-util.c index 9759f46163..598e89591c 100644 --- a/src/locale/keymap-util.c +++ b/src/locale/localed-util.c @@ -15,8 +15,7 @@ #include "fileio.h" #include "fs-util.h" #include "kbd-util.h" -#include "keymap-util.h" -#include "locale-util.h" +#include "localed-util.h" #include "macro.h" #include "mkdir-label.h" #include "nulstr-util.h" @@ -65,13 +64,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 +76,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 +88,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 +217,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/localed-util.h similarity index 85% rename from src/locale/keymap-util.h rename to src/locale/localed-util.h index c087dbcbbe..5470d1bb9b 100644 --- a/src/locale/keymap-util.h +++ b/src/locale/localed-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/localed.c b/src/locale/localed.c index 89bf9c6fba..2c324efb14 100644 --- a/src/locale/localed.c +++ b/src/locale/localed.c @@ -20,8 +20,7 @@ #include "def.h" #include "dlfcn-util.h" #include "kbd-util.h" -#include "keymap-util.h" -#include "locale-util.h" +#include "localed-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; - } + r = locale_context_merge(&c->locale_context, new_locale); + if (r < 0) + return r; - locale_simplify(new_locale); + locale_variables_simplify(new_locale); - for (LocaleVariable p = 0; p < _VARIABLE_LC_MAX; p++) - if (!streq_ptr(c->locale[p], new_locale[p])) { - modified = true; - break; - } - - 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/locale/meson.build b/src/locale/meson.build index 72c44aedcf..c1ae34069c 100644 --- a/src/locale/meson.build +++ b/src/locale/meson.build @@ -1,9 +1,9 @@ # SPDX-License-Identifier: LGPL-2.1-or-later systemd_localed_sources = files( + 'localed-util.c', + 'localed-util.h', 'localed.c', - 'keymap-util.c', - 'keymap-util.h', ) localectl_sources = files('localectl.c') @@ -30,7 +30,7 @@ if conf.get('ENABLE_LOCALED') == 1 endif tests += [ - [files('test-keymap-util.c', - 'keymap-util.c', - 'keymap-util.h')], + [files('test-localed-util.c', + 'localed-util.c', + 'localed-util.h')], ] diff --git a/src/locale/test-keymap-util.c b/src/locale/test-localed-util.c similarity index 99% rename from src/locale/test-keymap-util.c rename to src/locale/test-localed-util.c index 668f94ec80..b48fc9f58f 100644 --- a/src/locale/test-keymap-util.c +++ b/src/locale/test-localed-util.c @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "alloc-util.h" -#include "keymap-util.h" +#include "localed-util.h" #include "log.h" #include "string-util.h" #include "tests.h" 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 72457d3da3..598a64593b 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -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', diff --git a/test/TEST-71-HOSTNAME/Makefile b/test/TEST-71-HOSTNAME/Makefile new file mode 120000 index 0000000000..e9f93b1104 --- /dev/null +++ b/test/TEST-71-HOSTNAME/Makefile @@ -0,0 +1 @@ +../TEST-01-BASIC/Makefile \ No newline at end of file diff --git a/test/TEST-71-HOSTNAME/test.sh b/test/TEST-71-HOSTNAME/test.sh new file mode 100755 index 0000000000..7b3d2d12d4 --- /dev/null +++ b/test/TEST-71-HOSTNAME/test.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -e + +TEST_DESCRIPTION="test hostnamed" + +# shellcheck source=test/test-functions +. "${TEST_BASE_DIR:?}/test-functions" + +do_test "$@" diff --git a/test/TEST-73-LOCALE/Makefile b/test/TEST-73-LOCALE/Makefile new file mode 120000 index 0000000000..e9f93b1104 --- /dev/null +++ b/test/TEST-73-LOCALE/Makefile @@ -0,0 +1 @@ +../TEST-01-BASIC/Makefile \ No newline at end of file diff --git a/test/TEST-73-LOCALE/test.sh b/test/TEST-73-LOCALE/test.sh new file mode 100755 index 0000000000..a33ddaba86 --- /dev/null +++ b/test/TEST-73-LOCALE/test.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -e + +TEST_DESCRIPTION="test localed" + +# shellcheck source=test/test-functions +. "${TEST_BASE_DIR:?}/test-functions" + +do_test "$@" diff --git a/test/test-functions b/test/test-functions index 71413624f9..8fba54c9e0 100644 --- a/test/test-functions +++ b/test/test-functions @@ -702,7 +702,9 @@ setup_basic_environment() { install_pam install_dbus install_fonts + install_locales install_keymaps + install_x11_keymaps install_terminfo install_execs install_fs_tools @@ -1249,7 +1251,7 @@ install_missing_libraries() { local lib path # A number of dependencies is now optional via dlopen, so the install # script will not pick them up, since it looks at linkage. - for lib in libcryptsetup libidn libidn2 pwquality libqrencode tss2-esys tss2-rc tss2-mu tss2-tcti-device libfido2 libbpf libelf libdw; do + for lib in libcryptsetup libidn libidn2 pwquality libqrencode tss2-esys tss2-rc tss2-mu tss2-tcti-device libfido2 libbpf libelf libdw xkbcommon; do ddebug "Searching for $lib via pkg-config" if pkg-config --exists "$lib"; then path="$(pkg-config --variable=libdir "$lib")" @@ -1265,6 +1267,10 @@ install_missing_libraries() { # (eg: libcryptsetup), so just ignore them inst_libs "${path}/${lib}.so" || true inst_library "${path}/${lib}.so" || true + + if [[ "$lib" == "libxkbcommon" ]]; then + install_x11_keymaps full + fi else ddebug "$lib.pc not found, skipping" continue @@ -1911,29 +1917,85 @@ install_pam() { done } +install_locales() { + # install only C.UTF-8 and English locales + dinfo "Install locales" + + if command -v meson >/dev/null \ + && (meson configure "${BUILD_DIR:?}" | grep 'localegen-path */') \ + || get_bool "$LOOKS_LIKE_DEBIAN"; then + # locale-gen support + image_install -o locale-gen localedef + inst /etc/locale.gen || : + inst /usr/share/i18n/SUPPORTED || : + inst_recursive /usr/share/i18n/charmaps + inst_recursive /usr/share/i18n/locales + inst_recursive /usr/share/locale/en + inst_recursive /usr/share/locale/en_* + fi + + inst_recursive /usr/lib/locale/C.utf8 + inst_recursive /usr/lib/locale/C.UTF-8 + inst_recursive /usr/lib/locale/en_*.utf8 + inst_recursive /usr/lib/locale/en_*.UTF-8 +} + # shellcheck disable=SC2120 install_keymaps() { - dinfo "Install keymaps" - # The first three paths may be deprecated. - # It seems now the last two paths are used by many distributions. - for i in \ - /usr/lib/kbd/keymaps/include/* \ - /usr/lib/kbd/keymaps/i386/include/* \ - /usr/lib/kbd/keymaps/i386/qwerty/us.* \ - /usr/lib/kbd/keymaps/legacy/include/* \ - /usr/lib/kbd/keymaps/legacy/i386/qwerty/us.*; do - [[ -f "$i" ]] || continue - inst "$i" - done + local i p + local -a prefix=( + "/usr" + ) - # When it takes any argument, then install more keymaps. - if [[ $# -gt 1 ]]; then - for i in \ - /usr/lib/kbd/keymaps/i386/*/* \ - /usr/lib/kbd/keymaps/legacy/i386/*/*; do - [[ -f "$i" ]] || continue - inst "$i" + dinfo "Install console keymaps" + + if command -v meson >/dev/null \ + && [[ "$(meson configure "${BUILD_DIR:?}" | grep 'split-usr' | awk '{ print $2 }')" == "true" ]] \ + || [[ ! -L /lib ]]; then + prefix+=( + "" + ) + fi + + if (( $# == 0 )); then + for p in "${prefix[@]}"; do + # The first three paths may be deprecated. + # It seems now the last three paths are used by many distributions. + for i in \ + "$p"/lib/kbd/keymaps/include/* \ + "$p"/lib/kbd/keymaps/i386/include/* \ + "$p"/lib/kbd/keymaps/i386/qwerty/us.* \ + "$p"/lib/kbd/keymaps/legacy/include/* \ + "$p"/lib/kbd/keymaps/legacy/i386/qwerty/us.* \ + "$p"/lib/kbd/keymaps/xkb/us*; do + [[ -f "$i" ]] || continue + inst "$i" + done done + else + # When it takes any argument, then install more keymaps. + for p in "${prefix[@]}"; do + for i in \ + "$p"/lib/kbd/keymaps/include/* \ + "$p"/lib/kbd/keymaps/i386/*/* \ + "$p"/lib/kbd/keymaps/legacy/i386/*/* \ + "$p"/lib/kbd/keymaps/xkb/*; do + [[ -f "$i" ]] || continue + inst "$i" + done + done + fi +} + +install_x11_keymaps() { + dinfo "Install x11 keymaps" + + if (( $# == 0 )); then + # Install only keymap list. + inst /usr/share/X11/xkb/rules/base.lst + else + # When it takes any argument, then install all keymaps. + inst_recursive /usr/share/X11/xkb fi } @@ -2259,8 +2321,7 @@ convert_abs_rel() { __abssize=${#__absolute[@]} __cursize=${#__current[@]} - while [[ "${__absolute[__level]}" == "${__current[__level]}" ]] - do + while [[ "${__absolute[__level]}" == "${__current[__level]}" ]]; do (( __level++ )) if (( __level > __abssize || __level > __cursize )) then @@ -2268,8 +2329,7 @@ convert_abs_rel() { fi done - for ((__i = __level; __i < __cursize-1; __i++)) - do + for ((__i = __level; __i < __cursize-1; __i++)); do if ((__i > __level)) then __newpath=$__newpath"/" @@ -2277,8 +2337,7 @@ convert_abs_rel() { __newpath=$__newpath".." done - for ((__i = __level; __i < __abssize; __i++)) - do + for ((__i = __level; __i < __abssize; __i++)); do if [[ -n $__newpath ]] then __newpath=$__newpath"/" @@ -2609,6 +2668,20 @@ inst_any() { return 1 } +inst_recursive() { + local p item + + for p in "$@"; do + while read -r item; do + if [[ -d "$item" ]]; then + inst_dir "$item" + elif [[ -f "$item" ]]; then + inst_simple "$item" + fi + done < <(find "$p" 2>/dev/null) + done +} + # image_install [-o ] [ ... ] # Install to the test image # -o optionally install the and don't fail, if it is not there diff --git a/test/units/assert.sh b/test/units/assert.sh index db67dad268..66357ab688 100644 --- a/test/units/assert.sh +++ b/test/units/assert.sh @@ -3,42 +3,56 @@ # utility functions for shell tests -assert_true() { +assert_true() {( local rc - set +e + set +ex + "$@" rc=$? - set -e - if [[ "$rc" != "0" ]]; then + if [[ $rc -ne 0 ]]; then echo "FAIL: command '$*' failed with exit code $rc" >&2 exit 1 fi -} +)} -assert_eq() { - if [[ "$1" != "$2" ]]; then +assert_eq() {( + set +ex + + if [[ "${1?}" != "${2?}" ]]; then echo "FAIL: expected: '$2' actual: '$1'" >&2 exit 1 fi -} +)} -assert_in() { - if ! echo "$2" | grep -q "$1"; then +assert_in() {( + set +ex + + if ! [[ "${2?}" =~ ${1?} ]]; then echo "FAIL: '$1' not found in:" >&2 echo "$2" >&2 exit 1 fi -} +)} + +assert_not_in() {( + set +ex + + if [[ "${2?}" =~ ${1?} ]]; then + echo "FAIL: '$1' found in:" >&2 + echo "$2" >&2 + exit 1 + fi +)} + +assert_rc() {( + local rc exp="${1?}" + + set +ex -assert_rc() { - local exp=$1 - local rc shift - set +e "$@" rc=$? - set -e assert_eq "$rc" "$exp" -} +)} diff --git a/test/units/testsuite-17.06.sh b/test/units/testsuite-17.06.sh index f2a0442dc3..224063f6a6 100755 --- a/test/units/testsuite-17.06.sh +++ b/test/units/testsuite-17.06.sh @@ -18,18 +18,18 @@ function check_validity() { function check() { local i j - for ((i=0;i<2;i++)); do + for ((i = 0; i < 2; i++)); do systemctl restart systemd-udevd.service udevadm control --ping udevadm settle check_validity - for ((j=0;j<2;j++)); do + for ((j = 0; j < 2; j++)); do udevadm trigger -w --action add --subsystem-match=block check_validity done - for ((j=0;j<2;j++)); do + for ((j = 0; j < 2; j++)); do udevadm trigger -w --action change --subsystem-match=block check_validity done diff --git a/test/units/testsuite-35.sh b/test/units/testsuite-35.sh index 0a7198c3fe..05d2a6c4c3 100755 --- a/test/units/testsuite-35.sh +++ b/test/units/testsuite-35.sh @@ -91,7 +91,7 @@ test_suspend_on_lid() { fi KILL_PID= - trap test_suspend_tear_down EXIT + trap test_suspend_tear_down RETURN # create fake suspend mkdir -p /run/systemd/system/systemd-suspend.service.d @@ -149,7 +149,7 @@ EOF evemu-device /run/lidswitch.evemu & KILL_PID="$!" - for ((i=0;i<20;i++)); do + for ((i = 0; i < 20; i++)); do if (( i != 0 )); then sleep .5; fi INPUT_NAME=$(grep -l '^Fake Lid Switch' /sys/class/input/*/device/name || :) @@ -184,9 +184,6 @@ EOF echo "logind crashed" >&2 exit 1 fi - - test_suspend_tear_down - trap - EXIT } test_shutdown() { @@ -269,7 +266,7 @@ test_session() { return fi - trap test_session_tear_down EXIT + trap test_session_tear_down RETURN # add user useradd -s /bin/bash logind-test-user @@ -287,7 +284,7 @@ EOF # check session ret=1 - for ((i=0;i<30;i++)); do + for ((i = 0; i < 30; i++)); do if (( i != 0)); then sleep 1; fi if check_session; then ret=0 @@ -315,7 +312,7 @@ EOF # coldplug: logind started with existing device systemctl stop systemd-logind.service modprobe scsi_debug - for ((i=0;i<30;i++)); do + for ((i = 0; i < 30; i++)); do if (( i != 0)); then sleep 1; fi if dev=/dev/$(ls /sys/bus/pseudo/drivers/scsi_debug/adapter*/host*/target*/*:*/block 2>/dev/null); then break @@ -342,7 +339,7 @@ EOF # hotplug: new device appears while logind is running rmmod scsi_debug modprobe scsi_debug - for ((i=0;i<30;i++)); do + for ((i = 0; i < 30; i++)); do if (( i != 0)); then sleep 1; fi if dev=/dev/$(ls /sys/bus/pseudo/drivers/scsi_debug/adapter*/host*/target*/*:*/block 2>/dev/null); then break @@ -361,9 +358,6 @@ EOF getfacl -p "$dev" >&2 exit 1 fi - - test_session_tear_down - trap - EXIT } : >/failed diff --git a/test/units/testsuite-45.sh b/test/units/testsuite-45.sh index ac7860dccd..322d8086e7 100755 --- a/test/units/testsuite-45.sh +++ b/test/units/testsuite-45.sh @@ -7,9 +7,24 @@ set -o pipefail # shellcheck source=test/units/assert.sh . "$(dirname "$0")"/assert.sh +restore_timezone() { + if [[ -f /tmp/timezone.bak ]]; then + mv /tmp/timezone.bak /etc/timezone + else + rm -f /etc/timezone + fi +} + test_timezone() { local ORIG_TZ= + # Debian/Ubuntu specific file + if [[ -f /etc/timezone ]]; then + mv /etc/timezone /tmp/timezone.bak + fi + + trap restore_timezone RETURN + if [[ -L /etc/localtime ]]; then ORIG_TZ=$(readlink /etc/localtime | sed 's#^.*zoneinfo/##') echo "original tz: $ORIG_TZ" @@ -21,12 +36,18 @@ test_timezone() { echo 'change timezone' assert_eq "$(timedatectl --no-pager set-timezone Europe/Kiev 2>&1)" "" assert_eq "$(readlink /etc/localtime | sed 's#^.*zoneinfo/##')" "Europe/Kiev" - assert_in "Time.*zone: Europe/Kiev (EEST, +" "$(timedatectl --no-pager)" + if [[ -f /etc/timezone ]]; then + assert_eq "$(cat /etc/timezone)" "Europe/Kiev" + fi + assert_in "Time zone: Europe/Kiev \(EEST, \+0[0-9]00\)" "$(timedatectl)" if [[ -n "$ORIG_TZ" ]]; then echo 'reset timezone to original' - assert_eq "$(timedatectl --no-pager set-timezone "$ORIG_TZ" 2>&1)" "" + assert_eq "$(timedatectl set-timezone "$ORIG_TZ" 2>&1)" "" assert_eq "$(readlink /etc/localtime | sed 's#^.*zoneinfo/##')" "$ORIG_TZ" + if [[ -f /etc/timezone ]]; then + assert_eq "$(cat /etc/timezone)" "$ORIG_TZ" + fi fi } @@ -51,7 +72,7 @@ test_adjtime() { mv /etc/adjtime /etc/adjtime.bak fi - trap restore_adjtime EXIT + trap restore_adjtime RETURN echo 'no adjtime file' rm -f /etc/adjtime @@ -158,9 +179,6 @@ LOCAL" assert_eq "$(cat /etc/adjtime)" "0.0 0 0 0 LOCAL" - - restore_adjtime - trap - EXIT } assert_ntp() { @@ -173,7 +191,7 @@ start_mon() { } wait_mon() { - for ((i=0;i<10;i++)); do + for ((i = 0; i < 10; i++)); do if (( i != 0 )); then sleep 1; fi if grep -q "$1" "$mon"; then break; fi done @@ -204,13 +222,13 @@ EOF echo 'disable NTP' timedatectl set-ntp false - for ((i=0;i<10;i++)); do + for ((i = 0; i < 10; i++)); do if (( i != 0 )); then sleep 1; fi - if [[ "$(systemctl --no-pager show systemd-timesyncd --property ActiveState)" == "ActiveState=inactive" ]]; then + if [[ "$(systemctl show systemd-timesyncd --property ActiveState)" == "ActiveState=inactive" ]]; then break; fi done - assert_eq "$(systemctl --no-pager show systemd-timesyncd --property ActiveState)" "ActiveState=inactive" + assert_eq "$(systemctl show systemd-timesyncd --property ActiveState)" "ActiveState=inactive" assert_ntp "false" assert_rc 3 systemctl is-active --quiet systemd-timesyncd @@ -219,13 +237,13 @@ EOF timedatectl set-ntp true wait_mon "NTP" "BOOLEAN true" assert_ntp "true" - for ((i=0;i<10;i++)); do + for ((i = 0; i < 10; i++)); do if (( i != 0 )); then sleep 1; fi - if [[ "$(systemctl --no-pager show systemd-timesyncd --property ActiveState)" == "ActiveState=active" ]]; then + if [[ "$(systemctl show systemd-timesyncd --property ActiveState)" == "ActiveState=active" ]]; then break; fi done - assert_eq "$(systemctl --no-pager show systemd-timesyncd --property ActiveState)" "ActiveState=active" + assert_eq "$(systemctl show systemd-timesyncd --property ActiveState)" "ActiveState=active" assert_rc 0 systemctl is-active --quiet systemd-timesyncd echo 're-disable NTP' diff --git a/test/units/testsuite-46.sh b/test/units/testsuite-46.sh index 6ce988c4a9..82da3b755c 100755 --- a/test/units/testsuite-46.sh +++ b/test/units/testsuite-46.sh @@ -27,7 +27,7 @@ inspect() { } wait_for_state() { - for ((i=0;i<10;i++)) ; do + for ((i = 0; i < 10; i++)) ; do homectl inspect "$1" | grep -qF "State: $2" && break sleep .5 done diff --git a/test/units/testsuite-60.sh b/test/units/testsuite-60.sh index 8cb3f466b6..3a097816ca 100755 --- a/test/units/testsuite-60.sh +++ b/test/units/testsuite-60.sh @@ -32,7 +32,7 @@ EOF # Trigger the mount ratelimiting cd "$(mktemp -d)" mkdir foo - for ((i=0;i<50;++i)); do + for ((i = 0; i < 50; i++)); do mount --bind foo foo umount foo done diff --git a/test/units/testsuite-71.service b/test/units/testsuite-71.service new file mode 100644 index 0000000000..019e8bff24 --- /dev/null +++ b/test/units/testsuite-71.service @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=TEST-71-HOSTNAME + +[Service] +ExecStartPre=rm -f /failed /testok +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh +StandardOutput=journal+console +StandardError=journal+console +Type=oneshot diff --git a/test/units/testsuite-71.sh b/test/units/testsuite-71.sh new file mode 100755 index 0000000000..34fcaad961 --- /dev/null +++ b/test/units/testsuite-71.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later + +set -eux +set -o pipefail + +# shellcheck source=test/units/assert.sh +. "$(dirname "$0")"/assert.sh + +restore_hostname() { + if [[ -e /tmp/hostname.bak ]]; then + mv /tmp/hostname.bak /etc/hostname + else + rm -f /etc/hostname + fi +} + +test_hostname() { + local orig= + + if [[ -f /etc/hostname ]]; then + cp /etc/hostname /tmp/hostname.bak + orig=$(cat /etc/hostname) + fi + + trap restore_hostname RETURN + + # should activate daemon and work + if [[ -n "$orig" ]]; then + assert_in "Static hostname: $orig" "$(hostnamectl)" + fi + assert_in "Kernel: $(uname -s) $(uname -r)" "$(hostnamectl)" + + # change hostname + assert_rc 0 hostnamectl set-hostname testhost + assert_eq "$(cat /etc/hostname)" "testhost" + assert_in "Static hostname: testhost" "$(hostnamectl)" + + if [[ -n "$orig" ]]; then + # reset to original + assert_rc 0 hostnamectl set-hostname "$orig" + assert_eq "$(cat /etc/hostname)" "$orig" + assert_in "Static hostname: $orig" "$(hostnamectl)" + fi +} + +: >/failed + +test_hostname + +touch /testok +rm /failed diff --git a/test/units/testsuite-73.service b/test/units/testsuite-73.service new file mode 100644 index 0000000000..bee8c4215d --- /dev/null +++ b/test/units/testsuite-73.service @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=TEST-73-LOCALE + +[Service] +ExecStartPre=rm -f /failed /testok +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh +StandardOutput=journal+console +StandardError=journal+console +Type=oneshot diff --git a/test/units/testsuite-73.sh b/test/units/testsuite-73.sh new file mode 100755 index 0000000000..d96e9f4151 --- /dev/null +++ b/test/units/testsuite-73.sh @@ -0,0 +1,391 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later + +set -eux +set -o pipefail + +# shellcheck source=test/units/assert.sh +. "$(dirname "$0")"/assert.sh + +enable_debug() { + mkdir -p /run/systemd/system/systemd-localed.service.d + cat >>/run/systemd/system/systemd-localed.service.d/override.conf <>/run/systemd/system/systemd-vconsole-setup.service.d/override.conf </dev/null 2>&1 && + ! localectl list-locales | grep -F "en_US.UTF-8"; then + # ensure at least one utf8 locale exist + echo "en_US.UTF-8 UTF-8" > /etc/locale.gen + locale-gen en_US.UTF-8 + fi + + # create invalid locale + mkdir -p /usr/lib/locale/xx_XX.UTF-8 + assert_not_in "xx_XX.UTF-8" "$(localectl list-locales)" + + if [[ -z "$(localectl list-locales)" ]]; then + echo "No locale installed, skipping test." + return + fi + + # warn when kernel command line has locale settings + output=$(SYSTEMD_PROC_CMDLINE="locale.LANG=C.UTF-8 locale.LC_CTYPE=ja_JP.UTF-8" localectl 2>&1) + assert_in "Warning:" "$output" + assert_in "Command Line: LANG=C.UTF-8" "$output" + assert_in "LC_CTYPE=ja_JP.UTF-8" "$output" + assert_in "System Locale:" "$output" + + # change locale + for i in $(localectl list-locales); do + assert_rc 0 localectl set-locale "LANG=C" "LC_CTYPE=$i" + if [[ -f /etc/default/locale ]]; then + assert_eq "$(cat /etc/default/locale)" "LANG=C +LC_CTYPE=$i" + else + assert_eq "$(cat /etc/locale.conf)" "LANG=C +LC_CTYPE=$i" + fi + output=$(localectl) + assert_in "System Locale: LANG=C" "$output" + assert_in "LC_CTYPE=$i" "$output" + output=$(systemctl show-environment) + assert_in "LANG=C" "$output" + assert_in "LC_CTYPE=$i" "$output" + + assert_rc 0 localectl set-locale "$i" + if [[ -f /etc/default/locale ]]; then + # Debian/Ubuntu patch is buggy, and LC_CTYPE= still exists. + assert_eq "$(cat /etc/default/locale)" "LANG=$i +LC_CTYPE=$i" + else + assert_eq "$(cat /etc/locale.conf)" "LANG=$i" + fi + output=$(localectl) + assert_in "System Locale: LANG=$i" "$output" + assert_not_in "LC_CTYPE=" "$output" + output=$(systemctl show-environment) + assert_in "LANG=$i" "$output" + assert_not_in "LC_CTYPE=" "$output" + done + + # test if localed auto-runs locale-gen + if command -v locale-gen >/dev/null 2>&1 && + ! localectl list-locales | grep -F "de_DE.UTF-8"; then + + # clear previous locale + systemctl stop systemd-localed.service + rm -f /etc/locale.conf /etc/default/locale + + # change locale + assert_rc 0 localectl set-locale de_DE.UTF-8 + if [[ -f /etc/default/locale ]]; then + assert_eq "$(cat /etc/default/locale)" "LANG=de_DE.UTF-8" + else + assert_eq "$(cat /etc/locale.conf)" "LANG=de_DE.UTF-8" + fi + assert_in "System Locale: LANG=de_DE.UTF-8" "$(localectl)" + assert_in "LANG=de_DE.UTF-8" "$(systemctl show-environment)" + + # ensure tested locale exists and works now + assert_in "de_DE.UTF-8" "$(localectl list-locales)" + fi +} + +backup_keymap() { + if [[ -f /etc/vconsole.conf ]]; then + cp /etc/vconsole.conf /tmp/vconsole.conf.bak + fi + + if [[ -f /etc/X11/xorg.conf.d/00-keyboard.conf ]]; then + cp /etc/X11/xorg.conf.d/00-keyboard.conf /tmp/00-keyboard.conf.bak + fi + + # Debian/Ubuntu specific file + if [[ -f /etc/default/keyboard ]]; then + cp /etc/default/keyboard /tmp/default-keyboard.bak + fi + + mkdir -p /etc/default +} + +restore_keymap() { + if [[ -f /tmp/vconsole.conf.bak ]]; then + mv /tmp/vconsole.conf.bak /etc/vconsole.conf + else + rm -f /etc/vconsole.conf + fi + + if [[ -f /tmp/00-keyboard.conf.bak ]]; then + mv /tmp/00-keyboard.conf.bak /etc/X11/xorg.conf.d/00-keyboard.conf + else + rm -f /etc/X11/xorg.conf.d/00-keyboard.conf + fi + + if [[ -f /tmp/default-keyboard.bak ]]; then + mv /tmp/default-keyboard.bak /etc/default/keyboard + else + rm -f /etc/default/keyboard + rmdir --ignore-fail-on-non-empty /etc/default + fi +} + +wait_vconsole_setup() { + local i ss + for ((i = 0; i < 20; i++)); do + if (( i != 0 )); then sleep .5; fi + ss="$(systemctl --property SubState --value show systemd-vconsole-setup.service)" + if [[ "$ss" == "exited" || "$ss" == "dead" || "$ss" == "condition" ]]; then + return 0 + elif [[ "$ss" == "failed" ]]; then + echo "WARNING: systemd-vconsole-setup.service failed, ignoring." >&2 + systemctl reset-failed systemd-vconsole-setup.service + return 0 + fi + done + + systemctl status systemd-vconsole-setup.service + return 1 +} + +test_vc_keymap() { + local i output + + if [[ -z "$(localectl list-keymaps)" ]]; then + echo "No vconsole keymap installed, skipping test." + return + fi + + backup_keymap + trap restore_keymap RETURN + + # should activate daemon and work + assert_in "VC Keymap:" "$(localectl)" + + for i in $(localectl list-keymaps); do + # clear previous conversion from VC -> X11 keymap + systemctl stop systemd-localed.service + wait_vconsole_setup + rm -f /etc/X11/xorg.conf.d/00-keyboard.conf /etc/default/keyboard + + # set VC keymap + assert_rc 0 localectl set-keymap "$i" + output=$(localectl) + + # check VC keymap + assert_in "KEYMAP=$i" "$(cat /etc/vconsole.conf)" + assert_in "VC Keymap: $i" "$output" + + # check VC -> X11 keymap conversion + if [[ "$i" == "us" ]]; then + assert_in "X11 Layout: us" "$output" + assert_in "X11 Model: pc105\+inet" "$output" + assert_not_in "X11 Variant:" "$output" + assert_in "X11 Options: terminate:ctrl_alt_bksp" "$output" + elif [[ "$i" == "us-acentos" ]]; then + assert_in "X11 Layout: us" "$output" + assert_in 'X11 Model: pc105$' "$output" + assert_in "X11 Variant: intl" "$output" + assert_in "X11 Options: terminate:ctrl_alt_bksp" "$output" + elif [[ "$i" =~ ^us-.* ]]; then + assert_in "X11 Layout: n/a" "$output" + assert_not_in "X11 Model:" "$output" + assert_not_in "X11 Variant:" "$output" + assert_not_in "X11 Options:" "$output" + fi + done + + # gets along without config file + systemctl stop systemd-localed.service + wait_vconsole_setup + rm -f /etc/vconsole.conf + assert_in "VC Keymap: n/a" "$(localectl)" +} + +test_x11_keymap() { + local output + + if [[ -z "$(localectl list-x11-keymap-layouts)" ]]; then + echo "No x11 keymap installed, skipping test." + return + fi + + backup_keymap + trap restore_keymap RETURN + + # should activate daemon and work + assert_in "X11 Layout:" "$(localectl)" + + # set x11 keymap (layout, model, variant, options) + assert_rc 0 localectl set-x11-keymap us pc105+inet intl terminate:ctrl_alt_bksp + + if [[ -f /etc/default/keyboard ]]; then + assert_eq "$(cat /etc/default/keyboard)" "XKBLAYOUT=us +XKBMODEL=pc105+inet +XKBVARIANT=intl +XKBOPTIONS=terminate:ctrl_alt_bksp" + else + output=$(cat /etc/X11/xorg.conf.d/00-keyboard.conf) + assert_in 'Option "XkbLayout" "us"' "$output" + assert_in 'Option "XkbModel" "pc105\+inet"' "$output" + assert_in 'Option "XkbVariant" "intl"' "$output" + assert_in 'Option "XkbOptions" "terminate:ctrl_alt_bksp"' "$output" + fi + + output=$(localectl) + assert_in "X11 Layout: us" "$output" + assert_in "X11 Model: pc105\+inet" "$output" + assert_in "X11 Variant: intl" "$output" + assert_in "X11 Options: terminate:ctrl_alt_bksp" "$output" + + # Debian/Ubuntu patch is buggy, unspecified settings are not cleared + rm -f /etc/default/keyboard + + # set x11 keymap (layout, model, variant) + assert_rc 0 localectl set-x11-keymap us pc105+inet intl + + if [[ -f /etc/default/keyboard ]]; then + assert_eq "$(cat /etc/default/keyboard)" "XKBLAYOUT=us +XKBMODEL=pc105+inet +XKBVARIANT=intl" + else + output=$(cat /etc/X11/xorg.conf.d/00-keyboard.conf) + assert_in 'Option "XkbLayout" "us"' "$output" + assert_in 'Option "XkbModel" "pc105\+inet"' "$output" + assert_in 'Option "XkbVariant" "intl"' "$output" + assert_not_in 'Option "XkbOptions"' "$output" + fi + + output=$(localectl) + assert_in "X11 Layout: us" "$output" + assert_in "X11 Model: pc105\+inet" "$output" + assert_in "X11 Variant: intl" "$output" + assert_not_in "X11 Options:" "$output" + + # Debian/Ubuntu patch is buggy, unspecified settings are not cleared + rm -f /etc/default/keyboard + + # set x11 keymap (layout, model) + assert_rc 0 localectl set-x11-keymap us pc105+inet + + if [[ -f /etc/default/keyboard ]]; then + assert_eq "$(cat /etc/default/keyboard)" "XKBLAYOUT=us +XKBMODEL=pc105+inet" + else + output=$(cat /etc/X11/xorg.conf.d/00-keyboard.conf) + assert_in 'Option "XkbLayout" "us"' "$output" + assert_in 'Option "XkbModel" "pc105\+inet"' "$output" + assert_not_in 'Option "XkbVariant"' "$output" + assert_not_in 'Option "XkbOptions"' "$output" + fi + + output=$(localectl) + assert_in "X11 Layout: us" "$output" + assert_in "X11 Model: pc105\+inet" "$output" + assert_not_in "X11 Variant:" "$output" + assert_not_in "X11 Options:" "$output" + + # Debian/Ubuntu patch is buggy, unspecified settings are not cleared + rm -f /etc/default/keyboard + + # set x11 keymap (layout) + assert_rc 0 localectl set-x11-keymap us + + if [[ -f /etc/default/keyboard ]]; then + assert_eq "$(cat /etc/default/keyboard)" "XKBLAYOUT=us" + else + output=$(cat /etc/X11/xorg.conf.d/00-keyboard.conf) + assert_in 'Option "XkbLayout" "us"' "$output" + assert_not_in 'Option "XkbModel"' "$output" + assert_not_in 'Option "XkbVariant"' "$output" + assert_not_in 'Option "XkbOptions"' "$output" + fi + + output=$(localectl) + assert_in "X11 Layout: us" "$output" + assert_not_in "X11 Model:" "$output" + assert_not_in "X11 Variant:" "$output" + assert_not_in "X11 Options:" "$output" + + # gets along without config file + systemctl stop systemd-localed.service + rm -f /etc/X11/xorg.conf.d/00-keyboard.conf /etc/default/keyboard + output=$(localectl) + assert_in "X11 Layout: n/a" "$output" + assert_not_in "X11 Model:" "$output" + assert_not_in "X11 Variant:" "$output" + assert_not_in "X11 Options:" "$output" +} + +: >/failed + +enable_debug +test_locale +test_vc_keymap +test_x11_keymap + +touch /testok +rm /failed