1 /* SPDX-License-Identifier: LGPL-2.1+ */
8 #include <xkbcommon/xkbcommon.h>
14 #include "alloc-util.h"
15 #include "bus-error.h"
16 #include "bus-message.h"
19 #include "keymap-util.h"
20 #include "locale-util.h"
22 #include "path-util.h"
23 #include "selinux-util.h"
24 #include "signal-util.h"
25 #include "string-util.h"
27 #include "user-util.h"
29 static Hashmap
*polkit_registry
= NULL
;
31 static int locale_update_system_manager(Context
*c
, sd_bus
*bus
) {
32 _cleanup_free_
char **l_unset
= NULL
;
33 _cleanup_strv_free_
char **l_set
= NULL
;
34 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*m
= NULL
;
35 sd_bus_error error
= SD_BUS_ERROR_NULL
;
36 unsigned c_set
, c_unset
, p
;
41 l_unset
= new0(char*, _VARIABLE_LC_MAX
);
45 l_set
= new0(char*, _VARIABLE_LC_MAX
);
49 for (p
= 0, c_set
= 0, c_unset
= 0; p
< _VARIABLE_LC_MAX
; p
++) {
52 name
= locale_variable_to_string(p
);
55 if (isempty(c
->locale
[p
]))
56 l_unset
[c_set
++] = (char*) name
;
60 if (asprintf(&s
, "%s=%s", name
, c
->locale
[p
]) < 0)
67 assert(c_set
+ c_unset
== _VARIABLE_LC_MAX
);
68 r
= sd_bus_message_new_method_call(bus
, &m
,
69 "org.freedesktop.systemd1",
70 "/org/freedesktop/systemd1",
71 "org.freedesktop.systemd1.Manager",
72 "UnsetAndSetEnvironment");
76 r
= sd_bus_message_append_strv(m
, l_unset
);
80 r
= sd_bus_message_append_strv(m
, l_set
);
84 r
= sd_bus_call(bus
, m
, 0, &error
, NULL
);
86 log_error_errno(r
, "Failed to update the manager environment, ignoring: %m");
91 static int vconsole_reload(sd_bus
*bus
) {
92 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
97 r
= sd_bus_call_method(bus
,
98 "org.freedesktop.systemd1",
99 "/org/freedesktop/systemd1",
100 "org.freedesktop.systemd1.Manager",
104 "ss", "systemd-vconsole-setup.service", "replace");
107 return log_error_errno(r
, "Failed to issue method call: %s", bus_error_message(&error
, -r
));
112 static int vconsole_convert_to_x11_and_emit(Context
*c
, sd_bus_message
*m
) {
117 r
= x11_read_data(c
, m
);
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(sd_bus_message_get_bus(m
),
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_message
*m
) {
143 r
= vconsole_read_data(c
, m
);
147 r
= x11_convert_to_vconsole(c
);
152 r
= vconsole_write_data(c
);
154 log_error_errno(r
, "Failed to save virtual console keymap: %m");
156 sd_bus_emit_properties_changed(sd_bus_message_get_bus(m
),
157 "/org/freedesktop/locale1",
158 "org.freedesktop.locale1",
159 "VConsoleKeymap", "VConsoleKeymapToggle", NULL
);
161 return vconsole_reload(sd_bus_message_get_bus(m
));
164 static int property_get_locale(
167 const char *interface
,
168 const char *property
,
169 sd_bus_message
*reply
,
171 sd_bus_error
*error
) {
173 Context
*c
= userdata
;
174 _cleanup_strv_free_
char **l
= NULL
;
177 r
= locale_read_data(c
, reply
);
181 l
= new0(char*, _VARIABLE_LC_MAX
+1);
185 for (p
= 0, q
= 0; p
< _VARIABLE_LC_MAX
; p
++) {
189 name
= locale_variable_to_string(p
);
192 if (isempty(c
->locale
[p
]))
195 if (asprintf(&t
, "%s=%s", name
, c
->locale
[p
]) < 0)
201 return sd_bus_message_append_strv(reply
, l
);
204 static int property_get_vconsole(
207 const char *interface
,
208 const char *property
,
209 sd_bus_message
*reply
,
211 sd_bus_error
*error
) {
213 Context
*c
= userdata
;
216 r
= vconsole_read_data(c
, reply
);
220 if (streq(property
, "VConsoleKeymap"))
221 return sd_bus_message_append_basic(reply
, 's', c
->vc_keymap
);
222 else if (streq(property
, "VConsoleKeymapToggle"))
223 return sd_bus_message_append_basic(reply
, 's', c
->vc_keymap_toggle
);
228 static int property_get_xkb(
231 const char *interface
,
232 const char *property
,
233 sd_bus_message
*reply
,
235 sd_bus_error
*error
) {
237 Context
*c
= userdata
;
240 r
= x11_read_data(c
, reply
);
244 if (streq(property
, "X11Layout"))
245 return sd_bus_message_append_basic(reply
, 's', c
->x11_layout
);
246 else if (streq(property
, "X11Model"))
247 return sd_bus_message_append_basic(reply
, 's', c
->x11_model
);
248 else if (streq(property
, "X11Variant"))
249 return sd_bus_message_append_basic(reply
, 's', c
->x11_variant
);
250 else if (streq(property
, "X11Options"))
251 return sd_bus_message_append_basic(reply
, 's', c
->x11_options
);
256 static void locale_free(char ***l
) {
259 for (p
= 0; p
< _VARIABLE_LC_MAX
; p
++)
260 (*l
)[p
] = mfree((*l
)[p
]);
263 static int method_set_locale(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
264 Context
*c
= userdata
;
265 _cleanup_strv_free_
char **settings
= NULL
, **l
= NULL
;
266 char *new_locale
[_VARIABLE_LC_MAX
] = {}, **i
;
267 _cleanup_(locale_free
) _unused_
char **dummy
= new_locale
;
268 bool modified
= false;
269 int interactive
, p
, r
;
274 r
= bus_message_read_strv_extend(m
, &l
);
278 r
= sd_bus_message_read_basic(m
, 'b', &interactive
);
282 /* If single locale without variable name is provided, then we assume it is LANG=. */
283 if (strv_length(l
) == 1 && !strchr(*l
, '=')) {
284 if (!locale_is_valid(*l
))
285 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid Locale data.");
287 new_locale
[VARIABLE_LANG
] = strdup(*l
);
288 if (!new_locale
[VARIABLE_LANG
])
294 /* Check whether a variable is valid */
298 for (p
= 0; p
< _VARIABLE_LC_MAX
; p
++) {
302 name
= locale_variable_to_string(p
);
306 if (startswith(*i
, name
) &&
308 locale_is_valid((*i
) + k
+ 1)) {
311 new_locale
[p
] = strdup((*i
) + k
+ 1);
320 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid Locale data.");
323 /* If LANG was specified, but not LANGUAGE, check if we should
324 * set it based on the language fallback table. */
325 if (!isempty(new_locale
[VARIABLE_LANG
]) &&
326 isempty(new_locale
[VARIABLE_LANGUAGE
])) {
327 _cleanup_free_
char *language
= NULL
;
329 (void) find_language_fallback(new_locale
[VARIABLE_LANG
], &language
);
331 log_debug("Converted LANG=%s to LANGUAGE=%s", new_locale
[VARIABLE_LANG
], language
);
332 free_and_replace(new_locale
[VARIABLE_LANGUAGE
], language
);
336 r
= locale_read_data(c
, m
);
338 log_error_errno(r
, "Failed to read locale data: %m");
339 return sd_bus_error_setf(error
, SD_BUS_ERROR_FAILED
, "Failed to read locale data");
342 /* Merge with the current settings */
343 for (p
= 0; p
< _VARIABLE_LC_MAX
; p
++)
344 if (!isempty(c
->locale
[p
]) && isempty(new_locale
[p
])) {
345 new_locale
[p
] = strdup(c
->locale
[p
]);
350 locale_simplify(new_locale
);
352 for (p
= 0; p
< _VARIABLE_LC_MAX
; p
++)
353 if (!streq_ptr(c
->locale
[p
], new_locale
[p
])) {
359 log_debug("Locale settings were not modified.");
360 return sd_bus_reply_method_return(m
, NULL
);
363 r
= bus_verify_polkit_async(
366 "org.freedesktop.locale1.set-locale",
375 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
377 for (p
= 0; p
< _VARIABLE_LC_MAX
; p
++)
378 free_and_replace(c
->locale
[p
], new_locale
[p
]);
380 r
= locale_write_data(c
, &settings
);
382 log_error_errno(r
, "Failed to set locale: %m");
383 return sd_bus_error_set_errnof(error
, r
, "Failed to set locale: %m");
386 (void) locale_update_system_manager(c
, sd_bus_message_get_bus(m
));
389 _cleanup_free_
char *line
;
391 line
= strv_join(settings
, ", ");
392 log_info("Changed locale to %s.", strnull(line
));
394 log_info("Changed locale to unset.");
396 (void) sd_bus_emit_properties_changed(
397 sd_bus_message_get_bus(m
),
398 "/org/freedesktop/locale1",
399 "org.freedesktop.locale1",
402 return sd_bus_reply_method_return(m
, NULL
);
405 static int method_set_vc_keyboard(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
406 Context
*c
= userdata
;
407 const char *keymap
, *keymap_toggle
;
408 int convert
, interactive
, r
;
413 r
= sd_bus_message_read(m
, "ssbb", &keymap
, &keymap_toggle
, &convert
, &interactive
);
417 keymap
= empty_to_null(keymap
);
418 keymap_toggle
= empty_to_null(keymap_toggle
);
420 r
= vconsole_read_data(c
, m
);
422 log_error_errno(r
, "Failed to read virtual console keymap data: %m");
423 return sd_bus_error_setf(error
, SD_BUS_ERROR_FAILED
, "Failed to read virtual console keymap data");
426 if (streq_ptr(keymap
, c
->vc_keymap
) &&
427 streq_ptr(keymap_toggle
, c
->vc_keymap_toggle
))
428 return sd_bus_reply_method_return(m
, NULL
);
430 if ((keymap
&& (!filename_is_valid(keymap
) || !string_is_safe(keymap
))) ||
431 (keymap_toggle
&& (!filename_is_valid(keymap_toggle
) || !string_is_safe(keymap_toggle
))))
432 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Received invalid keymap data");
434 r
= bus_verify_polkit_async(
437 "org.freedesktop.locale1.set-keyboard",
446 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
448 if (free_and_strdup(&c
->vc_keymap
, keymap
) < 0 ||
449 free_and_strdup(&c
->vc_keymap_toggle
, keymap_toggle
) < 0)
452 r
= vconsole_write_data(c
);
454 log_error_errno(r
, "Failed to set virtual console keymap: %m");
455 return sd_bus_error_set_errnof(error
, r
, "Failed to set virtual console keymap: %m");
458 log_info("Changed virtual console keymap to '%s' toggle '%s'",
459 strempty(c
->vc_keymap
), strempty(c
->vc_keymap_toggle
));
461 r
= vconsole_reload(sd_bus_message_get_bus(m
));
463 log_error_errno(r
, "Failed to request keymap reload: %m");
465 (void) sd_bus_emit_properties_changed(
466 sd_bus_message_get_bus(m
),
467 "/org/freedesktop/locale1",
468 "org.freedesktop.locale1",
469 "VConsoleKeymap", "VConsoleKeymapToggle", NULL
);
472 r
= vconsole_convert_to_x11_and_emit(c
, m
);
474 log_error_errno(r
, "Failed to convert keymap data: %m");
477 return sd_bus_reply_method_return(m
, NULL
);
483 static void log_xkb(struct xkb_context
*ctx
, enum xkb_log_level lvl
, const char *format
, va_list args
) {
486 fmt
= strjoina("libxkbcommon: ", format
);
487 #pragma GCC diagnostic push
488 #pragma GCC diagnostic ignored "-Wformat-nonliteral"
489 log_internalv(LOG_DEBUG
, 0, __FILE__
, __LINE__
, __func__
, fmt
, args
);
490 #pragma GCC diagnostic pop
493 #define LOAD_SYMBOL(symbol, dl, name) \
495 (symbol) = (typeof(symbol)) dlvsym((dl), (name), "V_0.5.0"); \
496 (symbol) ? 0 : -EOPNOTSUPP; \
499 static int verify_xkb_rmlvo(const char *model
, const char *layout
, const char *variant
, const char *options
) {
501 /* We dlopen() the library in order to make the dependency soft. The library (and what it pulls in) is huge
502 * after all, hence let's support XKB maps when the library is around, and refuse otherwise. The function
503 * pointers to the shared library are below: */
505 struct xkb_context
* (*symbol_xkb_context_new
)(enum xkb_context_flags flags
) = NULL
;
506 void (*symbol_xkb_context_unref
)(struct xkb_context
*context
) = NULL
;
507 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
;
508 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
;
509 void (*symbol_xkb_keymap_unref
)(struct xkb_keymap
*keymap
) = NULL
;
511 const struct xkb_rule_names rmlvo
= {
517 struct xkb_context
*ctx
= NULL
;
518 struct xkb_keymap
*km
= NULL
;
522 /* Compile keymap from RMLVO information to check out its validity */
524 dl
= dlopen("libxkbcommon.so.0", RTLD_LAZY
);
528 r
= LOAD_SYMBOL(symbol_xkb_context_new
, dl
, "xkb_context_new");
532 r
= LOAD_SYMBOL(symbol_xkb_context_unref
, dl
, "xkb_context_unref");
536 r
= LOAD_SYMBOL(symbol_xkb_context_set_log_fn
, dl
, "xkb_context_set_log_fn");
540 r
= LOAD_SYMBOL(symbol_xkb_keymap_new_from_names
, dl
, "xkb_keymap_new_from_names");
544 r
= LOAD_SYMBOL(symbol_xkb_keymap_unref
, dl
, "xkb_keymap_unref");
548 ctx
= symbol_xkb_context_new(XKB_CONTEXT_NO_ENVIRONMENT_NAMES
);
554 symbol_xkb_context_set_log_fn(ctx
, log_xkb
);
556 km
= symbol_xkb_keymap_new_from_names(ctx
, &rmlvo
, XKB_KEYMAP_COMPILE_NO_FLAGS
);
565 if (symbol_xkb_keymap_unref
&& km
)
566 symbol_xkb_keymap_unref(km
);
568 if (symbol_xkb_context_unref
&& ctx
)
569 symbol_xkb_context_unref(ctx
);
577 static int verify_xkb_rmlvo(const char *model
, const char *layout
, const char *variant
, const char *options
) {
583 static int method_set_x11_keyboard(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
584 Context
*c
= userdata
;
585 const char *layout
, *model
, *variant
, *options
;
586 int convert
, interactive
, r
;
591 r
= sd_bus_message_read(m
, "ssssbb", &layout
, &model
, &variant
, &options
, &convert
, &interactive
);
595 layout
= empty_to_null(layout
);
596 model
= empty_to_null(model
);
597 variant
= empty_to_null(variant
);
598 options
= empty_to_null(options
);
600 r
= x11_read_data(c
, m
);
602 log_error_errno(r
, "Failed to read x11 keyboard layout data: %m");
603 return sd_bus_error_setf(error
, SD_BUS_ERROR_FAILED
, "Failed to read x11 keyboard layout data");
606 if (streq_ptr(layout
, c
->x11_layout
) &&
607 streq_ptr(model
, c
->x11_model
) &&
608 streq_ptr(variant
, c
->x11_variant
) &&
609 streq_ptr(options
, c
->x11_options
))
610 return sd_bus_reply_method_return(m
, NULL
);
612 if ((layout
&& !string_is_safe(layout
)) ||
613 (model
&& !string_is_safe(model
)) ||
614 (variant
&& !string_is_safe(variant
)) ||
615 (options
&& !string_is_safe(options
)))
616 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Received invalid keyboard data");
618 r
= verify_xkb_rmlvo(model
, layout
, variant
, options
);
620 log_error_errno(r
, "Cannot compile XKB keymap for new x11 keyboard layout ('%s' / '%s' / '%s' / '%s'): %m",
621 strempty(model
), strempty(layout
), strempty(variant
), strempty(options
));
623 if (r
== -EOPNOTSUPP
)
624 return sd_bus_error_setf(error
, SD_BUS_ERROR_NOT_SUPPORTED
, "Local keyboard configuration not supported on this system.");
626 return sd_bus_error_set(error
, SD_BUS_ERROR_INVALID_ARGS
, "Specified keymap cannot be compiled, refusing as invalid.");
629 r
= bus_verify_polkit_async(
632 "org.freedesktop.locale1.set-keyboard",
641 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
643 if (free_and_strdup(&c
->x11_layout
, layout
) < 0 ||
644 free_and_strdup(&c
->x11_model
, model
) < 0 ||
645 free_and_strdup(&c
->x11_variant
, variant
) < 0 ||
646 free_and_strdup(&c
->x11_options
, options
) < 0)
649 r
= x11_write_data(c
);
651 log_error_errno(r
, "Failed to set X11 keyboard layout: %m");
652 return sd_bus_error_set_errnof(error
, r
, "Failed to set X11 keyboard layout: %m");
655 log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
656 strempty(c
->x11_layout
),
657 strempty(c
->x11_model
),
658 strempty(c
->x11_variant
),
659 strempty(c
->x11_options
));
661 (void) sd_bus_emit_properties_changed(
662 sd_bus_message_get_bus(m
),
663 "/org/freedesktop/locale1",
664 "org.freedesktop.locale1",
665 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL
);
668 r
= x11_convert_to_vconsole_and_emit(c
, m
);
670 log_error_errno(r
, "Failed to convert keymap data: %m");
673 return sd_bus_reply_method_return(m
, NULL
);
676 static const sd_bus_vtable locale_vtable
[] = {
677 SD_BUS_VTABLE_START(0),
678 SD_BUS_PROPERTY("Locale", "as", property_get_locale
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
679 SD_BUS_PROPERTY("X11Layout", "s", property_get_xkb
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
680 SD_BUS_PROPERTY("X11Model", "s", property_get_xkb
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
681 SD_BUS_PROPERTY("X11Variant", "s", property_get_xkb
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
682 SD_BUS_PROPERTY("X11Options", "s", property_get_xkb
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
683 SD_BUS_PROPERTY("VConsoleKeymap", "s", property_get_vconsole
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
684 SD_BUS_PROPERTY("VConsoleKeymapToggle", "s", property_get_vconsole
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
685 SD_BUS_METHOD("SetLocale", "asb", NULL
, method_set_locale
, SD_BUS_VTABLE_UNPRIVILEGED
),
686 SD_BUS_METHOD("SetVConsoleKeyboard", "ssbb", NULL
, method_set_vc_keyboard
, SD_BUS_VTABLE_UNPRIVILEGED
),
687 SD_BUS_METHOD("SetX11Keyboard", "ssssbb", NULL
, method_set_x11_keyboard
, SD_BUS_VTABLE_UNPRIVILEGED
),
691 static int connect_bus(Context
*c
, sd_event
*event
, sd_bus
**_bus
) {
692 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
699 r
= sd_bus_default_system(&bus
);
701 return log_error_errno(r
, "Failed to get system bus connection: %m");
703 r
= sd_bus_add_object_vtable(bus
, NULL
, "/org/freedesktop/locale1", "org.freedesktop.locale1", locale_vtable
, c
);
705 return log_error_errno(r
, "Failed to register object: %m");
707 r
= sd_bus_request_name_async(bus
, NULL
, "org.freedesktop.locale1", 0, NULL
, NULL
);
709 return log_error_errno(r
, "Failed to request name: %m");
711 r
= sd_bus_attach_event(bus
, event
, 0);
713 return log_error_errno(r
, "Failed to attach bus to event loop: %m");
715 *_bus
= TAKE_PTR(bus
);
720 int main(int argc
, char *argv
[]) {
721 _cleanup_(context_free
) Context context
= {
722 .locale_mtime
= USEC_INFINITY
,
723 .vc_mtime
= USEC_INFINITY
,
724 .x11_mtime
= USEC_INFINITY
,
726 _cleanup_(sd_event_unrefp
) sd_event
*event
= NULL
;
727 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
730 log_set_target(LOG_TARGET_AUTO
);
731 log_parse_environment();
738 log_error("This program takes no arguments.");
743 assert_se(sigprocmask_many(SIG_BLOCK
, NULL
, SIGTERM
, SIGINT
, -1) >= 0);
745 r
= sd_event_default(&event
);
747 log_error_errno(r
, "Failed to allocate event loop: %m");
751 (void) sd_event_set_watchdog(event
, true);
753 r
= sd_event_add_signal(event
, NULL
, SIGINT
, NULL
, NULL
);
757 r
= sd_event_add_signal(event
, NULL
, SIGTERM
, NULL
, NULL
);
761 r
= connect_bus(&context
, event
, &bus
);
765 r
= bus_event_loop_with_idle(event
, bus
, "org.freedesktop.locale1", DEFAULT_EXIT_USEC
, NULL
, NULL
);
767 log_error_errno(r
, "Failed to run event loop: %m");
770 bus_verify_polkit_async_registry_free(polkit_registry
);
772 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;