2 This file is part of systemd.
4 Copyright 2011 Lennart Poettering
5 Copyright 2013 Kay Sievers
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
26 #include <xkbcommon/xkbcommon.h>
32 #include "alloc-util.h"
33 #include "bus-error.h"
34 #include "bus-message.h"
37 #include "keymap-util.h"
38 #include "locale-util.h"
40 #include "path-util.h"
41 #include "selinux-util.h"
42 #include "string-util.h"
44 #include "user-util.h"
46 static Hashmap
*polkit_registry
= NULL
;
48 static int locale_update_system_manager(Context
*c
, sd_bus
*bus
) {
49 _cleanup_free_
char **l_unset
= NULL
;
50 _cleanup_strv_free_
char **l_set
= NULL
;
51 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*m
= NULL
;
52 sd_bus_error error
= SD_BUS_ERROR_NULL
;
53 unsigned c_set
, c_unset
, p
;
58 l_unset
= new0(char*, _VARIABLE_LC_MAX
);
62 l_set
= new0(char*, _VARIABLE_LC_MAX
);
66 for (p
= 0, c_set
= 0, c_unset
= 0; p
< _VARIABLE_LC_MAX
; p
++) {
69 name
= locale_variable_to_string(p
);
72 if (isempty(c
->locale
[p
]))
73 l_unset
[c_set
++] = (char*) name
;
77 if (asprintf(&s
, "%s=%s", name
, c
->locale
[p
]) < 0)
84 assert(c_set
+ c_unset
== _VARIABLE_LC_MAX
);
85 r
= sd_bus_message_new_method_call(bus
, &m
,
86 "org.freedesktop.systemd1",
87 "/org/freedesktop/systemd1",
88 "org.freedesktop.systemd1.Manager",
89 "UnsetAndSetEnvironment");
93 r
= sd_bus_message_append_strv(m
, l_unset
);
97 r
= sd_bus_message_append_strv(m
, l_set
);
101 r
= sd_bus_call(bus
, m
, 0, &error
, NULL
);
103 log_error_errno(r
, "Failed to update the manager environment: %m");
108 static int vconsole_reload(sd_bus
*bus
) {
109 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
114 r
= sd_bus_call_method(bus
,
115 "org.freedesktop.systemd1",
116 "/org/freedesktop/systemd1",
117 "org.freedesktop.systemd1.Manager",
121 "ss", "systemd-vconsole-setup.service", "replace");
124 log_error("Failed to issue method call: %s", bus_error_message(&error
, -r
));
128 static int vconsole_convert_to_x11_and_emit(Context
*c
, sd_bus
*bus
) {
133 r
= vconsole_convert_to_x11(c
);
138 r
= x11_write_data(c
);
140 return log_error_errno(r
, "Failed to write X11 keyboard layout: %m");
142 sd_bus_emit_properties_changed(bus
,
143 "/org/freedesktop/locale1",
144 "org.freedesktop.locale1",
145 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL
);
150 static int x11_convert_to_vconsole_and_emit(Context
*c
, sd_bus
*bus
) {
155 r
= x11_convert_to_vconsole(c
);
160 r
= vconsole_write_data(c
);
162 log_error_errno(r
, "Failed to save virtual console keymap: %m");
164 sd_bus_emit_properties_changed(bus
,
165 "/org/freedesktop/locale1",
166 "org.freedesktop.locale1",
167 "VConsoleKeymap", "VConsoleKeymapToggle", NULL
);
169 return vconsole_reload(bus
);
172 static int property_get_locale(
175 const char *interface
,
176 const char *property
,
177 sd_bus_message
*reply
,
179 sd_bus_error
*error
) {
181 Context
*c
= userdata
;
182 _cleanup_strv_free_
char **l
= NULL
;
185 l
= new0(char*, _VARIABLE_LC_MAX
+1);
189 for (p
= 0, q
= 0; p
< _VARIABLE_LC_MAX
; p
++) {
193 name
= locale_variable_to_string(p
);
196 if (isempty(c
->locale
[p
]))
199 if (asprintf(&t
, "%s=%s", name
, c
->locale
[p
]) < 0)
205 return sd_bus_message_append_strv(reply
, l
);
208 static int method_set_locale(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
209 Context
*c
= userdata
;
210 _cleanup_strv_free_
char **l
= NULL
;
212 const char *lang
= NULL
;
214 bool modified
= false;
215 bool have
[_VARIABLE_LC_MAX
] = {};
222 r
= bus_message_read_strv_extend(m
, &l
);
226 r
= sd_bus_message_read_basic(m
, 'b', &interactive
);
230 /* Check whether a variable changed and if it is valid */
234 for (p
= 0; p
< _VARIABLE_LC_MAX
; p
++) {
238 name
= locale_variable_to_string(p
);
242 if (startswith(*i
, name
) &&
244 locale_is_valid((*i
) + k
+ 1)) {
248 if (p
== VARIABLE_LANG
)
251 if (!streq_ptr(*i
+ k
+ 1, c
->locale
[p
]))
259 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid Locale data.");
262 /* If LANG was specified, but not LANGUAGE, check if we should
263 * set it based on the language fallback table. */
264 if (have
[VARIABLE_LANG
] && !have
[VARIABLE_LANGUAGE
]) {
265 _cleanup_free_
char *language
= NULL
;
269 (void) find_language_fallback(lang
, &language
);
271 log_debug("Converted LANG=%s to LANGUAGE=%s", lang
, language
);
272 if (!streq_ptr(language
, c
->locale
[VARIABLE_LANGUAGE
])) {
273 r
= strv_extendf(&l
, "LANGUAGE=%s", language
);
277 have
[VARIABLE_LANGUAGE
] = true;
283 /* Check whether a variable is unset */
285 for (p
= 0; p
< _VARIABLE_LC_MAX
; p
++)
286 if (!isempty(c
->locale
[p
]) && !have
[p
]) {
292 _cleanup_strv_free_
char **settings
= NULL
;
294 r
= bus_verify_polkit_async(
297 "org.freedesktop.locale1.set-locale",
306 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
309 for (p
= 0; p
< _VARIABLE_LC_MAX
; p
++) {
313 name
= locale_variable_to_string(p
);
317 if (startswith(*i
, name
) && (*i
)[k
] == '=') {
318 r
= free_and_strdup(&c
->locale
[p
], *i
+ k
+ 1);
325 for (p
= 0; p
< _VARIABLE_LC_MAX
; p
++) {
329 c
->locale
[p
] = mfree(c
->locale
[p
]);
334 r
= locale_write_data(c
, &settings
);
336 log_error_errno(r
, "Failed to set locale: %m");
337 return sd_bus_error_set_errnof(error
, r
, "Failed to set locale: %m");
340 locale_update_system_manager(c
, sd_bus_message_get_bus(m
));
343 _cleanup_free_
char *line
;
345 line
= strv_join(settings
, ", ");
346 log_info("Changed locale to %s.", strnull(line
));
348 log_info("Changed locale to unset.");
350 (void) sd_bus_emit_properties_changed(
351 sd_bus_message_get_bus(m
),
352 "/org/freedesktop/locale1",
353 "org.freedesktop.locale1",
356 log_debug("Locale settings were not modified.");
359 return sd_bus_reply_method_return(m
, NULL
);
362 static int method_set_vc_keyboard(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
363 Context
*c
= userdata
;
364 const char *keymap
, *keymap_toggle
;
365 int convert
, interactive
;
371 r
= sd_bus_message_read(m
, "ssbb", &keymap
, &keymap_toggle
, &convert
, &interactive
);
375 keymap
= empty_to_null(keymap
);
376 keymap_toggle
= empty_to_null(keymap_toggle
);
378 if (!streq_ptr(keymap
, c
->vc_keymap
) ||
379 !streq_ptr(keymap_toggle
, c
->vc_keymap_toggle
)) {
381 if ((keymap
&& (!filename_is_valid(keymap
) || !string_is_safe(keymap
))) ||
382 (keymap_toggle
&& (!filename_is_valid(keymap_toggle
) || !string_is_safe(keymap_toggle
))))
383 return sd_bus_error_set_errnof(error
, -EINVAL
, "Received invalid keymap data");
385 r
= bus_verify_polkit_async(
388 "org.freedesktop.locale1.set-keyboard",
397 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
399 if (free_and_strdup(&c
->vc_keymap
, keymap
) < 0 ||
400 free_and_strdup(&c
->vc_keymap_toggle
, keymap_toggle
) < 0)
403 r
= vconsole_write_data(c
);
405 log_error_errno(r
, "Failed to set virtual console keymap: %m");
406 return sd_bus_error_set_errnof(error
, r
, "Failed to set virtual console keymap: %m");
409 log_info("Changed virtual console keymap to '%s' toggle '%s'",
410 strempty(c
->vc_keymap
), strempty(c
->vc_keymap_toggle
));
412 r
= vconsole_reload(sd_bus_message_get_bus(m
));
414 log_error_errno(r
, "Failed to request keymap reload: %m");
416 (void) sd_bus_emit_properties_changed(
417 sd_bus_message_get_bus(m
),
418 "/org/freedesktop/locale1",
419 "org.freedesktop.locale1",
420 "VConsoleKeymap", "VConsoleKeymapToggle", NULL
);
423 r
= vconsole_convert_to_x11_and_emit(c
, sd_bus_message_get_bus(m
));
425 log_error_errno(r
, "Failed to convert keymap data: %m");
429 return sd_bus_reply_method_return(m
, NULL
);
435 static void log_xkb(struct xkb_context
*ctx
, enum xkb_log_level lvl
, const char *format
, va_list args
) {
438 fmt
= strjoina("libxkbcommon: ", format
);
439 #pragma GCC diagnostic push
440 #pragma GCC diagnostic ignored "-Wformat-nonliteral"
441 log_internalv(LOG_DEBUG
, 0, __FILE__
, __LINE__
, __func__
, fmt
, args
);
442 #pragma GCC diagnostic pop
445 #define LOAD_SYMBOL(symbol, dl, name) \
447 (symbol) = (typeof(symbol)) dlvsym((dl), (name), "V_0.5.0"); \
448 (symbol) ? 0 : -EOPNOTSUPP; \
451 static int verify_xkb_rmlvo(const char *model
, const char *layout
, const char *variant
, const char *options
) {
453 /* We dlopen() the library in order to make the dependency soft. The library (and what it pulls in) is huge
454 * after all, hence let's support XKB maps when the library is around, and refuse otherwise. The function
455 * pointers to the shared library are below: */
457 struct xkb_context
* (*symbol_xkb_context_new
)(enum xkb_context_flags flags
) = NULL
;
458 void (*symbol_xkb_context_unref
)(struct xkb_context
*context
) = NULL
;
459 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
;
460 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
;
461 void (*symbol_xkb_keymap_unref
)(struct xkb_keymap
*keymap
) = NULL
;
463 const struct xkb_rule_names rmlvo
= {
469 struct xkb_context
*ctx
= NULL
;
470 struct xkb_keymap
*km
= NULL
;
474 /* Compile keymap from RMLVO information to check out its validity */
476 dl
= dlopen("libxkbcommon.so.0", RTLD_LAZY
);
480 r
= LOAD_SYMBOL(symbol_xkb_context_new
, dl
, "xkb_context_new");
484 r
= LOAD_SYMBOL(symbol_xkb_context_unref
, dl
, "xkb_context_unref");
488 r
= LOAD_SYMBOL(symbol_xkb_context_set_log_fn
, dl
, "xkb_context_set_log_fn");
492 r
= LOAD_SYMBOL(symbol_xkb_keymap_new_from_names
, dl
, "xkb_keymap_new_from_names");
496 r
= LOAD_SYMBOL(symbol_xkb_keymap_unref
, dl
, "xkb_keymap_unref");
500 ctx
= symbol_xkb_context_new(XKB_CONTEXT_NO_ENVIRONMENT_NAMES
);
506 symbol_xkb_context_set_log_fn(ctx
, log_xkb
);
508 km
= symbol_xkb_keymap_new_from_names(ctx
, &rmlvo
, XKB_KEYMAP_COMPILE_NO_FLAGS
);
517 if (symbol_xkb_keymap_unref
&& km
)
518 symbol_xkb_keymap_unref(km
);
520 if (symbol_xkb_context_unref
&& ctx
)
521 symbol_xkb_context_unref(ctx
);
529 static int verify_xkb_rmlvo(const char *model
, const char *layout
, const char *variant
, const char *options
) {
535 static int method_set_x11_keyboard(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
536 Context
*c
= userdata
;
537 const char *layout
, *model
, *variant
, *options
;
538 int convert
, interactive
;
544 r
= sd_bus_message_read(m
, "ssssbb", &layout
, &model
, &variant
, &options
, &convert
, &interactive
);
548 layout
= empty_to_null(layout
);
549 model
= empty_to_null(model
);
550 variant
= empty_to_null(variant
);
551 options
= empty_to_null(options
);
553 if (!streq_ptr(layout
, c
->x11_layout
) ||
554 !streq_ptr(model
, c
->x11_model
) ||
555 !streq_ptr(variant
, c
->x11_variant
) ||
556 !streq_ptr(options
, c
->x11_options
)) {
558 if ((layout
&& !string_is_safe(layout
)) ||
559 (model
&& !string_is_safe(model
)) ||
560 (variant
&& !string_is_safe(variant
)) ||
561 (options
&& !string_is_safe(options
)))
562 return sd_bus_error_set_errnof(error
, -EINVAL
, "Received invalid keyboard data");
564 r
= bus_verify_polkit_async(
567 "org.freedesktop.locale1.set-keyboard",
576 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
578 r
= verify_xkb_rmlvo(model
, layout
, variant
, options
);
580 log_error_errno(r
, "Cannot compile XKB keymap for new x11 keyboard layout ('%s' / '%s' / '%s' / '%s'): %m",
581 strempty(model
), strempty(layout
), strempty(variant
), strempty(options
));
583 if (r
== -EOPNOTSUPP
)
584 return sd_bus_error_setf(error
, SD_BUS_ERROR_NOT_SUPPORTED
, "Local keyboard configuration not supported on this system.");
586 return sd_bus_error_set(error
, SD_BUS_ERROR_INVALID_ARGS
, "Specified keymap cannot be compiled, refusing as invalid.");
589 if (free_and_strdup(&c
->x11_layout
, layout
) < 0 ||
590 free_and_strdup(&c
->x11_model
, model
) < 0 ||
591 free_and_strdup(&c
->x11_variant
, variant
) < 0 ||
592 free_and_strdup(&c
->x11_options
, options
) < 0)
595 r
= x11_write_data(c
);
597 log_error_errno(r
, "Failed to set X11 keyboard layout: %m");
598 return sd_bus_error_set_errnof(error
, r
, "Failed to set X11 keyboard layout: %m");
601 log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
602 strempty(c
->x11_layout
),
603 strempty(c
->x11_model
),
604 strempty(c
->x11_variant
),
605 strempty(c
->x11_options
));
607 (void) sd_bus_emit_properties_changed(
608 sd_bus_message_get_bus(m
),
609 "/org/freedesktop/locale1",
610 "org.freedesktop.locale1",
611 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL
);
614 r
= x11_convert_to_vconsole_and_emit(c
, sd_bus_message_get_bus(m
));
616 log_error_errno(r
, "Failed to convert keymap data: %m");
620 return sd_bus_reply_method_return(m
, NULL
);
623 static const sd_bus_vtable locale_vtable
[] = {
624 SD_BUS_VTABLE_START(0),
625 SD_BUS_PROPERTY("Locale", "as", property_get_locale
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
626 SD_BUS_PROPERTY("X11Layout", "s", NULL
, offsetof(Context
, x11_layout
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
627 SD_BUS_PROPERTY("X11Model", "s", NULL
, offsetof(Context
, x11_model
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
628 SD_BUS_PROPERTY("X11Variant", "s", NULL
, offsetof(Context
, x11_variant
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
629 SD_BUS_PROPERTY("X11Options", "s", NULL
, offsetof(Context
, x11_options
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
630 SD_BUS_PROPERTY("VConsoleKeymap", "s", NULL
, offsetof(Context
, vc_keymap
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
631 SD_BUS_PROPERTY("VConsoleKeymapToggle", "s", NULL
, offsetof(Context
, vc_keymap_toggle
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
632 SD_BUS_METHOD("SetLocale", "asb", NULL
, method_set_locale
, SD_BUS_VTABLE_UNPRIVILEGED
),
633 SD_BUS_METHOD("SetVConsoleKeyboard", "ssbb", NULL
, method_set_vc_keyboard
, SD_BUS_VTABLE_UNPRIVILEGED
),
634 SD_BUS_METHOD("SetX11Keyboard", "ssssbb", NULL
, method_set_x11_keyboard
, SD_BUS_VTABLE_UNPRIVILEGED
),
638 static int connect_bus(Context
*c
, sd_event
*event
, sd_bus
**_bus
) {
639 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
646 r
= sd_bus_default_system(&bus
);
648 return log_error_errno(r
, "Failed to get system bus connection: %m");
650 r
= sd_bus_add_object_vtable(bus
, NULL
, "/org/freedesktop/locale1", "org.freedesktop.locale1", locale_vtable
, c
);
652 return log_error_errno(r
, "Failed to register object: %m");
654 r
= sd_bus_request_name(bus
, "org.freedesktop.locale1", 0);
656 return log_error_errno(r
, "Failed to register name: %m");
658 r
= sd_bus_attach_event(bus
, event
, 0);
660 return log_error_errno(r
, "Failed to attach bus to event loop: %m");
668 int main(int argc
, char *argv
[]) {
669 _cleanup_(context_free
) Context context
= {};
670 _cleanup_(sd_event_unrefp
) sd_event
*event
= NULL
;
671 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
674 log_set_target(LOG_TARGET_AUTO
);
675 log_parse_environment();
682 log_error("This program takes no arguments.");
687 r
= sd_event_default(&event
);
689 log_error_errno(r
, "Failed to allocate event loop: %m");
693 sd_event_set_watchdog(event
, true);
695 r
= connect_bus(&context
, event
, &bus
);
699 r
= context_read_data(&context
);
701 log_error_errno(r
, "Failed to read locale data: %m");
705 r
= bus_event_loop_with_idle(event
, bus
, "org.freedesktop.locale1", DEFAULT_EXIT_USEC
, NULL
, NULL
);
707 log_error_errno(r
, "Failed to run event loop: %m");
710 bus_verify_polkit_async_registry_free(polkit_registry
);
712 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;