diff --git a/TODO.md b/TODO.md index 7672fdb9f7..5c8f92ac42 100644 --- a/TODO.md +++ b/TODO.md @@ -136,24 +136,12 @@ SPDX-License-Identifier: LGPL-2.1-or-later the Varlinkifcation has progressed various non-desktop usescase might not need D-Bus running at all anymore. -- Reuse the LoaderKeyboardLayout EFI variable/find_vconsole_keymap_for_bcp47() - to pre-select kbd layout in systemd-firstboot. (Right now it's only used in - vconsole-setup as fallback). - -- Start using the PlatformLang EFI variable to pre-select language in - systemd-firstboot. (At least HP laptops seem to make good use of/configure - the value) - - format-table: introduce the concept of a "title" for a table, which remains closely associated with the table. in most cases where want to output multiple tables from the same tool we want to separate things with a title, hence we might as well associate the title with the table itself, and streamline a few things. -- report: add filtering by metric prefix, so that we reports don't have to - include everything, and we have a way to have "small" and "big" reports, that - have different number of fields. - - allow metrics to indicate which values mean "nothing"/"invalid"/"zero"/"please-suppress". Then use that to reduce noise in systemd-report output. @@ -912,7 +900,6 @@ SPDX-License-Identifier: LGPL-2.1-or-later possibly up to 100ms supposedly) - **EFI:** - - honor language efi variables for default language selection (if there are any?) - honor timezone efi variables for default timezone selection (if there are any?) - enable LockMLOCK to take a percentage value relative to physical memory @@ -3038,8 +3025,6 @@ SPDX-License-Identifier: LGPL-2.1-or-later data in the image, make sure the image filename actually matches this, so that images cannot be misused. -- when no locale is configured, default to UEFI's PlatformLang variable - - when switching root from initrd to host, set the machine_id env var so that if the host has no machine ID set yet we continue to use the random one the initrd had set. diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index 5b3fc8990d..0d5b57138b 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -283,6 +283,7 @@ static void clear_by_backspace(size_t n) { int ask_string_full( char **ret, + const char *prefill, GetCompletionsCallback get_completions, void *userdata, const char *text, ...) { @@ -296,8 +297,17 @@ int ask_string_full( _cleanup_free_ char *string = NULL; size_t n = 0; - if (get_completions) { - /* Figure out what string to preselect the query with */ + if (prefill) { + /* Prefill query with explicit data if specified */ + + string = strdup(prefill); + if (!string) + return -ENOMEM; + + n = strlen(string); + + } else if (get_completions) { + /* Otherwise, figure out what string to preselect the query with */ _cleanup_strv_free_ char **completions = NULL; r = get_completions("", GET_COMPLETIONS_PRESELECT, &completions, userdata); if (r < 0) diff --git a/src/basic/terminal-util.h b/src/basic/terminal-util.h index abf999e6d5..f8cd74f410 100644 --- a/src/basic/terminal-util.h +++ b/src/basic/terminal-util.h @@ -95,8 +95,8 @@ typedef enum GetCompletionsFlags { } GetCompletionsFlags; typedef int (*GetCompletionsCallback)(const char *key, GetCompletionsFlags flags, char ***ret_list, void *userdata); -int ask_string_full(char **ret, GetCompletionsCallback get_completions, void *userdata, const char *text, ...) _printf_(4, 5); -#define ask_string(ret, text, ...) ask_string_full(ret, NULL, NULL, text, ##__VA_ARGS__) +int ask_string_full(char **ret, const char *prefill, GetCompletionsCallback get_completions, void *userdata, const char *text, ...) _printf_(5, 6); +#define ask_string(ret, text, ...) ask_string_full(ret, NULL, NULL, NULL, text, ##__VA_ARGS__) bool any_key_to_proceed(void); int show_menu(char **x, size_t n_columns, size_t column_width, unsigned ellipsize_percentage, const char *grey_prefix, bool with_numbers); diff --git a/src/bootctl/bootctl-status.c b/src/bootctl/bootctl-status.c index 4cd0e3aca0..4aac25a74d 100644 --- a/src/bootctl/bootctl-status.c +++ b/src/bootctl/bootctl-status.c @@ -17,6 +17,7 @@ #include "efivars.h" #include "errno-util.h" #include "fd-util.h" +#include "locale-setup.h" #include "log.h" #include "pager.h" #include "pretty-print.h" @@ -501,6 +502,18 @@ int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { errno = -k; printf(" Boot into FW: %sfailed%s (%m)\n", ansi_highlight_red(), ansi_normal()); } + + _cleanup_free_ char *lang = NULL; + k = locale_lang_from_efi(&lang, /* flags= */ 0); + if (k > 0) + printf(" Platform Lang: %s\n", lang); + else if (k == 0) + printf(" Platform Lang: n/a\n"); + else { + errno = -k; + printf(" Platform Lang: %sfailed%s (%m)\n", ansi_highlight_red(), ansi_normal()); + } + printf("\n"); if (loader) { diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index 8048ec0e81..60ec63e2da 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -8,6 +8,7 @@ #include "sd-varlink.h" #include "alloc-util.h" +#include "ansi-color.h" #include "ask-password-api.h" #include "build.h" #include "bus-error.h" @@ -26,6 +27,7 @@ #include "format-table.h" #include "fs-util.h" #include "glyph-util.h" +#include "help-util.h" #include "hostname-util.h" #include "image-policy.h" #include "kbd-util.h" @@ -44,7 +46,6 @@ #include "password-quality-util.h" #include "path-util.h" #include "plymouth-util.h" -#include "pretty-print.h" #include "proc-cmdline.h" #include "prompt-util.h" #include "runtime-scope.h" @@ -293,8 +294,12 @@ static int prompt_locale(int rfd, sd_varlink **mute_console_link) { } else { print_welcome(rfd, mute_console_link); + _cleanup_free_ char *prefill = NULL; + (void) locale_lang_from_efi(&prefill, LOCALE_REQUIRE_INSTALLED|LOCALE_SUPPRESS_EN_US); + r = prompt_loop("Please enter the new system locale name or number", GLYPH_WORLD, + prefill, locales, /* accepted= */ NULL, /* ellipsize_percentage= */ 60, @@ -312,6 +317,7 @@ static int prompt_locale(int rfd, sd_varlink **mute_console_link) { r = prompt_loop("Please enter the new system message locale name or number", GLYPH_WORLD, + /* prefill= */ NULL, locales, /* accepted= */ NULL, /* ellipsize_percentage= */ 60, @@ -454,9 +460,13 @@ static int prompt_keymap(int rfd, sd_varlink **mute_console_link) { print_welcome(rfd, mute_console_link); + _cleanup_free_ char *prefill = NULL; + (void) vconsole_keymap_from_efi(&prefill); + return prompt_loop( "Please enter the new keymap name or number", GLYPH_KEYBOARD, + prefill, kmaps, /* accepted= */ NULL, /* ellipsize_percentage= */ 60, @@ -573,6 +583,7 @@ static int prompt_timezone(int rfd, sd_varlink **mute_console_link) { return prompt_loop( "Please enter the new timezone name or number", GLYPH_CLOCK, + /* prefill= */ NULL, zones, /* accepted= */ NULL, /* ellipsize_percentage= */ 30, @@ -680,6 +691,7 @@ static int prompt_hostname(int rfd, sd_varlink **mute_console_link) { r = prompt_loop("Please enter the new hostname", GLYPH_LABEL, + /* prefill= */ NULL, /* menu= */ NULL, /* accepted= */ NULL, /* ellipsize_percentage= */ 100, @@ -888,6 +900,7 @@ static int prompt_root_shell(int rfd, sd_varlink **mute_console_link) { return prompt_loop( "Please enter the new root shell", GLYPH_SHELL, + /* prefill= */ NULL, /* menu= */ NULL, /* accepted= */ NULL, /* ellipsize_percentage= */ 0, @@ -1240,29 +1253,22 @@ static int process_reset(int rfd) { } static int help(void) { - _cleanup_free_ char *link = NULL; _cleanup_(table_unrefp) Table *options = NULL; int r; - r = terminal_urlify_man("systemd-firstboot", "1", &link); - if (r < 0) - return log_oom(); - r = option_parser_get_help_table(&options); if (r < 0) return r; - printf("%s [OPTIONS...]\n\n" - "%sConfigures basic settings of the system.%s\n\n", - program_invocation_short_name, - ansi_highlight(), - ansi_normal()); + help_cmdline("[OPTIONS...]"); + help_abstract("Configures basic settings of the system."); + help_section("Options"); r = table_print_or_warn(options); if (r < 0) return r; - printf("\nSee the %s for details.\n", link); + help_man_page_reference("systemd-firstboot", "1"); return 0; } diff --git a/src/home/homectl-prompts.c b/src/home/homectl-prompts.c index 71640377e3..95a6ec3d04 100644 --- a/src/home/homectl-prompts.c +++ b/src/home/homectl-prompts.c @@ -117,6 +117,7 @@ int prompt_groups(const char *username, char ***ret_groups) { _cleanup_free_ char *s = NULL; r = ask_string_full( &s, + /* prefill= */ NULL, group_completion_callback, &available, "%s Please enter an auxiliary group for user %s (empty to continue, \"list\" to list available groups): ", @@ -231,6 +232,7 @@ int prompt_shell(const char *username, char **ret_shell) { return prompt_loop( q, GLYPH_SHELL, + /* prefill= */ NULL, /* menu= */ NULL, /* accepted= */ NULL, /* ellipsize_percentage= */ 0, diff --git a/src/home/homectl.c b/src/home/homectl.c index ab81efbed6..6891475f37 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -3009,6 +3009,7 @@ static int create_interactively(void) { r = prompt_loop("Please enter user name to create", GLYPH_IDCARD, + /* prefill= */ NULL, /* menu= */ NULL, /* accepted= */ NULL, /* ellipsize_percentage= */ 60, diff --git a/src/shared/locale-setup.c b/src/shared/locale-setup.c index 6b66c15443..294963640e 100644 --- a/src/shared/locale-setup.c +++ b/src/shared/locale-setup.c @@ -5,10 +5,12 @@ #include #include "alloc-util.h" +#include "efivars.h" #include "env-file.h" #include "env-util.h" #include "errno-util.h" #include "fd-util.h" +#include "iovec-util.h" #include "locale-setup.h" #include "log.h" #include "proc-cmdline.h" @@ -318,3 +320,77 @@ const char* etc_vconsole_conf(void) { return cached; } + +int locale_lang_from_efi(char **ret, LocaleLangFromEfiFlags flags) { + int r; + + assert(ret); + + if (!is_efi_boot()) { + *ret = NULL; + return 0; + } + + /* NB: unlike most other UEFI variables, PlatformLang is actually in 7bit ASCII! Hence we are not + * using efi_get_variable_string() here */ + _cleanup_(iovec_done) struct iovec iov = {}; + r = efi_get_variable(EFI_GLOBAL_VARIABLE_STR("PlatformLang"), /* ret_attribute= */ NULL, &iov.iov_base, &iov.iov_len); + if (r == -ENOENT) { + *ret = NULL; + return 0; + } + if (r < 0) + return log_debug_errno(r, "Failed to read PlatformLang EFI variable: %m"); + + _cleanup_free_ char *tag = NULL; + r = make_cstring(iov.iov_base, iov.iov_len, MAKE_CSTRING_ALLOW_TRAILING_NUL, &tag); + if (r < 0) + return log_debug_errno(r, "Failed to convert PlatformLang EFI variable to C string: %m"); + + /* Convert the UEFI BCP47 language tag into a glibc tag. We'll not bother with the complexity of the + * whole spec, but just convert "xx-XX" into "xx_XX", with some flexibility on the case + * sensitivity. This is a best-effort thing anyway. */ + + if (strlen(tag) != 5 || + !strchr(LETTERS, tag[0]) || + !strchr(LETTERS, tag[1]) || + tag[2] != '-' || + !strchr(LETTERS, tag[3]) || + !strchr(LETTERS, tag[4])) { + log_debug("PlatformLang variable does not have the form 'xx-XX', ignoring: %s", tag); + *ret = NULL; + return 0; + } + + tag[0] = ascii_tolower(tag[0]); + tag[1] = ascii_tolower(tag[1]); + tag[2] = '_'; + tag[3] = ascii_toupper(tag[3]); + tag[4] = ascii_toupper(tag[4]); + + /* Let's optionally suppress en_US locale, since that's almost certainly just the built-in default + * locale of the firmware. Since we typically prefer C.UTF-8 over en_US.UTF-8 as default, let's hence + * suppress it. */ + if (FLAGS_SET(flags, LOCALE_SUPPRESS_EN_US) && streq(tag, "en_US")) { + log_debug("Firmware language is en_US, suppressing because likely just the firmware default."); + *ret = NULL; + return 0; + } + + if (!strextend(&tag, ".UTF-8")) + return -ENOMEM; + + if (FLAGS_SET(flags, LOCALE_REQUIRE_INSTALLED)) { + r = locale_is_installed(tag); + if (r < 0) + return r; + if (r == 0) { + log_debug("Determined locale '%s' from PlatformLang, but it isn't installed, ignoring.", tag); + *ret = NULL; + return 0; + } + } + + *ret = TAKE_PTR(tag); + return 1; +} diff --git a/src/shared/locale-setup.h b/src/shared/locale-setup.h index 8b71064cad..d720a53adb 100644 --- a/src/shared/locale-setup.h +++ b/src/shared/locale-setup.h @@ -31,3 +31,10 @@ int locale_setup(char ***environment); const char* etc_locale_conf(void); const char* etc_vconsole_conf(void); + +typedef enum LocaleLangFromEfiFlags { + LOCALE_REQUIRE_INSTALLED = 1 << 0, + LOCALE_SUPPRESS_EN_US = 1 << 1, +} LocaleLangFromEfiFlags; + +int locale_lang_from_efi(char **ret, LocaleLangFromEfiFlags flags); diff --git a/src/shared/prompt-util.c b/src/shared/prompt-util.c index 2f334f9b52..69728e0cb4 100644 --- a/src/shared/prompt-util.c +++ b/src/shared/prompt-util.c @@ -60,6 +60,7 @@ static int get_completions( int prompt_loop( const char *text, Glyph emoji, + const char *prefill, /* if non-NULL: prefill prompt with this string */ char **menu, /* if non-NULL: choices to suggest */ char **accepted, /* if non-NULL: choices to accept (should be a superset of 'menu') */ unsigned ellipsize_percentage, @@ -116,6 +117,7 @@ int prompt_loop( _cleanup_free_ char *p = NULL; r = ask_string_full( &p, + prefill, get_completions, &(CompletionData) { menu, accepted }, "%s%s%s%s: ", diff --git a/src/shared/prompt-util.h b/src/shared/prompt-util.h index 436127b32c..7afccd1b3d 100644 --- a/src/shared/prompt-util.h +++ b/src/shared/prompt-util.h @@ -14,6 +14,7 @@ typedef enum PromptFlags { int prompt_loop(const char *text, Glyph emoji, + const char *prefill, char **menu, char **accepted, unsigned ellipsize_percentage, diff --git a/src/shared/vconsole-util.c b/src/shared/vconsole-util.c index aa156f4736..9e56ce823d 100644 --- a/src/shared/vconsole-util.c +++ b/src/shared/vconsole-util.c @@ -4,6 +4,7 @@ #include #include "alloc-util.h" +#include "efivars.h" #include "env-util.h" #include "extract-word.h" #include "fd-util.h" @@ -655,6 +656,32 @@ int find_vconsole_keymap_for_bcp47(const char *tag, char **ret) { return 1; } +int vconsole_keymap_from_efi(char **ret) { + int r; + + assert(ret); + + if (!is_efi_boot()) { + *ret = NULL; + return 0; + } + + _cleanup_free_ char *tag = NULL; + r = efi_get_variable_string(EFI_LOADER_VARIABLE_STR("LoaderKeyboardLayout"), &tag); + if (r == -ENOENT) { + *ret = NULL; + return 0; + } + if (r < 0) + return log_debug_errno(r, "Failed to read LoaderKeyboardLayout EFI variable: %m"); + + r = find_vconsole_keymap_for_bcp47(tag, ret); + if (r < 0) + return log_debug_errno(r, "Failed to look up vconsole keymap for firmware tag '%s': %m", tag); + + return r; +} + int vconsole_serialize(const VCContext *vc, const X11Context *xc, char ***env) { int r; diff --git a/src/shared/vconsole-util.h b/src/shared/vconsole-util.h index 3d52ec9866..416eb86eb5 100644 --- a/src/shared/vconsole-util.h +++ b/src/shared/vconsole-util.h @@ -40,5 +40,6 @@ int vconsole_convert_to_x11(const VCContext *vc, X11VerifyCallback verify, X11Co int x11_convert_to_vconsole(const X11Context *xc, VCContext *ret); int find_vconsole_keymap_for_bcp47(const char *tag, char **ret); +int vconsole_keymap_from_efi(char **ret); int vconsole_serialize(const VCContext *vc, const X11Context *xc, char ***env); diff --git a/src/sysinstall/sysinstall.c b/src/sysinstall/sysinstall.c index 7d08b4866b..0092f3f9c8 100644 --- a/src/sysinstall/sysinstall.c +++ b/src/sysinstall/sysinstall.c @@ -384,6 +384,7 @@ static int prompt_block_device(sd_varlink **repart_link, char **ret_node) { r = prompt_loop("Please enter target disk device", GLYPH_COMPUTER_DISK, + /* prefill= */ NULL, menu, accepted, /* ellipsize_percentage= */ 20, @@ -540,6 +541,7 @@ static int prompt_erase( "Please type 'keep' to install the OS in addition to what the disk already contains, or 'erase' to erase all data on the disk" : "Please type 'erase' to confirm that all data on the disk shall be erased", GLYPH_BROOM, + /* prefill= */ NULL, /* menu= */ l, /* accepted= */ l, /* ellipsize_percentage= */ 20, @@ -578,6 +580,7 @@ static int prompt_touch_variables(void) { _cleanup_free_ char *reply = NULL; r = prompt_loop("Type 'yes' to register OS installation in firmware variables of the local system, 'no' otherwise", GLYPH_ROCKET, + /* prefill= */ "yes", /* menu= */ l, /* accepted= */ l, /* ellipsize_percentage= */ 20, @@ -616,6 +619,7 @@ static int prompt_confirm(void) { r = prompt_loop(arg_summary ? "Please type 'yes' to confirm the choices above and begin the installation" : "Please type 'yes' to begin the installation", GLYPH_WARNING_SIGN, + /* prefill= */ NULL, /* menu= */ l, /* accepted= */ l, /* ellipsize_percentage= */ 20, diff --git a/src/vconsole/vconsole-setup.c b/src/vconsole/vconsole-setup.c index c1bf2b0f4f..f8c5dffd1e 100644 --- a/src/vconsole/vconsole-setup.c +++ b/src/vconsole/vconsole-setup.c @@ -15,7 +15,6 @@ #include "alloc-util.h" #include "creds-util.h" -#include "efivars.h" #include "env-file.h" #include "errno-util.h" #include "fd-util.h" @@ -76,25 +75,15 @@ static void context_merge_config( static int context_read_efi(Context *c) { _cleanup_(context_done) Context v = {}; - _cleanup_free_ char *tag = NULL; int r; assert(c); - if (!is_efi_boot()) - return 0; - - r = efi_get_variable_string(EFI_LOADER_VARIABLE_STR("LoaderKeyboardLayout"), &tag); - if (r == -ENOENT) - return 0; + r = vconsole_keymap_from_efi(&v.keymap); if (r < 0) - return log_debug_errno(r, "Failed to read LoaderKeyboardLayout EFI variable, ignoring: %m"); - - r = find_vconsole_keymap_for_bcp47(tag, &v.keymap); - if (r < 0) - return log_debug_errno(r, "Failed to look up vconsole keymap for firmware tag '%s', ignoring: %m", tag); + return r; if (r == 0) { - log_debug("No vconsole keymap matches firmware-provided keyboard layout '%s', ignoring.", tag); + log_debug("No vconsole keymap matches firmware-provided keyboard layout, ignoring."); return 0; }