2 This file is part of systemd.
4 Copyright 2011 Lennart Poettering
5 Copyright 2013 Kay Sievers
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
26 #include <xkbcommon/xkbcommon.h>
32 #include "alloc-util.h"
33 #include "bus-error.h"
34 #include "bus-message.h"
37 #include "keymap-util.h"
38 #include "locale-util.h"
40 #include "path-util.h"
41 #include "selinux-util.h"
42 #include "string-util.h"
44 #include "user-util.h"
46 static Hashmap
*polkit_registry
= NULL
;
48 static int locale_update_system_manager(Context
*c
, sd_bus
*bus
) {
49 _cleanup_free_
char **l_unset
= NULL
;
50 _cleanup_strv_free_
char **l_set
= NULL
;
51 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*m
= NULL
;
52 sd_bus_error error
= SD_BUS_ERROR_NULL
;
53 unsigned c_set
, c_unset
, p
;
58 l_unset
= new0(char*, _VARIABLE_LC_MAX
);
62 l_set
= new0(char*, _VARIABLE_LC_MAX
);
66 for (p
= 0, c_set
= 0, c_unset
= 0; p
< _VARIABLE_LC_MAX
; p
++) {
69 name
= locale_variable_to_string(p
);
72 if (isempty(c
->locale
[p
]))
73 l_unset
[c_set
++] = (char*) name
;
77 if (asprintf(&s
, "%s=%s", name
, c
->locale
[p
]) < 0)
84 assert(c_set
+ c_unset
== _VARIABLE_LC_MAX
);
85 r
= sd_bus_message_new_method_call(bus
, &m
,
86 "org.freedesktop.systemd1",
87 "/org/freedesktop/systemd1",
88 "org.freedesktop.systemd1.Manager",
89 "UnsetAndSetEnvironment");
93 r
= sd_bus_message_append_strv(m
, l_unset
);
97 r
= sd_bus_message_append_strv(m
, l_set
);
101 r
= sd_bus_call(bus
, m
, 0, &error
, NULL
);
103 log_error_errno(r
, "Failed to update the manager environment: %m");
108 static int vconsole_reload(sd_bus
*bus
) {
109 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
114 r
= sd_bus_call_method(bus
,
115 "org.freedesktop.systemd1",
116 "/org/freedesktop/systemd1",
117 "org.freedesktop.systemd1.Manager",
121 "ss", "systemd-vconsole-setup.service", "replace");
124 log_error("Failed to issue method call: %s", bus_error_message(&error
, -r
));
128 static int vconsole_convert_to_x11_and_emit(Context
*c
, sd_bus
*bus
) {
133 r
= vconsole_convert_to_x11(c
);
138 r
= x11_write_data(c
);
140 return log_error_errno(r
, "Failed to write X11 keyboard layout: %m");
142 sd_bus_emit_properties_changed(bus
,
143 "/org/freedesktop/locale1",
144 "org.freedesktop.locale1",
145 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL
);
150 static int x11_convert_to_vconsole_and_emit(Context
*c
, sd_bus
*bus
) {
155 r
= x11_convert_to_vconsole(c
);
160 r
= vconsole_write_data(c
);
162 log_error_errno(r
, "Failed to save virtual console keymap: %m");
164 sd_bus_emit_properties_changed(bus
,
165 "/org/freedesktop/locale1",
166 "org.freedesktop.locale1",
167 "VConsoleKeymap", "VConsoleKeymapToggle", NULL
);
169 return vconsole_reload(bus
);
172 static int property_get_locale(
175 const char *interface
,
176 const char *property
,
177 sd_bus_message
*reply
,
179 sd_bus_error
*error
) {
181 Context
*c
= userdata
;
182 _cleanup_strv_free_
char **l
= NULL
;
185 l
= new0(char*, _VARIABLE_LC_MAX
+1);
189 for (p
= 0, q
= 0; p
< _VARIABLE_LC_MAX
; p
++) {
193 name
= locale_variable_to_string(p
);
196 if (isempty(c
->locale
[p
]))
199 if (asprintf(&t
, "%s=%s", name
, c
->locale
[p
]) < 0)
205 return sd_bus_message_append_strv(reply
, l
);
208 static int method_set_locale(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
209 Context
*c
= userdata
;
210 _cleanup_strv_free_
char **l
= NULL
;
212 const char *lang
= NULL
;
214 bool modified
= false;
215 bool have
[_VARIABLE_LC_MAX
] = {};
222 r
= bus_message_read_strv_extend(m
, &l
);
226 r
= sd_bus_message_read_basic(m
, 'b', &interactive
);
230 /* Check whether a variable changed and if it is valid */
234 for (p
= 0; p
< _VARIABLE_LC_MAX
; p
++) {
238 name
= locale_variable_to_string(p
);
242 if (startswith(*i
, name
) &&
244 locale_is_valid((*i
) + k
+ 1)) {
248 if (p
== VARIABLE_LANG
)
251 if (!streq_ptr(*i
+ k
+ 1, c
->locale
[p
]))
259 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid Locale data.");
262 /* If LANG was specified, but not LANGUAGE, check if we should
263 * set it based on the language fallback table. */
264 if (have
[VARIABLE_LANG
] && !have
[VARIABLE_LANGUAGE
]) {
265 _cleanup_free_
char *language
= NULL
;
269 (void) find_language_fallback(lang
, &language
);
271 log_debug("Converted LANG=%s to LANGUAGE=%s", lang
, language
);
272 if (!streq_ptr(language
, c
->locale
[VARIABLE_LANGUAGE
])) {
273 r
= strv_extendf(&l
, "LANGUAGE=%s", language
);
277 have
[VARIABLE_LANGUAGE
] = true;
283 /* Check whether a variable is unset */
285 for (p
= 0; p
< _VARIABLE_LC_MAX
; p
++)
286 if (!isempty(c
->locale
[p
]) && !have
[p
]) {
292 _cleanup_strv_free_
char **settings
= NULL
;
294 r
= bus_verify_polkit_async(
297 "org.freedesktop.locale1.set-locale",
306 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
309 for (p
= 0; p
< _VARIABLE_LC_MAX
; p
++) {
313 name
= locale_variable_to_string(p
);
317 if (startswith(*i
, name
) && (*i
)[k
] == '=') {
318 r
= free_and_strdup(&c
->locale
[p
], *i
+ k
+ 1);
325 for (p
= 0; p
< _VARIABLE_LC_MAX
; p
++) {
329 c
->locale
[p
] = mfree(c
->locale
[p
]);
334 r
= locale_write_data(c
, &settings
);
336 log_error_errno(r
, "Failed to set locale: %m");
337 return sd_bus_error_set_errnof(error
, r
, "Failed to set locale: %m");
340 locale_update_system_manager(c
, sd_bus_message_get_bus(m
));
343 _cleanup_free_
char *line
;
345 line
= strv_join(settings
, ", ");
346 log_info("Changed locale to %s.", strnull(line
));
348 log_info("Changed locale to unset.");
350 (void) sd_bus_emit_properties_changed(
351 sd_bus_message_get_bus(m
),
352 "/org/freedesktop/locale1",
353 "org.freedesktop.locale1",
356 log_debug("Locale settings were not modified.");
359 return sd_bus_reply_method_return(m
, NULL
);
362 static int method_set_vc_keyboard(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
363 Context
*c
= userdata
;
364 const char *keymap
, *keymap_toggle
;
365 int convert
, interactive
;
371 r
= sd_bus_message_read(m
, "ssbb", &keymap
, &keymap_toggle
, &convert
, &interactive
);
375 keymap
= empty_to_null(keymap
);
376 keymap_toggle
= empty_to_null(keymap_toggle
);
378 if (!streq_ptr(keymap
, c
->vc_keymap
) ||
379 !streq_ptr(keymap_toggle
, c
->vc_keymap_toggle
)) {
381 if ((keymap
&& (!filename_is_valid(keymap
) || !string_is_safe(keymap
))) ||
382 (keymap_toggle
&& (!filename_is_valid(keymap_toggle
) || !string_is_safe(keymap_toggle
))))
383 return sd_bus_error_set_errnof(error
, -EINVAL
, "Received invalid keymap data");
385 r
= bus_verify_polkit_async(
388 "org.freedesktop.locale1.set-keyboard",
397 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
399 if (free_and_strdup(&c
->vc_keymap
, keymap
) < 0 ||
400 free_and_strdup(&c
->vc_keymap_toggle
, keymap_toggle
) < 0)
403 r
= vconsole_write_data(c
);
405 log_error_errno(r
, "Failed to set virtual console keymap: %m");
406 return sd_bus_error_set_errnof(error
, r
, "Failed to set virtual console keymap: %m");
409 log_info("Changed virtual console keymap to '%s' toggle '%s'",
410 strempty(c
->vc_keymap
), strempty(c
->vc_keymap_toggle
));
412 r
= vconsole_reload(sd_bus_message_get_bus(m
));
414 log_error_errno(r
, "Failed to request keymap reload: %m");
416 (void) sd_bus_emit_properties_changed(
417 sd_bus_message_get_bus(m
),
418 "/org/freedesktop/locale1",
419 "org.freedesktop.locale1",
420 "VConsoleKeymap", "VConsoleKeymapToggle", NULL
);
423 r
= vconsole_convert_to_x11_and_emit(c
, sd_bus_message_get_bus(m
));
425 log_error_errno(r
, "Failed to convert keymap data: %m");
429 return sd_bus_reply_method_return(m
, NULL
);
432 #ifdef HAVE_XKBCOMMON
435 static void log_xkb(struct xkb_context
*ctx
, enum xkb_log_level lvl
, const char *format
, va_list args
) {
438 fmt
= strjoina("libxkbcommon: ", format
);
439 log_internalv(LOG_DEBUG
, 0, __FILE__
, __LINE__
, __func__
, fmt
, args
);
442 #define LOAD_SYMBOL(symbol, dl, name) \
444 (symbol) = (typeof(symbol)) dlvsym((dl), (name), "V_0.5.0"); \
445 (symbol) ? 0 : -EOPNOTSUPP; \
448 static int verify_xkb_rmlvo(const char *model
, const char *layout
, const char *variant
, const char *options
) {
450 /* We dlopen() the library in order to make the dependency soft. The library (and what it pulls in) is huge
451 * after all, hence let's support XKB maps when the library is around, and refuse otherwise. The function
452 * pointers to the shared library are below: */
454 struct xkb_context
* (*symbol_xkb_context_new
)(enum xkb_context_flags flags
) = NULL
;
455 void (*symbol_xkb_context_unref
)(struct xkb_context
*context
) = NULL
;
456 void (*symbol_xkb_context_set_log_fn
)(struct xkb_context
*context
, void (*log_fn
)(struct xkb_context
*context
, enum xkb_log_level level
, const char *format
, va_list args
)) = NULL
;
457 struct xkb_keymap
* (*symbol_xkb_keymap_new_from_names
)(struct xkb_context
*context
, const struct xkb_rule_names
*names
, enum xkb_keymap_compile_flags flags
) = NULL
;
458 void (*symbol_xkb_keymap_unref
)(struct xkb_keymap
*keymap
) = NULL
;
460 const struct xkb_rule_names rmlvo
= {
466 struct xkb_context
*ctx
= NULL
;
467 struct xkb_keymap
*km
= NULL
;
471 /* Compile keymap from RMLVO information to check out its validity */
473 dl
= dlopen("libxkbcommon.so.0", RTLD_LAZY
);
477 r
= LOAD_SYMBOL(symbol_xkb_context_new
, dl
, "xkb_context_new");
481 r
= LOAD_SYMBOL(symbol_xkb_context_unref
, dl
, "xkb_context_unref");
485 r
= LOAD_SYMBOL(symbol_xkb_context_set_log_fn
, dl
, "xkb_context_set_log_fn");
489 r
= LOAD_SYMBOL(symbol_xkb_keymap_new_from_names
, dl
, "xkb_keymap_new_from_names");
493 r
= LOAD_SYMBOL(symbol_xkb_keymap_unref
, dl
, "xkb_keymap_unref");
497 ctx
= symbol_xkb_context_new(XKB_CONTEXT_NO_ENVIRONMENT_NAMES
);
503 symbol_xkb_context_set_log_fn(ctx
, log_xkb
);
505 km
= symbol_xkb_keymap_new_from_names(ctx
, &rmlvo
, XKB_KEYMAP_COMPILE_NO_FLAGS
);
514 if (symbol_xkb_keymap_unref
&& km
)
515 symbol_xkb_keymap_unref(km
);
517 if (symbol_xkb_context_unref
&& ctx
)
518 symbol_xkb_context_unref(ctx
);
526 static int verify_xkb_rmlvo(const char *model
, const char *layout
, const char *variant
, const char *options
) {
532 static int method_set_x11_keyboard(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
533 Context
*c
= userdata
;
534 const char *layout
, *model
, *variant
, *options
;
535 int convert
, interactive
;
541 r
= sd_bus_message_read(m
, "ssssbb", &layout
, &model
, &variant
, &options
, &convert
, &interactive
);
545 layout
= empty_to_null(layout
);
546 model
= empty_to_null(model
);
547 variant
= empty_to_null(variant
);
548 options
= empty_to_null(options
);
550 if (!streq_ptr(layout
, c
->x11_layout
) ||
551 !streq_ptr(model
, c
->x11_model
) ||
552 !streq_ptr(variant
, c
->x11_variant
) ||
553 !streq_ptr(options
, c
->x11_options
)) {
555 if ((layout
&& !string_is_safe(layout
)) ||
556 (model
&& !string_is_safe(model
)) ||
557 (variant
&& !string_is_safe(variant
)) ||
558 (options
&& !string_is_safe(options
)))
559 return sd_bus_error_set_errnof(error
, -EINVAL
, "Received invalid keyboard data");
561 r
= bus_verify_polkit_async(
564 "org.freedesktop.locale1.set-keyboard",
573 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
575 r
= verify_xkb_rmlvo(model
, layout
, variant
, options
);
577 log_error_errno(r
, "Cannot compile XKB keymap for new x11 keyboard layout ('%s' / '%s' / '%s' / '%s'): %m",
578 strempty(model
), strempty(layout
), strempty(variant
), strempty(options
));
580 if (r
== -EOPNOTSUPP
)
581 return sd_bus_error_setf(error
, SD_BUS_ERROR_NOT_SUPPORTED
, "Local keyboard configuration not supported on this system.");
583 return sd_bus_error_set(error
, SD_BUS_ERROR_INVALID_ARGS
, "Specified keymap cannot be compiled, refusing as invalid.");
586 if (free_and_strdup(&c
->x11_layout
, layout
) < 0 ||
587 free_and_strdup(&c
->x11_model
, model
) < 0 ||
588 free_and_strdup(&c
->x11_variant
, variant
) < 0 ||
589 free_and_strdup(&c
->x11_options
, options
) < 0)
592 r
= x11_write_data(c
);
594 log_error_errno(r
, "Failed to set X11 keyboard layout: %m");
595 return sd_bus_error_set_errnof(error
, r
, "Failed to set X11 keyboard layout: %m");
598 log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
599 strempty(c
->x11_layout
),
600 strempty(c
->x11_model
),
601 strempty(c
->x11_variant
),
602 strempty(c
->x11_options
));
604 (void) sd_bus_emit_properties_changed(
605 sd_bus_message_get_bus(m
),
606 "/org/freedesktop/locale1",
607 "org.freedesktop.locale1",
608 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL
);
611 r
= x11_convert_to_vconsole_and_emit(c
, sd_bus_message_get_bus(m
));
613 log_error_errno(r
, "Failed to convert keymap data: %m");
617 return sd_bus_reply_method_return(m
, NULL
);
620 static const sd_bus_vtable locale_vtable
[] = {
621 SD_BUS_VTABLE_START(0),
622 SD_BUS_PROPERTY("Locale", "as", property_get_locale
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
623 SD_BUS_PROPERTY("X11Layout", "s", NULL
, offsetof(Context
, x11_layout
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
624 SD_BUS_PROPERTY("X11Model", "s", NULL
, offsetof(Context
, x11_model
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
625 SD_BUS_PROPERTY("X11Variant", "s", NULL
, offsetof(Context
, x11_variant
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
626 SD_BUS_PROPERTY("X11Options", "s", NULL
, offsetof(Context
, x11_options
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
627 SD_BUS_PROPERTY("VConsoleKeymap", "s", NULL
, offsetof(Context
, vc_keymap
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
628 SD_BUS_PROPERTY("VConsoleKeymapToggle", "s", NULL
, offsetof(Context
, vc_keymap_toggle
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
629 SD_BUS_METHOD("SetLocale", "asb", NULL
, method_set_locale
, SD_BUS_VTABLE_UNPRIVILEGED
),
630 SD_BUS_METHOD("SetVConsoleKeyboard", "ssbb", NULL
, method_set_vc_keyboard
, SD_BUS_VTABLE_UNPRIVILEGED
),
631 SD_BUS_METHOD("SetX11Keyboard", "ssssbb", NULL
, method_set_x11_keyboard
, SD_BUS_VTABLE_UNPRIVILEGED
),
635 static int connect_bus(Context
*c
, sd_event
*event
, sd_bus
**_bus
) {
636 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
643 r
= sd_bus_default_system(&bus
);
645 return log_error_errno(r
, "Failed to get system bus connection: %m");
647 r
= sd_bus_add_object_vtable(bus
, NULL
, "/org/freedesktop/locale1", "org.freedesktop.locale1", locale_vtable
, c
);
649 return log_error_errno(r
, "Failed to register object: %m");
651 r
= sd_bus_request_name(bus
, "org.freedesktop.locale1", 0);
653 return log_error_errno(r
, "Failed to register name: %m");
655 r
= sd_bus_attach_event(bus
, event
, 0);
657 return log_error_errno(r
, "Failed to attach bus to event loop: %m");
665 int main(int argc
, char *argv
[]) {
666 _cleanup_(context_free
) Context context
= {};
667 _cleanup_(sd_event_unrefp
) sd_event
*event
= NULL
;
668 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
671 log_set_target(LOG_TARGET_AUTO
);
672 log_parse_environment();
679 log_error("This program takes no arguments.");
684 r
= sd_event_default(&event
);
686 log_error_errno(r
, "Failed to allocate event loop: %m");
690 sd_event_set_watchdog(event
, true);
692 r
= connect_bus(&context
, event
, &bus
);
696 r
= context_read_data(&context
);
698 log_error_errno(r
, "Failed to read locale data: %m");
702 r
= bus_event_loop_with_idle(event
, bus
, "org.freedesktop.locale1", DEFAULT_EXIT_USEC
, NULL
, NULL
);
704 log_error_errno(r
, "Failed to run event loop: %m");
707 bus_verify_polkit_async_registry_free(polkit_registry
);
709 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;