1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2011 Lennart Poettering
7 Copyright 2013 Kay Sievers
9 systemd is free software; you can redistribute it and/or modify it
10 under the terms of the GNU Lesser General Public License as published by
11 the Free Software Foundation; either version 2.1 of the License, or
12 (at your option) any later version.
14 systemd is distributed in the hope that it will be useful, but
15 WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 Lesser General Public License for more details.
19 You should have received a copy of the GNU Lesser General Public License
20 along with systemd; If not, see <http://www.gnu.org/licenses/>.
35 #include "fileio-label.h"
37 #include "bus-error.h"
38 #include "bus-message.h"
39 #include "event-util.h"
40 #include "locale-util.h"
41 #include "selinux-util.h"
44 #include <xkbcommon/xkbcommon.h>
48 /* We don't list LC_ALL here on purpose. People should be
49 * using LANG instead. */
62 LOCALE_LC_MEASUREMENT
,
63 LOCALE_LC_IDENTIFICATION
,
67 static const char * const names
[_LOCALE_MAX
] = {
68 [LOCALE_LANG
] = "LANG",
69 [LOCALE_LANGUAGE
] = "LANGUAGE",
70 [LOCALE_LC_CTYPE
] = "LC_CTYPE",
71 [LOCALE_LC_NUMERIC
] = "LC_NUMERIC",
72 [LOCALE_LC_TIME
] = "LC_TIME",
73 [LOCALE_LC_COLLATE
] = "LC_COLLATE",
74 [LOCALE_LC_MONETARY
] = "LC_MONETARY",
75 [LOCALE_LC_MESSAGES
] = "LC_MESSAGES",
76 [LOCALE_LC_PAPER
] = "LC_PAPER",
77 [LOCALE_LC_NAME
] = "LC_NAME",
78 [LOCALE_LC_ADDRESS
] = "LC_ADDRESS",
79 [LOCALE_LC_TELEPHONE
] = "LC_TELEPHONE",
80 [LOCALE_LC_MEASUREMENT
] = "LC_MEASUREMENT",
81 [LOCALE_LC_IDENTIFICATION
] = "LC_IDENTIFICATION"
84 typedef struct Context
{
85 char *locale
[_LOCALE_MAX
];
93 char *vc_keymap_toggle
;
95 Hashmap
*polkit_registry
;
98 static const char* nonempty(const char *s
) {
99 return isempty(s
) ? NULL
: s
;
102 static void free_and_replace(char **s
, char *v
) {
107 static bool startswith_comma(const char *s
, const char *prefix
) {
110 return s
&& (t
= startswith(s
, prefix
)) && (*t
== ',');
113 static void context_free_x11(Context
*c
) {
114 free_and_replace(&c
->x11_layout
, NULL
);
115 free_and_replace(&c
->x11_model
, NULL
);
116 free_and_replace(&c
->x11_variant
, NULL
);
117 free_and_replace(&c
->x11_options
, NULL
);
120 static void context_free_vconsole(Context
*c
) {
121 free_and_replace(&c
->vc_keymap
, NULL
);
122 free_and_replace(&c
->vc_keymap_toggle
, NULL
);
125 static void context_free_locale(Context
*c
) {
128 for (p
= 0; p
< _LOCALE_MAX
; p
++)
129 free_and_replace(&c
->locale
[p
], NULL
);
132 static void context_free(Context
*c
) {
133 context_free_locale(c
);
135 context_free_vconsole(c
);
137 bus_verify_polkit_async_registry_free(c
->polkit_registry
);
140 static void locale_simplify(Context
*c
) {
143 for (p
= LOCALE_LANG
+1; p
< _LOCALE_MAX
; p
++)
144 if (isempty(c
->locale
[p
]) || streq_ptr(c
->locale
[LOCALE_LANG
], c
->locale
[p
]))
145 free_and_replace(&c
->locale
[p
], NULL
);
148 static int locale_read_data(Context
*c
) {
151 context_free_locale(c
);
153 r
= parse_env_file("/etc/locale.conf", NEWLINE
,
154 "LANG", &c
->locale
[LOCALE_LANG
],
155 "LANGUAGE", &c
->locale
[LOCALE_LANGUAGE
],
156 "LC_CTYPE", &c
->locale
[LOCALE_LC_CTYPE
],
157 "LC_NUMERIC", &c
->locale
[LOCALE_LC_NUMERIC
],
158 "LC_TIME", &c
->locale
[LOCALE_LC_TIME
],
159 "LC_COLLATE", &c
->locale
[LOCALE_LC_COLLATE
],
160 "LC_MONETARY", &c
->locale
[LOCALE_LC_MONETARY
],
161 "LC_MESSAGES", &c
->locale
[LOCALE_LC_MESSAGES
],
162 "LC_PAPER", &c
->locale
[LOCALE_LC_PAPER
],
163 "LC_NAME", &c
->locale
[LOCALE_LC_NAME
],
164 "LC_ADDRESS", &c
->locale
[LOCALE_LC_ADDRESS
],
165 "LC_TELEPHONE", &c
->locale
[LOCALE_LC_TELEPHONE
],
166 "LC_MEASUREMENT", &c
->locale
[LOCALE_LC_MEASUREMENT
],
167 "LC_IDENTIFICATION", &c
->locale
[LOCALE_LC_IDENTIFICATION
],
173 /* Fill in what we got passed from systemd. */
174 for (p
= 0; p
< _LOCALE_MAX
; p
++) {
177 r
= free_and_strdup(&c
->locale
[p
],
178 nonempty(getenv(names
[p
])));
190 static int vconsole_read_data(Context
*c
) {
193 context_free_vconsole(c
);
195 r
= parse_env_file("/etc/vconsole.conf", NEWLINE
,
196 "KEYMAP", &c
->vc_keymap
,
197 "KEYMAP_TOGGLE", &c
->vc_keymap_toggle
,
200 if (r
< 0 && r
!= -ENOENT
)
206 static int x11_read_data(Context
*c
) {
207 _cleanup_fclose_
FILE *f
;
209 bool in_section
= false;
214 f
= fopen("/etc/X11/xorg.conf.d/00-keyboard.conf", "re");
216 return errno
== ENOENT
? 0 : -errno
;
218 while (fgets(line
, sizeof(line
), f
)) {
224 if (l
[0] == 0 || l
[0] == '#')
227 if (in_section
&& first_word(l
, "Option")) {
228 _cleanup_strv_free_
char **a
= NULL
;
230 r
= strv_split_quoted(&a
, l
, 0);
234 if (strv_length(a
) == 3) {
235 if (streq(a
[1], "XkbLayout")) {
236 free_and_replace(&c
->x11_layout
, a
[2]);
238 } else if (streq(a
[1], "XkbModel")) {
239 free_and_replace(&c
->x11_model
, a
[2]);
241 } else if (streq(a
[1], "XkbVariant")) {
242 free_and_replace(&c
->x11_variant
, a
[2]);
244 } else if (streq(a
[1], "XkbOptions")) {
245 free_and_replace(&c
->x11_options
, a
[2]);
250 } else if (!in_section
&& first_word(l
, "Section")) {
251 _cleanup_strv_free_
char **a
= NULL
;
253 r
= strv_split_quoted(&a
, l
, 0);
257 if (strv_length(a
) == 2 && streq(a
[1], "InputClass"))
260 } else if (in_section
&& first_word(l
, "EndSection"))
267 static int context_read_data(Context
*c
) {
270 r
= locale_read_data(c
);
271 q
= vconsole_read_data(c
);
272 p
= x11_read_data(c
);
274 return r
< 0 ? r
: q
< 0 ? q
: p
;
277 static int locale_write_data(Context
*c
, char ***settings
) {
279 _cleanup_strv_free_
char **l
= NULL
;
281 /* Set values will be returned as strv in *settings on success. */
283 r
= load_env_file(NULL
, "/etc/locale.conf", NULL
, &l
);
284 if (r
< 0 && r
!= -ENOENT
)
287 for (p
= 0; p
< _LOCALE_MAX
; p
++) {
288 _cleanup_free_
char *t
= NULL
;
293 if (isempty(c
->locale
[p
])) {
294 l
= strv_env_unset(l
, names
[p
]);
298 if (asprintf(&t
, "%s=%s", names
[p
], c
->locale
[p
]) < 0)
301 u
= strv_env_set(l
, t
);
309 if (strv_isempty(l
)) {
310 if (unlink("/etc/locale.conf") < 0)
311 return errno
== ENOENT
? 0 : -errno
;
316 r
= write_env_file_label("/etc/locale.conf", l
);
325 static int locale_update_system_manager(Context
*c
, sd_bus
*bus
) {
326 _cleanup_free_
char **l_unset
= NULL
;
327 _cleanup_strv_free_
char **l_set
= NULL
;
328 _cleanup_bus_message_unref_ sd_bus_message
*m
= NULL
;
329 sd_bus_error error
= SD_BUS_ERROR_NULL
;
330 unsigned c_set
, c_unset
, p
;
335 l_unset
= new0(char*, _LOCALE_MAX
);
339 l_set
= new0(char*, _LOCALE_MAX
);
343 for (p
= 0, c_set
= 0, c_unset
= 0; p
< _LOCALE_MAX
; p
++) {
346 if (isempty(c
->locale
[p
]))
347 l_unset
[c_set
++] = (char*) names
[p
];
351 if (asprintf(&s
, "%s=%s", names
[p
], c
->locale
[p
]) < 0)
354 l_set
[c_unset
++] = s
;
358 assert(c_set
+ c_unset
== _LOCALE_MAX
);
359 r
= sd_bus_message_new_method_call(bus
, &m
,
360 "org.freedesktop.systemd1",
361 "/org/freedesktop/systemd1",
362 "org.freedesktop.systemd1.Manager",
363 "UnsetAndSetEnvironment");
367 r
= sd_bus_message_append_strv(m
, l_unset
);
371 r
= sd_bus_message_append_strv(m
, l_set
);
375 r
= sd_bus_call(bus
, m
, 0, &error
, NULL
);
377 log_error_errno(r
, "Failed to update the manager environment: %m");
382 static int vconsole_write_data(Context
*c
) {
384 _cleanup_strv_free_
char **l
= NULL
;
386 r
= load_env_file(NULL
, "/etc/vconsole.conf", NULL
, &l
);
387 if (r
< 0 && r
!= -ENOENT
)
390 if (isempty(c
->vc_keymap
))
391 l
= strv_env_unset(l
, "KEYMAP");
393 _cleanup_free_
char *s
= NULL
;
396 s
= strappend("KEYMAP=", c
->vc_keymap
);
400 u
= strv_env_set(l
, s
);
408 if (isempty(c
->vc_keymap_toggle
))
409 l
= strv_env_unset(l
, "KEYMAP_TOGGLE");
411 _cleanup_free_
char *s
= NULL
;
414 s
= strappend("KEYMAP_TOGGLE=", c
->vc_keymap_toggle
);
418 u
= strv_env_set(l
, s
);
426 if (strv_isempty(l
)) {
427 if (unlink("/etc/vconsole.conf") < 0)
428 return errno
== ENOENT
? 0 : -errno
;
433 return write_env_file_label("/etc/vconsole.conf", l
);
436 static int x11_write_data(Context
*c
) {
437 _cleanup_fclose_
FILE *f
= NULL
;
438 _cleanup_free_
char *temp_path
= NULL
;
441 if (isempty(c
->x11_layout
) &&
442 isempty(c
->x11_model
) &&
443 isempty(c
->x11_variant
) &&
444 isempty(c
->x11_options
)) {
446 if (unlink("/etc/X11/xorg.conf.d/00-keyboard.conf") < 0)
447 return errno
== ENOENT
? 0 : -errno
;
452 mkdir_p_label("/etc/X11/xorg.conf.d", 0755);
454 r
= fopen_temporary("/etc/X11/xorg.conf.d/00-keyboard.conf", &f
, &temp_path
);
458 fchmod(fileno(f
), 0644);
460 fputs("# Read and parsed by systemd-localed. It's probably wise not to edit this file\n"
461 "# manually too freely.\n"
462 "Section \"InputClass\"\n"
463 " Identifier \"system-keyboard\"\n"
464 " MatchIsKeyboard \"on\"\n", f
);
466 if (!isempty(c
->x11_layout
))
467 fprintf(f
, " Option \"XkbLayout\" \"%s\"\n", c
->x11_layout
);
469 if (!isempty(c
->x11_model
))
470 fprintf(f
, " Option \"XkbModel\" \"%s\"\n", c
->x11_model
);
472 if (!isempty(c
->x11_variant
))
473 fprintf(f
, " Option \"XkbVariant\" \"%s\"\n", c
->x11_variant
);
475 if (!isempty(c
->x11_options
))
476 fprintf(f
, " Option \"XkbOptions\" \"%s\"\n", c
->x11_options
);
478 fputs("EndSection\n", f
);
481 if (ferror(f
) || rename(temp_path
, "/etc/X11/xorg.conf.d/00-keyboard.conf") < 0) {
483 unlink("/etc/X11/xorg.conf.d/00-keyboard.conf");
490 static int vconsole_reload(sd_bus
*bus
) {
491 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
496 r
= sd_bus_call_method(bus
,
497 "org.freedesktop.systemd1",
498 "/org/freedesktop/systemd1",
499 "org.freedesktop.systemd1.Manager",
503 "ss", "systemd-vconsole-setup.service", "replace");
506 log_error("Failed to issue method call: %s", bus_error_message(&error
, -r
));
510 static const char* strnulldash(const char *s
) {
511 return isempty(s
) || streq(s
, "-") ? NULL
: s
;
514 static int read_next_mapping(const char* filename
,
515 unsigned min_fields
, unsigned max_fields
,
516 FILE *f
, unsigned *n
, char ***a
) {
528 if (!fgets(line
, sizeof(line
), f
)) {
531 return errno
? -errno
: -EIO
;
539 if (l
[0] == 0 || l
[0] == '#')
542 r
= strv_split_quoted(&b
, l
, 0);
546 length
= strv_length(b
);
547 if (length
< min_fields
|| length
> max_fields
) {
548 log_error("Invalid line %s:%u, ignoring.", filename
, *n
);
559 static int vconsole_convert_to_x11(Context
*c
, sd_bus
*bus
) {
560 bool modified
= false;
564 if (isempty(c
->vc_keymap
)) {
567 !isempty(c
->x11_layout
) ||
568 !isempty(c
->x11_model
) ||
569 !isempty(c
->x11_variant
) ||
570 !isempty(c
->x11_options
);
574 _cleanup_fclose_
FILE *f
= NULL
;
577 f
= fopen(SYSTEMD_KBD_MODEL_MAP
, "re");
582 _cleanup_strv_free_
char **a
= NULL
;
585 r
= read_next_mapping(SYSTEMD_KBD_MODEL_MAP
, 5, UINT_MAX
, f
, &n
, &a
);
591 if (!streq(c
->vc_keymap
, a
[0]))
594 if (!streq_ptr(c
->x11_layout
, strnulldash(a
[1])) ||
595 !streq_ptr(c
->x11_model
, strnulldash(a
[2])) ||
596 !streq_ptr(c
->x11_variant
, strnulldash(a
[3])) ||
597 !streq_ptr(c
->x11_options
, strnulldash(a
[4]))) {
599 if (free_and_strdup(&c
->x11_layout
, strnulldash(a
[1])) < 0 ||
600 free_and_strdup(&c
->x11_model
, strnulldash(a
[2])) < 0 ||
601 free_and_strdup(&c
->x11_variant
, strnulldash(a
[3])) < 0 ||
602 free_and_strdup(&c
->x11_options
, strnulldash(a
[4])) < 0)
615 r
= x11_write_data(c
);
617 return log_error_errno(r
, "Failed to set X11 keyboard layout: %m");
619 log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
620 strempty(c
->x11_layout
),
621 strempty(c
->x11_model
),
622 strempty(c
->x11_variant
),
623 strempty(c
->x11_options
));
625 sd_bus_emit_properties_changed(bus
,
626 "/org/freedesktop/locale1",
627 "org.freedesktop.locale1",
628 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL
);
630 log_debug("X11 keyboard layout was not modified.");
635 static int find_converted_keymap(const char *x11_layout
, const char *x11_variant
, char **new_keymap
) {
637 _cleanup_free_
char *n
;
640 n
= strjoin(x11_layout
, "-", x11_variant
, NULL
);
642 n
= strdup(x11_layout
);
646 NULSTR_FOREACH(dir
, KBD_KEYMAP_DIRS
) {
647 _cleanup_free_
char *p
= NULL
, *pz
= NULL
;
650 p
= strjoin(dir
, "xkb/", n
, ".map", NULL
);
651 pz
= strjoin(dir
, "xkb/", n
, ".map.gz", NULL
);
655 uncompressed
= access(p
, F_OK
) == 0;
656 if (uncompressed
|| access(pz
, F_OK
) == 0) {
657 log_debug("Found converted keymap %s at %s",
658 n
, uncompressed
? p
: pz
);
669 static int find_legacy_keymap(Context
*c
, char **new_keymap
) {
670 _cleanup_fclose_
FILE *f
;
672 unsigned best_matching
= 0;
675 f
= fopen(SYSTEMD_KBD_MODEL_MAP
, "re");
680 _cleanup_strv_free_
char **a
= NULL
;
681 unsigned matching
= 0;
683 r
= read_next_mapping(SYSTEMD_KBD_MODEL_MAP
, 5, UINT_MAX
, f
, &n
, &a
);
689 /* Determine how well matching this entry is */
690 if (streq_ptr(c
->x11_layout
, a
[1]))
691 /* If we got an exact match, this is best */
694 /* We have multiple X layouts, look for an
695 * entry that matches our key with everything
696 * but the first layout stripped off. */
697 if (startswith_comma(c
->x11_layout
, a
[1]))
702 /* If that didn't work, strip off the
703 * other layouts from the entry, too */
704 x
= strndupa(a
[1], strcspn(a
[1], ","));
705 if (startswith_comma(c
->x11_layout
, x
))
711 if (isempty(c
->x11_model
) || streq_ptr(c
->x11_model
, a
[2])) {
714 if (streq_ptr(c
->x11_variant
, a
[3])) {
717 if (streq_ptr(c
->x11_options
, a
[4]))
723 /* The best matching entry so far, then let's save that */
724 if (matching
>= MAX(best_matching
, 1u)) {
725 log_debug("Found legacy keymap %s with score %u",
728 if (matching
> best_matching
) {
729 best_matching
= matching
;
731 r
= free_and_strdup(new_keymap
, a
[0]);
738 if (best_matching
< 10 && c
->x11_layout
) {
739 /* The best match is only the first part of the X11
740 * keymap. Check if we have a converted map which
741 * matches just the first layout.
743 char *l
, *v
= NULL
, *converted
;
745 l
= strndupa(c
->x11_layout
, strcspn(c
->x11_layout
, ","));
747 v
= strndupa(c
->x11_variant
, strcspn(c
->x11_variant
, ","));
748 r
= find_converted_keymap(l
, v
, &converted
);
752 free_and_replace(new_keymap
, converted
);
758 static int find_language_fallback(const char *lang
, char **language
) {
759 _cleanup_fclose_
FILE *f
= NULL
;
764 f
= fopen(SYSTEMD_LANGUAGE_FALLBACK_MAP
, "re");
769 _cleanup_strv_free_
char **a
= NULL
;
772 r
= read_next_mapping(SYSTEMD_LANGUAGE_FALLBACK_MAP
, 2, 2, f
, &n
, &a
);
776 if (streq(lang
, a
[0])) {
777 assert(strv_length(a
) == 2);
784 assert_not_reached("should not be here");
787 static int x11_convert_to_vconsole(Context
*c
, sd_bus
*bus
) {
788 bool modified
= false;
793 if (isempty(c
->x11_layout
)) {
796 !isempty(c
->vc_keymap
) ||
797 !isempty(c
->vc_keymap_toggle
);
801 char *new_keymap
= NULL
;
803 r
= find_converted_keymap(c
->x11_layout
, c
->x11_variant
, &new_keymap
);
807 r
= find_legacy_keymap(c
, &new_keymap
);
812 if (!streq_ptr(c
->vc_keymap
, new_keymap
)) {
813 free_and_replace(&c
->vc_keymap
, new_keymap
);
814 free_and_replace(&c
->vc_keymap_toggle
, NULL
);
821 r
= vconsole_write_data(c
);
823 log_error_errno(r
, "Failed to set virtual console keymap: %m");
825 log_info("Changed virtual console keymap to '%s' toggle '%s'",
826 strempty(c
->vc_keymap
), strempty(c
->vc_keymap_toggle
));
828 sd_bus_emit_properties_changed(bus
,
829 "/org/freedesktop/locale1",
830 "org.freedesktop.locale1",
831 "VConsoleKeymap", "VConsoleKeymapToggle", NULL
);
833 return vconsole_reload(bus
);
835 log_debug("Virtual console keymap was not modified.");
840 static int property_get_locale(
843 const char *interface
,
844 const char *property
,
845 sd_bus_message
*reply
,
847 sd_bus_error
*error
) {
849 Context
*c
= userdata
;
850 _cleanup_strv_free_
char **l
= NULL
;
853 l
= new0(char*, _LOCALE_MAX
+1);
857 for (p
= 0, q
= 0; p
< _LOCALE_MAX
; p
++) {
860 if (isempty(c
->locale
[p
]))
863 if (asprintf(&t
, "%s=%s", names
[p
], c
->locale
[p
]) < 0)
869 return sd_bus_message_append_strv(reply
, l
);
872 static int method_set_locale(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
873 Context
*c
= userdata
;
874 _cleanup_strv_free_
char **l
= NULL
;
876 const char *lang
= NULL
;
878 bool modified
= false;
879 bool have
[_LOCALE_MAX
] = {};
886 r
= bus_message_read_strv_extend(m
, &l
);
890 r
= sd_bus_message_read_basic(m
, 'b', &interactive
);
894 /* Check whether a variable changed and if it is valid */
898 for (p
= 0; p
< _LOCALE_MAX
; p
++) {
901 k
= strlen(names
[p
]);
902 if (startswith(*i
, names
[p
]) &&
904 locale_is_valid((*i
) + k
+ 1)) {
908 if (p
== LOCALE_LANG
)
911 if (!streq_ptr(*i
+ k
+ 1, c
->locale
[p
]))
919 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid Locale data.");
922 /* If LANG was specified, but not LANGUAGE, check if we should
923 * set it based on the language fallback table. */
924 if (have
[LOCALE_LANG
] && !have
[LOCALE_LANGUAGE
]) {
925 _cleanup_free_
char *language
= NULL
;
929 (void) find_language_fallback(lang
, &language
);
931 log_debug("Converted LANG=%s to LANGUAGE=%s", lang
, language
);
932 if (!streq_ptr(language
, c
->locale
[LOCALE_LANGUAGE
])) {
933 r
= strv_extendf(&l
, "LANGUAGE=%s", language
);
937 have
[LOCALE_LANGUAGE
] = true;
943 /* Check whether a variable is unset */
945 for (p
= 0; p
< _LOCALE_MAX
; p
++)
946 if (!isempty(c
->locale
[p
]) && !have
[p
]) {
952 _cleanup_strv_free_
char **settings
= NULL
;
954 r
= bus_verify_polkit_async(
957 "org.freedesktop.locale1.set-locale",
965 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
968 for (p
= 0; p
< _LOCALE_MAX
; p
++) {
971 k
= strlen(names
[p
]);
972 if (startswith(*i
, names
[p
]) && (*i
)[k
] == '=') {
973 r
= free_and_strdup(&c
->locale
[p
], *i
+ k
+ 1);
980 for (p
= 0; p
< _LOCALE_MAX
; p
++) {
984 free_and_replace(&c
->locale
[p
], NULL
);
989 r
= locale_write_data(c
, &settings
);
991 log_error_errno(r
, "Failed to set locale: %m");
992 return sd_bus_error_set_errnof(error
, r
, "Failed to set locale: %s", strerror(-r
));
995 locale_update_system_manager(c
, sd_bus_message_get_bus(m
));
998 _cleanup_free_
char *line
;
1000 line
= strv_join(settings
, ", ");
1001 log_info("Changed locale to %s.", strnull(line
));
1003 log_info("Changed locale to unset.");
1005 (void) sd_bus_emit_properties_changed(
1006 sd_bus_message_get_bus(m
),
1007 "/org/freedesktop/locale1",
1008 "org.freedesktop.locale1",
1011 log_debug("Locale settings were not modified.");
1014 return sd_bus_reply_method_return(m
, NULL
);
1017 static int method_set_vc_keyboard(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
1018 Context
*c
= userdata
;
1019 const char *keymap
, *keymap_toggle
;
1020 int convert
, interactive
;
1026 r
= sd_bus_message_read(m
, "ssbb", &keymap
, &keymap_toggle
, &convert
, &interactive
);
1030 if (isempty(keymap
))
1033 if (isempty(keymap_toggle
))
1034 keymap_toggle
= NULL
;
1036 if (!streq_ptr(keymap
, c
->vc_keymap
) ||
1037 !streq_ptr(keymap_toggle
, c
->vc_keymap_toggle
)) {
1039 if ((keymap
&& (!filename_is_valid(keymap
) || !string_is_safe(keymap
))) ||
1040 (keymap_toggle
&& (!filename_is_valid(keymap_toggle
) || !string_is_safe(keymap_toggle
))))
1041 return sd_bus_error_set_errnof(error
, -EINVAL
, "Received invalid keymap data");
1043 r
= bus_verify_polkit_async(
1046 "org.freedesktop.locale1.set-keyboard",
1049 &c
->polkit_registry
,
1054 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
1056 if (free_and_strdup(&c
->vc_keymap
, keymap
) < 0 ||
1057 free_and_strdup(&c
->vc_keymap_toggle
, keymap_toggle
) < 0)
1060 r
= vconsole_write_data(c
);
1062 log_error_errno(r
, "Failed to set virtual console keymap: %m");
1063 return sd_bus_error_set_errnof(error
, r
, "Failed to set virtual console keymap: %s", strerror(-r
));
1066 log_info("Changed virtual console keymap to '%s' toggle '%s'",
1067 strempty(c
->vc_keymap
), strempty(c
->vc_keymap_toggle
));
1069 r
= vconsole_reload(sd_bus_message_get_bus(m
));
1071 log_error_errno(r
, "Failed to request keymap reload: %m");
1073 (void) sd_bus_emit_properties_changed(
1074 sd_bus_message_get_bus(m
),
1075 "/org/freedesktop/locale1",
1076 "org.freedesktop.locale1",
1077 "VConsoleKeymap", "VConsoleKeymapToggle", NULL
);
1080 r
= vconsole_convert_to_x11(c
, sd_bus_message_get_bus(m
));
1082 log_error_errno(r
, "Failed to convert keymap data: %m");
1086 return sd_bus_reply_method_return(m
, NULL
);
1089 #ifdef HAVE_XKBCOMMON
1090 static void log_xkb(struct xkb_context
*ctx
, enum xkb_log_level lvl
, const char *format
, va_list args
) {
1093 fmt
= strjoina("libxkbcommon: ", format
);
1094 log_internalv(LOG_DEBUG
, 0, __FILE__
, __LINE__
, __func__
, fmt
, args
);
1097 static int verify_xkb_rmlvo(const char *model
, const char *layout
, const char *variant
, const char *options
) {
1098 const struct xkb_rule_names rmlvo
= {
1104 struct xkb_context
*ctx
= NULL
;
1105 struct xkb_keymap
*km
= NULL
;
1108 /* compile keymap from RMLVO information to check out its validity */
1110 ctx
= xkb_context_new(XKB_CONTEXT_NO_ENVIRONMENT_NAMES
);
1116 xkb_context_set_log_fn(ctx
, log_xkb
);
1118 km
= xkb_keymap_new_from_names(ctx
, &rmlvo
, XKB_KEYMAP_COMPILE_NO_FLAGS
);
1127 xkb_keymap_unref(km
);
1128 xkb_context_unref(ctx
);
1132 static int verify_xkb_rmlvo(const char *model
, const char *layout
, const char *variant
, const char *options
) {
1137 static int method_set_x11_keyboard(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
1138 Context
*c
= userdata
;
1139 const char *layout
, *model
, *variant
, *options
;
1140 int convert
, interactive
;
1146 r
= sd_bus_message_read(m
, "ssssbb", &layout
, &model
, &variant
, &options
, &convert
, &interactive
);
1150 if (isempty(layout
))
1156 if (isempty(variant
))
1159 if (isempty(options
))
1162 if (!streq_ptr(layout
, c
->x11_layout
) ||
1163 !streq_ptr(model
, c
->x11_model
) ||
1164 !streq_ptr(variant
, c
->x11_variant
) ||
1165 !streq_ptr(options
, c
->x11_options
)) {
1167 if ((layout
&& !string_is_safe(layout
)) ||
1168 (model
&& !string_is_safe(model
)) ||
1169 (variant
&& !string_is_safe(variant
)) ||
1170 (options
&& !string_is_safe(options
)))
1171 return sd_bus_error_set_errnof(error
, -EINVAL
, "Received invalid keyboard data");
1173 r
= bus_verify_polkit_async(
1176 "org.freedesktop.locale1.set-keyboard",
1179 &c
->polkit_registry
,
1184 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
1186 r
= verify_xkb_rmlvo(model
, layout
, variant
, options
);
1188 log_error_errno(r
, "Cannot compile XKB keymap for new x11 keyboard layout ('%s' / '%s' / '%s' / '%s'): %m",
1189 strempty(model
), strempty(layout
), strempty(variant
), strempty(options
));
1190 return sd_bus_error_set(error
, SD_BUS_ERROR_INVALID_ARGS
, "Cannot compile XKB keymap, refusing");
1193 if (free_and_strdup(&c
->x11_layout
, layout
) < 0 ||
1194 free_and_strdup(&c
->x11_model
, model
) < 0 ||
1195 free_and_strdup(&c
->x11_variant
, variant
) < 0 ||
1196 free_and_strdup(&c
->x11_options
, options
) < 0)
1199 r
= x11_write_data(c
);
1201 log_error_errno(r
, "Failed to set X11 keyboard layout: %m");
1202 return sd_bus_error_set_errnof(error
, r
, "Failed to set X11 keyboard layout: %s", strerror(-r
));
1205 log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
1206 strempty(c
->x11_layout
),
1207 strempty(c
->x11_model
),
1208 strempty(c
->x11_variant
),
1209 strempty(c
->x11_options
));
1211 (void) sd_bus_emit_properties_changed(
1212 sd_bus_message_get_bus(m
),
1213 "/org/freedesktop/locale1",
1214 "org.freedesktop.locale1",
1215 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL
);
1218 r
= x11_convert_to_vconsole(c
, sd_bus_message_get_bus(m
));
1220 log_error_errno(r
, "Failed to convert keymap data: %m");
1224 return sd_bus_reply_method_return(m
, NULL
);
1227 static const sd_bus_vtable locale_vtable
[] = {
1228 SD_BUS_VTABLE_START(0),
1229 SD_BUS_PROPERTY("Locale", "as", property_get_locale
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1230 SD_BUS_PROPERTY("X11Layout", "s", NULL
, offsetof(Context
, x11_layout
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1231 SD_BUS_PROPERTY("X11Model", "s", NULL
, offsetof(Context
, x11_model
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1232 SD_BUS_PROPERTY("X11Variant", "s", NULL
, offsetof(Context
, x11_variant
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1233 SD_BUS_PROPERTY("X11Options", "s", NULL
, offsetof(Context
, x11_options
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1234 SD_BUS_PROPERTY("VConsoleKeymap", "s", NULL
, offsetof(Context
, vc_keymap
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1235 SD_BUS_PROPERTY("VConsoleKeymapToggle", "s", NULL
, offsetof(Context
, vc_keymap_toggle
), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
1236 SD_BUS_METHOD("SetLocale", "asb", NULL
, method_set_locale
, SD_BUS_VTABLE_UNPRIVILEGED
),
1237 SD_BUS_METHOD("SetVConsoleKeyboard", "ssbb", NULL
, method_set_vc_keyboard
, SD_BUS_VTABLE_UNPRIVILEGED
),
1238 SD_BUS_METHOD("SetX11Keyboard", "ssssbb", NULL
, method_set_x11_keyboard
, SD_BUS_VTABLE_UNPRIVILEGED
),
1242 static int connect_bus(Context
*c
, sd_event
*event
, sd_bus
**_bus
) {
1243 _cleanup_bus_close_unref_ sd_bus
*bus
= NULL
;
1250 r
= sd_bus_default_system(&bus
);
1252 return log_error_errno(r
, "Failed to get system bus connection: %m");
1254 r
= sd_bus_add_object_vtable(bus
, NULL
, "/org/freedesktop/locale1", "org.freedesktop.locale1", locale_vtable
, c
);
1256 return log_error_errno(r
, "Failed to register object: %m");
1258 r
= sd_bus_request_name(bus
, "org.freedesktop.locale1", 0);
1260 return log_error_errno(r
, "Failed to register name: %m");
1262 r
= sd_bus_attach_event(bus
, event
, 0);
1264 return log_error_errno(r
, "Failed to attach bus to event loop: %m");
1272 int main(int argc
, char *argv
[]) {
1273 _cleanup_(context_free
) Context context
= {};
1274 _cleanup_event_unref_ sd_event
*event
= NULL
;
1275 _cleanup_bus_close_unref_ sd_bus
*bus
= NULL
;
1278 log_set_target(LOG_TARGET_AUTO
);
1279 log_parse_environment();
1283 mac_selinux_init("/etc");
1286 log_error("This program takes no arguments.");
1291 r
= sd_event_default(&event
);
1293 log_error_errno(r
, "Failed to allocate event loop: %m");
1297 sd_event_set_watchdog(event
, true);
1299 r
= connect_bus(&context
, event
, &bus
);
1303 r
= context_read_data(&context
);
1305 log_error_errno(r
, "Failed to read locale data: %m");
1309 r
= bus_event_loop_with_idle(event
, bus
, "org.freedesktop.locale1", DEFAULT_EXIT_USEC
, NULL
, NULL
);
1311 log_error_errno(r
, "Failed to run event loop: %m");
1316 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;