[systemd-devel] [PATCH] localed: validate set-x11-keymap input

Jan Synacek jsynacek at redhat.com
Tue Nov 4 03:05:48 PST 2014


Try to validate the input similarly to how setxkbmap does it. Multiple
layouts and variants can be specified, separated by a comma. Variants
can also be left out, meaning that the user doesn't want any particular
variant for the respective layout.

Variants are validated respectively to their layouts. First variant
validates against first layout, second variant to second layout, etc. If
there are more entries of either layouts or variants, only their
respective counterparts are validated, the rest is ignored.

Examples:
$ set-x11-keymap us,cz  pc105 ,qwerty
"us" is not validated, because no respective variant was specified. "cz"
is checked for an existing "qwerty" variant, the check succeeds.

$ set-x11-keymap us pc105 ,qwerty
"us" is not validated as in the above example. The rest of the variants
is ignored, because there are no respective layouts.

$ set-x11-keymap us,cz pc105 qwerty
"us" is validated against the "qwerty" variant, check fails, because
there is no "qwerty" variant for the "us" layout.

$ set-x11-keymap us,cz pc105 euro,qwerty
Validation succeeds, there is the "euro" variant for the "us" layout,
and "qwerty" variant for the "cz" layout.

http://lists.freedesktop.org/archives/systemd-devel/2014-October/024411.html
---
 Makefile.am            |   2 +
 src/locale/localectl.c |  77 ++------------------
 src/locale/localed.c   |   8 ++
 src/shared/xkb-util.c  | 194 +++++++++++++++++++++++++++++++++++++++++++++++++
 src/shared/xkb-util.h  |  39 ++++++++++
 5 files changed, 248 insertions(+), 72 deletions(-)
 create mode 100644 src/shared/xkb-util.c
 create mode 100644 src/shared/xkb-util.h

diff --git a/Makefile.am b/Makefile.am
index ff5f61b..f17bec6 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -770,6 +770,8 @@ libsystemd_shared_la_SOURCES = \
 	src/shared/time-util.h \
 	src/shared/locale-util.c \
 	src/shared/locale-util.h \
+	src/shared/xkb-util.c \
+	src/shared/xkb-util.h \
 	src/shared/mempool.c \
 	src/shared/mempool.h \
 	src/shared/hashmap.c \
diff --git a/src/locale/localectl.c b/src/locale/localectl.c
index d4a2d29..8f9e4da 100644
--- a/src/locale/localectl.c
+++ b/src/locale/localectl.c
@@ -46,6 +46,7 @@
 #include "virt.h"
 #include "fileio.h"
 #include "locale-util.h"
+#include "xkb-util.h"
 
 static bool arg_no_pager = false;
 static bool arg_ask_password = true;
