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"
18 #include "daemon-util.h"
20 #include "localed-util.h"
22 #include "main-func.h"
23 #include "missing_capability.h"
24 #include "path-util.h"
25 #include "selinux-util.h"
26 #include "service-util.h"
27 #include "signal-util.h"
28 #include "string-util.h"
30 #include "user-util.h"
32 static int vconsole_reload(sd_bus
*bus
) {
33 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
38 r
= bus_call_method(bus
, bus_systemd_mgr
, "RestartUnit", &error
, NULL
, "ss", "systemd-vconsole-setup.service", "replace");
40 return log_error_errno(r
, "Failed to issue method call: %s", bus_error_message(&error
, r
));
44 static int property_get_locale(
47 const char *interface
,
49 sd_bus_message
*reply
,
51 sd_bus_error
*error
) {
53 Context
*c
= ASSERT_PTR(userdata
);
54 _cleanup_strv_free_
char **l
= NULL
;
57 r
= locale_read_data(c
, reply
);
61 r
= locale_context_build_env(&c
->locale_context
, &l
, NULL
);
65 return sd_bus_message_append_strv(reply
, l
);
68 static int property_get_vconsole(
71 const char *interface
,
73 sd_bus_message
*reply
,
75 sd_bus_error
*error
) {
77 Context
*c
= ASSERT_PTR(userdata
);
82 r
= vconsole_read_data(c
, reply
);
86 if (streq(property
, "VConsoleKeymap"))
87 return sd_bus_message_append_basic(reply
, 's', c
->vc
.keymap
);
88 if (streq(property
, "VConsoleKeymapToggle"))
89 return sd_bus_message_append_basic(reply
, 's', c
->vc
.toggle
);
94 static int property_get_xkb(
97 const char *interface
,
99 sd_bus_message
*reply
,
101 sd_bus_error
*error
) {
103 Context
*c
= ASSERT_PTR(userdata
);
104 const X11Context
*xc
;
109 r
= vconsole_read_data(c
, reply
);
113 r
= x11_read_data(c
, reply
);
117 xc
= context_get_x11_context(c
);
119 if (streq(property
, "X11Layout"))
120 return sd_bus_message_append_basic(reply
, 's', xc
->layout
);
121 if (streq(property
, "X11Model"))
122 return sd_bus_message_append_basic(reply
, 's', xc
->model
);
123 if (streq(property
, "X11Variant"))
124 return sd_bus_message_append_basic(reply
, 's', xc
->variant
);
125 if (streq(property
, "X11Options"))
126 return sd_bus_message_append_basic(reply
, 's', xc
->options
);
131 static int process_locale_list_item(
132 const char *assignment
,
133 char *new_locale
[static _VARIABLE_LC_MAX
],
135 sd_bus_error
*error
) {
140 for (LocaleVariable p
= 0; p
< _VARIABLE_LC_MAX
; p
++) {
141 const char *name
, *e
;
143 assert_se(name
= locale_variable_to_string(p
));
145 e
= startswith(assignment
, name
);
154 if (!locale_is_valid(e
))
155 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Locale %s is not valid, refusing.", e
);
156 if (!use_localegen
&& locale_is_installed(e
) <= 0)
157 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Locale %s not installed, refusing.", e
);
159 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Locale variable %s set twice, refusing.", name
);
161 return strdup_to(&new_locale
[p
], e
);
164 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Locale assignment %s not valid, refusing.", assignment
);
167 static int locale_gen_process_locale(char *new_locale
[static _VARIABLE_LC_MAX
], sd_bus_error
*error
) {
172 for (LocaleVariable p
= 0; p
< _VARIABLE_LC_MAX
; p
++) {
173 if (p
== VARIABLE_LANGUAGE
)
175 if (isempty(new_locale
[p
]))
177 if (locale_is_installed(new_locale
[p
]))
180 r
= locale_gen_enable_locale(new_locale
[p
]);
182 log_error_errno(r
, "Refused to enable locale for generation: %m");
183 return sd_bus_error_setf(error
,
184 SD_BUS_ERROR_INVALID_ARGS
,
185 "Specified locale is not installed and non-UTF-8 locale will not be auto-generated: %s",
189 log_error_errno(r
, "Failed to enable invalid locale %s for generation.", new_locale
[p
]);
190 return sd_bus_error_setf(error
,
191 SD_BUS_ERROR_INVALID_ARGS
,
192 "Cannot enable locale generation for invalid locale: %s",
196 log_error_errno(r
, "Failed to enable locale for generation: %m");
197 return sd_bus_error_set_errnof(error
, r
, "Failed to enable locale generation: %m");
200 r
= locale_gen_run();
202 log_error_errno(r
, "Failed to generate locale: %m");
203 return sd_bus_error_set_errnof(error
, r
, "Failed to generate locale: %m");
210 static int method_set_locale(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
211 _cleanup_(locale_variables_freep
) char *new_locale
[_VARIABLE_LC_MAX
] = {};
212 _cleanup_strv_free_
char **l
= NULL
, **l_set
= NULL
, **l_unset
= NULL
;
213 Context
*c
= ASSERT_PTR(userdata
);
219 r
= sd_bus_message_read_strv(m
, &l
);
221 return bus_log_parse_error(r
);
223 r
= sd_bus_message_read_basic(m
, 'b', &interactive
);
225 return bus_log_parse_error(r
);
227 use_localegen
= locale_gen_check_available();
229 /* If single locale without variable name is provided, then we assume it is LANG=. */
230 if (strv_length(l
) == 1 && !strchr(l
[0], '=')) {
231 if (!locale_is_valid(l
[0]))
232 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid locale specification: %s", l
[0]);
233 if (!use_localegen
&& locale_is_installed(l
[0]) <= 0)
234 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Specified locale is not installed: %s", l
[0]);
236 new_locale
[VARIABLE_LANG
] = strdup(l
[0]);
237 if (!new_locale
[VARIABLE_LANG
])
243 /* Check whether a variable is valid */
245 r
= process_locale_list_item(*i
, new_locale
, use_localegen
, error
);
250 /* If LANG was specified, but not LANGUAGE, check if we should
251 * set it based on the language fallback table. */
252 if (!isempty(new_locale
[VARIABLE_LANG
]) &&
253 isempty(new_locale
[VARIABLE_LANGUAGE
])) {
254 _cleanup_free_
char *language
= NULL
;
256 (void) find_language_fallback(new_locale
[VARIABLE_LANG
], &language
);
258 log_debug("Converted LANG=%s to LANGUAGE=%s", new_locale
[VARIABLE_LANG
], language
);
259 free_and_replace(new_locale
[VARIABLE_LANGUAGE
], language
);
263 r
= locale_read_data(c
, m
);
265 log_error_errno(r
, "Failed to read locale data: %m");
266 return sd_bus_error_set(error
, SD_BUS_ERROR_FAILED
, "Failed to read locale data");
269 /* Merge with the current settings */
270 r
= locale_context_merge(&c
->locale_context
, new_locale
);
274 locale_variables_simplify(new_locale
);
276 if (locale_context_equal(&c
->locale_context
, new_locale
)) {
277 log_debug("Locale settings were not modified.");
278 return sd_bus_reply_method_return(m
, NULL
);
281 r
= bus_verify_polkit_async_full(
283 "org.freedesktop.locale1.set-locale",
285 /* good_user= */ UID_INVALID
,
286 interactive
? POLKIT_ALLOW_INTERACTIVE
: 0,
292 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
294 /* Generate locale in case it is missing and the system is using locale-gen */
296 r
= locale_gen_process_locale(new_locale
, error
);
301 locale_context_take(&c
->locale_context
, new_locale
);
303 /* Write locale configuration */
304 r
= locale_context_save(&c
->locale_context
, &l_set
, &l_unset
);
306 log_error_errno(r
, "Failed to set locale: %m");
307 return sd_bus_error_set_errnof(error
, r
, "Failed to set locale: %m");
310 /* Since we just updated the locale configuration file, ask the system manager to read it again to
311 * update its default locale settings. It's important to not use UnsetAndSetEnvironment or a similar
312 * method because in this case unsetting variables means restoring them to PID1 default values, which
313 * may be outdated, since locale.conf has just changed and PID1 hasn't read it */
314 (void) bus_service_manager_reload(sd_bus_message_get_bus(m
));
316 if (!strv_isempty(l_set
)) {
317 _cleanup_free_
char *line
= NULL
;
319 line
= strv_join(l_set
, ", ");
320 log_info("Changed locale to %s.", strnull(line
));
322 log_info("Changed locale to unset.");
324 (void) sd_bus_emit_properties_changed(
325 sd_bus_message_get_bus(m
),
326 "/org/freedesktop/locale1",
327 "org.freedesktop.locale1",
330 return sd_bus_reply_method_return(m
, NULL
);
333 static int method_set_vc_keyboard(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
334 _cleanup_(x11_context_clear
) X11Context converted
= {};
335 Context
*c
= ASSERT_PTR(userdata
);
336 int convert
, interactive
, r
;
342 r
= sd_bus_message_read(m
, "ssbb", &in
.keymap
, &in
.toggle
, &convert
, &interactive
);
344 return bus_log_parse_error(r
);
346 vc_context_empty_to_null(&in
);
348 r
= vc_context_verify_and_warn(&in
, LOG_ERR
, error
);
352 r
= vconsole_read_data(c
, m
);
354 log_error_errno(r
, "Failed to read virtual console keymap data: %m");
355 return sd_bus_error_set_errnof(error
, r
, "Failed to read virtual console keymap data: %m");
358 r
= x11_read_data(c
, m
);
360 log_error_errno(r
, "Failed to read X11 keyboard layout data: %m");
361 return sd_bus_error_set_errnof(error
, r
, "Failed to read X11 keyboard layout data: %m");
365 r
= vconsole_convert_to_x11(&in
, &converted
);
367 log_error_errno(r
, "Failed to convert keymap data: %m");
368 return sd_bus_error_set_errnof(error
, r
, "Failed to convert keymap data: %m");
371 if (x11_context_isempty(&converted
))
372 log_notice("No conversion found for virtual console keymap \"%s\".", strempty(in
.keymap
));
374 log_info("The virtual console keymap '%s' is converted to X11 keyboard layout '%s' model '%s' variant '%s' options '%s'",
375 in
.keymap
, strempty(converted
.layout
), strempty(converted
.model
), strempty(converted
.variant
), strempty(converted
.options
));
377 /* save the result of conversion to emit changed properties later. */
378 x_needs_update
= !x11_context_equal(&c
->x11_from_vc
, &converted
) || !x11_context_equal(&c
->x11_from_xorg
, &converted
);
380 x_needs_update
= !x11_context_equal(&c
->x11_from_vc
, &c
->x11_from_xorg
);
382 if (vc_context_equal(&c
->vc
, &in
) && !x_needs_update
)
383 return sd_bus_reply_method_return(m
, NULL
);
385 r
= bus_verify_polkit_async_full(
387 "org.freedesktop.locale1.set-keyboard",
389 /* good_user= */ UID_INVALID
,
390 interactive
? POLKIT_ALLOW_INTERACTIVE
: 0,
396 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
398 r
= vc_context_copy(&c
->vc
, &in
);
402 if (x_needs_update
) {
404 r
= x11_context_copy(&c
->x11_from_vc
, &converted
);
407 x11_context_replace(&c
->x11_from_xorg
, &converted
);
409 const X11Context
*xc
= context_get_x11_context(c
);
411 /* Even if the conversion is not requested, sync the two X11 contexts. */
412 r
= x11_context_copy(&c
->x11_from_vc
, xc
);
416 r
= x11_context_copy(&c
->x11_from_xorg
, xc
);
422 r
= vconsole_write_data(c
);
424 log_warning_errno(r
, "Failed to write virtual console keymap, ignoring: %m");
426 if (x_needs_update
) {
427 r
= x11_write_data(c
);
429 log_warning_errno(r
, "Failed to write X11 keyboard layout, ignoring: %m");
432 log_info("Changed virtual console keymap to '%s' toggle '%s'",
433 strempty(c
->vc
.keymap
), strempty(c
->vc
.toggle
));
435 (void) vconsole_reload(sd_bus_message_get_bus(m
));
437 (void) sd_bus_emit_properties_changed(
438 sd_bus_message_get_bus(m
),
439 "/org/freedesktop/locale1",
440 "org.freedesktop.locale1",
441 "VConsoleKeymap", "VConsoleKeymapToggle",
442 x_needs_update
? "X11Layout" : NULL
,
443 x_needs_update
? "X11Model" : NULL
,
444 x_needs_update
? "X11Variant" : NULL
,
445 x_needs_update
? "X11Options" : NULL
,
448 return sd_bus_reply_method_return(m
, NULL
);
451 static int method_set_x11_keyboard(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
452 _cleanup_(vc_context_clear
) VCContext converted
= {};
453 Context
*c
= ASSERT_PTR(userdata
);
454 int convert
, interactive
, r
;
459 r
= sd_bus_message_read(m
, "ssssbb", &in
.layout
, &in
.model
, &in
.variant
, &in
.options
, &convert
, &interactive
);
461 return bus_log_parse_error(r
);
463 x11_context_empty_to_null(&in
);
465 r
= x11_context_verify_and_warn(&in
, LOG_ERR
, error
);
469 r
= vconsole_read_data(c
, m
);
471 log_error_errno(r
, "Failed to read virtual console keymap data: %m");
472 return sd_bus_error_set_errnof(error
, r
, "Failed to read virtual console keymap data: %m");
475 r
= x11_read_data(c
, m
);
477 log_error_errno(r
, "Failed to read x11 keyboard layout data: %m");
478 return sd_bus_error_set(error
, SD_BUS_ERROR_FAILED
, "Failed to read x11 keyboard layout data");
482 r
= x11_convert_to_vconsole(&in
, &converted
);
484 log_error_errno(r
, "Failed to convert keymap data: %m");
485 return sd_bus_error_set_errnof(error
, r
, "Failed to convert keymap data: %m");
488 if (vc_context_isempty(&converted
))
489 /* We search for layout-variant match first, but then we also look
490 * for anything which matches just the layout. So it's accurate to say
491 * that we couldn't find anything which matches the layout. */
492 log_notice("No conversion to virtual console map found for \"%s\".", strempty(in
.layout
));
494 log_info("The X11 keyboard layout '%s' is converted to virtual console keymap '%s'",
495 in
.layout
, converted
.keymap
);
497 /* save the result of conversion to emit changed properties later. */
498 convert
= !vc_context_equal(&c
->vc
, &converted
);
501 if (x11_context_equal(&c
->x11_from_vc
, &in
) && x11_context_equal(&c
->x11_from_xorg
, &in
) && !convert
)
502 return sd_bus_reply_method_return(m
, NULL
);
504 r
= bus_verify_polkit_async_full(
506 "org.freedesktop.locale1.set-keyboard",
508 /* good_user= */ UID_INVALID
,
509 interactive
? POLKIT_ALLOW_INTERACTIVE
: 0,
515 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
517 r
= x11_context_copy(&c
->x11_from_vc
, &in
);
521 r
= x11_context_copy(&c
->x11_from_xorg
, &in
);
526 vc_context_replace(&c
->vc
, &converted
);
528 r
= vconsole_write_data(c
);
530 log_warning_errno(r
, "Failed to update vconsole.conf, ignoring: %m");
532 r
= x11_write_data(c
);
534 log_warning_errno(r
, "Failed to write X11 keyboard layout, ignoring: %m");
536 log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
539 strempty(in
.variant
),
540 strempty(in
.options
));
542 (void) sd_bus_emit_properties_changed(
543 sd_bus_message_get_bus(m
),
544 "/org/freedesktop/locale1",
545 "org.freedesktop.locale1",
546 "X11Layout", "X11Model", "X11Variant", "X11Options",
547 convert
? "VConsoleKeymap" : NULL
,
548 convert
? "VConsoleKeymapToggle" : NULL
,
552 (void) vconsole_reload(sd_bus_message_get_bus(m
));
554 return sd_bus_reply_method_return(m
, NULL
);
557 static const sd_bus_vtable locale_vtable
[] = {
558 SD_BUS_VTABLE_START(0),
559 SD_BUS_PROPERTY("Locale", "as", property_get_locale
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
560 SD_BUS_PROPERTY("X11Layout", "s", property_get_xkb
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
561 SD_BUS_PROPERTY("X11Model", "s", property_get_xkb
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
562 SD_BUS_PROPERTY("X11Variant", "s", property_get_xkb
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
563 SD_BUS_PROPERTY("X11Options", "s", property_get_xkb
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
564 SD_BUS_PROPERTY("VConsoleKeymap", "s", property_get_vconsole
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
565 SD_BUS_PROPERTY("VConsoleKeymapToggle", "s", property_get_vconsole
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
567 SD_BUS_METHOD_WITH_ARGS("SetLocale",
568 SD_BUS_ARGS("as", locale
, "b", interactive
),
571 SD_BUS_VTABLE_UNPRIVILEGED
),
572 SD_BUS_METHOD_WITH_ARGS("SetVConsoleKeyboard",
573 SD_BUS_ARGS("s", keymap
, "s", keymap_toggle
, "b", convert
, "b", interactive
),
575 method_set_vc_keyboard
,
576 SD_BUS_VTABLE_UNPRIVILEGED
),
577 SD_BUS_METHOD_WITH_ARGS("SetX11Keyboard",
578 SD_BUS_ARGS("s", layout
, "s", model
, "s", variant
, "s", options
, "b", convert
, "b", interactive
),
580 method_set_x11_keyboard
,
581 SD_BUS_VTABLE_UNPRIVILEGED
),
586 static const BusObjectImplementation manager_object
= {
587 "/org/freedesktop/locale1",
588 "org.freedesktop.locale1",
589 .vtables
= BUS_VTABLES(locale_vtable
),
592 static int connect_bus(Context
*c
, sd_event
*event
, sd_bus
**_bus
) {
593 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
600 r
= sd_bus_default_system(&bus
);
602 return log_error_errno(r
, "Failed to get system bus connection: %m");
604 r
= bus_add_implementation(bus
, &manager_object
, c
);
608 r
= bus_log_control_api_register(bus
);
612 r
= sd_bus_request_name_async(bus
, NULL
, "org.freedesktop.locale1", 0, NULL
, NULL
);
614 return log_error_errno(r
, "Failed to request name: %m");
616 r
= sd_bus_attach_event(bus
, event
, 0);
618 return log_error_errno(r
, "Failed to attach bus to event loop: %m");
620 *_bus
= TAKE_PTR(bus
);
625 static int run(int argc
, char *argv
[]) {
626 _cleanup_(context_clear
) Context context
= {};
627 _cleanup_(sd_event_unrefp
) sd_event
*event
= NULL
;
628 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
633 r
= service_parse_argv("systemd-localed.service",
634 "Manage system locale settings and key mappings.",
635 BUS_IMPLEMENTATIONS(&manager_object
,
636 &log_control_object
),
647 r
= sd_event_default(&event
);
649 return log_error_errno(r
, "Failed to allocate event loop: %m");
651 (void) sd_event_set_watchdog(event
, true);
653 r
= sd_event_set_signal_exit(event
, true);
655 return log_error_errno(r
, "Failed to install SIGINT/SIGTERM handlers: %m");
657 r
= connect_bus(&context
, event
, &bus
);
661 r
= sd_notify(false, NOTIFY_READY
);
663 log_warning_errno(r
, "Failed to send readiness notification, ignoring: %m");
665 r
= bus_event_loop_with_idle(event
, bus
, "org.freedesktop.locale1", DEFAULT_EXIT_USEC
, NULL
, NULL
);
667 return log_error_errno(r
, "Failed to run event loop: %m");
672 DEFINE_MAIN_FUNCTION(run
);