]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
locale: Move vconsole specific logic to shared/vconsole-util.h
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Wed, 5 Feb 2025 12:17:13 +0000 (13:17 +0100)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Fri, 7 Feb 2025 00:18:36 +0000 (09:18 +0900)
This allows reusing the logic in systemd-firstboot.c.

To avoid having to link libxkbcommon into libsystemd-shared, we add
a level of indirection to vconsole_convert_to_x11() so that the verify
function is passed in by the caller.

src/locale/localed-util.c
src/locale/localed-util.h
src/locale/localed.c
src/locale/test-localed-util.c
src/shared/meson.build
src/shared/vconsole-util.c [new file with mode: 0644]
src/shared/vconsole-util.h [new file with mode: 0644]

index 6413288ea3d5f85345b75cde6e035d274d771f23..7ff8676761efe118f666d0aff2f8d948d8b5c398 100644 (file)
@@ -5,19 +5,15 @@
 #include <sys/types.h>
 #include <unistd.h>
 
-#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"
 #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;
 
@@ -599,373 +398,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) {
index 0c68f2942ee58cdbae9ccfdc80c7698c723b5524..8a279c5e561fa1d0a5b2a39a16b80f0f60d84e6e 100644 (file)
@@ -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);
index 062744519db3c7092cf83571859ebc8efc587c73..54ab210bcd0b2ab579ea04824713f3c407d4ad7c 100644 (file)
@@ -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");
index e92c178a980b93e028a7ed0398622dd531249405..ae5e1f85d9228c0a369545255e72a8e9aeca35b4 100644 (file)
@@ -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);
index 5936e20de6f66eef2052cceb3f9613d3ffe13913..ed7cad7c883c2a4d02ef9e338f76c88c7c917d77 100644 (file)
@@ -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 (file)
index 0000000..080d280
--- /dev/null
@@ -0,0 +1,572 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#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;
+                }
+        }
+}
diff --git a/src/shared/vconsole-util.h b/src/shared/vconsole-util.h
new file mode 100644 (file)
index 0000000..2212a90
--- /dev/null
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <stdbool.h>
+
+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);