1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright 2011 Lennart Poettering
6 Copyright 2013 Kay Sievers
14 #include <xkbcommon/xkbcommon.h>
20 #include "alloc-util.h"
21 #include "bus-error.h"
22 #include "bus-message.h"
25 #include "keymap-util.h"
26 #include "locale-util.h"
28 #include "path-util.h"
29 #include "selinux-util.h"
30 #include "string-util.h"
32 #include "user-util.h"
34 static Hashmap
*polkit_registry
= NULL
;
36 static int locale_update_system_manager(Context
*c
, sd_bus
*bus
) {
37 _cleanup_free_
char **l_unset
= NULL
;
38 _cleanup_strv_free_
char **l_set
= NULL
;
39 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*m
= NULL
;
40 sd_bus_error error
= SD_BUS_ERROR_NULL
;
41 unsigned c_set
, c_unset
, p
;
46 l_unset
= new0(char*, _VARIABLE_LC_MAX
);
50 l_set
= new0(char*, _VARIABLE_LC_MAX
);
54 for (p
= 0, c_set
= 0, c_unset
= 0; p
< _VARIABLE_LC_MAX
; p
++) {
57 name
= locale_variable_to_string(p
);
60 if (isempty(c
->locale
[p
]))
61 l_unset
[c_set
++] = (char*) name
;
65 if (asprintf(&s
, "%s=%s", name
, c
->locale
[p
]) < 0)
72 assert(c_set
+ c_unset
== _VARIABLE_LC_MAX
);
73 r
= sd_bus_message_new_method_call(bus
, &m
,
74 "org.freedesktop.systemd1",
75 "/org/freedesktop/systemd1",
76 "org.freedesktop.systemd1.Manager",
77 "UnsetAndSetEnvironment");
81 r
= sd_bus_message_append_strv(m
, l_unset
);
85 r
= sd_bus_message_append_strv(m
, l_set
);
89 r
= sd_bus_call(bus
, m
, 0, &error
, NULL
);
91 log_error_errno(r
, "Failed to update the manager environment: %m");
96 static int vconsole_reload(sd_bus
*bus
) {
97 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
102 r
= sd_bus_call_method(bus
,
103 "org.freedesktop.systemd1",
104 "/org/freedesktop/systemd1",
105 "org.freedesktop.systemd1.Manager",
109 "ss", "systemd-vconsole-setup.service", "replace");
112 log_error("Failed to issue method call: %s", bus_error_message(&error
, -r
));
116 static int vconsole_convert_to_x11_and_emit(Context
*c
, sd_bus
*bus
) {
121 r
= vconsole_convert_to_x11(c
);
126 r
= x11_write_data(c
);
128 return log_error_errno(r
, "Failed to write X11 keyboard layout: %m");
130 sd_bus_emit_properties_changed(bus
,
131 "/org/freedesktop/locale1",
132 "org.freedesktop.locale1",
133 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL
);
138 static int x11_convert_to_vconsole_and_emit(Context
*c
, sd_bus
*bus
) {
143 r
= x11_convert_to_vconsole(c
);
148 r
= vconsole_write_data(c
);
150 log_error_errno(r
, "Failed to save virtual console keymap: %m");
152 sd_bus_emit_properties_changed(bus
,
153 "/org/freedesktop/locale1",
154 "org.freedesktop.locale1",
155 "VConsoleKeymap", "VConsoleKeymapToggle", NULL
);
157 return vconsole_reload(bus
);
160 static int property_get_locale(
163 const char *interface
,
164 const char *property
,
165 sd_bus_message
*reply
,
167 sd_bus_error
*error
) {
169 Context
*c
= userdata
;
170 _cleanup_strv_free_
char **l
= NULL
;
173 l
= new0(char*, _VARIABLE_LC_MAX
+1);
177 for (p
= 0, q
= 0; p
< _VARIABLE_LC_MAX
; p
++) {
181 name
= locale_variable_to_string(p
);
184 if (isempty(c
->locale
[p
]))
187 if (asprintf(&t
, "%s=%s", name
, c
->locale
[p
]) < 0)
193 return sd_bus_message_append_strv(reply
, l
);
196 static int method_set_locale(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
197 Context
*c
= userdata
;
198 _cleanup_strv_free_
char **l
= NULL
;
200 const char *lang
= NULL
;
202 bool modified
= false;
203 bool have
[_VARIABLE_LC_MAX
] = {};
210 r
= bus_message_read_strv_extend(m
, &l
);
214 r
= sd_bus_message_read_basic(m
, 'b', &interactive
);
218 /* Check whether a variable changed and if it is valid */
222 for (p
= 0; p
< _VARIABLE_LC_MAX
; p
++) {
226 name
= locale_variable_to_string(p
);
230 if (startswith(*i
, name
) &&
232 locale_is_valid((*i
) + k
+ 1)) {
236 if (p
== VARIABLE_LANG
)
239 if (!streq_ptr(*i
+ k
+ 1, c
->locale
[p
]))
247 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid Locale data.");
250 /* If LANG was specified, but not LANGUAGE, check if we should
251 * set it based on the language fallback table. */
252 if (have
[VARIABLE_LANG
] && !have
[VARIABLE_LANGUAGE
]) {
253 _cleanup_free_
char *language
= NULL
;
257 (void) find_language_fallback(lang
, &language
);
259 log_debug("Converted LANG=%s to LANGUAGE=%s", lang
, language
);
260 if (!streq_ptr(language
, c
->locale
[VARIABLE_LANGUAGE
])) {
261 r
= strv_extendf(&l
, "LANGUAGE=%s", language
);
265 have
[VARIABLE_LANGUAGE
] = true;
271 /* Check whether a variable is unset */
273 for (p
= 0; p
< _VARIABLE_LC_MAX
; p
++)
274 if (!isempty(c
->locale
[p
]) && !have
[p
]) {
280 _cleanup_strv_free_
char **settings
= NULL
;
282 r
= bus_verify_polkit_async(
285 "org.freedesktop.locale1.set-locale",
294 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
297 for (p
= 0; p
< _VARIABLE_LC_MAX
; p
++) {
301 name
= locale_variable_to_string(p
);
305 if (startswith(*i
, name
) && (*i
)[k
] == '=') {
306 r
= free_and_strdup(&c
->locale
[p
], *i
+ k
+ 1);
313 for (p
= 0; p
< _VARIABLE_LC_MAX
; p
++) {
317 c
->locale
[p
] = mfree(c
->locale
[p
]);
322 r
= locale_write_data(c
, &settings
);
324 log_error_errno(r
, "Failed to set locale: %m");
325 return sd_bus_error_set_errnof(error
, r
, "Failed to set locale: %m");
328 locale_update_system_manager(c
, sd_bus_message_get_bus(m
));
331 _cleanup_free_
char *line
;
333 line
= strv_join(settings
, ", ");
334 log_info("Changed locale to %s.", strnull(line
));
336 log_info("Changed locale to unset.");
338 (void) sd_bus_emit_properties_changed(
339 sd_bus_message_get_bus(m
),
340 "/org/freedesktop/locale1",
341 "org.freedesktop.locale1",
344 log_debug("Locale settings were not modified.");
346 return sd_bus_reply_method_return(m
, NULL
);
349 static int method_set_vc_keyboard(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
350 Context
*c
= userdata
;
351 const char *keymap
, *keymap_toggle
;
352 int convert
, interactive
;
358 r
= sd_bus_message_read(m
, "ssbb", &keymap
, &keymap_toggle
, &convert
, &interactive
);
362 keymap
= empty_to_null(keymap
);
363 keymap_toggle
= empty_to_null(keymap_toggle
);
365 if (!streq_ptr(keymap
, c
->vc_keymap
) ||
366 !streq_ptr(keymap_toggle
, c
->vc_keymap_toggle
)) {
368 if ((keymap
&& (!filename_is_valid(keymap
) || !string_is_safe(keymap
))) ||
369 (keymap_toggle
&& (!filename_is_valid(keymap_toggle
) || !string_is_safe(keymap_toggle
))))
370 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Received invalid keymap data");
372 r
= bus_verify_polkit_async(
375 "org.freedesktop.locale1.set-keyboard",
384 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
386 if (free_and_strdup(&c
->vc_keymap
, keymap
) < 0 ||
387 free_and_strdup(&c
->vc_keymap_toggle
, keymap_toggle
) < 0)
390 r
= vconsole_write_data(c
);
392 log_error_errno(r
, "Failed to set virtual console keymap: %m");
393 return sd_bus_error_set_errnof(error
, r
, "Failed to set virtual console keymap: %m");
396 log_info("Changed virtual console keymap to '%s' toggle '%s'",
397 strempty(c
->vc_keymap
), strempty(c
->vc_keymap_toggle
));
399 r
= vconsole_reload(sd_bus_message_get_bus(m
));
401 log_error_errno(r
, "Failed to request keymap reload: %m");
403 (void) sd_bus_emit_properties_changed(
404 sd_bus_message_get_bus(m
),
405 "/org/freedesktop/locale1",
406 "org.freedesktop.locale1",
407 "VConsoleKeymap", "VConsoleKeymapToggle", NULL
);
410 r
= vconsole_convert_to_x11_and_emit(c
, sd_bus_message_get_bus(m
));
412 log_error_errno(r
, "Failed to convert keymap data: %m");
416 return sd_bus_reply_method_return(m
, NULL
);
422 static void log_xkb(struct xkb_context
*ctx
, enum xkb_log_level lvl
, const char *format
, va_list args
) {
425 fmt
= strjoina("libxkbcommon: ", format
);
426 #pragma GCC diagnostic push
427 #pragma GCC diagnostic ignored "-Wformat-nonliteral"
428 log_internalv(LOG_DEBUG
, 0, __FILE__
, __LINE__
, __func__
, fmt
, args
);
429 #pragma GCC diagnostic pop
432 #define LOAD_SYMBOL(symbol, dl, name) \
434 (symbol) = (typeof(symbol)) dlvsym((dl), (name), "V_0.5.0"); \
435 (symbol) ? 0 : -EOPNOTSUPP; \
438 static int verify_xkb_rmlvo(const char *model
, const char *layout
, const char *variant
, const char *options
) {
440 /* We dlopen() the library in order to make the dependency soft. The library (and what it pulls in) is huge
441 * after all, hence let's support XKB maps when the library is around, and refuse otherwise. The function
442 * pointers to the shared library are below: */
444 struct xkb_context
* (*symbol_xkb_context_new
)(enum xkb_context_flags flags
) = NULL
;
445 void (*symbol_xkb_context_unref
)(struct xkb_context
*context
) = NULL
;
446 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
;
447 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
;
448 void (*symbol_xkb_keymap_unref
)(struct xkb_keymap
*keymap
) = NULL
;
450 const struct xkb_rule_names rmlvo
= {
456 struct xkb_context
*ctx
= NULL
;
457 struct xkb_keymap
*km
= NULL
;
461 /* Compile keymap from RMLVO information to check out its validity */
463 dl
= dlopen("libxkbcommon.so.0", RTLD_LAZY
);
467 r
= LOAD_SYMBOL(symbol_xkb_context_new
, dl
, "xkb_context_new");
471 r
= LOAD_SYMBOL(symbol_xkb_context_unref
, dl
, "xkb_context_unref");
475 r
= LOAD_SYMBOL(symbol_xkb_context_set_log_fn
, dl
, "xkb_context_set_log_fn");
479 r
= LOAD_SYMBOL(symbol_xkb_keymap_new_from_names
, dl
, "xkb_keymap_new_from_names");
483 r
= LOAD_SYMBOL(symbol_xkb_keymap_unref
, dl
, "xkb_keymap_unref");
487 ctx
= symbol_xkb_context_new(XKB_CONTEXT_NO_ENVIRONMENT_NAMES
);
493 symbol_xkb_context_set_log_fn(ctx
, log_xkb
);
495 km
= symbol_xkb_keymap_new_from_names(ctx
, &rmlvo
, XKB_KEYMAP_COMPILE_NO_FLAGS
);
504 if (symbol_xkb_keymap_unref
&& km
)
505 symbol_xkb_keymap_unref(km
);
507 if (symbol_xkb_context_unref
&& ctx
)
508 symbol_xkb_context_unref(ctx
);
516 static int verify_xkb_rmlvo(const char *model
, const char *layout
, const char *variant
, const char *options
) {
522 static int method_set_x11_keyboard(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
523 Context
*c
= userdata
;
524 const char *layout
, *model
, *variant
, *options
;
525 int convert
, interactive
;
531 r
= sd_bus_message_read(m
, "ssssbb", &layout
, &model
, &variant
, &options
, &convert
, &interactive
);
535 layout
= empty_to_null(layout
);
536 model
= empty_to_null(model
);
537 variant
= empty_to_null(variant
);
538 options
= empty_to_null(options
);
540 if (!streq_ptr(layout
, c
->x11_layout
) ||
541 !streq_ptr(model
, c
->x11_model
) ||
542 !streq_ptr(variant
, c
->x11_variant
) ||
543 !streq_ptr(options
, c
->x11_options
)) {
545 if ((layout
&& !string_is_safe(layout
)) ||
546 (model
&& !string_is_safe(model
)) ||
547 (variant
&& !string_is_safe(variant
)) ||
548 (options
&& !string_is_safe(options
)))
549 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Received invalid keyboard data");
551 r
= bus_verify_polkit_async(
554 "org.freedesktop.locale1.set-keyboard",
563 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
565 r
= verify_xkb_rmlvo(model
, layout
, variant
, options
);
567 log_error_errno(r
, "Cannot compile XKB keymap for new x11 keyboard layout ('%s' / '%s' / '%s' / '%s'): %m",
568 strempty(model
), strempty(layout
), strempty(variant
), strempty(options
));
570 if (r
== -EOPNOTSUPP
)
571 return sd_bus_error_setf(error
, SD_BUS_ERROR_NOT_SUPPORTED
, "Local keyboard configuration not supported on this system.");
573 return sd_bus_error_set(error
, SD_BUS_ERROR_INVALID_ARGS
, "Specified keymap cannot be compiled, refusing as invalid.");
576 if (free_and_strdup(&c
->x11_layout
, layout
) < 0 ||
577 free_and_strdup(&c
->x11_model
, model
) < 0 ||
578 free_and_strdup(&c
->x11_variant
, variant
) < 0 ||
579 free_and_strdup(&c
->x11_options
, options
) < 0)
582 r
= x11_write_data(c
);
584 log_error_errno(r
, "Failed to set X11 keyboard layout: %m");
585 return sd_bus_error_set_errnof(error
, r
, "Failed to set X11 keyboard layout: %m");
588 log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
589 strempty(c
->x11_layout
),
590 strempty(c
->x11_model
),
591 strempty(c
->x11_variant
),
592 strempty(c
->x11_options
));
594 (void) sd_bus_emit_properties_changed(
595 sd_bus_message_get_bus(m
),
596 "/org/freedesktop/locale1",
597 "org.freedesktop.locale1",
598 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL
);
601 r
= x11_convert_to_vconsole_and_emit(c
, sd_bus_message_get_bus(m
));
603 log_error_errno(r
, "Failed to convert keymap data: %m");
607 return sd_bus_reply_method_return(m
, NULL
);
610 static const sd_bus_vtable locale_vtable
[] = {
611 SD_BUS_VTABLE_START(0),
612 SD_BUS_PROPERTY("Locale", "as", property_get_locale
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
613 SD_BUS_PROPERTY("X11Layout", "s", NULL
, offsetof(Context
, x11_layout
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
614 SD_BUS_PROPERTY("X11Model", "s", NULL
, offsetof(Context
, x11_model
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
615 SD_BUS_PROPERTY("X11Variant", "s", NULL
, offsetof(Context
, x11_variant
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
616 SD_BUS_PROPERTY("X11Options", "s", NULL
, offsetof(Context
, x11_options
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
617 SD_BUS_PROPERTY("VConsoleKeymap", "s", NULL
, offsetof(Context
, vc_keymap
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
618 SD_BUS_PROPERTY("VConsoleKeymapToggle", "s", NULL
, offsetof(Context
, vc_keymap_toggle
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
619 SD_BUS_METHOD("SetLocale", "asb", NULL
, method_set_locale
, SD_BUS_VTABLE_UNPRIVILEGED
),
620 SD_BUS_METHOD("SetVConsoleKeyboard", "ssbb", NULL
, method_set_vc_keyboard
, SD_BUS_VTABLE_UNPRIVILEGED
),
621 SD_BUS_METHOD("SetX11Keyboard", "ssssbb", NULL
, method_set_x11_keyboard
, SD_BUS_VTABLE_UNPRIVILEGED
),
625 static int connect_bus(Context
*c
, sd_event
*event
, sd_bus
**_bus
) {
626 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
633 r
= sd_bus_default_system(&bus
);
635 return log_error_errno(r
, "Failed to get system bus connection: %m");
637 r
= sd_bus_add_object_vtable(bus
, NULL
, "/org/freedesktop/locale1", "org.freedesktop.locale1", locale_vtable
, c
);
639 return log_error_errno(r
, "Failed to register object: %m");
641 r
= sd_bus_request_name_async(bus
, NULL
, "org.freedesktop.locale1", 0, NULL
, NULL
);
643 return log_error_errno(r
, "Failed to request name: %m");
645 r
= sd_bus_attach_event(bus
, event
, 0);
647 return log_error_errno(r
, "Failed to attach bus to event loop: %m");
649 *_bus
= TAKE_PTR(bus
);
654 int main(int argc
, char *argv
[]) {
655 _cleanup_(context_free
) Context context
= {};
656 _cleanup_(sd_event_unrefp
) sd_event
*event
= NULL
;
657 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
660 log_set_target(LOG_TARGET_AUTO
);
661 log_parse_environment();
668 log_error("This program takes no arguments.");
673 r
= sd_event_default(&event
);
675 log_error_errno(r
, "Failed to allocate event loop: %m");
679 sd_event_set_watchdog(event
, true);
681 r
= connect_bus(&context
, event
, &bus
);
685 r
= context_read_data(&context
);
687 log_error_errno(r
, "Failed to read locale data: %m");
691 r
= bus_event_loop_with_idle(event
, bus
, "org.freedesktop.locale1", DEFAULT_EXIT_USEC
, NULL
, NULL
);
693 log_error_errno(r
, "Failed to run event loop: %m");
696 bus_verify_polkit_async_registry_free(polkit_registry
);
698 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;