1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
10 #include "alloc-util.h"
11 #include "bus-error.h"
12 #include "bus-locator.h"
13 #include "bus-log-control-api.h"
14 #include "bus-message.h"
15 #include "bus-polkit.h"
16 #include "bus-unit-util.h"
17 #include "constants.h"
19 #include "localed-util.h"
21 #include "main-func.h"
22 #include "missing_capability.h"
23 #include "path-util.h"
24 #include "selinux-util.h"
25 #include "service-util.h"
26 #include "signal-util.h"
27 #include "string-util.h"
29 #include "user-util.h"
31 static int vconsole_reload(sd_bus
*bus
) {
32 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
37 r
= bus_call_method(bus
, bus_systemd_mgr
, "RestartUnit", &error
, NULL
, "ss", "systemd-vconsole-setup.service", "replace");
39 return log_error_errno(r
, "Failed to issue method call: %s", bus_error_message(&error
, r
));
43 static int property_get_locale(
46 const char *interface
,
48 sd_bus_message
*reply
,
50 sd_bus_error
*error
) {
52 Context
*c
= ASSERT_PTR(userdata
);
53 _cleanup_strv_free_
char **l
= NULL
;
56 r
= locale_read_data(c
, reply
);
60 r
= locale_context_build_env(&c
->locale_context
, &l
, NULL
);
64 return sd_bus_message_append_strv(reply
, l
);
67 static int property_get_vconsole(
70 const char *interface
,
72 sd_bus_message
*reply
,
74 sd_bus_error
*error
) {
76 Context
*c
= ASSERT_PTR(userdata
);
81 r
= vconsole_read_data(c
, reply
);
85 if (streq(property
, "VConsoleKeymap"))
86 return sd_bus_message_append_basic(reply
, 's', c
->vc
.keymap
);
87 if (streq(property
, "VConsoleKeymapToggle"))
88 return sd_bus_message_append_basic(reply
, 's', c
->vc
.toggle
);
93 static int property_get_xkb(
96 const char *interface
,
98 sd_bus_message
*reply
,
100 sd_bus_error
*error
) {
102 Context
*c
= ASSERT_PTR(userdata
);
103 const X11Context
*xc
;
108 r
= vconsole_read_data(c
, reply
);
112 r
= x11_read_data(c
, reply
);
116 xc
= context_get_x11_context(c
);
118 if (streq(property
, "X11Layout"))
119 return sd_bus_message_append_basic(reply
, 's', xc
->layout
);
120 if (streq(property
, "X11Model"))
121 return sd_bus_message_append_basic(reply
, 's', xc
->model
);
122 if (streq(property
, "X11Variant"))
123 return sd_bus_message_append_basic(reply
, 's', xc
->variant
);
124 if (streq(property
, "X11Options"))
125 return sd_bus_message_append_basic(reply
, 's', xc
->options
);
130 static int process_locale_list_item(
131 const char *assignment
,
132 char *new_locale
[static _VARIABLE_LC_MAX
],
134 sd_bus_error
*error
) {
139 for (LocaleVariable p
= 0; p
< _VARIABLE_LC_MAX
; p
++) {
140 const char *name
, *e
;
142 assert_se(name
= locale_variable_to_string(p
));
144 e
= startswith(assignment
, name
);
153 if (!locale_is_valid(e
))
154 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Locale %s is not valid, refusing.", e
);
155 if (!use_localegen
&& locale_is_installed(e
) <= 0)
156 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Locale %s not installed, refusing.", e
);
158 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Locale variable %s set twice, refusing.", name
);
160 new_locale
[p
] = strdup(e
);
167 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Locale assignment %s not valid, refusing.", assignment
);
170 static int locale_gen_process_locale(char *new_locale
[static _VARIABLE_LC_MAX
], sd_bus_error
*error
) {
175 for (LocaleVariable p
= 0; p
< _VARIABLE_LC_MAX
; p
++) {
176 if (p
== VARIABLE_LANGUAGE
)
178 if (isempty(new_locale
[p
]))
180 if (locale_is_installed(new_locale
[p
]))
183 r
= locale_gen_enable_locale(new_locale
[p
]);
185 log_error_errno(r
, "Refused to enable locale for generation: %m");
186 return sd_bus_error_setf(error
,
187 SD_BUS_ERROR_INVALID_ARGS
,
188 "Specified locale is not installed and non-UTF-8 locale will not be auto-generated: %s",
192 log_error_errno(r
, "Failed to enable invalid locale %s for generation.", new_locale
[p
]);
193 return sd_bus_error_setf(error
,
194 SD_BUS_ERROR_INVALID_ARGS
,
195 "Cannot enable locale generation for invalid locale: %s",
199 log_error_errno(r
, "Failed to enable locale for generation: %m");
200 return sd_bus_error_set_errnof(error
, r
, "Failed to enable locale generation: %m");
203 r
= locale_gen_run();
205 log_error_errno(r
, "Failed to generate locale: %m");
206 return sd_bus_error_set_errnof(error
, r
, "Failed to generate locale: %m");
213 static int method_set_locale(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
214 _cleanup_(locale_variables_freep
) char *new_locale
[_VARIABLE_LC_MAX
] = {};
215 _cleanup_strv_free_
char **l
= NULL
, **l_set
= NULL
, **l_unset
= NULL
;
216 Context
*c
= ASSERT_PTR(userdata
);
222 r
= sd_bus_message_read_strv(m
, &l
);
224 return bus_log_parse_error(r
);
226 r
= sd_bus_message_read_basic(m
, 'b', &interactive
);
228 return bus_log_parse_error(r
);
230 use_localegen
= locale_gen_check_available();
232 /* If single locale without variable name is provided, then we assume it is LANG=. */
233 if (strv_length(l
) == 1 && !strchr(l
[0], '=')) {
234 if (!locale_is_valid(l
[0]))
235 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid locale specification: %s", l
[0]);
236 if (!use_localegen
&& locale_is_installed(l
[0]) <= 0)
237 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Specified locale is not installed: %s", l
[0]);
239 new_locale
[VARIABLE_LANG
] = strdup(l
[0]);
240 if (!new_locale
[VARIABLE_LANG
])
246 /* Check whether a variable is valid */
248 r
= process_locale_list_item(*i
, new_locale
, use_localegen
, error
);
253 /* If LANG was specified, but not LANGUAGE, check if we should
254 * set it based on the language fallback table. */
255 if (!isempty(new_locale
[VARIABLE_LANG
]) &&
256 isempty(new_locale
[VARIABLE_LANGUAGE
])) {
257 _cleanup_free_
char *language
= NULL
;
259 (void) find_language_fallback(new_locale
[VARIABLE_LANG
], &language
);
261 log_debug("Converted LANG=%s to LANGUAGE=%s", new_locale
[VARIABLE_LANG
], language
);
262 free_and_replace(new_locale
[VARIABLE_LANGUAGE
], language
);
266 r
= locale_read_data(c
, m
);
268 log_error_errno(r
, "Failed to read locale data: %m");
269 return sd_bus_error_set(error
, SD_BUS_ERROR_FAILED
, "Failed to read locale data");
272 /* Merge with the current settings */
273 r
= locale_context_merge(&c
->locale_context
, new_locale
);
277 locale_variables_simplify(new_locale
);
279 if (locale_context_equal(&c
->locale_context
, new_locale
)) {
280 log_debug("Locale settings were not modified.");
281 return sd_bus_reply_method_return(m
, NULL
);
284 r
= bus_verify_polkit_async(
287 "org.freedesktop.locale1.set-locale",
296 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
298 /* Generate locale in case it is missing and the system is using locale-gen */
300 r
= locale_gen_process_locale(new_locale
, error
);
305 locale_context_take(&c
->locale_context
, new_locale
);
307 /* Write locale configuration */
308 r
= locale_context_save(&c
->locale_context
, &l_set
, &l_unset
);
310 log_error_errno(r
, "Failed to set locale: %m");
311 return sd_bus_error_set_errnof(error
, r
, "Failed to set locale: %m");
314 /* Since we just updated the locale configuration file, ask the system manager to read it again to
315 * update its default locale settings. It's important to not use UnsetAndSetEnvironment or a similar
316 * method because in this case unsetting variables means restoring them to PID1 default values, which
317 * may be outdated, since locale.conf has just changed and PID1 hasn't read it */
318 (void) bus_service_manager_reload(sd_bus_message_get_bus(m
));
320 if (!strv_isempty(l_set
)) {
321 _cleanup_free_
char *line
= NULL
;
323 line
= strv_join(l_set
, ", ");
324 log_info("Changed locale to %s.", strnull(line
));
326 log_info("Changed locale to unset.");
328 (void) sd_bus_emit_properties_changed(
329 sd_bus_message_get_bus(m
),
330 "/org/freedesktop/locale1",
331 "org.freedesktop.locale1",
334 return sd_bus_reply_method_return(m
, NULL
);
337 static int method_set_vc_keyboard(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
338 _cleanup_(x11_context_clear
) X11Context converted
= {};
339 Context
*c
= ASSERT_PTR(userdata
);
340 int convert
, interactive
, r
;
346 r
= sd_bus_message_read(m
, "ssbb", &in
.keymap
, &in
.toggle
, &convert
, &interactive
);
348 return bus_log_parse_error(r
);
350 vc_context_empty_to_null(&in
);
352 r
= vc_context_verify_and_warn(&in
, LOG_ERR
, error
);
356 r
= vconsole_read_data(c
, m
);
358 log_error_errno(r
, "Failed to read virtual console keymap data: %m");
359 return sd_bus_error_set_errnof(error
, r
, "Failed to read virtual console keymap data: %m");
362 r
= x11_read_data(c
, m
);
364 log_error_errno(r
, "Failed to read X11 keyboard layout data: %m");
365 return sd_bus_error_set_errnof(error
, r
, "Failed to read X11 keyboard layout data: %m");
369 r
= vconsole_convert_to_x11(&in
, &converted
);
371 log_error_errno(r
, "Failed to convert keymap data: %m");
372 return sd_bus_error_set_errnof(error
, r
, "Failed to convert keymap data: %m");
375 if (x11_context_isempty(&converted
))
376 log_notice("No conversion found for virtual console keymap \"%s\".", strempty(in
.keymap
));
378 log_info("The virtual console keymap '%s' is converted to X11 keyboard layout '%s' model '%s' variant '%s' options '%s'",
379 in
.keymap
, strempty(converted
.layout
), strempty(converted
.model
), strempty(converted
.variant
), strempty(converted
.options
));
381 /* save the result of conversion to emit changed properties later. */
382 x_needs_update
= !x11_context_equal(&c
->x11_from_vc
, &converted
) || !x11_context_equal(&c
->x11_from_xorg
, &converted
);
384 x_needs_update
= !x11_context_equal(&c
->x11_from_vc
, &c
->x11_from_xorg
);
386 if (vc_context_equal(&c
->vc
, &in
) && !x_needs_update
)
387 return sd_bus_reply_method_return(m
, NULL
);
389 r
= bus_verify_polkit_async(
392 "org.freedesktop.locale1.set-keyboard",
401 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
403 r
= vc_context_copy(&c
->vc
, &in
);
407 if (x_needs_update
) {
409 r
= x11_context_copy(&c
->x11_from_vc
, &converted
);
412 x11_context_replace(&c
->x11_from_xorg
, &converted
);
414 const X11Context
*xc
= context_get_x11_context(c
);
416 /* Even if the conversion is not requested, sync the two X11 contexts. */
417 r
= x11_context_copy(&c
->x11_from_vc
, xc
);
421 r
= x11_context_copy(&c
->x11_from_xorg
, xc
);
427 r
= vconsole_write_data(c
);
429 log_warning_errno(r
, "Failed to write virtual console keymap, ignoring: %m");
431 if (x_needs_update
) {
432 r
= x11_write_data(c
);
434 log_warning_errno(r
, "Failed to write X11 keyboard layout, ignoring: %m");
437 log_info("Changed virtual console keymap to '%s' toggle '%s'",
438 strempty(c
->vc
.keymap
), strempty(c
->vc
.toggle
));
440 (void) vconsole_reload(sd_bus_message_get_bus(m
));
442 (void) sd_bus_emit_properties_changed(
443 sd_bus_message_get_bus(m
),
444 "/org/freedesktop/locale1",
445 "org.freedesktop.locale1",
446 "VConsoleKeymap", "VConsoleKeymapToggle",
447 x_needs_update
? "X11Layout" : NULL
,
448 x_needs_update
? "X11Model" : NULL
,
449 x_needs_update
? "X11Variant" : NULL
,
450 x_needs_update
? "X11Options" : NULL
,
453 return sd_bus_reply_method_return(m
, NULL
);
456 static int method_set_x11_keyboard(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
457 _cleanup_(vc_context_clear
) VCContext converted
= {};
458 Context
*c
= ASSERT_PTR(userdata
);
459 int convert
, interactive
, r
;
464 r
= sd_bus_message_read(m
, "ssssbb", &in
.layout
, &in
.model
, &in
.variant
, &in
.options
, &convert
, &interactive
);
466 return bus_log_parse_error(r
);
468 x11_context_empty_to_null(&in
);
470 r
= x11_context_verify_and_warn(&in
, LOG_ERR
, error
);
474 r
= vconsole_read_data(c
, m
);
476 log_error_errno(r
, "Failed to read virtual console keymap data: %m");
477 return sd_bus_error_set_errnof(error
, r
, "Failed to read virtual console keymap data: %m");
480 r
= x11_read_data(c
, m
);
482 log_error_errno(r
, "Failed to read x11 keyboard layout data: %m");
483 return sd_bus_error_set(error
, SD_BUS_ERROR_FAILED
, "Failed to read x11 keyboard layout data");
487 r
= x11_convert_to_vconsole(&in
, &converted
);
489 log_error_errno(r
, "Failed to convert keymap data: %m");
490 return sd_bus_error_set_errnof(error
, r
, "Failed to convert keymap data: %m");
493 if (vc_context_isempty(&converted
))
494 /* We search for layout-variant match first, but then we also look
495 * for anything which matches just the layout. So it's accurate to say
496 * that we couldn't find anything which matches the layout. */
497 log_notice("No conversion to virtual console map found for \"%s\".", strempty(in
.layout
));
499 log_info("The X11 keyboard layout '%s' is converted to virtual console keymap '%s'",
500 in
.layout
, converted
.keymap
);
502 /* save the result of conversion to emit changed properties later. */
503 convert
= !vc_context_equal(&c
->vc
, &converted
);
506 if (x11_context_equal(&c
->x11_from_vc
, &in
) && x11_context_equal(&c
->x11_from_xorg
, &in
) && !convert
)
507 return sd_bus_reply_method_return(m
, NULL
);
509 r
= bus_verify_polkit_async(
512 "org.freedesktop.locale1.set-keyboard",
521 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
523 r
= x11_context_copy(&c
->x11_from_vc
, &in
);
527 r
= x11_context_copy(&c
->x11_from_xorg
, &in
);
532 vc_context_replace(&c
->vc
, &converted
);
534 r
= vconsole_write_data(c
);
536 log_warning_errno(r
, "Failed to update vconsole.conf, ignoring: %m");
538 r
= x11_write_data(c
);
540 log_warning_errno(r
, "Failed to write X11 keyboard layout, ignoring: %m");
542 log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
545 strempty(in
.variant
),
546 strempty(in
.options
));
548 (void) sd_bus_emit_properties_changed(
549 sd_bus_message_get_bus(m
),
550 "/org/freedesktop/locale1",
551 "org.freedesktop.locale1",
552 "X11Layout", "X11Model", "X11Variant", "X11Options",
553 convert
? "VConsoleKeymap" : NULL
,
554 convert
? "VConsoleKeymapToggle" : NULL
,
558 (void) vconsole_reload(sd_bus_message_get_bus(m
));
560 return sd_bus_reply_method_return(m
, NULL
);
563 static const sd_bus_vtable locale_vtable
[] = {
564 SD_BUS_VTABLE_START(0),
565 SD_BUS_PROPERTY("Locale", "as", property_get_locale
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
566 SD_BUS_PROPERTY("X11Layout", "s", property_get_xkb
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
567 SD_BUS_PROPERTY("X11Model", "s", property_get_xkb
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
568 SD_BUS_PROPERTY("X11Variant", "s", property_get_xkb
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
569 SD_BUS_PROPERTY("X11Options", "s", property_get_xkb
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
570 SD_BUS_PROPERTY("VConsoleKeymap", "s", property_get_vconsole
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
571 SD_BUS_PROPERTY("VConsoleKeymapToggle", "s", property_get_vconsole
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
573 SD_BUS_METHOD_WITH_ARGS("SetLocale",
574 SD_BUS_ARGS("as", locale
, "b", interactive
),
577 SD_BUS_VTABLE_UNPRIVILEGED
),
578 SD_BUS_METHOD_WITH_ARGS("SetVConsoleKeyboard",
579 SD_BUS_ARGS("s", keymap
, "s", keymap_toggle
, "b", convert
, "b", interactive
),
581 method_set_vc_keyboard
,
582 SD_BUS_VTABLE_UNPRIVILEGED
),
583 SD_BUS_METHOD_WITH_ARGS("SetX11Keyboard",
584 SD_BUS_ARGS("s", layout
, "s", model
, "s", variant
, "s", options
, "b", convert
, "b", interactive
),
586 method_set_x11_keyboard
,
587 SD_BUS_VTABLE_UNPRIVILEGED
),
592 static const BusObjectImplementation manager_object
= {
593 "/org/freedesktop/locale1",
594 "org.freedesktop.locale1",
595 .vtables
= BUS_VTABLES(locale_vtable
),
598 static int connect_bus(Context
*c
, sd_event
*event
, sd_bus
**_bus
) {
599 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
606 r
= sd_bus_default_system(&bus
);
608 return log_error_errno(r
, "Failed to get system bus connection: %m");
610 r
= bus_add_implementation(bus
, &manager_object
, c
);
614 r
= bus_log_control_api_register(bus
);
618 r
= sd_bus_request_name_async(bus
, NULL
, "org.freedesktop.locale1", 0, NULL
, NULL
);
620 return log_error_errno(r
, "Failed to request name: %m");
622 r
= sd_bus_attach_event(bus
, event
, 0);
624 return log_error_errno(r
, "Failed to attach bus to event loop: %m");
626 *_bus
= TAKE_PTR(bus
);
631 static int run(int argc
, char *argv
[]) {
632 _cleanup_(context_clear
) Context context
= {};
633 _cleanup_(sd_event_unrefp
) sd_event
*event
= NULL
;
634 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
639 r
= service_parse_argv("systemd-localed.service",
640 "Manage system locale settings and key mappings.",
641 BUS_IMPLEMENTATIONS(&manager_object
,
642 &log_control_object
),
653 assert_se(sigprocmask_many(SIG_BLOCK
, NULL
, SIGTERM
, SIGINT
, -1) >= 0);
655 r
= sd_event_default(&event
);
657 return log_error_errno(r
, "Failed to allocate event loop: %m");
659 (void) sd_event_set_watchdog(event
, true);
661 r
= sd_event_add_signal(event
, NULL
, SIGINT
, NULL
, NULL
);
663 return log_error_errno(r
, "Failed to install SIGINT handler: %m");
665 r
= sd_event_add_signal(event
, NULL
, SIGTERM
, NULL
, NULL
);
667 return log_error_errno(r
, "Failed to install SIGTERM handler: %m");
669 r
= connect_bus(&context
, event
, &bus
);
673 r
= bus_event_loop_with_idle(event
, bus
, "org.freedesktop.locale1", DEFAULT_EXIT_USEC
, NULL
, NULL
);
675 return log_error_errno(r
, "Failed to run event loop: %m");
680 DEFINE_MAIN_FUNCTION(run
);