1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
8 #include "alloc-util.h"
10 #include "bus-locator.h"
11 #include "bus-log-control-api.h"
12 #include "bus-object.h"
13 #include "bus-polkit.h"
14 #include "bus-unit-util.h"
16 #include "constants.h"
17 #include "daemon-util.h"
19 #include "label-util.h"
20 #include "localed-util.h"
22 #include "main-func.h"
23 #include "service-util.h"
24 #include "string-util.h"
27 static int vconsole_reload(sd_bus
*bus
) {
28 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
33 r
= bus_call_method(bus
, bus_systemd_mgr
, "RestartUnit", &error
, NULL
, "ss", "systemd-vconsole-setup.service", "replace");
35 return log_error_errno(r
, "Failed to issue method call: %s", bus_error_message(&error
, r
));
39 static int property_get_locale(
42 const char *interface
,
44 sd_bus_message
*reply
,
46 sd_bus_error
*error
) {
48 Context
*c
= ASSERT_PTR(userdata
);
49 _cleanup_strv_free_
char **l
= NULL
;
52 r
= locale_read_data(c
, reply
);
56 r
= locale_context_build_env(&c
->locale_context
, &l
, NULL
);
60 return sd_bus_message_append_strv(reply
, l
);
63 static int property_get_vconsole(
66 const char *interface
,
68 sd_bus_message
*reply
,
70 sd_bus_error
*error
) {
72 Context
*c
= ASSERT_PTR(userdata
);
77 r
= vconsole_read_data(c
, reply
);
81 if (streq(property
, "VConsoleKeymap"))
82 return sd_bus_message_append_basic(reply
, 's', c
->vc
.keymap
);
83 if (streq(property
, "VConsoleKeymapToggle"))
84 return sd_bus_message_append_basic(reply
, 's', c
->vc
.toggle
);
89 static int property_get_xkb(
92 const char *interface
,
94 sd_bus_message
*reply
,
96 sd_bus_error
*error
) {
98 Context
*c
= ASSERT_PTR(userdata
);
104 r
= vconsole_read_data(c
, reply
);
108 r
= x11_read_data(c
, reply
);
112 xc
= context_get_x11_context(c
);
114 if (streq(property
, "X11Layout"))
115 return sd_bus_message_append_basic(reply
, 's', xc
->layout
);
116 if (streq(property
, "X11Model"))
117 return sd_bus_message_append_basic(reply
, 's', xc
->model
);
118 if (streq(property
, "X11Variant"))
119 return sd_bus_message_append_basic(reply
, 's', xc
->variant
);
120 if (streq(property
, "X11Options"))
121 return sd_bus_message_append_basic(reply
, 's', xc
->options
);
126 static int process_locale_list_item(
127 const char *assignment
,
128 char *new_locale
[static _VARIABLE_LC_MAX
],
130 sd_bus_error
*error
) {
135 for (LocaleVariable p
= 0; p
< _VARIABLE_LC_MAX
; p
++) {
136 const char *name
, *e
;
138 assert_se(name
= locale_variable_to_string(p
));
140 e
= startswith(assignment
, name
);
149 if (!locale_is_valid(e
))
150 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Locale %s is not valid, refusing.", e
);
151 if (!use_localegen
&& locale_is_installed(e
) <= 0)
152 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Locale %s not installed, refusing.", e
);
154 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Locale variable %s set twice, refusing.", name
);
156 return strdup_to(&new_locale
[p
], e
);
159 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Locale assignment %s not valid, refusing.", assignment
);
162 static int locale_gen_process_locale(char *new_locale
[static _VARIABLE_LC_MAX
], sd_bus_error
*error
) {
167 for (LocaleVariable p
= 0; p
< _VARIABLE_LC_MAX
; p
++) {
168 if (p
== VARIABLE_LANGUAGE
)
170 if (isempty(new_locale
[p
]))
172 if (locale_is_installed(new_locale
[p
]))
175 r
= locale_gen_enable_locale(new_locale
[p
]);
177 log_error_errno(r
, "Refused to enable locale for generation: %m");
178 return sd_bus_error_setf(error
,
179 SD_BUS_ERROR_INVALID_ARGS
,
180 "Specified locale is not installed and non-UTF-8 locale will not be auto-generated: %s",
184 log_error_errno(r
, "Failed to enable invalid locale %s for generation.", new_locale
[p
]);
185 return sd_bus_error_setf(error
,
186 SD_BUS_ERROR_INVALID_ARGS
,
187 "Cannot enable locale generation for invalid locale: %s",
191 log_error_errno(r
, "Failed to enable locale for generation: %m");
192 return sd_bus_error_set_errnof(error
, r
, "Failed to enable locale generation: %m");
195 r
= locale_gen_run();
197 log_error_errno(r
, "Failed to generate locale: %m");
198 return sd_bus_error_set_errnof(error
, r
, "Failed to generate locale: %m");
205 static int method_set_locale(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
206 _cleanup_(locale_variables_freep
) char *new_locale
[_VARIABLE_LC_MAX
] = {};
207 _cleanup_strv_free_
char **l
= NULL
, **l_set
= NULL
, **l_unset
= NULL
;
208 Context
*c
= ASSERT_PTR(userdata
);
214 r
= sd_bus_message_read_strv(m
, &l
);
216 return bus_log_parse_error(r
);
218 r
= sd_bus_message_read_basic(m
, 'b', &interactive
);
220 return bus_log_parse_error(r
);
222 use_localegen
= locale_gen_check_available();
224 /* If single locale without variable name is provided, then we assume it is LANG=. */
225 if (strv_length(l
) == 1 && !strchr(l
[0], '=')) {
226 if (!locale_is_valid(l
[0]))
227 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid locale specification: %s", l
[0]);
228 if (!use_localegen
&& locale_is_installed(l
[0]) <= 0)
229 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Specified locale is not installed: %s", l
[0]);
231 new_locale
[VARIABLE_LANG
] = strdup(l
[0]);
232 if (!new_locale
[VARIABLE_LANG
])
238 /* Check whether a variable is valid */
240 r
= process_locale_list_item(*i
, new_locale
, use_localegen
, error
);
245 /* If LANG was specified, but not LANGUAGE, check if we should
246 * set it based on the language fallback table. */
247 if (!isempty(new_locale
[VARIABLE_LANG
]) &&
248 isempty(new_locale
[VARIABLE_LANGUAGE
])) {
249 _cleanup_free_
char *language
= NULL
;
251 (void) find_language_fallback(new_locale
[VARIABLE_LANG
], &language
);
253 log_debug("Converted LANG=%s to LANGUAGE=%s", new_locale
[VARIABLE_LANG
], language
);
254 free_and_replace(new_locale
[VARIABLE_LANGUAGE
], language
);
258 r
= locale_read_data(c
, m
);
260 log_error_errno(r
, "Failed to read locale data: %m");
261 return sd_bus_error_set(error
, SD_BUS_ERROR_FAILED
, "Failed to read locale data");
264 /* Merge with the current settings */
265 r
= locale_context_merge(&c
->locale_context
, new_locale
);
269 locale_variables_simplify(new_locale
);
271 if (locale_context_equal(&c
->locale_context
, new_locale
)) {
272 log_debug("Locale settings were not modified.");
273 return sd_bus_reply_method_return(m
, NULL
);
276 r
= bus_verify_polkit_async_full(
278 "org.freedesktop.locale1.set-locale",
280 /* good_user= */ UID_INVALID
,
281 interactive
? POLKIT_ALLOW_INTERACTIVE
: 0,
287 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
289 /* Generate locale in case it is missing and the system is using locale-gen */
291 r
= locale_gen_process_locale(new_locale
, error
);
296 locale_context_take(&c
->locale_context
, new_locale
);
298 /* Write locale configuration */
299 r
= locale_context_save(&c
->locale_context
, &l_set
, &l_unset
);
301 log_error_errno(r
, "Failed to set locale: %m");
302 return sd_bus_error_set_errnof(error
, r
, "Failed to set locale: %m");
305 /* Since we just updated the locale configuration file, ask the system manager to read it again to
306 * update its default locale settings. It's important to not use UnsetAndSetEnvironment or a similar
307 * method because in this case unsetting variables means restoring them to PID1 default values, which
308 * may be outdated, since locale.conf has just changed and PID1 hasn't read it */
309 (void) bus_service_manager_reload(sd_bus_message_get_bus(m
));
311 if (!strv_isempty(l_set
)) {
312 _cleanup_free_
char *line
= NULL
;
314 line
= strv_join(l_set
, ", ");
315 log_info("Changed locale to %s.", strnull(line
));
317 log_info("Changed locale to unset.");
319 (void) sd_bus_emit_properties_changed(
320 sd_bus_message_get_bus(m
),
321 "/org/freedesktop/locale1",
322 "org.freedesktop.locale1",
325 return sd_bus_reply_method_return(m
, NULL
);
328 static int method_set_vc_keyboard(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
329 _cleanup_(x11_context_clear
) X11Context converted
= {};
330 Context
*c
= ASSERT_PTR(userdata
);
331 int convert
, interactive
, r
;
337 r
= sd_bus_message_read(m
, "ssbb", &in
.keymap
, &in
.toggle
, &convert
, &interactive
);
339 return bus_log_parse_error(r
);
341 vc_context_empty_to_null(&in
);
343 r
= vc_context_verify_and_warn(&in
, LOG_ERR
, error
);
347 r
= vconsole_read_data(c
, m
);
349 log_error_errno(r
, "Failed to read virtual console keymap data: %m");
350 return sd_bus_error_set_errnof(error
, r
, "Failed to read virtual console keymap data: %m");
353 r
= x11_read_data(c
, m
);
355 log_error_errno(r
, "Failed to read X11 keyboard layout data: %m");
356 return sd_bus_error_set_errnof(error
, r
, "Failed to read X11 keyboard layout data: %m");
360 r
= vconsole_convert_to_x11(&in
, x11_context_verify
, &converted
);
362 log_error_errno(r
, "Failed to convert keymap data: %m");
363 return sd_bus_error_set_errnof(error
, r
, "Failed to convert keymap data: %m");
366 if (x11_context_isempty(&converted
))
367 log_notice("No conversion found for virtual console keymap \"%s\".", strempty(in
.keymap
));
369 log_info("The virtual console keymap '%s' is converted to X11 keyboard layout '%s' model '%s' variant '%s' options '%s'",
370 in
.keymap
, strempty(converted
.layout
), strempty(converted
.model
), strempty(converted
.variant
), strempty(converted
.options
));
372 /* save the result of conversion to emit changed properties later. */
373 x_needs_update
= !x11_context_equal(&c
->x11_from_vc
, &converted
) || !x11_context_equal(&c
->x11_from_xorg
, &converted
);
375 x_needs_update
= !x11_context_equal(&c
->x11_from_vc
, &c
->x11_from_xorg
);
377 if (vc_context_equal(&c
->vc
, &in
) && !x_needs_update
)
378 return sd_bus_reply_method_return(m
, NULL
);
380 r
= bus_verify_polkit_async_full(
382 "org.freedesktop.locale1.set-keyboard",
384 /* good_user= */ UID_INVALID
,
385 interactive
? POLKIT_ALLOW_INTERACTIVE
: 0,
391 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
393 r
= vc_context_copy(&c
->vc
, &in
);
397 if (x_needs_update
) {
399 r
= x11_context_copy(&c
->x11_from_vc
, &converted
);
402 x11_context_replace(&c
->x11_from_xorg
, &converted
);
404 const X11Context
*xc
= context_get_x11_context(c
);
406 /* Even if the conversion is not requested, sync the two X11 contexts. */
407 r
= x11_context_copy(&c
->x11_from_vc
, xc
);
411 r
= x11_context_copy(&c
->x11_from_xorg
, xc
);
417 r
= vconsole_write_data(c
);
419 log_warning_errno(r
, "Failed to write virtual console keymap, ignoring: %m");
421 if (x_needs_update
) {
422 r
= x11_write_data(c
);
424 log_warning_errno(r
, "Failed to write X11 keyboard layout, ignoring: %m");
427 log_info("Changed virtual console keymap to '%s' toggle '%s'",
428 strempty(c
->vc
.keymap
), strempty(c
->vc
.toggle
));
430 (void) vconsole_reload(sd_bus_message_get_bus(m
));
432 (void) sd_bus_emit_properties_changed(
433 sd_bus_message_get_bus(m
),
434 "/org/freedesktop/locale1",
435 "org.freedesktop.locale1",
436 "VConsoleKeymap", "VConsoleKeymapToggle",
437 x_needs_update
? "X11Layout" : NULL
,
438 x_needs_update
? "X11Model" : NULL
,
439 x_needs_update
? "X11Variant" : NULL
,
440 x_needs_update
? "X11Options" : NULL
,
443 return sd_bus_reply_method_return(m
, NULL
);
446 static int method_set_x11_keyboard(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
447 _cleanup_(vc_context_clear
) VCContext converted
= {};
448 Context
*c
= ASSERT_PTR(userdata
);
449 int convert
, interactive
, r
;
454 r
= sd_bus_message_read(m
, "ssssbb", &in
.layout
, &in
.model
, &in
.variant
, &in
.options
, &convert
, &interactive
);
456 return bus_log_parse_error(r
);
458 x11_context_empty_to_null(&in
);
460 r
= x11_context_verify_and_warn(&in
, LOG_ERR
, error
);
464 r
= vconsole_read_data(c
, m
);
466 log_error_errno(r
, "Failed to read virtual console keymap data: %m");
467 return sd_bus_error_set_errnof(error
, r
, "Failed to read virtual console keymap data: %m");
470 r
= x11_read_data(c
, m
);
472 log_error_errno(r
, "Failed to read x11 keyboard layout data: %m");
473 return sd_bus_error_set(error
, SD_BUS_ERROR_FAILED
, "Failed to read x11 keyboard layout data");
477 r
= x11_convert_to_vconsole(&in
, &converted
);
479 log_error_errno(r
, "Failed to convert keymap data: %m");
480 return sd_bus_error_set_errnof(error
, r
, "Failed to convert keymap data: %m");
483 if (vc_context_isempty(&converted
))
484 /* We search for layout-variant match first, but then we also look
485 * for anything which matches just the layout. So it's accurate to say
486 * that we couldn't find anything which matches the layout. */
487 log_notice("No conversion to virtual console map found for \"%s\".", strempty(in
.layout
));
489 log_info("The X11 keyboard layout '%s' is converted to virtual console keymap '%s'",
490 in
.layout
, converted
.keymap
);
492 /* save the result of conversion to emit changed properties later. */
493 convert
= !vc_context_equal(&c
->vc
, &converted
);
496 if (x11_context_equal(&c
->x11_from_vc
, &in
) && x11_context_equal(&c
->x11_from_xorg
, &in
) && !convert
)
497 return sd_bus_reply_method_return(m
, NULL
);
499 r
= bus_verify_polkit_async_full(
501 "org.freedesktop.locale1.set-keyboard",
503 /* good_user= */ UID_INVALID
,
504 interactive
? POLKIT_ALLOW_INTERACTIVE
: 0,
510 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
512 r
= x11_context_copy(&c
->x11_from_vc
, &in
);
516 r
= x11_context_copy(&c
->x11_from_xorg
, &in
);
521 vc_context_replace(&c
->vc
, &converted
);
523 r
= vconsole_write_data(c
);
525 log_warning_errno(r
, "Failed to update vconsole.conf, ignoring: %m");
527 r
= x11_write_data(c
);
529 log_warning_errno(r
, "Failed to write X11 keyboard layout, ignoring: %m");
531 log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
534 strempty(in
.variant
),
535 strempty(in
.options
));
537 (void) sd_bus_emit_properties_changed(
538 sd_bus_message_get_bus(m
),
539 "/org/freedesktop/locale1",
540 "org.freedesktop.locale1",
541 "X11Layout", "X11Model", "X11Variant", "X11Options",
542 convert
? "VConsoleKeymap" : NULL
,
543 convert
? "VConsoleKeymapToggle" : NULL
,
547 (void) vconsole_reload(sd_bus_message_get_bus(m
));
549 return sd_bus_reply_method_return(m
, NULL
);
552 static const sd_bus_vtable locale_vtable
[] = {
553 SD_BUS_VTABLE_START(0),
554 SD_BUS_PROPERTY("Locale", "as", property_get_locale
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
555 SD_BUS_PROPERTY("X11Layout", "s", property_get_xkb
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
556 SD_BUS_PROPERTY("X11Model", "s", property_get_xkb
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
557 SD_BUS_PROPERTY("X11Variant", "s", property_get_xkb
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
558 SD_BUS_PROPERTY("X11Options", "s", property_get_xkb
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
559 SD_BUS_PROPERTY("VConsoleKeymap", "s", property_get_vconsole
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
560 SD_BUS_PROPERTY("VConsoleKeymapToggle", "s", property_get_vconsole
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
562 SD_BUS_METHOD_WITH_ARGS("SetLocale",
563 SD_BUS_ARGS("as", locale
, "b", interactive
),
566 SD_BUS_VTABLE_UNPRIVILEGED
),
567 SD_BUS_METHOD_WITH_ARGS("SetVConsoleKeyboard",
568 SD_BUS_ARGS("s", keymap
, "s", keymap_toggle
, "b", convert
, "b", interactive
),
570 method_set_vc_keyboard
,
571 SD_BUS_VTABLE_UNPRIVILEGED
),
572 SD_BUS_METHOD_WITH_ARGS("SetX11Keyboard",
573 SD_BUS_ARGS("s", layout
, "s", model
, "s", variant
, "s", options
, "b", convert
, "b", interactive
),
575 method_set_x11_keyboard
,
576 SD_BUS_VTABLE_UNPRIVILEGED
),
581 static const BusObjectImplementation manager_object
= {
582 "/org/freedesktop/locale1",
583 "org.freedesktop.locale1",
584 .vtables
= BUS_VTABLES(locale_vtable
),
587 static int connect_bus(Context
*c
, sd_event
*event
, sd_bus
**_bus
) {
588 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
595 r
= sd_bus_default_system(&bus
);
597 return log_error_errno(r
, "Failed to get system bus connection: %m");
599 r
= bus_add_implementation(bus
, &manager_object
, c
);
603 r
= bus_log_control_api_register(bus
);
607 r
= sd_bus_request_name_async(bus
, NULL
, "org.freedesktop.locale1", 0, NULL
, NULL
);
609 return log_error_errno(r
, "Failed to request name: %m");
611 r
= sd_bus_attach_event(bus
, event
, 0);
613 return log_error_errno(r
, "Failed to attach bus to event loop: %m");
615 *_bus
= TAKE_PTR(bus
);
620 static bool context_check_idle(void *userdata
) {
621 Context
*c
= ASSERT_PTR(userdata
);
623 return hashmap_isempty(c
->polkit_registry
);
626 static int run(int argc
, char *argv
[]) {
627 _cleanup_(context_clear
) Context context
= {};
628 _cleanup_(sd_event_unrefp
) sd_event
*event
= NULL
;
629 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
634 r
= service_parse_argv("systemd-localed.service",
635 "Manage system locale settings and key mappings.",
636 BUS_IMPLEMENTATIONS(&manager_object
,
637 &log_control_object
),
648 r
= sd_event_default(&event
);
650 return log_error_errno(r
, "Failed to allocate event loop: %m");
652 (void) sd_event_set_watchdog(event
, true);
654 r
= sd_event_set_signal_exit(event
, true);
656 return log_error_errno(r
, "Failed to install SIGINT/SIGTERM handlers: %m");
658 r
= connect_bus(&context
, event
, &bus
);
662 r
= sd_notify(false, NOTIFY_READY_MESSAGE
);
664 log_warning_errno(r
, "Failed to send readiness notification, ignoring: %m");
666 r
= bus_event_loop_with_idle(
669 "org.freedesktop.locale1",
674 return log_error_errno(r
, "Failed to run event loop: %m");
679 DEFINE_MAIN_FUNCTION(run
);