firstboot: auto-fill keymap/locale questions with data from firmware, if available (#42177)
Let's make the installer experience a tiny bit nicer, and prefill everything with the data the firmware gives us.
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+18
-12
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -5,10 +5,12 @@
|
||||
#include <unistd.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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: ",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <unistd.h>
|
||||
|
||||
#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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user