1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright 2011 Lennart Poettering
6 Copyright 2013 Kay Sievers
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
27 #include <xkbcommon/xkbcommon.h>
33 #include "alloc-util.h"
34 #include "bus-error.h"
35 #include "bus-message.h"
38 #include "keymap-util.h"
39 #include "locale-util.h"
41 #include "path-util.h"
42 #include "selinux-util.h"
43 #include "string-util.h"
45 #include "user-util.h"
47 static Hashmap
*polkit_registry
= NULL
;
49 static int locale_update_system_manager(Context
*c
, sd_bus
*bus
) {
50 _cleanup_free_
char **l_unset
= NULL
;
51 _cleanup_strv_free_
char **l_set
= NULL
;
52 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*m
= NULL
;
53 sd_bus_error error
= SD_BUS_ERROR_NULL
;
54 unsigned c_set
, c_unset
, p
;
59 l_unset
= new0(char*, _VARIABLE_LC_MAX
);
63 l_set
= new0(char*, _VARIABLE_LC_MAX
);
67 for (p
= 0, c_set
= 0, c_unset
= 0; p
< _VARIABLE_LC_MAX
; p
++) {
70 name
= locale_variable_to_string(p
);
73 if (isempty(c
->locale
[p
]))
74 l_unset
[c_set
++] = (char*) name
;
78 if (asprintf(&s
, "%s=%s", name
, c
->locale
[p
]) < 0)
85 assert(c_set
+ c_unset
== _VARIABLE_LC_MAX
);
86 r
= sd_bus_message_new_method_call(bus
, &m
,
87 "org.freedesktop.systemd1",
88 "/org/freedesktop/systemd1",
89 "org.freedesktop.systemd1.Manager",
90 "UnsetAndSetEnvironment");
94 r
= sd_bus_message_append_strv(m
, l_unset
);
98 r
= sd_bus_message_append_strv(m
, l_set
);
102 r
= sd_bus_call(bus
, m
, 0, &error
, NULL
);
104 log_error_errno(r
, "Failed to update the manager environment: %m");
109 static int vconsole_reload(sd_bus
*bus
) {
110 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
115 r
= sd_bus_call_method(bus
,
116 "org.freedesktop.systemd1",
117 "/org/freedesktop/systemd1",
118 "org.freedesktop.systemd1.Manager",
122 "ss", "systemd-vconsole-setup.service", "replace");
125 log_error("Failed to issue method call: %s", bus_error_message(&error
, -r
));
129 static int vconsole_convert_to_x11_and_emit(Context
*c
, sd_bus
*bus
) {
134 r
= vconsole_convert_to_x11(c
);
139 r
= x11_write_data(c
);
141 return log_error_errno(r
, "Failed to write X11 keyboard layout: %m");
143 sd_bus_emit_properties_changed(bus
,
144 "/org/freedesktop/locale1",
145 "org.freedesktop.locale1",
146 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL
);
151 static int x11_convert_to_vconsole_and_emit(Context
*c
, sd_bus
*bus
) {
156 r
= x11_convert_to_vconsole(c
);
161 r
= vconsole_write_data(c
);
163 log_error_errno(r
, "Failed to save virtual console keymap: %m");
165 sd_bus_emit_properties_changed(bus
,
166 "/org/freedesktop/locale1",
167 "org.freedesktop.locale1",
168 "VConsoleKeymap", "VConsoleKeymapToggle", NULL
);
170 return vconsole_reload(bus
);
173 static int property_get_locale(
176 const char *interface
,
177 const char *property
,
178 sd_bus_message
*reply
,
180 sd_bus_error
*error
) {
182 Context
*c
= userdata
;
183 _cleanup_strv_free_
char **l
= NULL
;
186 l
= new0(char*, _VARIABLE_LC_MAX
+1);
190 for (p
= 0, q
= 0; p
< _VARIABLE_LC_MAX
; p
++) {
194 name
= locale_variable_to_string(p
);
197 if (isempty(c
->locale
[p
]))
200 if (asprintf(&t
, "%s=%s", name
, c
->locale
[p
]) < 0)
206 return sd_bus_message_append_strv(reply
, l
);
209 static int method_set_locale(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
210 Context
*c
= userdata
;
211 _cleanup_strv_free_
char **l
= NULL
;
213 const char *lang
= NULL
;
215 bool modified
= false;
216 bool have
[_VARIABLE_LC_MAX
] = {};
223 r
= bus_message_read_strv_extend(m
, &l
);
227 r
= sd_bus_message_read_basic(m
, 'b', &interactive
);
231 /* Check whether a variable changed and if it is valid */
235 for (p
= 0; p
< _VARIABLE_LC_MAX
; p
++) {
239 name
= locale_variable_to_string(p
);
243 if (startswith(*i
, name
) &&
245 locale_is_valid((*i
) + k
+ 1)) {
249 if (p
== VARIABLE_LANG
)
252 if (!streq_ptr(*i
+ k
+ 1, c
->locale
[p
]))
260 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid Locale data.");
263 /* If LANG was specified, but not LANGUAGE, check if we should
264 * set it based on the language fallback table. */
265 if (have
[VARIABLE_LANG
] && !have
[VARIABLE_LANGUAGE
]) {
266 _cleanup_free_
char *language
= NULL
;
270 (void) find_language_fallback(lang
, &language
);
272 log_debug("Converted LANG=%s to LANGUAGE=%s", lang
, language
);
273 if (!streq_ptr(language
, c
->locale
[VARIABLE_LANGUAGE
])) {
274 r
= strv_extendf(&l
, "LANGUAGE=%s", language
);
278 have
[VARIABLE_LANGUAGE
] = true;
284 /* Check whether a variable is unset */
286 for (p
= 0; p
< _VARIABLE_LC_MAX
; p
++)
287 if (!isempty(c
->locale
[p
]) && !have
[p
]) {
293 _cleanup_strv_free_
char **settings
= NULL
;
295 r
= bus_verify_polkit_async(
298 "org.freedesktop.locale1.set-locale",
307 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
310 for (p
= 0; p
< _VARIABLE_LC_MAX
; p
++) {
314 name
= locale_variable_to_string(p
);
318 if (startswith(*i
, name
) && (*i
)[k
] == '=') {
319 r
= free_and_strdup(&c
->locale
[p
], *i
+ k
+ 1);
326 for (p
= 0; p
< _VARIABLE_LC_MAX
; p
++) {
330 c
->locale
[p
] = mfree(c
->locale
[p
]);
335 r
= locale_write_data(c
, &settings
);
337 log_error_errno(r
, "Failed to set locale: %m");
338 return sd_bus_error_set_errnof(error
, r
, "Failed to set locale: %m");
341 locale_update_system_manager(c
, sd_bus_message_get_bus(m
));
344 _cleanup_free_
char *line
;
346 line
= strv_join(settings
, ", ");
347 log_info("Changed locale to %s.", strnull(line
));
349 log_info("Changed locale to unset.");
351 (void) sd_bus_emit_properties_changed(
352 sd_bus_message_get_bus(m
),
353 "/org/freedesktop/locale1",
354 "org.freedesktop.locale1",
357 log_debug("Locale settings were not modified.");
360 return sd_bus_reply_method_return(m
, NULL
);
363 static int method_set_vc_keyboard(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
364 Context
*c
= userdata
;
365 const char *keymap
, *keymap_toggle
;
366 int convert
, interactive
;
372 r
= sd_bus_message_read(m
, "ssbb", &keymap
, &keymap_toggle
, &convert
, &interactive
);
376 keymap
= empty_to_null(keymap
);
377 keymap_toggle
= empty_to_null(keymap_toggle
);
379 if (!streq_ptr(keymap
, c
->vc_keymap
) ||
380 !streq_ptr(keymap_toggle
, c
->vc_keymap_toggle
)) {
382 if ((keymap
&& (!filename_is_valid(keymap
) || !string_is_safe(keymap
))) ||
383 (keymap_toggle
&& (!filename_is_valid(keymap_toggle
) || !string_is_safe(keymap_toggle
))))
384 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Received invalid keymap data");
386 r
= bus_verify_polkit_async(
389 "org.freedesktop.locale1.set-keyboard",
398 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
400 if (free_and_strdup(&c
->vc_keymap
, keymap
) < 0 ||
401 free_and_strdup(&c
->vc_keymap_toggle
, keymap_toggle
) < 0)
404 r
= vconsole_write_data(c
);
406 log_error_errno(r
, "Failed to set virtual console keymap: %m");
407 return sd_bus_error_set_errnof(error
, r
, "Failed to set virtual console keymap: %m");
410 log_info("Changed virtual console keymap to '%s' toggle '%s'",
411 strempty(c
->vc_keymap
), strempty(c
->vc_keymap_toggle
));
413 r
= vconsole_reload(sd_bus_message_get_bus(m
));
415 log_error_errno(r
, "Failed to request keymap reload: %m");
417 (void) sd_bus_emit_properties_changed(
418 sd_bus_message_get_bus(m
),
419 "/org/freedesktop/locale1",
420 "org.freedesktop.locale1",
421 "VConsoleKeymap", "VConsoleKeymapToggle", NULL
);
424 r
= vconsole_convert_to_x11_and_emit(c
, sd_bus_message_get_bus(m
));
426 log_error_errno(r
, "Failed to convert keymap data: %m");
430 return sd_bus_reply_method_return(m
, NULL
);
436 static void log_xkb(struct xkb_context
*ctx
, enum xkb_log_level lvl
, const char *format
, va_list args
) {
439 fmt
= strjoina("libxkbcommon: ", format
);
440 #pragma GCC diagnostic push
441 #pragma GCC diagnostic ignored "-Wformat-nonliteral"
442 log_internalv(LOG_DEBUG
, 0, __FILE__
, __LINE__
, __func__
, fmt
, args
);
443 #pragma GCC diagnostic pop
446 #define LOAD_SYMBOL(symbol, dl, name) \
448 (symbol) = (typeof(symbol)) dlvsym((dl), (name), "V_0.5.0"); \
449 (symbol) ? 0 : -EOPNOTSUPP; \
452 static int verify_xkb_rmlvo(const char *model
, const char *layout
, const char *variant
, const char *options
) {
454 /* We dlopen() the library in order to make the dependency soft. The library (and what it pulls in) is huge
455 * after all, hence let's support XKB maps when the library is around, and refuse otherwise. The function
456 * pointers to the shared library are below: */
458 struct xkb_context
* (*symbol_xkb_context_new
)(enum xkb_context_flags flags
) = NULL
;
459 void (*symbol_xkb_context_unref
)(struct xkb_context
*context
) = NULL
;
460 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
;
461 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
;
462 void (*symbol_xkb_keymap_unref
)(struct xkb_keymap
*keymap
) = NULL
;
464 const struct xkb_rule_names rmlvo
= {
470 struct xkb_context
*ctx
= NULL
;
471 struct xkb_keymap
*km
= NULL
;
475 /* Compile keymap from RMLVO information to check out its validity */
477 dl
= dlopen("libxkbcommon.so.0", RTLD_LAZY
);
481 r
= LOAD_SYMBOL(symbol_xkb_context_new
, dl
, "xkb_context_new");
485 r
= LOAD_SYMBOL(symbol_xkb_context_unref
, dl
, "xkb_context_unref");
489 r
= LOAD_SYMBOL(symbol_xkb_context_set_log_fn
, dl
, "xkb_context_set_log_fn");
493 r
= LOAD_SYMBOL(symbol_xkb_keymap_new_from_names
, dl
, "xkb_keymap_new_from_names");
497 r
= LOAD_SYMBOL(symbol_xkb_keymap_unref
, dl
, "xkb_keymap_unref");
501 ctx
= symbol_xkb_context_new(XKB_CONTEXT_NO_ENVIRONMENT_NAMES
);
507 symbol_xkb_context_set_log_fn(ctx
, log_xkb
);
509 km
= symbol_xkb_keymap_new_from_names(ctx
, &rmlvo
, XKB_KEYMAP_COMPILE_NO_FLAGS
);
518 if (symbol_xkb_keymap_unref
&& km
)
519 symbol_xkb_keymap_unref(km
);
521 if (symbol_xkb_context_unref
&& ctx
)
522 symbol_xkb_context_unref(ctx
);
530 static int verify_xkb_rmlvo(const char *model
, const char *layout
, const char *variant
, const char *options
) {
536 static int method_set_x11_keyboard(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
537 Context
*c
= userdata
;
538 const char *layout
, *model
, *variant
, *options
;
539 int convert
, interactive
;
545 r
= sd_bus_message_read(m
, "ssssbb", &layout
, &model
, &variant
, &options
, &convert
, &interactive
);
549 layout
= empty_to_null(layout
);
550 model
= empty_to_null(model
);
551 variant
= empty_to_null(variant
);
552 options
= empty_to_null(options
);
554 if (!streq_ptr(layout
, c
->x11_layout
) ||
555 !streq_ptr(model
, c
->x11_model
) ||
556 !streq_ptr(variant
, c
->x11_variant
) ||
557 !streq_ptr(options
, c
->x11_options
)) {
559 if ((layout
&& !string_is_safe(layout
)) ||
560 (model
&& !string_is_safe(model
)) ||
561 (variant
&& !string_is_safe(variant
)) ||
562 (options
&& !string_is_safe(options
)))
563 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Received invalid keyboard data");
565 r
= bus_verify_polkit_async(
568 "org.freedesktop.locale1.set-keyboard",
577 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
579 r
= verify_xkb_rmlvo(model
, layout
, variant
, options
);
581 log_error_errno(r
, "Cannot compile XKB keymap for new x11 keyboard layout ('%s' / '%s' / '%s' / '%s'): %m",
582 strempty(model
), strempty(layout
), strempty(variant
), strempty(options
));
584 if (r
== -EOPNOTSUPP
)
585 return sd_bus_error_setf(error
, SD_BUS_ERROR_NOT_SUPPORTED
, "Local keyboard configuration not supported on this system.");
587 return sd_bus_error_set(error
, SD_BUS_ERROR_INVALID_ARGS
, "Specified keymap cannot be compiled, refusing as invalid.");
590 if (free_and_strdup(&c
->x11_layout
, layout
) < 0 ||
591 free_and_strdup(&c
->x11_model
, model
) < 0 ||
592 free_and_strdup(&c
->x11_variant
, variant
) < 0 ||
593 free_and_strdup(&c
->x11_options
, options
) < 0)
596 r
= x11_write_data(c
);
598 log_error_errno(r
, "Failed to set X11 keyboard layout: %m");
599 return sd_bus_error_set_errnof(error
, r
, "Failed to set X11 keyboard layout: %m");
602 log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
603 strempty(c
->x11_layout
),
604 strempty(c
->x11_model
),
605 strempty(c
->x11_variant
),
606 strempty(c
->x11_options
));
608 (void) sd_bus_emit_properties_changed(
609 sd_bus_message_get_bus(m
),
610 "/org/freedesktop/locale1",
611 "org.freedesktop.locale1",
612 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL
);
615 r
= x11_convert_to_vconsole_and_emit(c
, sd_bus_message_get_bus(m
));
617 log_error_errno(r
, "Failed to convert keymap data: %m");
621 return sd_bus_reply_method_return(m
, NULL
);
624 static const sd_bus_vtable locale_vtable
[] = {
625 SD_BUS_VTABLE_START(0),
626 SD_BUS_PROPERTY("Locale", "as", property_get_locale
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
627 SD_BUS_PROPERTY("X11Layout", "s", NULL
, offsetof(Context
, x11_layout
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
628 SD_BUS_PROPERTY("X11Model", "s", NULL
, offsetof(Context
, x11_model
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
629 SD_BUS_PROPERTY("X11Variant", "s", NULL
, offsetof(Context
, x11_variant
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
630 SD_BUS_PROPERTY("X11Options", "s", NULL
, offsetof(Context
, x11_options
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
631 SD_BUS_PROPERTY("VConsoleKeymap", "s", NULL
, offsetof(Context
, vc_keymap
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
632 SD_BUS_PROPERTY("VConsoleKeymapToggle", "s", NULL
, offsetof(Context
, vc_keymap_toggle
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
633 SD_BUS_METHOD("SetLocale", "asb", NULL
, method_set_locale
, SD_BUS_VTABLE_UNPRIVILEGED
),
634 SD_BUS_METHOD("SetVConsoleKeyboard", "ssbb", NULL
, method_set_vc_keyboard
, SD_BUS_VTABLE_UNPRIVILEGED
),
635 SD_BUS_METHOD("SetX11Keyboard", "ssssbb", NULL
, method_set_x11_keyboard
, SD_BUS_VTABLE_UNPRIVILEGED
),
639 static int connect_bus(Context
*c
, sd_event
*event
, sd_bus
**_bus
) {
640 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
647 r
= sd_bus_default_system(&bus
);
649 return log_error_errno(r
, "Failed to get system bus connection: %m");
651 r
= sd_bus_add_object_vtable(bus
, NULL
, "/org/freedesktop/locale1", "org.freedesktop.locale1", locale_vtable
, c
);
653 return log_error_errno(r
, "Failed to register object: %m");
655 r
= sd_bus_request_name(bus
, "org.freedesktop.locale1", 0);
657 return log_error_errno(r
, "Failed to register name: %m");
659 r
= sd_bus_attach_event(bus
, event
, 0);
661 return log_error_errno(r
, "Failed to attach bus to event loop: %m");
669 int main(int argc
, char *argv
[]) {
670 _cleanup_(context_free
) Context context
= {};
671 _cleanup_(sd_event_unrefp
) sd_event
*event
= NULL
;
672 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
675 log_set_target(LOG_TARGET_AUTO
);
676 log_parse_environment();
683 log_error("This program takes no arguments.");
688 r
= sd_event_default(&event
);
690 log_error_errno(r
, "Failed to allocate event loop: %m");
694 sd_event_set_watchdog(event
, true);
696 r
= connect_bus(&context
, event
, &bus
);
700 r
= context_read_data(&context
);
702 log_error_errno(r
, "Failed to read locale data: %m");
706 r
= bus_event_loop_with_idle(event
, bus
, "org.freedesktop.locale1", DEFAULT_EXIT_USEC
, NULL
, NULL
);
708 log_error_errno(r
, "Failed to run event loop: %m");
711 bus_verify_polkit_async_registry_free(polkit_registry
);
713 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;