systemd-firstboot: add vconsole keymap support (#7035)
authortblume <Thomas.Blume@suse.com>
Fri, 10 Nov 2017 09:31:44 +0000 (10:31 +0100)
committerLennart Poettering <lennart@poettering.net>
Fri, 10 Nov 2017 09:31:44 +0000 (10:31 +0100)
Enable systemd-firstboot to set the keymap.

RFE:

https://github.com/systemd/systemd/issues/6346

man/systemd-firstboot.xml
src/basic/locale-util.c
src/basic/locale-util.h
src/firstboot/firstboot.c
src/locale/localectl.c
src/test/test-locale-util.c

index 539422a..fee37ad 100644 (file)
@@ -77,6 +77,8 @@
       locale variables <varname>LANG=</varname> and
       <varname>LC_MESSAGES</varname></para></listitem>
 
+      <listitem><para>The system keyboard map</para></listitem>
+
       <listitem><para>The system time zone</para></listitem>
 
       <listitem><para>The system host name</para></listitem>
       </varlistentry>
 
       <varlistentry>
+        <term><option>--keymap=<replaceable>KEYMAP</replaceable></option></term>
+
+        <listitem><para>Sets the system keyboard layout. The argument should be a valid keyboard map,
+        such as <literal>de-latin1</literal>. This controls the <literal>KEYMAP</literal> entry in the
+        <citerefentry project='man-pages'><refentrytitle>vconsole.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+        configuration file.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
         <term><option>--timezone=<replaceable>TIMEZONE</replaceable></option></term>
 
         <listitem><para>Sets the system time zone. The argument should
 
       <varlistentry>
         <term><option>--prompt-locale</option></term>
+        <term><option>--prompt-keymap</option></term>
         <term><option>--prompt-timezone</option></term>
         <term><option>--prompt-hostname</option></term>
         <term><option>--prompt-root-password</option></term>
       <varlistentry>
         <term><option>--prompt</option></term>
 
-        <listitem><para>Query the user for locale, timezone, hostname
+        <listitem><para>Query the user for locale, keymap, timezone, hostname
         and root password. This is equivalent to specifying
         <option>--prompt-locale</option>,
+        <option>--prompt-keymap</option>,
         <option>--prompt-timezone</option>,
         <option>--prompt-hostname</option>,
         <option>--prompt-root-password</option> in combination.</para>
 
       <varlistentry>
         <term><option>--copy-locale</option></term>
+        <term><option>--copy-keymap</option></term>
         <term><option>--copy-timezone</option></term>
         <term><option>--copy-root-password</option></term>
 
       <varlistentry>
         <term><option>--copy</option></term>
 
-        <listitem><para>Copy locale, time zone and root password from
+        <listitem><para>Copy locale, keymap, time zone and root password from
         the host. This is equivalent to specifying
         <option>--copy-locale</option>,
+        <option>--copy-keymap</option>,
         <option>--copy-timezone</option>,
         <option>--copy-root-password</option> in combination.</para>
         </listitem>
     <para>
       <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
       <citerefentry project='man-pages'><refentrytitle>locale.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+      <citerefentry project='man-pages'><refentrytitle>vconsole.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
       <citerefentry><refentrytitle>localtime</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
       <citerefentry><refentrytitle>hostname</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
       <citerefentry><refentrytitle>machine-id</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
index ada0a28..0e546c0 100644 (file)
@@ -20,6 +20,7 @@
 #include <dirent.h>
 #include <errno.h>
 #include <fcntl.h>
+#include <ftw.h>
 #include <langinfo.h>
 #include <libintl.h>
 #include <locale.h>
@@ -30,6 +31,7 @@
 #include <sys/mman.h>
 #include <sys/stat.h>
 
+#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) {
 
index 0630a03..1048645 100644 (file)
@@ -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);
index 586674d..81f297c 100644 (file)
 
 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);
index cf54059..d9b0609 100644 (file)
@@ -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;
-        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);
 
index 427c698..9e69567 100644 (file)
@@ -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;
 }