1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2011 Lennart Poettering
7 Copyright 2013 Kay Sievers
9 systemd is free software; you can redistribute it and/or modify it
10 under the terms of the GNU Lesser General Public License as published by
11 the Free Software Foundation; either version 2.1 of the License, or
12 (at your option) any later version.
14 systemd is distributed in the hope that it will be useful, but
15 WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 Lesser General Public License for more details.
19 You should have received a copy of the GNU Lesser General Public License
20 along with systemd; If not, see <http://www.gnu.org/licenses/>.
35 #include "fileio-label.h"
37 #include "bus-error.h"
38 #include "bus-message.h"
39 #include "event-util.h"
40 #include "locale-util.h"
41 #include "selinux-util.h"
44 #include <xkbcommon/xkbcommon.h>
48 /* We don't list LC_ALL here on purpose. People should be
49 * using LANG instead. */
62 LOCALE_LC_MEASUREMENT,
63 LOCALE_LC_IDENTIFICATION,
67 static const char * const names[_LOCALE_MAX] = {
68 [LOCALE_LANG] = "LANG",
69 [LOCALE_LANGUAGE] = "LANGUAGE",
70 [LOCALE_LC_CTYPE] = "LC_CTYPE",
71 [LOCALE_LC_NUMERIC] = "LC_NUMERIC",
72 [LOCALE_LC_TIME] = "LC_TIME",
73 [LOCALE_LC_COLLATE] = "LC_COLLATE",
74 [LOCALE_LC_MONETARY] = "LC_MONETARY",
75 [LOCALE_LC_MESSAGES] = "LC_MESSAGES",
76 [LOCALE_LC_PAPER] = "LC_PAPER",
77 [LOCALE_LC_NAME] = "LC_NAME",
78 [LOCALE_LC_ADDRESS] = "LC_ADDRESS",
79 [LOCALE_LC_TELEPHONE] = "LC_TELEPHONE",
80 [LOCALE_LC_MEASUREMENT] = "LC_MEASUREMENT",
81 [LOCALE_LC_IDENTIFICATION] = "LC_IDENTIFICATION"
84 typedef struct Context {
85 char *locale[_LOCALE_MAX];
93 char *vc_keymap_toggle;
95 Hashmap *polkit_registry;
98 static const char* nonempty(const char *s) {
99 return isempty(s) ? NULL : s;
102 static void free_and_replace(char **s, char *v) {
107 static bool startswith_comma(const char *s, const char *prefix) {
110 return s && (t = startswith(s, prefix)) && (*t == ',');
113 static void context_free_x11(Context *c) {
114 free_and_replace(&c->x11_layout, NULL);
115 free_and_replace(&c->x11_model, NULL);
116 free_and_replace(&c->x11_variant, NULL);
117 free_and_replace(&c->x11_options, NULL);
120 static void context_free_vconsole(Context *c) {
121 free_and_replace(&c->vc_keymap, NULL);
122 free_and_replace(&c->vc_keymap_toggle, NULL);
125 static void context_free_locale(Context *c) {
128 for (p = 0; p < _LOCALE_MAX; p++)
129 free_and_replace(&c->locale[p], NULL);
132 static void context_free(Context *c) {
133 context_free_locale(c);
135 context_free_vconsole(c);
137 bus_verify_polkit_async_registry_free(c->polkit_registry);
140 static void locale_simplify(Context *c) {
143 for (p = LOCALE_LANG+1; p < _LOCALE_MAX; p++)
144 if (isempty(c->locale[p]) || streq_ptr(c->locale[LOCALE_LANG], c->locale[p]))
145 free_and_replace(&c->locale[p], NULL);
148 static int locale_read_data(Context *c) {
151 context_free_locale(c);
153 r = parse_env_file("/etc/locale.conf", NEWLINE,
154 "LANG", &c->locale[LOCALE_LANG],
155 "LANGUAGE", &c->locale[LOCALE_LANGUAGE],
156 "LC_CTYPE", &c->locale[LOCALE_LC_CTYPE],
157 "LC_NUMERIC", &c->locale[LOCALE_LC_NUMERIC],
158 "LC_TIME", &c->locale[LOCALE_LC_TIME],
159 "LC_COLLATE", &c->locale[LOCALE_LC_COLLATE],
160 "LC_MONETARY", &c->locale[LOCALE_LC_MONETARY],
161 "LC_MESSAGES", &c->locale[LOCALE_LC_MESSAGES],
162 "LC_PAPER", &c->locale[LOCALE_LC_PAPER],
163 "LC_NAME", &c->locale[LOCALE_LC_NAME],
164 "LC_ADDRESS", &c->locale[LOCALE_LC_ADDRESS],
165 "LC_TELEPHONE", &c->locale[LOCALE_LC_TELEPHONE],
166 "LC_MEASUREMENT", &c->locale[LOCALE_LC_MEASUREMENT],
167 "LC_IDENTIFICATION", &c->locale[LOCALE_LC_IDENTIFICATION],
173 /* Fill in what we got passed from systemd. */
174 for (p = 0; p < _LOCALE_MAX; p++) {
177 r = free_and_strdup(&c->locale[p],
178 nonempty(getenv(names[p])));
190 static int vconsole_read_data(Context *c) {
193 context_free_vconsole(c);
195 r = parse_env_file("/etc/vconsole.conf", NEWLINE,
196 "KEYMAP", &c->vc_keymap,
197 "KEYMAP_TOGGLE", &c->vc_keymap_toggle,
200 if (r < 0 && r != -ENOENT)
206 static int x11_read_data(Context *c) {
207 _cleanup_fclose_ FILE *f;
209 bool in_section = false;
214 f = fopen("/etc/X11/xorg.conf.d/00-keyboard.conf", "re");
216 return errno == ENOENT ? 0 : -errno;
218 while (fgets(line, sizeof(line), f)) {
224 if (l[0] == 0 || l[0] == '#')
227 if (in_section && first_word(l, "Option")) {
228 _cleanup_strv_free_ char **a = NULL;
230 r = strv_split_quoted(&a, l, false);
234 if (strv_length(a) == 3) {
235 if (streq(a[1], "XkbLayout")) {
236 free_and_replace(&c->x11_layout, a[2]);
238 } else if (streq(a[1], "XkbModel")) {
239 free_and_replace(&c->x11_model, a[2]);
241 } else if (streq(a[1], "XkbVariant")) {
242 free_and_replace(&c->x11_variant, a[2]);
244 } else if (streq(a[1], "XkbOptions")) {
245 free_and_replace(&c->x11_options, a[2]);
250 } else if (!in_section && first_word(l, "Section")) {
251 _cleanup_strv_free_ char **a = NULL;
253 r = strv_split_quoted(&a, l, false);
257 if (strv_length(a) == 2 && streq(a[1], "InputClass"))
260 } else if (in_section && first_word(l, "EndSection"))
267 static int context_read_data(Context *c) {
270 r = locale_read_data(c);
271 q = vconsole_read_data(c);
272 p = x11_read_data(c);
274 return r < 0 ? r : q < 0 ? q : p;
277 static int locale_write_data(Context *c, char ***settings) {
279 _cleanup_strv_free_ char **l = NULL;
281 /* Set values will be returned as strv in *settings on success. */
283 r = load_env_file(NULL, "/etc/locale.conf", NULL, &l);
284 if (r < 0 && r != -ENOENT)
287 for (p = 0; p < _LOCALE_MAX; p++) {
288 _cleanup_free_ char *t = NULL;
293 if (isempty(c->locale[p])) {
294 l = strv_env_unset(l, names[p]);
298 if (asprintf(&t, "%s=%s", names[p], c->locale[p]) < 0)
301 u = strv_env_set(l, t);
309 if (strv_isempty(l)) {
310 if (unlink("/etc/locale.conf") < 0)
311 return errno == ENOENT ? 0 : -errno;
316 r = write_env_file_label("/etc/locale.conf", l);
325 static int locale_update_system_manager(Context *c, sd_bus *bus) {
326 _cleanup_free_ char **l_unset = NULL;
327 _cleanup_strv_free_ char **l_set = NULL;
328 _cleanup_bus_message_unref_ sd_bus_message *m = NULL;
329 sd_bus_error error = SD_BUS_ERROR_NULL;
330 unsigned c_set, c_unset, p;
335 l_unset = new0(char*, _LOCALE_MAX);
339 l_set = new0(char*, _LOCALE_MAX);
343 for (p = 0, c_set = 0, c_unset = 0; p < _LOCALE_MAX; p++) {
346 if (isempty(c->locale[p]))
347 l_unset[c_set++] = (char*) names[p];
351 if (asprintf(&s, "%s=%s", names[p], c->locale[p]) < 0)
354 l_set[c_unset++] = s;
358 assert(c_set + c_unset == _LOCALE_MAX);
359 r = sd_bus_message_new_method_call(bus, &m,
360 "org.freedesktop.systemd1",
361 "/org/freedesktop/systemd1",
362 "org.freedesktop.systemd1.Manager",
363 "UnsetAndSetEnvironment");
367 r = sd_bus_message_append_strv(m, l_unset);
371 r = sd_bus_message_append_strv(m, l_set);
375 r = sd_bus_call(bus, m, 0, &error, NULL);
377 log_error_errno(r, "Failed to update the manager environment: %m");
382 static int vconsole_write_data(Context *c) {
384 _cleanup_strv_free_ char **l = NULL;
386 r = load_env_file(NULL, "/etc/vconsole.conf", NULL, &l);
387 if (r < 0 && r != -ENOENT)
390 if (isempty(c->vc_keymap))
391 l = strv_env_unset(l, "KEYMAP");
393 _cleanup_free_ char *s = NULL;
396 s = strappend("KEYMAP=", c->vc_keymap);
400 u = strv_env_set(l, s);
408 if (isempty(c->vc_keymap_toggle))
409 l = strv_env_unset(l, "KEYMAP_TOGGLE");
411 _cleanup_free_ char *s = NULL;
414 s = strappend("KEYMAP_TOGGLE=", c->vc_keymap_toggle);
418 u = strv_env_set(l, s);
426 if (strv_isempty(l)) {
427 if (unlink("/etc/vconsole.conf") < 0)
428 return errno == ENOENT ? 0 : -errno;
433 return write_env_file_label("/etc/vconsole.conf", l);
436 static int x11_write_data(Context *c) {
437 _cleanup_fclose_ FILE *f = NULL;
438 _cleanup_free_ char *temp_path = NULL;
441 if (isempty(c->x11_layout) &&
442 isempty(c->x11_model) &&
443 isempty(c->x11_variant) &&
444 isempty(c->x11_options)) {
446 if (unlink("/etc/X11/xorg.conf.d/00-keyboard.conf") < 0)
447 return errno == ENOENT ? 0 : -errno;
452 mkdir_p_label("/etc/X11/xorg.conf.d", 0755);
454 r = fopen_temporary("/etc/X11/xorg.conf.d/00-keyboard.conf", &f, &temp_path);
458 fchmod(fileno(f), 0644);
460 fputs("# Read and parsed by systemd-localed. It's probably wise not to edit this file\n"
461 "# manually too freely.\n"
462 "Section \"InputClass\"\n"
463 " Identifier \"system-keyboard\"\n"
464 " MatchIsKeyboard \"on\"\n", f);
466 if (!isempty(c->x11_layout))
467 fprintf(f, " Option \"XkbLayout\" \"%s\"\n", c->x11_layout);
469 if (!isempty(c->x11_model))
470 fprintf(f, " Option \"XkbModel\" \"%s\"\n", c->x11_model);
472 if (!isempty(c->x11_variant))
473 fprintf(f, " Option \"XkbVariant\" \"%s\"\n", c->x11_variant);
475 if (!isempty(c->x11_options))
476 fprintf(f, " Option \"XkbOptions\" \"%s\"\n", c->x11_options);
478 fputs("EndSection\n", f);
481 if (ferror(f) || rename(temp_path, "/etc/X11/xorg.conf.d/00-keyboard.conf") < 0) {
483 unlink("/etc/X11/xorg.conf.d/00-keyboard.conf");
490 static int vconsole_reload(sd_bus *bus) {
491 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
496 r = sd_bus_call_method(bus,
497 "org.freedesktop.systemd1",
498 "/org/freedesktop/systemd1",
499 "org.freedesktop.systemd1.Manager",
503 "ss", "systemd-vconsole-setup.service", "replace");
506 log_error("Failed to issue method call: %s", bus_error_message(&error, -r));
510 static const char* strnulldash(const char *s) {
511 return isempty(s) || streq(s, "-") ? NULL : s;
514 static int read_next_mapping(const char* filename,
515 unsigned min_fields, unsigned max_fields,
516 FILE *f, unsigned *n, char ***a) {
528 if (!fgets(line, sizeof(line), f)) {
531 return errno ? -errno : -EIO;
539 if (l[0] == 0 || l[0] == '#')
542 r = strv_split_quoted(&b, l, false);
546 length = strv_length(b);
547 if (length < min_fields || length > max_fields) {
548 log_error("Invalid line %s:%u, ignoring.", filename, *n);
559 static int vconsole_convert_to_x11(Context *c, sd_bus *bus) {
560 bool modified = false;
564 if (isempty(c->vc_keymap)) {
567 !isempty(c->x11_layout) ||
568 !isempty(c->x11_model) ||
569 !isempty(c->x11_variant) ||
570 !isempty(c->x11_options);
574 _cleanup_fclose_ FILE *f = NULL;
577 f = fopen(SYSTEMD_KBD_MODEL_MAP, "re");
582 _cleanup_strv_free_ char **a = NULL;
585 r = read_next_mapping(SYSTEMD_KBD_MODEL_MAP, 5, UINT_MAX, f, &n, &a);
591 if (!streq(c->vc_keymap, a[0]))
594 if (!streq_ptr(c->x11_layout, strnulldash(a[1])) ||
595 !streq_ptr(c->x11_model, strnulldash(a[2])) ||
596 !streq_ptr(c->x11_variant, strnulldash(a[3])) ||
597 !streq_ptr(c->x11_options, strnulldash(a[4]))) {
599 if (free_and_strdup(&c->x11_layout, strnulldash(a[1])) < 0 ||
600 free_and_strdup(&c->x11_model, strnulldash(a[2])) < 0 ||
601 free_and_strdup(&c->x11_variant, strnulldash(a[3])) < 0 ||
602 free_and_strdup(&c->x11_options, strnulldash(a[4])) < 0)
615 r = x11_write_data(c);
617 return log_error_errno(r, "Failed to set X11 keyboard layout: %m");
619 log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
620 strempty(c->x11_layout),
621 strempty(c->x11_model),
622 strempty(c->x11_variant),
623 strempty(c->x11_options));
625 sd_bus_emit_properties_changed(bus,
626 "/org/freedesktop/locale1",
627 "org.freedesktop.locale1",
628 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL);
630 log_debug("X11 keyboard layout was not modified.");
635 static int find_converted_keymap(const char *x11_layout, const char *x11_variant, char **new_keymap) {
637 _cleanup_free_ char *n;
640 n = strjoin(x11_layout, "-", x11_variant, NULL);
642 n = strdup(x11_layout);
646 NULSTR_FOREACH(dir, KBD_KEYMAP_DIRS) {
647 _cleanup_free_ char *p = NULL, *pz = NULL;
650 p = strjoin(dir, "xkb/", n, ".map", NULL);
651 pz = strjoin(dir, "xkb/", n, ".map.gz", NULL);
655 uncompressed = access(p, F_OK) == 0;
656 if (uncompressed || access(pz, F_OK) == 0) {
657 log_debug("Found converted keymap %s at %s",
658 n, uncompressed ? p : pz);
669 static int find_legacy_keymap(Context *c, char **new_keymap) {
670 _cleanup_fclose_ FILE *f;
672 unsigned best_matching = 0;
675 f = fopen(SYSTEMD_KBD_MODEL_MAP, "re");
680 _cleanup_strv_free_ char **a = NULL;
681 unsigned matching = 0;
683 r = read_next_mapping(SYSTEMD_KBD_MODEL_MAP, 5, UINT_MAX, f, &n, &a);
689 /* Determine how well matching this entry is */
690 if (streq_ptr(c->x11_layout, a[1]))
691 /* If we got an exact match, this is best */
694 /* We have multiple X layouts, look for an
695 * entry that matches our key with everything
696 * but the first layout stripped off. */
697 if (startswith_comma(c->x11_layout, a[1]))
702 /* If that didn't work, strip off the
703 * other layouts from the entry, too */
704 x = strndupa(a[1], strcspn(a[1], ","));
705 if (startswith_comma(c->x11_layout, x))
711 if (isempty(c->x11_model) || streq_ptr(c->x11_model, a[2])) {
714 if (streq_ptr(c->x11_variant, a[3])) {
717 if (streq_ptr(c->x11_options, a[4]))
723 /* The best matching entry so far, then let's save that */
724 if (matching >= MAX(best_matching, 1u)) {
725 log_debug("Found legacy keymap %s with score %u",
728 if (matching > best_matching) {
729 best_matching = matching;
731 r = free_and_strdup(new_keymap, a[0]);
738 if (best_matching < 10 && c->x11_layout) {
739 /* The best match is only the first part of the X11
740 * keymap. Check if we have a converted map which
741 * matches just the first layout.
743 char *l, *v = NULL, *converted;
745 l = strndupa(c->x11_layout, strcspn(c->x11_layout, ","));
747 v = strndupa(c->x11_variant, strcspn(c->x11_variant, ","));
748 r = find_converted_keymap(l, v, &converted);
752 free_and_replace(new_keymap, converted);
758 static int find_language_fallback(const char *lang, char **language) {
759 _cleanup_fclose_ FILE *f = NULL;
764 f = fopen(SYSTEMD_LANGUAGE_FALLBACK_MAP, "re");
769 _cleanup_strv_free_ char **a = NULL;
772 r = read_next_mapping(SYSTEMD_LANGUAGE_FALLBACK_MAP, 2, 2, f, &n, &a);
776 if (streq(lang, a[0])) {
777 assert(strv_length(a) == 2);
784 assert_not_reached("should not be here");
787 static int x11_convert_to_vconsole(Context *c, sd_bus *bus) {
788 bool modified = false;
793 if (isempty(c->x11_layout)) {
796 !isempty(c->vc_keymap) ||
797 !isempty(c->vc_keymap_toggle);
801 char *new_keymap = NULL;
803 r = find_converted_keymap(c->x11_layout, c->x11_variant, &new_keymap);
807 r = find_legacy_keymap(c, &new_keymap);
812 if (!streq_ptr(c->vc_keymap, new_keymap)) {
813 free_and_replace(&c->vc_keymap, new_keymap);
814 free_and_replace(&c->vc_keymap_toggle, NULL);
821 r = vconsole_write_data(c);
823 log_error_errno(r, "Failed to set virtual console keymap: %m");
825 log_info("Changed virtual console keymap to '%s' toggle '%s'",
826 strempty(c->vc_keymap), strempty(c->vc_keymap_toggle));
828 sd_bus_emit_properties_changed(bus,
829 "/org/freedesktop/locale1",
830 "org.freedesktop.locale1",
831 "VConsoleKeymap", "VConsoleKeymapToggle", NULL);
833 return vconsole_reload(bus);
835 log_debug("Virtual console keymap was not modified.");
840 static int property_get_locale(
843 const char *interface,
844 const char *property,
845 sd_bus_message *reply,
847 sd_bus_error *error) {
849 Context *c = userdata;
850 _cleanup_strv_free_ char **l = NULL;
853 l = new0(char*, _LOCALE_MAX+1);
857 for (p = 0, q = 0; p < _LOCALE_MAX; p++) {
860 if (isempty(c->locale[p]))
863 if (asprintf(&t, "%s=%s", names[p], c->locale[p]) < 0)
869 return sd_bus_message_append_strv(reply, l);
872 static int method_set_locale(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
873 Context *c = userdata;
874 _cleanup_strv_free_ char **l = NULL;
876 const char *lang = NULL;
878 bool modified = false;
879 bool have[_LOCALE_MAX] = {};
883 r = bus_message_read_strv_extend(m, &l);
887 r = sd_bus_message_read_basic(m, 'b', &interactive);
891 /* Check whether a variable changed and if it is valid */
895 for (p = 0; p < _LOCALE_MAX; p++) {
898 k = strlen(names[p]);
899 if (startswith(*i, names[p]) &&
901 locale_is_valid((*i) + k + 1)) {
905 if (p == LOCALE_LANG)
908 if (!streq_ptr(*i + k + 1, c->locale[p]))
916 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid Locale data.");
919 /* If LANG was specified, but not LANGUAGE, check if we should
920 * set it based on the language fallback table. */
921 if (have[LOCALE_LANG] && !have[LOCALE_LANGUAGE]) {
922 _cleanup_free_ char *language = NULL;
926 (void) find_language_fallback(lang, &language);
928 log_debug("Converted LANG=%s to LANGUAGE=%s", lang, language);
929 if (!streq_ptr(language, c->locale[LOCALE_LANGUAGE])) {
930 r = strv_extendf(&l, "LANGUAGE=%s", language);
934 have[LOCALE_LANGUAGE] = true;
940 /* Check whether a variable is unset */
942 for (p = 0; p < _LOCALE_MAX; p++)
943 if (!isempty(c->locale[p]) && !have[p]) {
949 _cleanup_strv_free_ char **settings = NULL;
951 r = bus_verify_polkit_async(
954 "org.freedesktop.locale1.set-locale",
962 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
965 for (p = 0; p < _LOCALE_MAX; p++) {
968 k = strlen(names[p]);
969 if (startswith(*i, names[p]) && (*i)[k] == '=') {
970 r = free_and_strdup(&c->locale[p], *i + k + 1);
977 for (p = 0; p < _LOCALE_MAX; p++) {
981 free_and_replace(&c->locale[p], NULL);
986 r = locale_write_data(c, &settings);
988 log_error_errno(r, "Failed to set locale: %m");
989 return sd_bus_error_set_errnof(error, r, "Failed to set locale: %s", strerror(-r));
992 locale_update_system_manager(c, bus);
995 _cleanup_free_ char *line;
997 line = strv_join(settings, ", ");
998 log_info("Changed locale to %s.", strnull(line));
1000 log_info("Changed locale to unset.");
1002 sd_bus_emit_properties_changed(bus,
1003 "/org/freedesktop/locale1",
1004 "org.freedesktop.locale1",
1007 log_debug("Locale settings were not modified.");
1010 return sd_bus_reply_method_return(m, NULL);
1013 static int method_set_vc_keyboard(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
1014 Context *c = userdata;
1015 const char *keymap, *keymap_toggle;
1016 int convert, interactive;
1019 r = sd_bus_message_read(m, "ssbb", &keymap, &keymap_toggle, &convert, &interactive);
1023 if (isempty(keymap))
1026 if (isempty(keymap_toggle))
1027 keymap_toggle = NULL;
1029 if (!streq_ptr(keymap, c->vc_keymap) ||
1030 !streq_ptr(keymap_toggle, c->vc_keymap_toggle)) {
1032 if ((keymap && (!filename_is_valid(keymap) || !string_is_safe(keymap))) ||
1033 (keymap_toggle && (!filename_is_valid(keymap_toggle) || !string_is_safe(keymap_toggle))))
1034 return sd_bus_error_set_errnof(error, -EINVAL, "Received invalid keymap data");
1036 r = bus_verify_polkit_async(
1039 "org.freedesktop.locale1.set-keyboard",
1042 &c->polkit_registry,
1047 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
1049 if (free_and_strdup(&c->vc_keymap, keymap) < 0 ||
1050 free_and_strdup(&c->vc_keymap_toggle, keymap_toggle) < 0)
1053 r = vconsole_write_data(c);
1055 log_error_errno(r, "Failed to set virtual console keymap: %m");
1056 return sd_bus_error_set_errnof(error, r, "Failed to set virtual console keymap: %s", strerror(-r));
1059 log_info("Changed virtual console keymap to '%s' toggle '%s'",
1060 strempty(c->vc_keymap), strempty(c->vc_keymap_toggle));
1062 r = vconsole_reload(bus);
1064 log_error_errno(r, "Failed to request keymap reload: %m");
1066 sd_bus_emit_properties_changed(bus,
1067 "/org/freedesktop/locale1",
1068 "org.freedesktop.locale1",
1069 "VConsoleKeymap", "VConsoleKeymapToggle", NULL);
1072 r = vconsole_convert_to_x11(c, bus);
1074 log_error_errno(r, "Failed to convert keymap data: %m");
1078 return sd_bus_reply_method_return(m, NULL);
1081 #ifdef HAVE_XKBCOMMON
1082 static void log_xkb(struct xkb_context *ctx, enum xkb_log_level lvl, const char *format, va_list args) {
1085 fmt = strjoina("libxkbcommon: ", format);
1086 log_internalv(LOG_DEBUG, 0, __FILE__, __LINE__, __func__, fmt, args);
1089 static int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, const char *options) {
1090 const struct xkb_rule_names rmlvo = {
1096 struct xkb_context *ctx = NULL;
1097 struct xkb_keymap *km = NULL;
1100 /* compile keymap from RMLVO information to check out its validity */
1102 ctx = xkb_context_new(XKB_CONTEXT_NO_ENVIRONMENT_NAMES);
1108 xkb_context_set_log_fn(ctx, log_xkb);
1110 km = xkb_keymap_new_from_names(ctx, &rmlvo, XKB_KEYMAP_COMPILE_NO_FLAGS);
1119 xkb_keymap_unref(km);
1120 xkb_context_unref(ctx);
1124 static int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, const char *options) {
1129 static int method_set_x11_keyboard(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
1130 Context *c = userdata;
1131 const char *layout, *model, *variant, *options;
1132 int convert, interactive;
1135 r = sd_bus_message_read(m, "ssssbb", &layout, &model, &variant, &options, &convert, &interactive);
1139 if (isempty(layout))
1145 if (isempty(variant))
1148 if (isempty(options))
1151 if (!streq_ptr(layout, c->x11_layout) ||
1152 !streq_ptr(model, c->x11_model) ||
1153 !streq_ptr(variant, c->x11_variant) ||
1154 !streq_ptr(options, c->x11_options)) {
1156 if ((layout && !string_is_safe(layout)) ||
1157 (model && !string_is_safe(model)) ||
1158 (variant && !string_is_safe(variant)) ||
1159 (options && !string_is_safe(options)))
1160 return sd_bus_error_set_errnof(error, -EINVAL, "Received invalid keyboard data");
1162 r = bus_verify_polkit_async(
1165 "org.freedesktop.locale1.set-keyboard",
1168 &c->polkit_registry,
1173 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
1175 r = verify_xkb_rmlvo(model, layout, variant, options);
1177 log_error_errno(r, "Cannot compile XKB keymap for new x11 keyboard layout ('%s' / '%s' / '%s' / '%s'): %m",
1178 strempty(model), strempty(layout), strempty(variant), strempty(options));
1179 return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Cannot compile XKB keymap, refusing");
1182 if (free_and_strdup(&c->x11_layout, layout) < 0 ||
1183 free_and_strdup(&c->x11_model, model) < 0 ||
1184 free_and_strdup(&c->x11_variant, variant) < 0 ||
1185 free_and_strdup(&c->x11_options, options) < 0)
1188 r = x11_write_data(c);
1190 log_error_errno(r, "Failed to set X11 keyboard layout: %m");
1191 return sd_bus_error_set_errnof(error, r, "Failed to set X11 keyboard layout: %s", strerror(-r));
1194 log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
1195 strempty(c->x11_layout),
1196 strempty(c->x11_model),
1197 strempty(c->x11_variant),
1198 strempty(c->x11_options));
1200 sd_bus_emit_properties_changed(bus,
1201 "/org/freedesktop/locale1",
1202 "org.freedesktop.locale1",
1203 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL);
1206 r = x11_convert_to_vconsole(c, bus);
1208 log_error_errno(r, "Failed to convert keymap data: %m");
1212 return sd_bus_reply_method_return(m, NULL);
1215 static const sd_bus_vtable locale_vtable[] = {
1216 SD_BUS_VTABLE_START(0),
1217 SD_BUS_PROPERTY("Locale", "as", property_get_locale, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1218 SD_BUS_PROPERTY("X11Layout", "s", NULL, offsetof(Context, x11_layout), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1219 SD_BUS_PROPERTY("X11Model", "s", NULL, offsetof(Context, x11_model), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1220 SD_BUS_PROPERTY("X11Variant", "s", NULL, offsetof(Context, x11_variant), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1221 SD_BUS_PROPERTY("X11Options", "s", NULL, offsetof(Context, x11_options), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1222 SD_BUS_PROPERTY("VConsoleKeymap", "s", NULL, offsetof(Context, vc_keymap), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1223 SD_BUS_PROPERTY("VConsoleKeymapToggle", "s", NULL, offsetof(Context, vc_keymap_toggle), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1224 SD_BUS_METHOD("SetLocale", "asb", NULL, method_set_locale, SD_BUS_VTABLE_UNPRIVILEGED),
1225 SD_BUS_METHOD("SetVConsoleKeyboard", "ssbb", NULL, method_set_vc_keyboard, SD_BUS_VTABLE_UNPRIVILEGED),
1226 SD_BUS_METHOD("SetX11Keyboard", "ssssbb", NULL, method_set_x11_keyboard, SD_BUS_VTABLE_UNPRIVILEGED),
1230 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
1231 _cleanup_bus_close_unref_ sd_bus *bus = NULL;
1238 r = sd_bus_default_system(&bus);
1240 return log_error_errno(r, "Failed to get system bus connection: %m");
1242 r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/locale1", "org.freedesktop.locale1", locale_vtable, c);
1244 return log_error_errno(r, "Failed to register object: %m");
1246 r = sd_bus_request_name(bus, "org.freedesktop.locale1", 0);
1248 return log_error_errno(r, "Failed to register name: %m");
1250 r = sd_bus_attach_event(bus, event, 0);
1252 return log_error_errno(r, "Failed to attach bus to event loop: %m");
1260 int main(int argc, char *argv[]) {
1261 _cleanup_(context_free) Context context = {};
1262 _cleanup_event_unref_ sd_event *event = NULL;
1263 _cleanup_bus_close_unref_ sd_bus *bus = NULL;
1266 log_set_target(LOG_TARGET_AUTO);
1267 log_parse_environment();
1271 mac_selinux_init("/etc");
1274 log_error("This program takes no arguments.");
1279 r = sd_event_default(&event);
1281 log_error_errno(r, "Failed to allocate event loop: %m");
1285 sd_event_set_watchdog(event, true);
1287 r = connect_bus(&context, event, &bus);
1291 r = context_read_data(&context);
1293 log_error_errno(r, "Failed to read locale data: %m");
1297 r = bus_event_loop_with_idle(event, bus, "org.freedesktop.locale1", DEFAULT_EXIT_USEC, NULL, NULL);
1299 log_error_errno(r, "Failed to run event loop: %m");
1304 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;