From ed457f1380d690e26163fd3ad6aff946048e1064 Mon Sep 17 00:00:00 2001 From: tblume Date: Fri, 10 Nov 2017 10:31:44 +0100 Subject: [PATCH] systemd-firstboot: add vconsole keymap support (#7035) Enable systemd-firstboot to set the keymap. RFE: https://github.com/systemd/systemd/issues/6346 --- man/systemd-firstboot.xml | 20 +++++- src/basic/locale-util.c | 95 ++++++++++++++++++++++++++++ src/basic/locale-util.h | 3 + src/firstboot/firstboot.c | 121 +++++++++++++++++++++++++++++++++++- src/locale/localectl.c | 65 ++----------------- src/test/test-locale-util.c | 29 +++++++++ 6 files changed, 269 insertions(+), 64 deletions(-) diff --git a/man/systemd-firstboot.xml b/man/systemd-firstboot.xml index 539422ab98..fee37ad19c 100644 --- a/man/systemd-firstboot.xml +++ b/man/systemd-firstboot.xml @@ -77,6 +77,8 @@ locale variables LANG= and LC_MESSAGES + The system keyboard map + The system time zone The system host name @@ -135,6 +137,15 @@ configuration file. + + + + Sets the system keyboard layout. The argument should be a valid keyboard map, + such as de-latin1. This controls the KEYMAP entry in the + vconsole.conf5 + configuration file. + + @@ -182,6 +193,7 @@ + @@ -195,9 +207,10 @@ - Query the user for locale, timezone, hostname + Query the user for locale, keymap, timezone, hostname and root password. This is equivalent to specifying , + , , , in combination. @@ -206,6 +219,7 @@ + @@ -217,9 +231,10 @@ - Copy locale, time zone and root password from + Copy locale, keymap, time zone and root password from the host. This is equivalent to specifying , + , , in combination. @@ -265,6 +280,7 @@ systemd1, locale.conf5, + vconsole.conf5, localtime5, hostname5, machine-id5, diff --git a/src/basic/locale-util.c b/src/basic/locale-util.c index ada0a28cd8..0e546c0507 100644 --- a/src/basic/locale-util.c +++ b/src/basic/locale-util.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -30,6 +31,7 @@ #include #include +#include "def.h" #include "dirent-util.h" #include "fd-util.h" #include "hashmap.h" @@ -270,6 +272,99 @@ out: return (bool) cached_answer; } +static thread_local Set *keymaps = NULL; + +static int nftw_cb( + const char *fpath, + const struct stat *sb, + int tflag, + struct FTW *ftwbuf) { + + char *p, *e; + int r; + + if (tflag != FTW_F) + return 0; + + if (!endswith(fpath, ".map") && + !endswith(fpath, ".map.gz")) + return 0; + + p = strdup(basename(fpath)); + if (!p) + return FTW_STOP; + + e = endswith(p, ".map"); + if (e) + *e = 0; + + e = endswith(p, ".map.gz"); + if (e) + *e = 0; + + r = set_consume(keymaps, p); + if (r < 0 && r != -EEXIST) + return r; + + return 0; +} + +int get_keymaps(char ***ret) { + _cleanup_strv_free_ char **l = NULL; + const char *dir; + int r; + + keymaps = set_new(&string_hash_ops); + if (!keymaps) + return -ENOMEM; + + NULSTR_FOREACH(dir, KBD_KEYMAP_DIRS) { + r = nftw(dir, nftw_cb, 20, FTW_MOUNT|FTW_PHYS|FTW_ACTIONRETVAL); + + if (r == FTW_STOP) + log_debug("Directory not found %s", dir); + else if (r < 0) + log_debug_errno(r, "Can't add keymap: %m"); + } + + l = set_get_strv(keymaps); + if (!l) { + set_free_free(keymaps); + return -ENOMEM; + } + + set_free(keymaps); + + if (strv_isempty(l)) + return -ENOENT; + + strv_sort(l); + + *ret = l; + l = NULL; + + return 0; +} + +bool keymap_is_valid(const char *name) { + + if (isempty(name)) + return false; + + if (strlen(name) >= 128) + return false; + + if (!utf8_is_valid(name)) + return false; + + if (!filename_is_valid(name)) + return false; + + if (!string_is_safe(name)) + return false; + + return true; +} const char *special_glyph(SpecialGlyph code) { diff --git a/src/basic/locale-util.h b/src/basic/locale-util.h index 0630a034ab..104864501d 100644 --- a/src/basic/locale-util.h +++ b/src/basic/locale-util.h @@ -71,3 +71,6 @@ const char *special_glyph(SpecialGlyph code) _const_; const char* locale_variable_to_string(LocaleVariable i) _const_; LocaleVariable locale_variable_from_string(const char *s) _pure_; + +int get_keymaps(char ***l); +bool keymap_is_valid(const char *name); diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index 586674d458..81f297c5ec 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -44,16 +44,19 @@ static char *arg_root = NULL; static char *arg_locale = NULL; /* $LANG */ +static char *arg_keymap = NULL; static char *arg_locale_messages = NULL; /* $LC_MESSAGES */ static char *arg_timezone = NULL; static char *arg_hostname = NULL; static sd_id128_t arg_machine_id = {}; static char *arg_root_password = NULL; static bool arg_prompt_locale = false; +static bool arg_prompt_keymap = false; static bool arg_prompt_timezone = false; static bool arg_prompt_hostname = false; static bool arg_prompt_root_password = false; static bool arg_copy_locale = false; +static bool arg_copy_keymap = false; static bool arg_copy_timezone = false; static bool arg_copy_root_password = false; @@ -285,6 +288,84 @@ static int process_locale(void) { return 0; } +static int prompt_keymap(void) { + _cleanup_strv_free_ char **kmaps = NULL; + int r; + + if (arg_keymap) + return 0; + + if (!arg_prompt_keymap) + return 0; + + r = get_keymaps(&kmaps); + if (r == -ENOENT) /* no keymaps installed */ + return r; + if (r < 0) + return log_error_errno(r, "Failed to read keymaps: %m"); + + print_welcome(); + + printf("\nAvailable keymaps:\n\n"); + r = show_menu(kmaps, 3, 22, 60); + if (r < 0) + return r; + + putchar('\n'); + + r = prompt_loop("Please enter system keymap name or number", kmaps, keymap_is_valid, &arg_keymap); + if (r < 0) + return r; + + if (isempty(arg_keymap)) + return 0; + + return 0; +} + +static int process_keymap(void) { + const char *etc_vconsoleconf; + char **keymap; + int r; + + etc_vconsoleconf = prefix_roota(arg_root, "/etc/vconsole.conf"); + if (laccess(etc_vconsoleconf, F_OK) >= 0) + return 0; + + if (arg_copy_keymap && arg_root) { + + mkdir_parents(etc_vconsoleconf, 0755); + r = copy_file("/etc/vconsole.conf", etc_vconsoleconf, 0, 0644, 0, COPY_REFLINK); + if (r != -ENOENT) { + if (r < 0) + return log_error_errno(r, "Failed to copy %s: %m", etc_vconsoleconf); + + log_info("%s copied.", etc_vconsoleconf); + return 0; + } + } + + r = prompt_keymap(); + if (r == -ENOENT) + return 0; /* don't fail if no keymaps are installed */ + if (r < 0) + return r; + + if (!isempty(arg_keymap)) + keymap = STRV_MAKE(strjoina("KEYMAP=", arg_keymap)); + + if (!keymap) + return 0; + + mkdir_parents(etc_vconsoleconf, 0755); + r = write_env_file(etc_vconsoleconf, keymap); + if (r < 0) + return log_error_errno(r, "Failed to write %s: %m", etc_vconsoleconf); + + log_info("%s written.", etc_vconsoleconf); + return 0; +} + static int prompt_timezone(void) { _cleanup_strv_free_ char **zones = NULL; int r; @@ -613,20 +694,23 @@ static void help(void) { " --root=PATH Operate on an alternate filesystem root\n" " --locale=LOCALE Set primary locale (LANG=)\n" " --locale-messages=LOCALE Set message locale (LC_MESSAGES=)\n" + " --keymap=KEYMAP Set keymap\n" " --timezone=TIMEZONE Set timezone\n" " --hostname=NAME Set host name\n" " --machine-ID=ID Set machine ID\n" " --root-password=PASSWORD Set root password\n" " --root-password-file=FILE Set root password from file\n" " --prompt-locale Prompt the user for locale settings\n" + " --prompt-keymap Prompt the user for keymap settings\n" " --prompt-timezone Prompt the user for timezone\n" " --prompt-hostname Prompt the user for hostname\n" " --prompt-root-password Prompt the user for root password\n" " --prompt Prompt for all of the above\n" " --copy-locale Copy locale from host\n" + " --copy-keymap Copy keymap from host\n" " --copy-timezone Copy timezone from host\n" " --copy-root-password Copy root password from host\n" - " --copy Copy locale, timezone, root password\n" + " --copy Copy locale, keymap, timezone, root password\n" " --setup-machine-id Generate a new random machine ID\n" , program_invocation_short_name); } @@ -638,6 +722,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_ROOT, ARG_LOCALE, ARG_LOCALE_MESSAGES, + ARG_KEYMAP, ARG_TIMEZONE, ARG_HOSTNAME, ARG_MACHINE_ID, @@ -645,11 +730,13 @@ static int parse_argv(int argc, char *argv[]) { ARG_ROOT_PASSWORD_FILE, ARG_PROMPT, ARG_PROMPT_LOCALE, + ARG_PROMPT_KEYMAP, ARG_PROMPT_TIMEZONE, ARG_PROMPT_HOSTNAME, ARG_PROMPT_ROOT_PASSWORD, ARG_COPY, ARG_COPY_LOCALE, + ARG_COPY_KEYMAP, ARG_COPY_TIMEZONE, ARG_COPY_ROOT_PASSWORD, ARG_SETUP_MACHINE_ID, @@ -661,6 +748,7 @@ static int parse_argv(int argc, char *argv[]) { { "root", required_argument, NULL, ARG_ROOT }, { "locale", required_argument, NULL, ARG_LOCALE }, { "locale-messages", required_argument, NULL, ARG_LOCALE_MESSAGES }, + { "keymap", required_argument, NULL, ARG_KEYMAP }, { "timezone", required_argument, NULL, ARG_TIMEZONE }, { "hostname", required_argument, NULL, ARG_HOSTNAME }, { "machine-id", required_argument, NULL, ARG_MACHINE_ID }, @@ -668,11 +756,13 @@ static int parse_argv(int argc, char *argv[]) { { "root-password-file", required_argument, NULL, ARG_ROOT_PASSWORD_FILE }, { "prompt", no_argument, NULL, ARG_PROMPT }, { "prompt-locale", no_argument, NULL, ARG_PROMPT_LOCALE }, + { "prompt-keymap", no_argument, NULL, ARG_PROMPT_KEYMAP }, { "prompt-timezone", no_argument, NULL, ARG_PROMPT_TIMEZONE }, { "prompt-hostname", no_argument, NULL, ARG_PROMPT_HOSTNAME }, { "prompt-root-password", no_argument, NULL, ARG_PROMPT_ROOT_PASSWORD }, { "copy", no_argument, NULL, ARG_COPY }, { "copy-locale", no_argument, NULL, ARG_COPY_LOCALE }, + { "copy-keymap", no_argument, NULL, ARG_COPY_KEYMAP }, { "copy-timezone", no_argument, NULL, ARG_COPY_TIMEZONE }, { "copy-root-password", no_argument, NULL, ARG_COPY_ROOT_PASSWORD }, { "setup-machine-id", no_argument, NULL, ARG_SETUP_MACHINE_ID }, @@ -725,6 +815,18 @@ static int parse_argv(int argc, char *argv[]) { break; + case ARG_KEYMAP: + if (!keymap_is_valid(optarg)) { + log_error("Keymap %s is not valid.", optarg); + return -EINVAL; + } + + r = free_and_strdup(&arg_keymap, optarg); + if (r < 0) + return log_oom(); + + break; + case ARG_TIMEZONE: if (!timezone_is_valid(optarg)) { log_error("Timezone %s is not valid.", optarg); @@ -774,13 +876,17 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_PROMPT: - arg_prompt_locale = arg_prompt_timezone = arg_prompt_hostname = arg_prompt_root_password = true; + arg_prompt_locale = arg_prompt_keymap = arg_prompt_timezone = arg_prompt_hostname = arg_prompt_root_password = true; break; case ARG_PROMPT_LOCALE: arg_prompt_locale = true; break; + case ARG_PROMPT_KEYMAP: + arg_prompt_keymap = true; + break; + case ARG_PROMPT_TIMEZONE: arg_prompt_timezone = true; break; @@ -794,13 +900,17 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_COPY: - arg_copy_locale = arg_copy_timezone = arg_copy_root_password = true; + arg_copy_locale = arg_copy_keymap = arg_copy_timezone = arg_copy_root_password = true; break; case ARG_COPY_LOCALE: arg_copy_locale = true; break; + case ARG_COPY_KEYMAP: + arg_copy_keymap = true; + break; + case ARG_COPY_TIMEZONE: arg_copy_timezone = true; break; @@ -855,6 +965,10 @@ int main(int argc, char *argv[]) { if (r < 0) goto finish; + r = process_keymap(); + if (r < 0) + goto finish; + r = process_timezone(); if (r < 0) goto finish; @@ -875,6 +989,7 @@ finish: free(arg_root); free(arg_locale); free(arg_locale_messages); + free(arg_keymap); free(arg_timezone); free(arg_hostname); string_erase(arg_root_password); diff --git a/src/locale/localectl.c b/src/locale/localectl.c index cf54059026..d9b060972d 100644 --- a/src/locale/localectl.c +++ b/src/locale/localectl.c @@ -261,68 +261,15 @@ static int set_vconsole_keymap(sd_bus *bus, char **args, unsigned n) { return r; } -static Set *keymaps = NULL; - -static int nftw_cb( - const char *fpath, - const struct stat *sb, - int tflag, - struct FTW *ftwbuf) { - - char *p, *e; +static int list_vconsole_keymaps(sd_bus *bus, char **args, unsigned n) { + _cleanup_strv_free_ char **l = NULL; int r; - if (tflag != FTW_F) - return 0; + assert(args); - if (!endswith(fpath, ".map") && - !endswith(fpath, ".map.gz")) - return 0; - - p = strdup(basename(fpath)); - if (!p) - return log_oom(); - - e = endswith(p, ".map"); - if (e) - *e = 0; - - e = endswith(p, ".map.gz"); - if (e) - *e = 0; - - r = set_consume(keymaps, p); - if (r < 0 && r != -EEXIST) - return log_error_errno(r, "Can't add keymap: %m"); - - return 0; -} - -static int list_vconsole_keymaps(sd_bus *bus, char **args, unsigned n) { - _cleanup_strv_free_ char **l = NULL; - const char *dir; - - keymaps = set_new(&string_hash_ops); - if (!keymaps) - return log_oom(); - - NULSTR_FOREACH(dir, KBD_KEYMAP_DIRS) - nftw(dir, nftw_cb, 20, FTW_MOUNT|FTW_PHYS); - - l = set_get_strv(keymaps); - if (!l) { - set_free_free(keymaps); - return log_oom(); - } - - set_free(keymaps); - - if (strv_isempty(l)) { - log_error("Couldn't find any console keymaps."); - return -ENOENT; - } - - strv_sort(l); + r = get_keymaps(&l); + if (r < 0) + return log_error_errno(r, "Failed to read list of keymaps: %m"); pager_open(arg_no_pager, false); diff --git a/src/test/test-locale-util.c b/src/test/test-locale-util.c index 427c698d1d..9e69567e4c 100644 --- a/src/test/test-locale-util.c +++ b/src/test/test-locale-util.c @@ -50,9 +50,38 @@ static void test_locale_is_valid(void) { assert_se(!locale_is_valid("\x01gar\x02 bage\x03")); } +static void test_keymaps(void) { + _cleanup_strv_free_ char **kmaps = NULL; + char **p; + int r; + + assert_se(!keymap_is_valid("")); + assert_se(!keymap_is_valid("/usr/bin/foo")); + assert_se(!keymap_is_valid("\x01gar\x02 bage\x03")); + + r = get_keymaps(&kmaps); + if (r == -ENOENT) + return; /* skip test if no keymaps are installed */ + + assert_se(r >= 0); + assert_se(kmaps); + + STRV_FOREACH(p, kmaps) { + puts(*p); + assert_se(keymap_is_valid(*p)); + } + + assert_se(keymap_is_valid("uk")); + assert_se(keymap_is_valid("de-nodeadkeys")); + assert_se(keymap_is_valid("ANSI-dvorak")); + assert_se(keymap_is_valid("unicode")); +} + int main(int argc, char *argv[]) { test_get_locales(); test_locale_is_valid(); + test_keymaps(); + return 0; }