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/>.
28 #include <xkbcommon/xkbcommon.h>
33 #include "alloc-util.h"
34 #include "bus-error.h"
35 #include "bus-message.h"
40 #include "fileio-label.h"
42 #include "locale-util.h"
44 #include "path-util.h"
45 #include "selinux-util.h"
47 #include "user-util.h"
51 /* We don't list LC_ALL here on purpose. People should be
52 * using LANG instead. */
65 LOCALE_LC_MEASUREMENT
,
66 LOCALE_LC_IDENTIFICATION
,
70 static const char * const names
[_LOCALE_MAX
] = {
71 [LOCALE_LANG
] = "LANG",
72 [LOCALE_LANGUAGE
] = "LANGUAGE",
73 [LOCALE_LC_CTYPE
] = "LC_CTYPE",
74 [LOCALE_LC_NUMERIC
] = "LC_NUMERIC",
75 [LOCALE_LC_TIME
] = "LC_TIME",
76 [LOCALE_LC_COLLATE
] = "LC_COLLATE",
77 [LOCALE_LC_MONETARY
] = "LC_MONETARY",
78 [LOCALE_LC_MESSAGES
] = "LC_MESSAGES",
79 [LOCALE_LC_PAPER
] = "LC_PAPER",
80 [LOCALE_LC_NAME
] = "LC_NAME",
81 [LOCALE_LC_ADDRESS
] = "LC_ADDRESS",
82 [LOCALE_LC_TELEPHONE
] = "LC_TELEPHONE",
83 [LOCALE_LC_MEASUREMENT
] = "LC_MEASUREMENT",
84 [LOCALE_LC_IDENTIFICATION
] = "LC_IDENTIFICATION"
87 typedef struct Context
{
88 char *locale
[_LOCALE_MAX
];
96 char *vc_keymap_toggle
;
98 Hashmap
*polkit_registry
;
101 static const char* nonempty(const char *s
) {
102 return isempty(s
) ? NULL
: s
;
105 static bool startswith_comma(const char *s
, const char *prefix
) {
108 return s
&& (t
= startswith(s
, prefix
)) && (*t
== ',');
111 static void context_free_x11(Context
*c
) {
112 c
->x11_layout
= mfree(c
->x11_layout
);
113 c
->x11_options
= mfree(c
->x11_options
);
114 c
->x11_model
= mfree(c
->x11_model
);
115 c
->x11_variant
= mfree(c
->x11_variant
);
118 static void context_free_vconsole(Context
*c
) {
119 c
->vc_keymap
= mfree(c
->vc_keymap
);
120 c
->vc_keymap_toggle
= mfree(c
->vc_keymap_toggle
);
123 static void context_free_locale(Context
*c
) {
126 for (p
= 0; p
< _LOCALE_MAX
; p
++)
127 c
->locale
[p
] = mfree(c
->locale
[p
]);
130 static void context_free(Context
*c
) {
131 context_free_locale(c
);
133 context_free_vconsole(c
);
135 bus_verify_polkit_async_registry_free(c
->polkit_registry
);
138 static void locale_simplify(Context
*c
) {
141 for (p
= LOCALE_LANG
+1; p
< _LOCALE_MAX
; p
++)
142 if (isempty(c
->locale
[p
]) || streq_ptr(c
->locale
[LOCALE_LANG
], c
->locale
[p
]))
143 c
->locale
[p
] = mfree(c
->locale
[p
]);
146 static int locale_read_data(Context
*c
) {
149 context_free_locale(c
);
151 r
= parse_env_file("/etc/locale.conf", NEWLINE
,
152 "LANG", &c
->locale
[LOCALE_LANG
],
153 "LANGUAGE", &c
->locale
[LOCALE_LANGUAGE
],
154 "LC_CTYPE", &c
->locale
[LOCALE_LC_CTYPE
],
155 "LC_NUMERIC", &c
->locale
[LOCALE_LC_NUMERIC
],
156 "LC_TIME", &c
->locale
[LOCALE_LC_TIME
],
157 "LC_COLLATE", &c
->locale
[LOCALE_LC_COLLATE
],
158 "LC_MONETARY", &c
->locale
[LOCALE_LC_MONETARY
],
159 "LC_MESSAGES", &c
->locale
[LOCALE_LC_MESSAGES
],
160 "LC_PAPER", &c
->locale
[LOCALE_LC_PAPER
],
161 "LC_NAME", &c
->locale
[LOCALE_LC_NAME
],
162 "LC_ADDRESS", &c
->locale
[LOCALE_LC_ADDRESS
],
163 "LC_TELEPHONE", &c
->locale
[LOCALE_LC_TELEPHONE
],
164 "LC_MEASUREMENT", &c
->locale
[LOCALE_LC_MEASUREMENT
],
165 "LC_IDENTIFICATION", &c
->locale
[LOCALE_LC_IDENTIFICATION
],
171 /* Fill in what we got passed from systemd. */
172 for (p
= 0; p
< _LOCALE_MAX
; p
++) {
175 r
= free_and_strdup(&c
->locale
[p
],
176 nonempty(getenv(names
[p
])));
188 static int vconsole_read_data(Context
*c
) {
191 context_free_vconsole(c
);
193 r
= parse_env_file("/etc/vconsole.conf", NEWLINE
,
194 "KEYMAP", &c
->vc_keymap
,
195 "KEYMAP_TOGGLE", &c
->vc_keymap_toggle
,
198 if (r
< 0 && r
!= -ENOENT
)
204 static int x11_read_data(Context
*c
) {
205 _cleanup_fclose_
FILE *f
;
207 bool in_section
= false;
212 f
= fopen("/etc/X11/xorg.conf.d/00-keyboard.conf", "re");
214 return errno
== ENOENT
? 0 : -errno
;
216 while (fgets(line
, sizeof(line
), f
)) {
222 if (l
[0] == 0 || l
[0] == '#')
225 if (in_section
&& first_word(l
, "Option")) {
226 _cleanup_strv_free_
char **a
= NULL
;
228 r
= strv_split_extract(&a
, l
, WHITESPACE
, EXTRACT_QUOTES
);
232 if (strv_length(a
) == 3) {
235 if (streq(a
[1], "XkbLayout"))
237 else if (streq(a
[1], "XkbModel"))
239 else if (streq(a
[1], "XkbVariant"))
241 else if (streq(a
[1], "XkbOptions"))
251 } else if (!in_section
&& first_word(l
, "Section")) {
252 _cleanup_strv_free_
char **a
= NULL
;
254 r
= strv_split_extract(&a
, l
, WHITESPACE
, EXTRACT_QUOTES
);
258 if (strv_length(a
) == 2 && streq(a
[1], "InputClass"))
261 } else if (in_section
&& first_word(l
, "EndSection"))
268 static int context_read_data(Context
*c
) {
271 r
= locale_read_data(c
);
272 q
= vconsole_read_data(c
);
273 p
= x11_read_data(c
);
275 return r
< 0 ? r
: q
< 0 ? q
: p
;
278 static int locale_write_data(Context
*c
, char ***settings
) {
280 _cleanup_strv_free_
char **l
= NULL
;
282 /* Set values will be returned as strv in *settings on success. */
284 r
= load_env_file(NULL
, "/etc/locale.conf", NULL
, &l
);
285 if (r
< 0 && r
!= -ENOENT
)
288 for (p
= 0; p
< _LOCALE_MAX
; p
++) {
289 _cleanup_free_
char *t
= NULL
;
294 if (isempty(c
->locale
[p
])) {
295 l
= strv_env_unset(l
, names
[p
]);
299 if (asprintf(&t
, "%s=%s", names
[p
], c
->locale
[p
]) < 0)
302 u
= strv_env_set(l
, t
);
310 if (strv_isempty(l
)) {
311 if (unlink("/etc/locale.conf") < 0)
312 return errno
== ENOENT
? 0 : -errno
;
317 r
= write_env_file_label("/etc/locale.conf", l
);
326 static int locale_update_system_manager(Context
*c
, sd_bus
*bus
) {
327 _cleanup_free_
char **l_unset
= NULL
;
328 _cleanup_strv_free_
char **l_set
= NULL
;
329 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*m
= NULL
;
330 sd_bus_error error
= SD_BUS_ERROR_NULL
;
331 unsigned c_set
, c_unset
, p
;
336 l_unset
= new0(char*, _LOCALE_MAX
);
340 l_set
= new0(char*, _LOCALE_MAX
);
344 for (p
= 0, c_set
= 0, c_unset
= 0; p
< _LOCALE_MAX
; p
++) {
347 if (isempty(c
->locale
[p
]))
348 l_unset
[c_set
++] = (char*) names
[p
];
352 if (asprintf(&s
, "%s=%s", names
[p
], c
->locale
[p
]) < 0)
355 l_set
[c_unset
++] = s
;
359 assert(c_set
+ c_unset
== _LOCALE_MAX
);
360 r
= sd_bus_message_new_method_call(bus
, &m
,
361 "org.freedesktop.systemd1",
362 "/org/freedesktop/systemd1",
363 "org.freedesktop.systemd1.Manager",
364 "UnsetAndSetEnvironment");
368 r
= sd_bus_message_append_strv(m
, l_unset
);
372 r
= sd_bus_message_append_strv(m
, l_set
);
376 r
= sd_bus_call(bus
, m
, 0, &error
, NULL
);
378 log_error_errno(r
, "Failed to update the manager environment: %m");
383 static int vconsole_write_data(Context
*c
) {
385 _cleanup_strv_free_
char **l
= NULL
;
387 r
= load_env_file(NULL
, "/etc/vconsole.conf", NULL
, &l
);
388 if (r
< 0 && r
!= -ENOENT
)
391 if (isempty(c
->vc_keymap
))
392 l
= strv_env_unset(l
, "KEYMAP");
394 _cleanup_free_
char *s
= NULL
;
397 s
= strappend("KEYMAP=", c
->vc_keymap
);
401 u
= strv_env_set(l
, s
);
409 if (isempty(c
->vc_keymap_toggle
))
410 l
= strv_env_unset(l
, "KEYMAP_TOGGLE");
412 _cleanup_free_
char *s
= NULL
;
415 s
= strappend("KEYMAP_TOGGLE=", c
->vc_keymap_toggle
);
419 u
= strv_env_set(l
, s
);
427 if (strv_isempty(l
)) {
428 if (unlink("/etc/vconsole.conf") < 0)
429 return errno
== ENOENT
? 0 : -errno
;
434 return write_env_file_label("/etc/vconsole.conf", l
);
437 static int x11_write_data(Context
*c
) {
438 _cleanup_fclose_
FILE *f
= NULL
;
439 _cleanup_free_
char *temp_path
= NULL
;
442 if (isempty(c
->x11_layout
) &&
443 isempty(c
->x11_model
) &&
444 isempty(c
->x11_variant
) &&
445 isempty(c
->x11_options
)) {
447 if (unlink("/etc/X11/xorg.conf.d/00-keyboard.conf") < 0)
448 return errno
== ENOENT
? 0 : -errno
;
453 mkdir_p_label("/etc/X11/xorg.conf.d", 0755);
455 r
= fopen_temporary("/etc/X11/xorg.conf.d/00-keyboard.conf", &f
, &temp_path
);
459 fchmod(fileno(f
), 0644);
461 fputs("# Read and parsed by systemd-localed. It's probably wise not to edit this file\n"
462 "# manually too freely.\n"
463 "Section \"InputClass\"\n"
464 " Identifier \"system-keyboard\"\n"
465 " MatchIsKeyboard \"on\"\n", f
);
467 if (!isempty(c
->x11_layout
))
468 fprintf(f
, " Option \"XkbLayout\" \"%s\"\n", c
->x11_layout
);
470 if (!isempty(c
->x11_model
))
471 fprintf(f
, " Option \"XkbModel\" \"%s\"\n", c
->x11_model
);
473 if (!isempty(c
->x11_variant
))
474 fprintf(f
, " Option \"XkbVariant\" \"%s\"\n", c
->x11_variant
);
476 if (!isempty(c
->x11_options
))
477 fprintf(f
, " Option \"XkbOptions\" \"%s\"\n", c
->x11_options
);
479 fputs("EndSection\n", f
);
481 r
= fflush_and_check(f
);
485 if (rename(temp_path
, "/etc/X11/xorg.conf.d/00-keyboard.conf") < 0) {
493 (void) unlink("/etc/X11/xorg.conf.d/00-keyboard.conf");
496 (void) unlink(temp_path
);
501 static int vconsole_reload(sd_bus
*bus
) {
502 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
507 r
= sd_bus_call_method(bus
,
508 "org.freedesktop.systemd1",
509 "/org/freedesktop/systemd1",
510 "org.freedesktop.systemd1.Manager",
514 "ss", "systemd-vconsole-setup.service", "replace");
517 log_error("Failed to issue method call: %s", bus_error_message(&error
, -r
));
521 static const char* strnulldash(const char *s
) {
522 return isempty(s
) || streq(s
, "-") ? NULL
: s
;
525 static int read_next_mapping(const char* filename
,
526 unsigned min_fields
, unsigned max_fields
,
527 FILE *f
, unsigned *n
, char ***a
) {
539 if (!fgets(line
, sizeof(line
), f
)) {
542 return errno
> 0 ? -errno
: -EIO
;
550 if (l
[0] == 0 || l
[0] == '#')
553 r
= strv_split_extract(&b
, l
, WHITESPACE
, EXTRACT_QUOTES
);
557 length
= strv_length(b
);
558 if (length
< min_fields
|| length
> max_fields
) {
559 log_error("Invalid line %s:%u, ignoring.", filename
, *n
);
570 static int vconsole_convert_to_x11(Context
*c
, sd_bus
*bus
) {
571 bool modified
= false;
575 if (isempty(c
->vc_keymap
)) {
578 !isempty(c
->x11_layout
) ||
579 !isempty(c
->x11_model
) ||
580 !isempty(c
->x11_variant
) ||
581 !isempty(c
->x11_options
);
585 _cleanup_fclose_
FILE *f
= NULL
;
588 f
= fopen(SYSTEMD_KBD_MODEL_MAP
, "re");
593 _cleanup_strv_free_
char **a
= NULL
;
596 r
= read_next_mapping(SYSTEMD_KBD_MODEL_MAP
, 5, UINT_MAX
, f
, &n
, &a
);
602 if (!streq(c
->vc_keymap
, a
[0]))
605 if (!streq_ptr(c
->x11_layout
, strnulldash(a
[1])) ||
606 !streq_ptr(c
->x11_model
, strnulldash(a
[2])) ||
607 !streq_ptr(c
->x11_variant
, strnulldash(a
[3])) ||
608 !streq_ptr(c
->x11_options
, strnulldash(a
[4]))) {
610 if (free_and_strdup(&c
->x11_layout
, strnulldash(a
[1])) < 0 ||
611 free_and_strdup(&c
->x11_model
, strnulldash(a
[2])) < 0 ||
612 free_and_strdup(&c
->x11_variant
, strnulldash(a
[3])) < 0 ||
613 free_and_strdup(&c
->x11_options
, strnulldash(a
[4])) < 0)
626 r
= x11_write_data(c
);
628 return log_error_errno(r
, "Failed to set X11 keyboard layout: %m");
630 log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
631 strempty(c
->x11_layout
),
632 strempty(c
->x11_model
),
633 strempty(c
->x11_variant
),
634 strempty(c
->x11_options
));
636 sd_bus_emit_properties_changed(bus
,
637 "/org/freedesktop/locale1",
638 "org.freedesktop.locale1",
639 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL
);
641 log_debug("X11 keyboard layout was not modified.");
646 static int find_converted_keymap(const char *x11_layout
, const char *x11_variant
, char **new_keymap
) {
648 _cleanup_free_
char *n
;
651 n
= strjoin(x11_layout
, "-", x11_variant
, NULL
);
653 n
= strdup(x11_layout
);
657 NULSTR_FOREACH(dir
, KBD_KEYMAP_DIRS
) {
658 _cleanup_free_
char *p
= NULL
, *pz
= NULL
;
661 p
= strjoin(dir
, "xkb/", n
, ".map", NULL
);
662 pz
= strjoin(dir
, "xkb/", n
, ".map.gz", NULL
);
666 uncompressed
= access(p
, F_OK
) == 0;
667 if (uncompressed
|| access(pz
, F_OK
) == 0) {
668 log_debug("Found converted keymap %s at %s",
669 n
, uncompressed
? p
: pz
);
680 static int find_legacy_keymap(Context
*c
, char **new_keymap
) {
681 _cleanup_fclose_
FILE *f
;
683 unsigned best_matching
= 0;
686 f
= fopen(SYSTEMD_KBD_MODEL_MAP
, "re");
691 _cleanup_strv_free_
char **a
= NULL
;
692 unsigned matching
= 0;
694 r
= read_next_mapping(SYSTEMD_KBD_MODEL_MAP
, 5, UINT_MAX
, f
, &n
, &a
);
700 /* Determine how well matching this entry is */
701 if (streq_ptr(c
->x11_layout
, a
[1]))
702 /* If we got an exact match, this is best */
705 /* We have multiple X layouts, look for an
706 * entry that matches our key with everything
707 * but the first layout stripped off. */
708 if (startswith_comma(c
->x11_layout
, a
[1]))
713 /* If that didn't work, strip off the
714 * other layouts from the entry, too */
715 x
= strndupa(a
[1], strcspn(a
[1], ","));
716 if (startswith_comma(c
->x11_layout
, x
))
722 if (isempty(c
->x11_model
) || streq_ptr(c
->x11_model
, a
[2])) {
725 if (streq_ptr(c
->x11_variant
, a
[3])) {
728 if (streq_ptr(c
->x11_options
, a
[4]))
734 /* The best matching entry so far, then let's save that */
735 if (matching
>= MAX(best_matching
, 1u)) {
736 log_debug("Found legacy keymap %s with score %u",
739 if (matching
> best_matching
) {
740 best_matching
= matching
;
742 r
= free_and_strdup(new_keymap
, a
[0]);
749 if (best_matching
< 10 && c
->x11_layout
) {
750 /* The best match is only the first part of the X11
751 * keymap. Check if we have a converted map which
752 * matches just the first layout.
754 char *l
, *v
= NULL
, *converted
;
756 l
= strndupa(c
->x11_layout
, strcspn(c
->x11_layout
, ","));
758 v
= strndupa(c
->x11_variant
, strcspn(c
->x11_variant
, ","));
759 r
= find_converted_keymap(l
, v
, &converted
);
764 *new_keymap
= converted
;
771 static int find_language_fallback(const char *lang
, char **language
) {
772 _cleanup_fclose_
FILE *f
= NULL
;
777 f
= fopen(SYSTEMD_LANGUAGE_FALLBACK_MAP
, "re");
782 _cleanup_strv_free_
char **a
= NULL
;
785 r
= read_next_mapping(SYSTEMD_LANGUAGE_FALLBACK_MAP
, 2, 2, f
, &n
, &a
);
789 if (streq(lang
, a
[0])) {
790 assert(strv_length(a
) == 2);
797 assert_not_reached("should not be here");
800 static int x11_convert_to_vconsole(Context
*c
, sd_bus
*bus
) {
801 bool modified
= false;
806 if (isempty(c
->x11_layout
)) {
809 !isempty(c
->vc_keymap
) ||
810 !isempty(c
->vc_keymap_toggle
);
814 char *new_keymap
= NULL
;
816 r
= find_converted_keymap(c
->x11_layout
, c
->x11_variant
, &new_keymap
);
820 r
= find_legacy_keymap(c
, &new_keymap
);
825 if (!streq_ptr(c
->vc_keymap
, new_keymap
)) {
827 c
->vc_keymap
= new_keymap
;
828 c
->vc_keymap_toggle
= mfree(c
->vc_keymap_toggle
);
835 r
= vconsole_write_data(c
);
837 log_error_errno(r
, "Failed to set virtual console keymap: %m");
839 log_info("Changed virtual console keymap to '%s' toggle '%s'",
840 strempty(c
->vc_keymap
), strempty(c
->vc_keymap_toggle
));
842 sd_bus_emit_properties_changed(bus
,
843 "/org/freedesktop/locale1",
844 "org.freedesktop.locale1",
845 "VConsoleKeymap", "VConsoleKeymapToggle", NULL
);
847 return vconsole_reload(bus
);
849 log_debug("Virtual console keymap was not modified.");
854 static int property_get_locale(
857 const char *interface
,
858 const char *property
,
859 sd_bus_message
*reply
,
861 sd_bus_error
*error
) {
863 Context
*c
= userdata
;
864 _cleanup_strv_free_
char **l
= NULL
;
867 l
= new0(char*, _LOCALE_MAX
+1);
871 for (p
= 0, q
= 0; p
< _LOCALE_MAX
; p
++) {
874 if (isempty(c
->locale
[p
]))
877 if (asprintf(&t
, "%s=%s", names
[p
], c
->locale
[p
]) < 0)
883 return sd_bus_message_append_strv(reply
, l
);
886 static int method_set_locale(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
887 Context
*c
= userdata
;
888 _cleanup_strv_free_
char **l
= NULL
;
890 const char *lang
= NULL
;
892 bool modified
= false;
893 bool have
[_LOCALE_MAX
] = {};
900 r
= bus_message_read_strv_extend(m
, &l
);
904 r
= sd_bus_message_read_basic(m
, 'b', &interactive
);
908 /* Check whether a variable changed and if it is valid */
912 for (p
= 0; p
< _LOCALE_MAX
; p
++) {
915 k
= strlen(names
[p
]);
916 if (startswith(*i
, names
[p
]) &&
918 locale_is_valid((*i
) + k
+ 1)) {
922 if (p
== LOCALE_LANG
)
925 if (!streq_ptr(*i
+ k
+ 1, c
->locale
[p
]))
933 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid Locale data.");
936 /* If LANG was specified, but not LANGUAGE, check if we should
937 * set it based on the language fallback table. */
938 if (have
[LOCALE_LANG
] && !have
[LOCALE_LANGUAGE
]) {
939 _cleanup_free_
char *language
= NULL
;
943 (void) find_language_fallback(lang
, &language
);
945 log_debug("Converted LANG=%s to LANGUAGE=%s", lang
, language
);
946 if (!streq_ptr(language
, c
->locale
[LOCALE_LANGUAGE
])) {
947 r
= strv_extendf(&l
, "LANGUAGE=%s", language
);
951 have
[LOCALE_LANGUAGE
] = true;
957 /* Check whether a variable is unset */
959 for (p
= 0; p
< _LOCALE_MAX
; p
++)
960 if (!isempty(c
->locale
[p
]) && !have
[p
]) {
966 _cleanup_strv_free_
char **settings
= NULL
;
968 r
= bus_verify_polkit_async(
971 "org.freedesktop.locale1.set-locale",
980 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
983 for (p
= 0; p
< _LOCALE_MAX
; p
++) {
986 k
= strlen(names
[p
]);
987 if (startswith(*i
, names
[p
]) && (*i
)[k
] == '=') {
988 r
= free_and_strdup(&c
->locale
[p
], *i
+ k
+ 1);
995 for (p
= 0; p
< _LOCALE_MAX
; p
++) {
999 c
->locale
[p
] = mfree(c
->locale
[p
]);
1004 r
= locale_write_data(c
, &settings
);
1006 log_error_errno(r
, "Failed to set locale: %m");
1007 return sd_bus_error_set_errnof(error
, r
, "Failed to set locale: %s", strerror(-r
));
1010 locale_update_system_manager(c
, sd_bus_message_get_bus(m
));
1013 _cleanup_free_
char *line
;
1015 line
= strv_join(settings
, ", ");
1016 log_info("Changed locale to %s.", strnull(line
));
1018 log_info("Changed locale to unset.");
1020 (void) sd_bus_emit_properties_changed(
1021 sd_bus_message_get_bus(m
),
1022 "/org/freedesktop/locale1",
1023 "org.freedesktop.locale1",
1026 log_debug("Locale settings were not modified.");
1029 return sd_bus_reply_method_return(m
, NULL
);
1032 static int method_set_vc_keyboard(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
1033 Context
*c
= userdata
;
1034 const char *keymap
, *keymap_toggle
;
1035 int convert
, interactive
;
1041 r
= sd_bus_message_read(m
, "ssbb", &keymap
, &keymap_toggle
, &convert
, &interactive
);
1045 if (isempty(keymap
))
1048 if (isempty(keymap_toggle
))
1049 keymap_toggle
= NULL
;
1051 if (!streq_ptr(keymap
, c
->vc_keymap
) ||
1052 !streq_ptr(keymap_toggle
, c
->vc_keymap_toggle
)) {
1054 if ((keymap
&& (!filename_is_valid(keymap
) || !string_is_safe(keymap
))) ||
1055 (keymap_toggle
&& (!filename_is_valid(keymap_toggle
) || !string_is_safe(keymap_toggle
))))
1056 return sd_bus_error_set_errnof(error
, -EINVAL
, "Received invalid keymap data");
1058 r
= bus_verify_polkit_async(
1061 "org.freedesktop.locale1.set-keyboard",
1065 &c
->polkit_registry
,
1070 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
1072 if (free_and_strdup(&c
->vc_keymap
, keymap
) < 0 ||
1073 free_and_strdup(&c
->vc_keymap_toggle
, keymap_toggle
) < 0)
1076 r
= vconsole_write_data(c
);
1078 log_error_errno(r
, "Failed to set virtual console keymap: %m");
1079 return sd_bus_error_set_errnof(error
, r
, "Failed to set virtual console keymap: %s", strerror(-r
));
1082 log_info("Changed virtual console keymap to '%s' toggle '%s'",
1083 strempty(c
->vc_keymap
), strempty(c
->vc_keymap_toggle
));
1085 r
= vconsole_reload(sd_bus_message_get_bus(m
));
1087 log_error_errno(r
, "Failed to request keymap reload: %m");
1089 (void) sd_bus_emit_properties_changed(
1090 sd_bus_message_get_bus(m
),
1091 "/org/freedesktop/locale1",
1092 "org.freedesktop.locale1",
1093 "VConsoleKeymap", "VConsoleKeymapToggle", NULL
);
1096 r
= vconsole_convert_to_x11(c
, sd_bus_message_get_bus(m
));
1098 log_error_errno(r
, "Failed to convert keymap data: %m");
1102 return sd_bus_reply_method_return(m
, NULL
);
1105 #ifdef HAVE_XKBCOMMON
1107 static void log_xkb(struct xkb_context
*ctx
, enum xkb_log_level lvl
, const char *format
, va_list args
) {
1110 fmt
= strjoina("libxkbcommon: ", format
);
1111 log_internalv(LOG_DEBUG
, 0, __FILE__
, __LINE__
, __func__
, fmt
, args
);
1114 static int verify_xkb_rmlvo(const char *model
, const char *layout
, const char *variant
, const char *options
) {
1115 const struct xkb_rule_names rmlvo
= {
1121 struct xkb_context
*ctx
= NULL
;
1122 struct xkb_keymap
*km
= NULL
;
1125 /* compile keymap from RMLVO information to check out its validity */
1127 ctx
= xkb_context_new(XKB_CONTEXT_NO_ENVIRONMENT_NAMES
);
1133 xkb_context_set_log_fn(ctx
, log_xkb
);
1135 km
= xkb_keymap_new_from_names(ctx
, &rmlvo
, XKB_KEYMAP_COMPILE_NO_FLAGS
);
1144 xkb_keymap_unref(km
);
1145 xkb_context_unref(ctx
);
1149 static int verify_xkb_rmlvo(const char *model
, const char *layout
, const char *variant
, const char *options
) {
1154 static int method_set_x11_keyboard(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
1155 Context
*c
= userdata
;
1156 const char *layout
, *model
, *variant
, *options
;
1157 int convert
, interactive
;
1163 r
= sd_bus_message_read(m
, "ssssbb", &layout
, &model
, &variant
, &options
, &convert
, &interactive
);
1167 if (isempty(layout
))
1173 if (isempty(variant
))
1176 if (isempty(options
))
1179 if (!streq_ptr(layout
, c
->x11_layout
) ||
1180 !streq_ptr(model
, c
->x11_model
) ||
1181 !streq_ptr(variant
, c
->x11_variant
) ||
1182 !streq_ptr(options
, c
->x11_options
)) {
1184 if ((layout
&& !string_is_safe(layout
)) ||
1185 (model
&& !string_is_safe(model
)) ||
1186 (variant
&& !string_is_safe(variant
)) ||
1187 (options
&& !string_is_safe(options
)))
1188 return sd_bus_error_set_errnof(error
, -EINVAL
, "Received invalid keyboard data");
1190 r
= bus_verify_polkit_async(
1193 "org.freedesktop.locale1.set-keyboard",
1197 &c
->polkit_registry
,
1202 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
1204 r
= verify_xkb_rmlvo(model
, layout
, variant
, options
);
1206 log_error_errno(r
, "Cannot compile XKB keymap for new x11 keyboard layout ('%s' / '%s' / '%s' / '%s'): %m",
1207 strempty(model
), strempty(layout
), strempty(variant
), strempty(options
));
1208 return sd_bus_error_set(error
, SD_BUS_ERROR_INVALID_ARGS
, "Cannot compile XKB keymap, refusing");
1211 if (free_and_strdup(&c
->x11_layout
, layout
) < 0 ||
1212 free_and_strdup(&c
->x11_model
, model
) < 0 ||
1213 free_and_strdup(&c
->x11_variant
, variant
) < 0 ||
1214 free_and_strdup(&c
->x11_options
, options
) < 0)
1217 r
= x11_write_data(c
);
1219 log_error_errno(r
, "Failed to set X11 keyboard layout: %m");
1220 return sd_bus_error_set_errnof(error
, r
, "Failed to set X11 keyboard layout: %s", strerror(-r
));
1223 log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
1224 strempty(c
->x11_layout
),
1225 strempty(c
->x11_model
),
1226 strempty(c
->x11_variant
),
1227 strempty(c
->x11_options
));
1229 (void) sd_bus_emit_properties_changed(
1230 sd_bus_message_get_bus(m
),
1231 "/org/freedesktop/locale1",
1232 "org.freedesktop.locale1",
1233 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL
);
1236 r
= x11_convert_to_vconsole(c
, sd_bus_message_get_bus(m
));
1238 log_error_errno(r
, "Failed to convert keymap data: %m");
1242 return sd_bus_reply_method_return(m
, NULL
);
1245 static const sd_bus_vtable locale_vtable
[] = {
1246 SD_BUS_VTABLE_START(0),
1247 SD_BUS_PROPERTY("Locale", "as", property_get_locale
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1248 SD_BUS_PROPERTY("X11Layout", "s", NULL
, offsetof(Context
, x11_layout
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1249 SD_BUS_PROPERTY("X11Model", "s", NULL
, offsetof(Context
, x11_model
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1250 SD_BUS_PROPERTY("X11Variant", "s", NULL
, offsetof(Context
, x11_variant
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1251 SD_BUS_PROPERTY("X11Options", "s", NULL
, offsetof(Context
, x11_options
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1252 SD_BUS_PROPERTY("VConsoleKeymap", "s", NULL
, offsetof(Context
, vc_keymap
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1253 SD_BUS_PROPERTY("VConsoleKeymapToggle", "s", NULL
, offsetof(Context
, vc_keymap_toggle
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1254 SD_BUS_METHOD("SetLocale", "asb", NULL
, method_set_locale
, SD_BUS_VTABLE_UNPRIVILEGED
),
1255 SD_BUS_METHOD("SetVConsoleKeyboard", "ssbb", NULL
, method_set_vc_keyboard
, SD_BUS_VTABLE_UNPRIVILEGED
),
1256 SD_BUS_METHOD("SetX11Keyboard", "ssssbb", NULL
, method_set_x11_keyboard
, SD_BUS_VTABLE_UNPRIVILEGED
),
1260 static int connect_bus(Context
*c
, sd_event
*event
, sd_bus
**_bus
) {
1261 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
1268 r
= sd_bus_default_system(&bus
);
1270 return log_error_errno(r
, "Failed to get system bus connection: %m");
1272 r
= sd_bus_add_object_vtable(bus
, NULL
, "/org/freedesktop/locale1", "org.freedesktop.locale1", locale_vtable
, c
);
1274 return log_error_errno(r
, "Failed to register object: %m");
1276 r
= sd_bus_request_name(bus
, "org.freedesktop.locale1", 0);
1278 return log_error_errno(r
, "Failed to register name: %m");
1280 r
= sd_bus_attach_event(bus
, event
, 0);
1282 return log_error_errno(r
, "Failed to attach bus to event loop: %m");
1290 int main(int argc
, char *argv
[]) {
1291 _cleanup_(context_free
) Context context
= {};
1292 _cleanup_(sd_event_unrefp
) sd_event
*event
= NULL
;
1293 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
1296 log_set_target(LOG_TARGET_AUTO
);
1297 log_parse_environment();
1301 mac_selinux_init("/etc");
1304 log_error("This program takes no arguments.");
1309 r
= sd_event_default(&event
);
1311 log_error_errno(r
, "Failed to allocate event loop: %m");
1315 sd_event_set_watchdog(event
, true);
1317 r
= connect_bus(&context
, event
, &bus
);
1321 r
= context_read_data(&context
);
1323 log_error_errno(r
, "Failed to read locale data: %m");
1327 r
= bus_event_loop_with_idle(event
, bus
, "org.freedesktop.locale1", DEFAULT_EXIT_USEC
, NULL
, NULL
);
1329 log_error_errno(r
, "Failed to run event loop: %m");
1334 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;