From 349fd7b2fe35d2fd6c7ba6493d2e44ae93013804 Mon Sep 17 00:00:00 2001 From: Thomas Blume Date: Wed, 18 Oct 2017 12:30:03 +0200 Subject: [PATCH 1/1] systemd-firstboot: add vconsole keymap support yast installer needs systemd-firstboot support for setting the keymap This is a temporary patch to be superseded when upstream commit is available, see https://github.com/systemd/systemd/pull/7035 for the relevant PR. [tblume: fixes bsc#1046436] --- src/basic/locale-util.c | 95 +++++++++++++++++++++++++++++++++++ src/basic/locale-util.h | 3 ++ src/firstboot/firstboot.c | 117 ++++++++++++++++++++++++++++++++++++++++++-- src/locale/localectl.c | 65 +++--------------------- src/test/test-locale-util.c | 29 +++++++++++ 5 files changed, 247 insertions(+), 62 deletions(-) diff --git a/src/basic/locale-util.c b/src/basic/locale-util.c index ada0a28cd..f3fa918b8 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 0630a034a..104864501 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 b3578d3e1..fd60ee518 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,80 @@ 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 < 0) + return log_error_errno(r, "Cannot query keymap list: %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 < 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; @@ -611,20 +688,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); } @@ -636,6 +716,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, @@ -643,11 +724,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, @@ -659,6 +742,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 }, @@ -666,11 +750,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 }, @@ -723,6 +809,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); @@ -772,13 +870,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; @@ -792,13 +894,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; @@ -853,6 +959,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; @@ -873,6 +983,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 0bd18a5c0..efdd73a8f 100644 --- a/src/locale/localectl.c +++ b/src/locale/localectl.c @@ -273,68 +273,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; - 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 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); + _cleanup_strv_free_ char **l = NULL; + int r; - if (strv_isempty(l)) { - log_error("Couldn't find any console keymaps."); - return -ENOENT; - } + assert(args); - 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 427c698d1..e6876c82e 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_get_keymaps(void) { + _cleanup_strv_free_ char **kmaps = NULL; + char **p; + int r; + + r = get_keymaps(&kmaps); + assert_se(r >= 0); + assert_se(kmaps); + + STRV_FOREACH(p, kmaps) { + puts(*p); + assert_se(keymap_is_valid(*p)); + } +} + +static void test_keymap_is_valid(void) { + 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")); + + assert_se(!keymap_is_valid("")); + assert_se(!keymap_is_valid("/usr/bin/foo")); + assert_se(!keymap_is_valid("\x01gar\x02 bage\x03")); +} + int main(int argc, char *argv[]) { test_get_locales(); test_locale_is_valid(); + test_get_keymaps(); + test_keymap_is_valid(); + return 0; } -- 2.14.2