@@ -389,14 +390,7 @@ static int set_x11_keymap(sd_bus *bus, char **args, unsigned n) {
 static int list_x11_keymaps(sd_bus *bus, char **args, unsigned n) {
         _cleanup_fclose_ FILE *f = NULL;
         _cleanup_strv_free_ char **list = NULL;
-        char line[LINE_MAX];
-        enum {
-                NONE,
-                MODELS,
-                LAYOUTS,
-                VARIANTS,
-                OPTIONS
-        } state = NONE, look_for;
+        enum keymap_state look_for;
         int r;
 
         if (n > 2) {
@@ -404,12 +398,6 @@ static int list_x11_keymaps(sd_bus *bus, char **args, unsigned n) {
                 return -EINVAL;
         }
 
-        f = fopen("/usr/share/X11/xkb/rules/base.lst", "re");
-        if (!f) {
-                log_error("Failed to open keyboard mapping list. %m");
-                return -errno;
-        }
-
         if (streq(args[0], "list-x11-keymap-models"))
                 look_for = MODELS;
         else if (streq(args[0], "list-x11-keymap-layouts"))
@@ -421,64 +409,9 @@ static int list_x11_keymaps(sd_bus *bus, char **args, unsigned n) {
         else
                 assert_not_reached("Wrong parameter");
 
-        FOREACH_LINE(line, f, break) {
-                char *l, *w;
-
-                l = strstrip(line);
-
-                if (isempty(l))
-                        continue;
-
-                if (l[0] == '!') {
-                        if (startswith(l, "! model"))
-                                state = MODELS;
-                        else if (startswith(l, "! layout"))
-                                state = LAYOUTS;
-                        else if (startswith(l, "! variant"))
-                                state = VARIANTS;
-                        else if (startswith(l, "! option"))
-                                state = OPTIONS;
-                        else
-                                state = NONE;
-
-                        continue;
-                }
-
-                if (state != look_for)
-                        continue;
-
-                w = l + strcspn(l, WHITESPACE);
-
-                if (n > 1) {
-                        char *e;
-
-                        if (*w == 0)
-                                continue;
-
-                        *w = 0;
-                        w++;
-                        w += strspn(w, WHITESPACE);
-
-                        e = strchr(w, ':');
-                        if (!e)
-                                continue;
-
-                        *e = 0;
-
-                        if (!streq(w, args[1]))
-                                continue;
-                } else
-                        *w = 0;
-
-                 r = strv_extend(&list, l);
-                 if (r < 0)
-                         return log_oom();
-        }
-
-        if (strv_isempty(list)) {
-                log_error("Couldn't find any entries.");
-                return -ENOENT;
-        }
+        r = xkb_get_keymaps(&list, look_for, (n > 1) ? args[1] : NULL);
+        if (r < 0)
+                return r;
 
         strv_sort(list);
         strv_uniq(list);
diff --git a/src/locale/localed.c b/src/locale/localed.c
index 552ffdf..508ba0d 100644
--- a/src/locale/localed.c
+++ b/src/locale/localed.c
@@ -40,6 +40,7 @@
 #include "bus-message.h"
 #include "event-util.h"
 #include "locale-util.h"
+#include "xkb-util.h"
 
 enum {
         /* We don't list LC_ALL here on purpose. People should be
@@ -1031,6 +1032,7 @@ static int method_set_x11_keyboard(sd_bus *bus, sd_bus_message *m, void *userdat
             !streq_ptr(model, c->x11_model) ||
             !streq_ptr(variant, c->x11_variant) ||
             !streq_ptr(options, c->x11_options)) {
+                _cleanup_free_ char *msg = NULL;
 
                 if ((layout && !string_is_safe(layout)) ||
                     (model && !string_is_safe(model)) ||
@@ -1050,6 +1052,12 @@ static int method_set_x11_keyboard(sd_bus *bus, sd_bus_message *m, void *userdat
                     free_and_strdup(&c->x11_options, options) < 0)
                         return -ENOMEM;
 
+                r = xkb_validate_keymaps(model, layout, variant, options, &msg);
+                if (r < 0) {
+                        log_error("Failed to validate X11 keyboard layout: %s", strerror(-r));
+                        return sd_bus_error_set(error, SD_BUS_ERROR_FAILED, msg);
+                }
+
                 r = x11_write_data(c);
                 if (r < 0) {
                         log_error("Failed to set X11 keyboard layout: %s", strerror(-r));
diff --git a/src/shared/xkb-util.c b/src/shared/xkb-util.c
new file mode 100644
index 0000000..68be3d3
--- /dev/null
+++ b/src/shared/xkb-util.c
@@ -0,0 +1,194 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2014 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "xkb-util.h"
+
+static char **xkb_parse_argument(const char *arg)
+{
+        _cleanup_free_ char *input;
+        char *token;
+        char **result = NULL;
+        int r;
+
+        assert(arg);
+
+        input = strdup(arg);
+        if (!input)
+                return NULL;
+
+        token = strsep(&input, ",");
+        while(token) {
+                r = strv_extend(&result, token);
+                if (r < 0)
+                        return NULL;
+                token = strsep(&input, ",");
+        }
+
+        return result;
+}
+
+int xkb_get_keymaps(char ***list, enum keymap_state look_for, const char *layout_prefix)
+{
+        _cleanup_fclose_ FILE *f;
+        char line[LINE_MAX];
+        enum keymap_state state = NONE;
+        int r;
+
+        f = fopen("/usr/share/X11/xkb/rules/base.lst", "re");
+        if (!f) {
+                log_error("Failed to open keyboard mapping list. %m");
+                return -errno;
+        }
+
+        FOREACH_LINE(line, f, break) {
+                char *l, *w;
+
+                l = strstrip(line);
+
+                if (isempty(l))
+                        continue;
+
+                if (l[0] == '!') {
+                        if (startswith(l, "! model"))
+                                state = MODELS;
+                        else if (startswith(l, "! layout"))
+                                state = LAYOUTS;
+                        else if (startswith(l, "! variant"))
+                                state = VARIANTS;
+                        else if (startswith(l, "! option"))
+                                state = OPTIONS;
+                        else
+                                state = NONE;
+
+                        continue;
+                }
+
+                if (state != look_for)
+                        continue;
+
+                w = l + strcspn(l, WHITESPACE);
+
+                if (layout_prefix) {
+                        char *e;
+
+                        if (*w == 0)
+                                continue;
+
+                        *w = 0;
+                        w++;
+                        w += strspn(w, WHITESPACE);
+
+                        e = strchr(w, ':');
+                        if (!e)
+                                continue;
+
+                        *e = 0;
+
+                        if (!streq(w, layout_prefix))
+                                continue;
+                } else
+                        *w = 0;
+
+                r = strv_extend(list, l);
+                if (r < 0)
+                        return log_oom();
+        }
+
+        if (strv_isempty(*list)) {
+                log_error("Couldn't find any entries."); /* TODO: improve error message */
+                return -ENOENT;
+        }
+
+        return 0;
+}
+
+int xkb_validate_keymaps(const char *model,
+                         const char *layouts_arg,
+                         const char *variants_arg,
+                         const char *options_arg,
+                         char **error)
+{
+        int r;
+
+        if (layouts_arg) {
+                _cleanup_strv_free_ char **layouts_list = NULL;
+                char **it, **layouts;
+                int i = 0;
+
+                r = xkb_get_keymaps(&layouts_list, LAYOUTS, NULL);
+                if (r < 0)
+                        return r;
+
+                layouts = strv_split(layouts_arg, ",");
+                if (!layouts)
+                        return log_oom();
+
+                STRV_FOREACH(it, layouts) {
+                        _cleanup_strv_free_ char **variants_list = NULL;
+                        bool variants_left = true;
+                        int n;
+
+                        if (!strv_find(layouts_list, *it)) {
+                                r = asprintf(error, "Requested layout '%s' not available.\n", *it);
+                                if (r < 0)
+                                        return log_oom();
+                                return -EINVAL;
+                        }
+
+                        if (variants_left && variants_arg) {
+                                _cleanup_strv_free_ char **variants;
+
+                                r = xkb_get_keymaps(&variants_list, VARIANTS, *it);
+                                if (r < 0)
+                                        return r;
+
+                                variants = xkb_parse_argument(variants_arg);
+                                if (!variants)
+                                        return log_oom();
+
+                                n = strv_length(variants);
+                                /* Layout doesn't have an existing variant, no need to check for it. */
+                                if (n == 0)
+                                        goto next;
+                                /* More layouts than variants, no need to look at variants anymore. */
+                                if (i >= n) {
+                                        variants_left = false;
+                                        goto next;
+                                }
+                                /* Variant left out, skip it. */
+                                if (streq(variants[i], ""))
+                                        goto next;
+
+                                if (!strv_find(variants_list, variants[i])) {
+                                        r = asprintf(error, "Requested variant '%s' for layout '%s' not available.\n", variants[i], *it);
+                                        if (r < 0)
+                                                return log_oom();
+                                        return -EINVAL;
+                                }
+                        }
+                next:
+                        ++i;
+                }
+        }
+        /* Since setxkbmap doesn't validate model and options, we
+           don't either. It might be good to add the check, though. */
+        return 0;
+}
diff --git a/src/shared/xkb-util.h b/src/shared/xkb-util.h
new file mode 100644
index 0000000..9616dad
--- /dev/null
+++ b/src/shared/xkb-util.h
@@ -0,0 +1,39 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+  This file is part of systemd.
+
+  Copyright 2014 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+
+#include "macro.h"
+#include "strv.h"
+#include "util.h"
+
+enum keymap_state {
+        NONE,
+        MODELS,
+        LAYOUTS,
+        VARIANTS,
+        OPTIONS
+};
+
+int xkb_get_keymaps(char ***list, enum keymap_state look_for, const char *layout_prefix);
+int xkb_validate_keymaps(const char *model, const char *layouts_arg, const char *variants_arg, const char *options_arg, char **error);
-- 
1.9.3



More information about the systemd-devel mailing list