diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index bfd4bbe9fb2..d00886fb808 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -51,6 +51,7 @@ #include "tmpfile-util.h" #include "umask-util.h" #include "user-util.h" +#include "vconsole-util.h" static char *arg_root = NULL; static char *arg_image = NULL; @@ -461,7 +462,7 @@ static int prompt_keymap(int rfd) { static int process_keymap(int rfd) { _cleanup_close_ int pfd = -EBADF; _cleanup_free_ char *f = NULL; - char **keymap; + _cleanup_strv_free_ char **keymap = NULL; int r; assert(rfd >= 0); @@ -502,7 +503,18 @@ static int process_keymap(int rfd) { if (isempty(arg_keymap)) return 0; - keymap = STRV_MAKE(strjoina("KEYMAP=", arg_keymap)); + VCContext vc = { + .keymap = arg_keymap, + }; + _cleanup_(x11_context_clear) X11Context xc = {}; + + r = vconsole_convert_to_x11(&vc, /* verify= */ NULL, &xc); + if (r < 0) + return log_error_errno(r, "Failed to convert keymap data: %m"); + + r = vconsole_serialize(&vc, &xc, &keymap); + if (r < 0) + return log_error_errno(r, "Failed to serialize keymap data: %m"); r = write_vconsole_conf(pfd, f, keymap); if (r < 0) diff --git a/src/locale/localed-util.c b/src/locale/localed-util.c index 6413288ea3d..d43ee48e12f 100644 --- a/src/locale/localed-util.c +++ b/src/locale/localed-util.c @@ -5,19 +5,15 @@ #include #include -#include "bus-polkit.h" #include "copy.h" #include "env-file-label.h" #include "env-file.h" #include "env-util.h" #include "fd-util.h" #include "fileio.h" -#include "fs-util.h" #include "kbd-util.h" #include "localed-util.h" -#include "macro.h" #include "mkdir-label.h" -#include "nulstr-util.h" #include "process-util.h" #include "stat-util.h" #include "string-util.h" @@ -25,134 +21,6 @@ #include "tmpfile-util.h" #include "xkbcommon-util.h" -static bool startswith_comma(const char *s, const char *prefix) { - assert(s); - assert(prefix); - - s = startswith(s, prefix); - if (!s) - return false; - - return IN_SET(*s, ',', '\0'); -} - -static const char* systemd_kbd_model_map(void) { - const char* s; - - s = getenv("SYSTEMD_KBD_MODEL_MAP"); - if (s) - return s; - - return SYSTEMD_KBD_MODEL_MAP; -} - -static const char* systemd_language_fallback_map(void) { - const char* s; - - s = getenv("SYSTEMD_LANGUAGE_FALLBACK_MAP"); - if (s) - return s; - - return SYSTEMD_LANGUAGE_FALLBACK_MAP; -} - -void x11_context_clear(X11Context *xc) { - assert(xc); - - xc->layout = mfree(xc->layout); - xc->options = mfree(xc->options); - xc->model = mfree(xc->model); - xc->variant = mfree(xc->variant); -} - -void x11_context_replace(X11Context *dest, X11Context *src) { - assert(dest); - assert(src); - - x11_context_clear(dest); - *dest = TAKE_STRUCT(*src); -} - -bool x11_context_isempty(const X11Context *xc) { - assert(xc); - - return - isempty(xc->layout) && - isempty(xc->model) && - isempty(xc->variant) && - isempty(xc->options); -} - -void x11_context_empty_to_null(X11Context *xc) { - assert(xc); - - /* Do not call x11_context_clear() for the passed object. */ - - xc->layout = empty_to_null(xc->layout); - xc->model = empty_to_null(xc->model); - xc->variant = empty_to_null(xc->variant); - xc->options = empty_to_null(xc->options); -} - -bool x11_context_is_safe(const X11Context *xc) { - assert(xc); - - return - (!xc->layout || string_is_safe(xc->layout)) && - (!xc->model || string_is_safe(xc->model)) && - (!xc->variant || string_is_safe(xc->variant)) && - (!xc->options || string_is_safe(xc->options)); -} - -bool x11_context_equal(const X11Context *a, const X11Context *b) { - assert(a); - assert(b); - - return - streq_ptr(a->layout, b->layout) && - streq_ptr(a->model, b->model) && - streq_ptr(a->variant, b->variant) && - streq_ptr(a->options, b->options); -} - -int x11_context_copy(X11Context *dest, const X11Context *src) { - bool modified; - int r; - - assert(dest); - - if (dest == src) - return 0; - - if (!src) { - modified = !x11_context_isempty(dest); - x11_context_clear(dest); - return modified; - } - - r = free_and_strdup(&dest->layout, src->layout); - if (r < 0) - return r; - modified = r > 0; - - r = free_and_strdup(&dest->model, src->model); - if (r < 0) - return r; - modified = modified || r > 0; - - r = free_and_strdup(&dest->variant, src->variant); - if (r < 0) - return r; - modified = modified || r > 0; - - r = free_and_strdup(&dest->options, src->options); - if (r < 0) - return r; - modified = modified || r > 0; - - return modified; -} - int x11_context_verify_and_warn(const X11Context *xc, int log_level, sd_bus_error *error) { int r; @@ -182,75 +50,6 @@ int x11_context_verify_and_warn(const X11Context *xc, int log_level, sd_bus_erro return 0; } -void vc_context_clear(VCContext *vc) { - assert(vc); - - vc->keymap = mfree(vc->keymap); - vc->toggle = mfree(vc->toggle); -} - -void vc_context_replace(VCContext *dest, VCContext *src) { - assert(dest); - assert(src); - - vc_context_clear(dest); - *dest = TAKE_STRUCT(*src); -} - -bool vc_context_isempty(const VCContext *vc) { - assert(vc); - - return - isempty(vc->keymap) && - isempty(vc->toggle); -} - -void vc_context_empty_to_null(VCContext *vc) { - assert(vc); - - /* Do not call vc_context_clear() for the passed object. */ - - vc->keymap = empty_to_null(vc->keymap); - vc->toggle = empty_to_null(vc->toggle); -} - -bool vc_context_equal(const VCContext *a, const VCContext *b) { - assert(a); - assert(b); - - return - streq_ptr(a->keymap, b->keymap) && - streq_ptr(a->toggle, b->toggle); -} - -int vc_context_copy(VCContext *dest, const VCContext *src) { - bool modified; - int r; - - assert(dest); - - if (dest == src) - return 0; - - if (!src) { - modified = !vc_context_isempty(dest); - vc_context_clear(dest); - return modified; - } - - r = free_and_strdup(&dest->keymap, src->keymap); - if (r < 0) - return r; - modified = r > 0; - - r = free_and_strdup(&dest->toggle, src->toggle); - if (r < 0) - return r; - modified = modified || r > 0; - - return modified; -} - static int verify_keymap(const char *keymap, int log_level, sd_bus_error *error) { int r; @@ -499,27 +298,7 @@ int vconsole_write_data(Context *c) { if (r < 0 && r != -ENOENT) return r; - r = strv_env_assign(&l, "KEYMAP", empty_to_null(c->vc.keymap)); - if (r < 0) - return r; - - r = strv_env_assign(&l, "KEYMAP_TOGGLE", empty_to_null(c->vc.toggle)); - if (r < 0) - return r; - - r = strv_env_assign(&l, "XKBLAYOUT", empty_to_null(xc->layout)); - if (r < 0) - return r; - - r = strv_env_assign(&l, "XKBMODEL", empty_to_null(xc->model)); - if (r < 0) - return r; - - r = strv_env_assign(&l, "XKBVARIANT", empty_to_null(xc->variant)); - if (r < 0) - return r; - - r = strv_env_assign(&l, "XKBOPTIONS", empty_to_null(xc->options)); + r = vconsole_serialize(&c->vc, xc, &l); if (r < 0) return r; @@ -599,373 +378,6 @@ int x11_write_data(Context *c) { return 0; } -static int read_next_mapping( - const char *filename, - unsigned min_fields, - unsigned max_fields, - FILE *f, - unsigned *n, - char ***ret) { - - assert(f); - assert(n); - assert(ret); - - for (;;) { - _cleanup_strv_free_ char **b = NULL; - _cleanup_free_ char *line = NULL; - size_t length; - int r; - - r = read_stripped_line(f, LONG_LINE_MAX, &line); - if (r < 0) - return r; - if (r == 0) - break; - - (*n)++; - - if (IN_SET(line[0], 0, '#')) - continue; - - r = strv_split_full(&b, line, WHITESPACE, EXTRACT_UNQUOTE); - if (r < 0) - return r; - - length = strv_length(b); - if (length < min_fields || length > max_fields) { - log_debug("Invalid line %s:%u, ignoring.", strna(filename), *n); - continue; - - } - - *ret = TAKE_PTR(b); - return 1; - } - - *ret = NULL; - return 0; -} - -int vconsole_convert_to_x11(const VCContext *vc, X11Context *ret) { - _cleanup_fclose_ FILE *f = NULL; - const char *map; - X11Context xc; - int r; - - assert(vc); - assert(ret); - - if (isempty(vc->keymap)) { - *ret = (X11Context) {}; - return 0; - } - - map = systemd_kbd_model_map(); - f = fopen(map, "re"); - if (!f) - return -errno; - - for (unsigned n = 0;;) { - _cleanup_strv_free_ char **a = NULL; - - r = read_next_mapping(map, 5, UINT_MAX, f, &n, &a); - if (r < 0) - return r; - if (r == 0) - break; - - if (!streq(vc->keymap, a[0])) - continue; - - xc = (X11Context) { - .layout = empty_or_dash_to_null(a[1]), - .model = empty_or_dash_to_null(a[2]), - .variant = empty_or_dash_to_null(a[3]), - .options = empty_or_dash_to_null(a[4]), - }; - - if (x11_context_verify(&xc) < 0) - continue; - - return x11_context_copy(ret, &xc); - } - - /* No custom mapping has been found, see if the keymap is a converted one. In such case deducing the - * corresponding x11 layout is easy. */ - _cleanup_free_ char *xlayout = NULL, *converted = NULL; - char *xvariant; - - xlayout = strdup(vc->keymap); - if (!xlayout) - return -ENOMEM; - xvariant = strchr(xlayout, '-'); - if (xvariant) { - xvariant[0] = '\0'; - xvariant++; - } - - /* Note: by default we use keyboard model "microsoftpro" which should be equivalent to "pc105" but - * with the internet/media key mapping added. */ - xc = (X11Context) { - .layout = xlayout, - .model = (char*) "microsoftpro", - .variant = xvariant, - .options = (char*) "terminate:ctrl_alt_bksp", - }; - - /* This sanity check seems redundant with the verification of the X11 layout done on the next - * step. However xkbcommon is an optional dependency hence the verification might be a NOP. */ - r = find_converted_keymap(&xc, &converted); - if (r == 0 && xc.variant) { - /* If we still haven't find a match, try with no variant, it's still better than nothing. */ - xc.variant = NULL; - r = find_converted_keymap(&xc, &converted); - } - if (r < 0) - return r; - - if (r == 0 || x11_context_verify(&xc) < 0) { - *ret = (X11Context) {}; - return 0; - } - - return x11_context_copy(ret, &xc); -} - -int find_converted_keymap(const X11Context *xc, char **ret) { - _cleanup_free_ char *n = NULL, *p = NULL, *pz = NULL; - _cleanup_strv_free_ char **keymap_dirs = NULL; - int r; - - assert(xc); - assert(!isempty(xc->layout)); - assert(ret); - - if (xc->variant) - n = strjoin(xc->layout, "-", xc->variant); - else - n = strdup(xc->layout); - if (!n) - return -ENOMEM; - - p = strjoin("xkb/", n, ".map"); - pz = strjoin("xkb/", n, ".map.gz"); - if (!p || !pz) - return -ENOMEM; - - r = keymap_directories(&keymap_dirs); - if (r < 0) - return r; - - STRV_FOREACH(dir, keymap_dirs) { - _cleanup_close_ int dir_fd = -EBADF; - bool uncompressed; - - dir_fd = open(*dir, O_CLOEXEC | O_DIRECTORY | O_PATH); - if (dir_fd < 0) { - if (errno != ENOENT) - log_debug_errno(errno, "Failed to open %s, ignoring: %m", *dir); - continue; - } - - uncompressed = faccessat(dir_fd, p, F_OK, 0) >= 0; - if (uncompressed || faccessat(dir_fd, pz, F_OK, 0) >= 0) { - log_debug("Found converted keymap %s at %s/%s", n, *dir, uncompressed ? p : pz); - *ret = TAKE_PTR(n); - return 1; - } - } - - *ret = NULL; - return 0; -} - -int find_legacy_keymap(const X11Context *xc, char **ret) { - const char *map; - _cleanup_fclose_ FILE *f = NULL; - _cleanup_free_ char *new_keymap = NULL; - unsigned best_matching = 0; - int r; - - assert(xc); - assert(!isempty(xc->layout)); - - map = systemd_kbd_model_map(); - f = fopen(map, "re"); - if (!f) - return -errno; - - for (unsigned n = 0;;) { - _cleanup_strv_free_ char **a = NULL; - unsigned matching = 0; - - r = read_next_mapping(map, 5, UINT_MAX, f, &n, &a); - if (r < 0) - return r; - if (r == 0) - break; - - /* Determine how well matching this entry is */ - if (streq(xc->layout, a[1])) - /* If we got an exact match, this is the best */ - matching = 10; - else { - /* see if we get an exact match with the order reversed */ - _cleanup_strv_free_ char **b = NULL; - _cleanup_free_ char *c = NULL; - r = strv_split_full(&b, a[1], ",", 0); - if (r < 0) - return r; - strv_reverse(b); - c = strv_join(b, ","); - if (!c) - return log_oom(); - if (streq(xc->layout, c)) - matching = 9; - else { - /* We have multiple X layouts, look for an - * entry that matches our key with everything - * but the first layout stripped off. */ - if (startswith_comma(xc->layout, a[1])) - matching = 5; - else { - _cleanup_free_ char *x = NULL; - - /* If that didn't work, strip off the - * other layouts from the entry, too */ - x = strdupcspn(a[1], ","); - if (!x) - return -ENOMEM; - if (startswith_comma(xc->layout, x)) - matching = 1; - } - } - } - - if (matching > 0) { - if (isempty(xc->model) || streq_ptr(xc->model, a[2])) { - matching++; - - if (streq_ptr(xc->variant, a[3]) || ((isempty(xc->variant) || streq_skip_trailing_chars(xc->variant, "", ",")) && streq(a[3], "-"))) { - matching++; - - if (streq_ptr(xc->options, a[4])) - matching++; - } - } - } - - /* The best matching entry so far, then let's save that */ - if (matching >= MAX(best_matching, 1u)) { - log_debug("Found legacy keymap %s with score %u", a[0], matching); - - if (matching > best_matching) { - best_matching = matching; - - r = free_and_strdup(&new_keymap, a[0]); - if (r < 0) - return r; - } - } - } - - if (best_matching < 9 && !isempty(xc->layout)) { - _cleanup_free_ char *l = NULL, *v = NULL, *converted = NULL; - - /* The best match is only the first part of the X11 - * keymap. Check if we have a converted map which - * matches just the first layout. - */ - - l = strdupcspn(xc->layout, ","); - if (!l) - return -ENOMEM; - - if (!isempty(xc->variant)) { - v = strdupcspn(xc->variant, ","); - if (!v) - return -ENOMEM; - } - - r = find_converted_keymap( - &(X11Context) { - .layout = l, - .variant = v, - }, - &converted); - if (r < 0) - return r; - if (r > 0) - free_and_replace(new_keymap, converted); - } - - *ret = TAKE_PTR(new_keymap); - return !!*ret; -} - -int x11_convert_to_vconsole(const X11Context *xc, VCContext *ret) { - _cleanup_free_ char *keymap = NULL; - int r; - - assert(xc); - assert(ret); - - if (isempty(xc->layout)) { - *ret = (VCContext) {}; - return 0; - } - - r = find_converted_keymap(xc, &keymap); - if (r == 0) { - r = find_legacy_keymap(xc, &keymap); - if (r == 0 && xc->variant) - /* If we still haven't find a match, try with no variant, it's still better than - * nothing. */ - r = find_converted_keymap( - &(X11Context) { - .layout = xc->layout, - }, - &keymap); - } - if (r < 0) - return r; - - *ret = (VCContext) { - .keymap = TAKE_PTR(keymap), - }; - return 0; -} - -int find_language_fallback(const char *lang, char **ret) { - const char *map; - _cleanup_fclose_ FILE *f = NULL; - unsigned n = 0; - int r; - - assert(lang); - assert(ret); - - map = systemd_language_fallback_map(); - f = fopen(map, "re"); - if (!f) - return -errno; - - for (;;) { - _cleanup_strv_free_ char **a = NULL; - - r = read_next_mapping(map, 2, 2, f, &n, &a); - if (r <= 0) - return r; - - if (streq(lang, a[0])) { - assert(strv_length(a) == 2); - *ret = TAKE_PTR(a[1]); - return 1; - } - } -} - bool locale_gen_check_available(void) { #if HAVE_LOCALEGEN if (access(LOCALEGEN_PATH, X_OK) < 0) { diff --git a/src/locale/localed-util.h b/src/locale/localed-util.h index 0c68f2942ee..8a279c5e561 100644 --- a/src/locale/localed-util.h +++ b/src/locale/localed-util.h @@ -7,18 +7,7 @@ #include "hashmap.h" #include "locale-setup.h" - -typedef struct X11Context { - char *layout; - char *model; - char *variant; - char *options; -} X11Context; - -typedef struct VCContext { - char *keymap; - char *toggle; -} VCContext; +#include "vconsole-util.h" typedef struct Context { sd_bus_message *locale_cache; @@ -36,13 +25,6 @@ typedef struct Context { Hashmap *polkit_registry; } Context; -void x11_context_clear(X11Context *xc); -void x11_context_replace(X11Context *dest, X11Context *src); -bool x11_context_isempty(const X11Context *xc); -void x11_context_empty_to_null(X11Context *xc); -bool x11_context_is_safe(const X11Context *xc); -bool x11_context_equal(const X11Context *a, const X11Context *b); -int x11_context_copy(X11Context *dest, const X11Context *src); int x11_context_verify_and_warn(const X11Context *xc, int log_level, sd_bus_error *error); static inline int x11_context_verify(const X11Context *xc) { return x11_context_verify_and_warn(xc, LOG_DEBUG, NULL); @@ -50,29 +32,17 @@ static inline int x11_context_verify(const X11Context *xc) { X11Context *context_get_x11_context(Context *c); -void vc_context_clear(VCContext *vc); -void vc_context_replace(VCContext *dest, VCContext *src); -bool vc_context_isempty(const VCContext *vc); -void vc_context_empty_to_null(VCContext *vc); -bool vc_context_equal(const VCContext *a, const VCContext *b); -int vc_context_copy(VCContext *dest, const VCContext *src); int vc_context_verify_and_warn(const VCContext *vc, int log_level, sd_bus_error *error); static inline int vc_context_verify(const VCContext *vc) { return vc_context_verify_and_warn(vc, LOG_DEBUG, NULL); } -int find_converted_keymap(const X11Context *xc, char **ret); -int find_legacy_keymap(const X11Context *xc, char **ret); -int find_language_fallback(const char *lang, char **ret); - int locale_read_data(Context *c, sd_bus_message *m); int vconsole_read_data(Context *c, sd_bus_message *m); int x11_read_data(Context *c, sd_bus_message *m); void context_clear(Context *c); -int vconsole_convert_to_x11(const VCContext *vc, X11Context *ret); int vconsole_write_data(Context *c); -int x11_convert_to_vconsole(const X11Context *xc, VCContext *ret); int x11_write_data(Context *c); bool locale_gen_check_available(void); diff --git a/src/locale/localed.c b/src/locale/localed.c index 062744519db..54ab210bcd0 100644 --- a/src/locale/localed.c +++ b/src/locale/localed.c @@ -362,7 +362,7 @@ static int method_set_vc_keyboard(sd_bus_message *m, void *userdata, sd_bus_erro } if (convert) { - r = vconsole_convert_to_x11(&in, &converted); + r = vconsole_convert_to_x11(&in, x11_context_verify, &converted); if (r < 0) { log_error_errno(r, "Failed to convert keymap data: %m"); return sd_bus_error_set_errnof(error, r, "Failed to convert keymap data: %m"); diff --git a/src/locale/test-localed-util.c b/src/locale/test-localed-util.c index e92c178a980..ae5e1f85d92 100644 --- a/src/locale/test-localed-util.c +++ b/src/locale/test-localed-util.c @@ -77,40 +77,40 @@ TEST(vconsole_convert_to_x11) { int r; log_info("/* test empty keymap */"); - assert_se(vconsole_convert_to_x11(&vc, &xc) >= 0); + assert_se(vconsole_convert_to_x11(&vc, x11_context_verify, &xc) >= 0); assert_se(x11_context_isempty(&xc)); log_info("/* test without variant, new mapping (es:) */"); assert_se(free_and_strdup(&vc.keymap, "es") >= 0); - assert_se(vconsole_convert_to_x11(&vc, &xc) >= 0); + assert_se(vconsole_convert_to_x11(&vc, x11_context_verify, &xc) >= 0); assert_se(streq(xc.layout, "es")); assert_se(xc.variant == NULL); x11_context_clear(&xc); log_info("/* test with known variant, new mapping (es:dvorak) */"); assert_se(free_and_strdup(&vc.keymap, "es-dvorak") >= 0); - assert_se(vconsole_convert_to_x11(&vc, &xc) >= 0); + assert_se(vconsole_convert_to_x11(&vc, x11_context_verify, &xc) >= 0); assert_se(streq(xc.layout, "es")); assert_se(streq(xc.variant, "dvorak")); x11_context_clear(&xc); log_info("/* test with old mapping (fr:latin9) */"); assert_se(free_and_strdup(&vc.keymap, "fr-latin9") >= 0); - assert_se(vconsole_convert_to_x11(&vc, &xc) >= 0); + assert_se(vconsole_convert_to_x11(&vc, x11_context_verify, &xc) >= 0); assert_se(streq(xc.layout, "fr")); assert_se(streq(xc.variant, "latin9")); x11_context_clear(&xc); log_info("/* test with a compound mapping (ru,us) */"); assert_se(free_and_strdup(&vc.keymap, "ru") >= 0); - assert_se(vconsole_convert_to_x11(&vc, &xc) >= 0); + assert_se(vconsole_convert_to_x11(&vc, x11_context_verify, &xc) >= 0); assert_se(streq(xc.layout, "ru,us")); assert_se(xc.variant == NULL); x11_context_clear(&xc); log_info("/* test with a simple mapping (us) */"); assert_se(free_and_strdup(&vc.keymap, "us") >= 0); - assert_se(vconsole_convert_to_x11(&vc, &xc) >= 0); + assert_se(vconsole_convert_to_x11(&vc, x11_context_verify, &xc) >= 0); assert_se(streq(xc.layout, "us")); assert_se(xc.variant == NULL); x11_context_clear(&xc); @@ -118,7 +118,7 @@ TEST(vconsole_convert_to_x11) { /* "gh" has no mapping in kbd-model-map and kbd provides a converted keymap for this layout. */ log_info("/* test with a converted keymap (gh:) */"); assert_se(free_and_strdup(&vc.keymap, "gh") >= 0); - r = vconsole_convert_to_x11(&vc, &xc); + r = vconsole_convert_to_x11(&vc, x11_context_verify, &xc); if (r == 0) { log_info("Skipping rest of %s: keymaps are not installed", __func__); return; @@ -130,14 +130,14 @@ TEST(vconsole_convert_to_x11) { log_info("/* test with converted keymap and with a known variant (gh:ewe) */"); assert_se(free_and_strdup(&vc.keymap, "gh-ewe") >= 0); - assert_se(vconsole_convert_to_x11(&vc, &xc) > 0); + assert_se(vconsole_convert_to_x11(&vc, x11_context_verify, &xc) > 0); assert_se(streq(xc.layout, "gh")); assert_se(streq(xc.variant, "ewe")); x11_context_clear(&xc); log_info("/* test with converted keymap and with an unknown variant (gh:ewe) */"); assert_se(free_and_strdup(&vc.keymap, "gh-foobar") > 0); - assert_se(vconsole_convert_to_x11(&vc, &xc) > 0); + assert_se(vconsole_convert_to_x11(&vc, x11_context_verify, &xc) > 0); assert_se(streq(xc.layout, "gh")); assert_se(xc.variant == NULL); x11_context_clear(&xc); diff --git a/src/shared/meson.build b/src/shared/meson.build index 5936e20de6f..ed7cad7c883 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -201,6 +201,7 @@ shared_sources = files( 'varlink-io.systemd.service.c', 'varlink-io.systemd.sysext.c', 'varlink-serialize.c', + 'vconsole-util.c', 'verb-log-control.c', 'verbs.c', 'vlan-util.c', diff --git a/src/shared/vconsole-util.c b/src/shared/vconsole-util.c new file mode 100644 index 00000000000..ce7d54d7e81 --- /dev/null +++ b/src/shared/vconsole-util.c @@ -0,0 +1,609 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "env-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "kbd-util.h" +#include "string-util.h" +#include "strv.h" +#include "vconsole-util.h" + +static bool startswith_comma(const char *s, const char *prefix) { + assert(s); + assert(prefix); + + s = startswith(s, prefix); + if (!s) + return false; + + return IN_SET(*s, ',', '\0'); +} + +static const char* systemd_kbd_model_map(void) { + const char* s; + + s = getenv("SYSTEMD_KBD_MODEL_MAP"); + if (s) + return s; + + return SYSTEMD_KBD_MODEL_MAP; +} + +static const char* systemd_language_fallback_map(void) { + const char* s; + + s = getenv("SYSTEMD_LANGUAGE_FALLBACK_MAP"); + if (s) + return s; + + return SYSTEMD_LANGUAGE_FALLBACK_MAP; +} + +void x11_context_clear(X11Context *xc) { + assert(xc); + + xc->layout = mfree(xc->layout); + xc->options = mfree(xc->options); + xc->model = mfree(xc->model); + xc->variant = mfree(xc->variant); +} + +void x11_context_replace(X11Context *dest, X11Context *src) { + assert(dest); + assert(src); + + x11_context_clear(dest); + *dest = TAKE_STRUCT(*src); +} + +bool x11_context_isempty(const X11Context *xc) { + assert(xc); + + return + isempty(xc->layout) && + isempty(xc->model) && + isempty(xc->variant) && + isempty(xc->options); +} + +void x11_context_empty_to_null(X11Context *xc) { + assert(xc); + + /* Do not call x11_context_clear() for the passed object. */ + + xc->layout = empty_to_null(xc->layout); + xc->model = empty_to_null(xc->model); + xc->variant = empty_to_null(xc->variant); + xc->options = empty_to_null(xc->options); +} + +bool x11_context_is_safe(const X11Context *xc) { + assert(xc); + + return + (!xc->layout || string_is_safe(xc->layout)) && + (!xc->model || string_is_safe(xc->model)) && + (!xc->variant || string_is_safe(xc->variant)) && + (!xc->options || string_is_safe(xc->options)); +} + +bool x11_context_equal(const X11Context *a, const X11Context *b) { + assert(a); + assert(b); + + return + streq_ptr(a->layout, b->layout) && + streq_ptr(a->model, b->model) && + streq_ptr(a->variant, b->variant) && + streq_ptr(a->options, b->options); +} + +int x11_context_copy(X11Context *dest, const X11Context *src) { + bool modified; + int r; + + assert(dest); + + if (dest == src) + return 0; + + if (!src) { + modified = !x11_context_isempty(dest); + x11_context_clear(dest); + return modified; + } + + r = free_and_strdup(&dest->layout, src->layout); + if (r < 0) + return r; + modified = r > 0; + + r = free_and_strdup(&dest->model, src->model); + if (r < 0) + return r; + modified = modified || r > 0; + + r = free_and_strdup(&dest->variant, src->variant); + if (r < 0) + return r; + modified = modified || r > 0; + + r = free_and_strdup(&dest->options, src->options); + if (r < 0) + return r; + modified = modified || r > 0; + + return modified; +} + +void vc_context_clear(VCContext *vc) { + assert(vc); + + vc->keymap = mfree(vc->keymap); + vc->toggle = mfree(vc->toggle); +} + +void vc_context_replace(VCContext *dest, VCContext *src) { + assert(dest); + assert(src); + + vc_context_clear(dest); + *dest = TAKE_STRUCT(*src); +} + +bool vc_context_isempty(const VCContext *vc) { + assert(vc); + + return + isempty(vc->keymap) && + isempty(vc->toggle); +} + +void vc_context_empty_to_null(VCContext *vc) { + assert(vc); + + /* Do not call vc_context_clear() for the passed object. */ + + vc->keymap = empty_to_null(vc->keymap); + vc->toggle = empty_to_null(vc->toggle); +} + +bool vc_context_equal(const VCContext *a, const VCContext *b) { + assert(a); + assert(b); + + return + streq_ptr(a->keymap, b->keymap) && + streq_ptr(a->toggle, b->toggle); +} + +int vc_context_copy(VCContext *dest, const VCContext *src) { + bool modified; + int r; + + assert(dest); + + if (dest == src) + return 0; + + if (!src) { + modified = !vc_context_isempty(dest); + vc_context_clear(dest); + return modified; + } + + r = free_and_strdup(&dest->keymap, src->keymap); + if (r < 0) + return r; + modified = r > 0; + + r = free_and_strdup(&dest->toggle, src->toggle); + if (r < 0) + return r; + modified = modified || r > 0; + + return modified; +} + +static int read_next_mapping( + const char *filename, + unsigned min_fields, + unsigned max_fields, + FILE *f, + unsigned *n, + char ***ret) { + + assert(f); + assert(n); + assert(ret); + + for (;;) { + _cleanup_strv_free_ char **b = NULL; + _cleanup_free_ char *line = NULL; + size_t length; + int r; + + r = read_stripped_line(f, LONG_LINE_MAX, &line); + if (r < 0) + return r; + if (r == 0) + break; + + (*n)++; + + if (IN_SET(line[0], 0, '#')) + continue; + + r = strv_split_full(&b, line, WHITESPACE, EXTRACT_UNQUOTE); + if (r < 0) + return r; + + length = strv_length(b); + if (length < min_fields || length > max_fields) { + log_debug("Invalid line %s:%u, ignoring.", strna(filename), *n); + continue; + + } + + *ret = TAKE_PTR(b); + return 1; + } + + *ret = NULL; + return 0; +} + +int vconsole_convert_to_x11(const VCContext *vc, X11VerifyCallback verify, X11Context *ret) { + _cleanup_fclose_ FILE *f = NULL; + const char *map; + X11Context xc; + int r; + + assert(vc); + assert(ret); + + if (isempty(vc->keymap)) { + *ret = (X11Context) {}; + return 0; + } + + map = systemd_kbd_model_map(); + f = fopen(map, "re"); + if (!f) + return -errno; + + for (unsigned n = 0;;) { + _cleanup_strv_free_ char **a = NULL; + + r = read_next_mapping(map, 5, UINT_MAX, f, &n, &a); + if (r < 0) + return r; + if (r == 0) + break; + + if (!streq(vc->keymap, a[0])) + continue; + + xc = (X11Context) { + .layout = empty_or_dash_to_null(a[1]), + .model = empty_or_dash_to_null(a[2]), + .variant = empty_or_dash_to_null(a[3]), + .options = empty_or_dash_to_null(a[4]), + }; + + if (verify && verify(&xc) < 0) + continue; + + return x11_context_copy(ret, &xc); + } + + /* No custom mapping has been found, see if the keymap is a converted one. In such case deducing the + * corresponding x11 layout is easy. */ + _cleanup_free_ char *xlayout = NULL, *converted = NULL; + char *xvariant; + + xlayout = strdup(vc->keymap); + if (!xlayout) + return -ENOMEM; + xvariant = strchr(xlayout, '-'); + if (xvariant) { + xvariant[0] = '\0'; + xvariant++; + } + + /* Note: by default we use keyboard model "microsoftpro" which should be equivalent to "pc105" but + * with the internet/media key mapping added. */ + xc = (X11Context) { + .layout = xlayout, + .model = (char*) "microsoftpro", + .variant = xvariant, + .options = (char*) "terminate:ctrl_alt_bksp", + }; + + /* This sanity check seems redundant with the verification of the X11 layout done on the next + * step. However xkbcommon is an optional dependency hence the verification might be a NOP. */ + r = find_converted_keymap(&xc, &converted); + if (r == 0 && xc.variant) { + /* If we still haven't find a match, try with no variant, it's still better than nothing. */ + xc.variant = NULL; + r = find_converted_keymap(&xc, &converted); + } + if (r < 0) + return r; + + if (r == 0 || (verify && verify(&xc) < 0)) { + *ret = (X11Context) {}; + return 0; + } + + return x11_context_copy(ret, &xc); +} + +int find_converted_keymap(const X11Context *xc, char **ret) { + _cleanup_free_ char *n = NULL, *p = NULL, *pz = NULL; + _cleanup_strv_free_ char **keymap_dirs = NULL; + int r; + + assert(xc); + assert(!isempty(xc->layout)); + assert(ret); + + if (xc->variant) + n = strjoin(xc->layout, "-", xc->variant); + else + n = strdup(xc->layout); + if (!n) + return -ENOMEM; + + p = strjoin("xkb/", n, ".map"); + pz = strjoin("xkb/", n, ".map.gz"); + if (!p || !pz) + return -ENOMEM; + + r = keymap_directories(&keymap_dirs); + if (r < 0) + return r; + + STRV_FOREACH(dir, keymap_dirs) { + _cleanup_close_ int dir_fd = -EBADF; + bool uncompressed; + + dir_fd = open(*dir, O_CLOEXEC | O_DIRECTORY | O_PATH); + if (dir_fd < 0) { + if (errno != ENOENT) + log_debug_errno(errno, "Failed to open %s, ignoring: %m", *dir); + continue; + } + + uncompressed = faccessat(dir_fd, p, F_OK, 0) >= 0; + if (uncompressed || faccessat(dir_fd, pz, F_OK, 0) >= 0) { + log_debug("Found converted keymap %s at %s/%s", n, *dir, uncompressed ? p : pz); + *ret = TAKE_PTR(n); + return 1; + } + } + + *ret = NULL; + return 0; +} + +int find_legacy_keymap(const X11Context *xc, char **ret) { + const char *map; + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *new_keymap = NULL; + unsigned best_matching = 0; + int r; + + assert(xc); + assert(!isempty(xc->layout)); + + map = systemd_kbd_model_map(); + f = fopen(map, "re"); + if (!f) + return -errno; + + for (unsigned n = 0;;) { + _cleanup_strv_free_ char **a = NULL; + unsigned matching = 0; + + r = read_next_mapping(map, 5, UINT_MAX, f, &n, &a); + if (r < 0) + return r; + if (r == 0) + break; + + /* Determine how well matching this entry is */ + if (streq(xc->layout, a[1])) + /* If we got an exact match, this is the best */ + matching = 10; + else { + /* see if we get an exact match with the order reversed */ + _cleanup_strv_free_ char **b = NULL; + _cleanup_free_ char *c = NULL; + r = strv_split_full(&b, a[1], ",", 0); + if (r < 0) + return r; + strv_reverse(b); + c = strv_join(b, ","); + if (!c) + return log_oom(); + if (streq(xc->layout, c)) + matching = 9; + else { + /* We have multiple X layouts, look for an + * entry that matches our key with everything + * but the first layout stripped off. */ + if (startswith_comma(xc->layout, a[1])) + matching = 5; + else { + _cleanup_free_ char *x = NULL; + + /* If that didn't work, strip off the + * other layouts from the entry, too */ + x = strdupcspn(a[1], ","); + if (!x) + return -ENOMEM; + if (startswith_comma(xc->layout, x)) + matching = 1; + } + } + } + + if (matching > 0) { + if (isempty(xc->model) || streq_ptr(xc->model, a[2])) { + matching++; + + if (streq_ptr(xc->variant, a[3]) || ((isempty(xc->variant) || streq_skip_trailing_chars(xc->variant, "", ",")) && streq(a[3], "-"))) { + matching++; + + if (streq_ptr(xc->options, a[4])) + matching++; + } + } + } + + /* The best matching entry so far, then let's save that */ + if (matching >= MAX(best_matching, 1u)) { + log_debug("Found legacy keymap %s with score %u", a[0], matching); + + if (matching > best_matching) { + best_matching = matching; + + r = free_and_strdup(&new_keymap, a[0]); + if (r < 0) + return r; + } + } + } + + if (best_matching < 9 && !isempty(xc->layout)) { + _cleanup_free_ char *l = NULL, *v = NULL, *converted = NULL; + + /* The best match is only the first part of the X11 + * keymap. Check if we have a converted map which + * matches just the first layout. + */ + + l = strdupcspn(xc->layout, ","); + if (!l) + return -ENOMEM; + + if (!isempty(xc->variant)) { + v = strdupcspn(xc->variant, ","); + if (!v) + return -ENOMEM; + } + + r = find_converted_keymap( + &(X11Context) { + .layout = l, + .variant = v, + }, + &converted); + if (r < 0) + return r; + if (r > 0) + free_and_replace(new_keymap, converted); + } + + *ret = TAKE_PTR(new_keymap); + return !!*ret; +} + +int x11_convert_to_vconsole(const X11Context *xc, VCContext *ret) { + _cleanup_free_ char *keymap = NULL; + int r; + + assert(xc); + assert(ret); + + if (isempty(xc->layout)) { + *ret = (VCContext) {}; + return 0; + } + + r = find_converted_keymap(xc, &keymap); + if (r == 0) { + r = find_legacy_keymap(xc, &keymap); + if (r == 0 && xc->variant) + /* If we still haven't find a match, try with no variant, it's still better than + * nothing. */ + r = find_converted_keymap( + &(X11Context) { + .layout = xc->layout, + }, + &keymap); + } + if (r < 0) + return r; + + *ret = (VCContext) { + .keymap = TAKE_PTR(keymap), + }; + return 0; +} + +int find_language_fallback(const char *lang, char **ret) { + const char *map; + _cleanup_fclose_ FILE *f = NULL; + unsigned n = 0; + int r; + + assert(lang); + assert(ret); + + map = systemd_language_fallback_map(); + f = fopen(map, "re"); + if (!f) + return -errno; + + for (;;) { + _cleanup_strv_free_ char **a = NULL; + + r = read_next_mapping(map, 2, 2, f, &n, &a); + if (r <= 0) + return r; + + if (streq(lang, a[0])) { + assert(strv_length(a) == 2); + *ret = TAKE_PTR(a[1]); + return 1; + } + } +} + +int vconsole_serialize(const VCContext *vc, const X11Context *xc, char ***env) { + int r; + + /* This function modifies the passed strv in place. */ + + assert(vc); + assert(xc); + assert(env); + + r = strv_env_assign(env, "KEYMAP", empty_to_null(vc->keymap)); + if (r < 0) + return r; + + r = strv_env_assign(env, "KEYMAP_TOGGLE", empty_to_null(vc->toggle)); + if (r < 0) + return r; + + r = strv_env_assign(env, "XKBLAYOUT", empty_to_null(xc->layout)); + if (r < 0) + return r; + + r = strv_env_assign(env, "XKBMODEL", empty_to_null(xc->model)); + if (r < 0) + return r; + + r = strv_env_assign(env, "XKBVARIANT", empty_to_null(xc->variant)); + if (r < 0) + return r; + + r = strv_env_assign(env, "XKBOPTIONS", empty_to_null(xc->options)); + if (r < 0) + return r; + + return 0; +} diff --git a/src/shared/vconsole-util.h b/src/shared/vconsole-util.h new file mode 100644 index 00000000000..c9acc97b298 --- /dev/null +++ b/src/shared/vconsole-util.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include + +typedef struct X11Context { + char *layout; + char *model; + char *variant; + char *options; +} X11Context; + +typedef struct VCContext { + char *keymap; + char *toggle; +} VCContext; + +void x11_context_clear(X11Context *xc); +void x11_context_replace(X11Context *dest, X11Context *src); +bool x11_context_isempty(const X11Context *xc); +void x11_context_empty_to_null(X11Context *xc); +bool x11_context_is_safe(const X11Context *xc); +bool x11_context_equal(const X11Context *a, const X11Context *b); +int x11_context_copy(X11Context *dest, const X11Context *src); + +int find_converted_keymap(const X11Context *xc, char **ret); +int find_legacy_keymap(const X11Context *xc, char **ret); +int find_language_fallback(const char *lang, char **ret); + +void vc_context_clear(VCContext *vc); +void vc_context_replace(VCContext *dest, VCContext *src); +bool vc_context_isempty(const VCContext *vc); +void vc_context_empty_to_null(VCContext *vc); +bool vc_context_equal(const VCContext *a, const VCContext *b); +int vc_context_copy(VCContext *dest, const VCContext *src); + +typedef int (*X11VerifyCallback)(const X11Context *xc); + +int vconsole_convert_to_x11(const VCContext *vc, X11VerifyCallback verify, X11Context *ret); +int x11_convert_to_vconsole(const X11Context *xc, VCContext *ret); + +int vconsole_serialize(const VCContext *vc, const X11Context *xc, char ***ret);