systemd-firstboot: add vconsole keymap support (#7035)

Enable systemd-firstboot to set the keymap.

RFE:

https://github.com/systemd/systemd/issues/6346
This commit is contained in:
tblume 2017-11-10 10:31:44 +01:00 committed by Lennart Poettering
parent c54515b1e4
commit ed457f1380
6 changed files with 269 additions and 64 deletions

View 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>
@ -135,6 +137,15 @@
configuration file.</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>
@ -182,6 +193,7 @@
<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>
@ -195,9 +207,10 @@
<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>
@ -206,6 +219,7 @@
<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>
@ -217,9 +231,10 @@
<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>
@ -265,6 +280,7 @@
<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>,

View 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) {

View 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);

View File

@ -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);

View 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;
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);

View 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;
}