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 "constants.h"
18 #include "localed-util.h"
20 #include "main-func.h"
21 #include "missing_capability.h"
22 #include "path-util.h"
23 #include "selinux-util.h"
24 #include "service-util.h"
25 #include "signal-util.h"
26 #include "string-util.h"
28 #include "user-util.h"
30 static int reload_system_manager(sd_bus
*bus
) {
31 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
36 r
= bus_call_method(bus
, bus_systemd_mgr
, "Reload", &error
, NULL
, NULL
);
38 return log_error_errno(r
, "Failed to reload system manager: %s", bus_error_message(&error
, r
));
42 static int vconsole_reload(sd_bus
*bus
) {
43 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
48 r
= bus_call_method(bus
, bus_systemd_mgr
, "RestartUnit", &error
, NULL
, "ss", "systemd-vconsole-setup.service", "replace");
50 return log_error_errno(r
, "Failed to issue method call: %s", bus_error_message(&error
, r
));
54 static int property_get_locale(
57 const char *interface
,
59 sd_bus_message
*reply
,
61 sd_bus_error
*error
) {
63 Context
*c
= ASSERT_PTR(userdata
);
64 _cleanup_strv_free_
char **l
= NULL
;
67 r
= locale_read_data(c
, reply
);
71 r
= locale_context_build_env(&c
->locale_context
, &l
, NULL
);
75 return sd_bus_message_append_strv(reply
, l
);
78 static int property_get_vconsole(
81 const char *interface
,
83 sd_bus_message
*reply
,
85 sd_bus_error
*error
) {
87 Context
*c
= ASSERT_PTR(userdata
);
92 r
= vconsole_read_data(c
, reply
);
96 if (streq(property
, "VConsoleKeymap"))
97 return sd_bus_message_append_basic(reply
, 's', c
->vc
.keymap
);
98 if (streq(property
, "VConsoleKeymapToggle"))
99 return sd_bus_message_append_basic(reply
, 's', c
->vc
.toggle
);
104 static int property_get_xkb(
107 const char *interface
,
108 const char *property
,
109 sd_bus_message
*reply
,
111 sd_bus_error
*error
) {
113 Context
*c
= ASSERT_PTR(userdata
);
114 const X11Context
*xc
;
119 r
= vconsole_read_data(c
, reply
);
123 r
= x11_read_data(c
, reply
);
127 xc
= context_get_x11_context(c
);
129 if (streq(property
, "X11Layout"))
130 return sd_bus_message_append_basic(reply
, 's', xc
->layout
);
131 if (streq(property
, "X11Model"))
132 return sd_bus_message_append_basic(reply
, 's', xc
->model
);
133 if (streq(property
, "X11Variant"))
134 return sd_bus_message_append_basic(reply
, 's', xc
->variant
);
135 if (streq(property
, "X11Options"))
136 return sd_bus_message_append_basic(reply
, 's', xc
->options
);
141 static int process_locale_list_item(
142 const char *assignment
,
143 char *new_locale
[static _VARIABLE_LC_MAX
],
145 sd_bus_error
*error
) {
150 for (LocaleVariable p
= 0; p
< _VARIABLE_LC_MAX
; p
++) {
151 const char *name
, *e
;
153 assert_se(name
= locale_variable_to_string(p
));
155 e
= startswith(assignment
, name
);
164 if (!locale_is_valid(e
))
165 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Locale %s is not valid, refusing.", e
);
166 if (!use_localegen
&& locale_is_installed(e
) <= 0)
167 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Locale %s not installed, refusing.", e
);
169 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Locale variable %s set twice, refusing.", name
);
171 new_locale
[p
] = strdup(e
);
178 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Locale assignment %s not valid, refusing.", assignment
);
181 static int locale_gen_process_locale(char *new_locale
[static _VARIABLE_LC_MAX
], sd_bus_error
*error
) {
186 for (LocaleVariable p
= 0; p
< _VARIABLE_LC_MAX
; p
++) {
187 if (p
== VARIABLE_LANGUAGE
)
189 if (isempty(new_locale
[p
]))
191 if (locale_is_installed(new_locale
[p
]))
194 r
= locale_gen_enable_locale(new_locale
[p
]);
196 log_error_errno(r
, "Refused to enable locale for generation: %m");
197 return sd_bus_error_setf(error
,
198 SD_BUS_ERROR_INVALID_ARGS
,
199 "Specified locale is not installed and non-UTF-8 locale will not be auto-generated: %s",
203 log_error_errno(r
, "Failed to enable invalid locale %s for generation.", new_locale
[p
]);
204 return sd_bus_error_setf(error
,
205 SD_BUS_ERROR_INVALID_ARGS
,
206 "Can not enable locale generation for invalid locale: %s",
210 log_error_errno(r
, "Failed to enable locale for generation: %m");
211 return sd_bus_error_set_errnof(error
, r
, "Failed to enable locale generation: %m");
214 r
= locale_gen_run();
216 log_error_errno(r
, "Failed to generate locale: %m");
217 return sd_bus_error_set_errnof(error
, r
, "Failed to generate locale: %m");
224 static int method_set_locale(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
225 _cleanup_(locale_variables_freep
) char *new_locale
[_VARIABLE_LC_MAX
] = {};
226 _cleanup_strv_free_
char **l
= NULL
, **l_set
= NULL
, **l_unset
= NULL
;
227 Context
*c
= ASSERT_PTR(userdata
);
233 r
= sd_bus_message_read_strv(m
, &l
);
235 return bus_log_parse_error(r
);
237 r
= sd_bus_message_read_basic(m
, 'b', &interactive
);
239 return bus_log_parse_error(r
);
241 use_localegen
= locale_gen_check_available();
243 /* If single locale without variable name is provided, then we assume it is LANG=. */
244 if (strv_length(l
) == 1 && !strchr(l
[0], '=')) {
245 if (!locale_is_valid(l
[0]))
246 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid locale specification: %s", l
[0]);
247 if (!use_localegen
&& locale_is_installed(l
[0]) <= 0)
248 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Specified locale is not installed: %s", l
[0]);
250 new_locale
[VARIABLE_LANG
] = strdup(l
[0]);
251 if (!new_locale
[VARIABLE_LANG
])
257 /* Check whether a variable is valid */
259 r
= process_locale_list_item(*i
, new_locale
, use_localegen
, error
);
264 /* If LANG was specified, but not LANGUAGE, check if we should
265 * set it based on the language fallback table. */
266 if (!isempty(new_locale
[VARIABLE_LANG
]) &&
267 isempty(new_locale
[VARIABLE_LANGUAGE
])) {
268 _cleanup_free_
char *language
= NULL
;
270 (void) find_language_fallback(new_locale
[VARIABLE_LANG
], &language
);
272 log_debug("Converted LANG=%s to LANGUAGE=%s", new_locale
[VARIABLE_LANG
], language
);
273 free_and_replace(new_locale
[VARIABLE_LANGUAGE
], language
);
277 r
= locale_read_data(c
, m
);
279 log_error_errno(r
, "Failed to read locale data: %m");
280 return sd_bus_error_set(error
, SD_BUS_ERROR_FAILED
, "Failed to read locale data");
283 /* Merge with the current settings */
284 r
= locale_context_merge(&c
->locale_context
, new_locale
);
288 locale_variables_simplify(new_locale
);
290 if (locale_context_equal(&c
->locale_context
, new_locale
)) {
291 log_debug("Locale settings were not modified.");
292 return sd_bus_reply_method_return(m
, 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 */
309 /* Generate locale in case it is missing and the system is using locale-gen */
311 r
= locale_gen_process_locale(new_locale
, error
);
316 locale_context_take(&c
->locale_context
, new_locale
);
318 /* Write locale configuration */
319 r
= locale_context_save(&c
->locale_context
, &l_set
, &l_unset
);
321 log_error_errno(r
, "Failed to set locale: %m");
322 return sd_bus_error_set_errnof(error
, r
, "Failed to set locale: %m");
325 /* Since we just updated the locale configuration file, ask the system manager to read it again to
326 * update its default locale settings. It's important to not use UnsetAndSetEnvironment or a similar
327 * method because in this case unsetting variables means restoring them to PID1 default values, which
328 * may be outdated, since locale.conf has just changed and PID1 hasn't read it */
329 (void) reload_system_manager(sd_bus_message_get_bus(m
));
331 if (!strv_isempty(l_set
)) {
332 _cleanup_free_
char *line
= NULL
;
334 line
= strv_join(l_set
, ", ");
335 log_info("Changed locale to %s.", strnull(line
));
337 log_info("Changed locale to unset.");
339 (void) sd_bus_emit_properties_changed(
340 sd_bus_message_get_bus(m
),
341 "/org/freedesktop/locale1",
342 "org.freedesktop.locale1",
345 return sd_bus_reply_method_return(m
, NULL
);
348 static int method_set_vc_keyboard(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
349 _cleanup_(x11_context_clear
) X11Context converted
= {};
350 Context
*c
= ASSERT_PTR(userdata
);
351 int convert
, interactive
, r
;
357 r
= sd_bus_message_read(m
, "ssbb", &in
.keymap
, &in
.toggle
, &convert
, &interactive
);
359 return bus_log_parse_error(r
);
361 vc_context_empty_to_null(&in
);
363 r
= vc_context_verify_and_warn(&in
, LOG_ERR
, error
);
367 r
= vconsole_read_data(c
, m
);
369 log_error_errno(r
, "Failed to read virtual console keymap data: %m");
370 return sd_bus_error_set_errnof(error
, r
, "Failed to read virtual console keymap data: %m");
373 r
= x11_read_data(c
, m
);
375 log_error_errno(r
, "Failed to read X11 keyboard layout data: %m");
376 return sd_bus_error_set_errnof(error
, r
, "Failed to read X11 keyboard layout data: %m");
380 r
= vconsole_convert_to_x11(&in
, &converted
);
382 log_error_errno(r
, "Failed to convert keymap data: %m");
383 return sd_bus_error_set_errnof(error
, r
, "Failed to convert keymap data: %m");
386 if (x11_context_isempty(&converted
))
387 log_notice("No conversion found for virtual console keymap \"%s\".", strempty(in
.keymap
));
389 log_info("The virtual console keymap '%s' is converted to X11 keyboard layout '%s' model '%s' variant '%s' options '%s'",
390 in
.keymap
, strempty(converted
.layout
), strempty(converted
.model
), strempty(converted
.variant
), strempty(converted
.options
));
392 /* save the result of conversion to emit changed properties later. */
393 x_needs_update
= !x11_context_equal(&c
->x11_from_vc
, &converted
) || !x11_context_equal(&c
->x11_from_xorg
, &converted
);
395 x_needs_update
= !x11_context_equal(&c
->x11_from_vc
, &c
->x11_from_xorg
);
397 if (vc_context_equal(&c
->vc
, &in
) && !x_needs_update
)
398 return sd_bus_reply_method_return(m
, NULL
);
400 r
= bus_verify_polkit_async(
403 "org.freedesktop.locale1.set-keyboard",
412 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
414 r
= vc_context_copy(&c
->vc
, &in
);
418 if (x_needs_update
) {
420 r
= x11_context_copy(&c
->x11_from_vc
, &converted
);
423 x11_context_replace(&c
->x11_from_xorg
, &converted
);
425 const X11Context
*xc
= context_get_x11_context(c
);
427 /* Even if the conversion is not requested, sync the two X11 contexts. */
428 r
= x11_context_copy(&c
->x11_from_vc
, xc
);
432 r
= x11_context_copy(&c
->x11_from_xorg
, xc
);
438 r
= vconsole_write_data(c
);
440 log_warning_errno(r
, "Failed to write virtual console keymap, ignoring: %m");
442 if (x_needs_update
) {
443 r
= x11_write_data(c
);
445 log_warning_errno(r
, "Failed to write X11 keyboard layout, ignoring: %m");
448 log_info("Changed virtual console keymap to '%s' toggle '%s'",
449 strempty(c
->vc
.keymap
), strempty(c
->vc
.toggle
));
451 (void) vconsole_reload(sd_bus_message_get_bus(m
));
453 (void) sd_bus_emit_properties_changed(
454 sd_bus_message_get_bus(m
),
455 "/org/freedesktop/locale1",
456 "org.freedesktop.locale1",
457 "VConsoleKeymap", "VConsoleKeymapToggle",
458 x_needs_update
? "X11Layout" : NULL
,
459 x_needs_update
? "X11Model" : NULL
,
460 x_needs_update
? "X11Variant" : NULL
,
461 x_needs_update
? "X11Options" : NULL
,
464 return sd_bus_reply_method_return(m
, NULL
);
467 static int method_set_x11_keyboard(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
468 _cleanup_(vc_context_clear
) VCContext converted
= {};
469 Context
*c
= ASSERT_PTR(userdata
);
470 int convert
, interactive
, r
;
475 r
= sd_bus_message_read(m
, "ssssbb", &in
.layout
, &in
.model
, &in
.variant
, &in
.options
, &convert
, &interactive
);
477 return bus_log_parse_error(r
);
479 x11_context_empty_to_null(&in
);
481 r
= x11_context_verify_and_warn(&in
, LOG_ERR
, error
);
485 r
= vconsole_read_data(c
, m
);
487 log_error_errno(r
, "Failed to read virtual console keymap data: %m");
488 return sd_bus_error_set_errnof(error
, r
, "Failed to read virtual console keymap data: %m");
491 r
= x11_read_data(c
, m
);
493 log_error_errno(r
, "Failed to read x11 keyboard layout data: %m");
494 return sd_bus_error_set(error
, SD_BUS_ERROR_FAILED
, "Failed to read x11 keyboard layout data");
498 r
= x11_convert_to_vconsole(&in
, &converted
);
500 log_error_errno(r
, "Failed to convert keymap data: %m");
501 return sd_bus_error_set_errnof(error
, r
, "Failed to convert keymap data: %m");
504 if (vc_context_isempty(&converted
))
505 /* We search for layout-variant match first, but then we also look
506 * for anything which matches just the layout. So it's accurate to say
507 * that we couldn't find anything which matches the layout. */
508 log_notice("No conversion to virtual console map found for \"%s\".", strempty(in
.layout
));
510 log_info("The X11 keyboard layout '%s' is converted to virtual console keymap '%s'",
511 in
.layout
, converted
.keymap
);
513 /* save the result of conversion to emit changed properties later. */
514 convert
= !vc_context_equal(&c
->vc
, &converted
);
517 if (x11_context_equal(&c
->x11_from_vc
, &in
) && x11_context_equal(&c
->x11_from_xorg
, &in
) && !convert
)
518 return sd_bus_reply_method_return(m
, NULL
);
520 r
= bus_verify_polkit_async(
523 "org.freedesktop.locale1.set-keyboard",
532 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
534 r
= x11_context_copy(&c
->x11_from_vc
, &in
);
538 r
= x11_context_copy(&c
->x11_from_xorg
, &in
);
543 vc_context_replace(&c
->vc
, &converted
);
545 r
= vconsole_write_data(c
);
547 log_warning_errno(r
, "Failed to update vconsole.conf, ignoring: %m");
549 r
= x11_write_data(c
);
551 log_warning_errno(r
, "Failed to write X11 keyboard layout, ignoring: %m");
553 log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
556 strempty(in
.variant
),
557 strempty(in
.options
));
559 (void) sd_bus_emit_properties_changed(
560 sd_bus_message_get_bus(m
),
561 "/org/freedesktop/locale1",
562 "org.freedesktop.locale1",
563 "X11Layout", "X11Model", "X11Variant", "X11Options",
564 convert
? "VConsoleKeymap" : NULL
,
565 convert
? "VConsoleKeymapToggle" : NULL
,
569 (void) vconsole_reload(sd_bus_message_get_bus(m
));
571 return sd_bus_reply_method_return(m
, NULL
);
574 static const sd_bus_vtable locale_vtable
[] = {
575 SD_BUS_VTABLE_START(0),
576 SD_BUS_PROPERTY("Locale", "as", property_get_locale
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
577 SD_BUS_PROPERTY("X11Layout", "s", property_get_xkb
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
578 SD_BUS_PROPERTY("X11Model", "s", property_get_xkb
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
579 SD_BUS_PROPERTY("X11Variant", "s", property_get_xkb
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
580 SD_BUS_PROPERTY("X11Options", "s", property_get_xkb
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
581 SD_BUS_PROPERTY("VConsoleKeymap", "s", property_get_vconsole
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
582 SD_BUS_PROPERTY("VConsoleKeymapToggle", "s", property_get_vconsole
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
584 SD_BUS_METHOD_WITH_ARGS("SetLocale",
585 SD_BUS_ARGS("as", locale
, "b", interactive
),
588 SD_BUS_VTABLE_UNPRIVILEGED
),
589 SD_BUS_METHOD_WITH_ARGS("SetVConsoleKeyboard",
590 SD_BUS_ARGS("s", keymap
, "s", keymap_toggle
, "b", convert
, "b", interactive
),
592 method_set_vc_keyboard
,
593 SD_BUS_VTABLE_UNPRIVILEGED
),
594 SD_BUS_METHOD_WITH_ARGS("SetX11Keyboard",
595 SD_BUS_ARGS("s", layout
, "s", model
, "s", variant
, "s", options
, "b", convert
, "b", interactive
),
597 method_set_x11_keyboard
,
598 SD_BUS_VTABLE_UNPRIVILEGED
),
603 static const BusObjectImplementation manager_object
= {
604 "/org/freedesktop/locale1",
605 "org.freedesktop.locale1",
606 .vtables
= BUS_VTABLES(locale_vtable
),
609 static int connect_bus(Context
*c
, sd_event
*event
, sd_bus
**_bus
) {
610 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
617 r
= sd_bus_default_system(&bus
);
619 return log_error_errno(r
, "Failed to get system bus connection: %m");
621 r
= bus_add_implementation(bus
, &manager_object
, c
);
625 r
= bus_log_control_api_register(bus
);
629 r
= sd_bus_request_name_async(bus
, NULL
, "org.freedesktop.locale1", 0, NULL
, NULL
);
631 return log_error_errno(r
, "Failed to request name: %m");
633 r
= sd_bus_attach_event(bus
, event
, 0);
635 return log_error_errno(r
, "Failed to attach bus to event loop: %m");
637 *_bus
= TAKE_PTR(bus
);
642 static int run(int argc
, char *argv
[]) {
643 _cleanup_(context_clear
) Context context
= {};
644 _cleanup_(sd_event_unrefp
) sd_event
*event
= NULL
;
645 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
650 r
= service_parse_argv("systemd-localed.service",
651 "Manage system locale settings and key mappings.",
652 BUS_IMPLEMENTATIONS(&manager_object
,
653 &log_control_object
),
664 assert_se(sigprocmask_many(SIG_BLOCK
, NULL
, SIGTERM
, SIGINT
, -1) >= 0);
666 r
= sd_event_default(&event
);
668 return log_error_errno(r
, "Failed to allocate event loop: %m");
670 (void) sd_event_set_watchdog(event
, true);
672 r
= sd_event_add_signal(event
, NULL
, SIGINT
, NULL
, NULL
);
674 return log_error_errno(r
, "Failed to install SIGINT handler: %m");
676 r
= sd_event_add_signal(event
, NULL
, SIGTERM
, NULL
, NULL
);
678 return log_error_errno(r
, "Failed to install SIGTERM handler: %m");
680 r
= connect_bus(&context
, event
, &bus
);
684 r
= bus_event_loop_with_idle(event
, bus
, "org.freedesktop.locale1", DEFAULT_EXIT_USEC
, NULL
, NULL
);
686 return log_error_errno(r
, "Failed to run event loop: %m");
691 DEFINE_MAIN_FUNCTION(run
);