1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
8 #include "bus-polkit.h"
9 #include "env-file-label.h"
13 #include "fileio-label.h"
16 #include "keymap-util.h"
17 #include "locale-util.h"
20 #include "nulstr-util.h"
21 #include "string-util.h"
23 #include "tmpfile-util.h"
25 static bool startswith_comma(const char *s
, const char *prefix
) {
26 s
= startswith(s
, prefix
);
30 return IN_SET(*s
, ',', '\0');
33 static const char* systemd_kbd_model_map(void) {
36 s
= getenv("SYSTEMD_KBD_MODEL_MAP");
40 return SYSTEMD_KBD_MODEL_MAP
;
43 static const char* systemd_language_fallback_map(void) {
46 s
= getenv("SYSTEMD_LANGUAGE_FALLBACK_MAP");
50 return SYSTEMD_LANGUAGE_FALLBACK_MAP
;
53 static void context_free_x11(Context
*c
) {
54 c
->x11_layout
= mfree(c
->x11_layout
);
55 c
->x11_options
= mfree(c
->x11_options
);
56 c
->x11_model
= mfree(c
->x11_model
);
57 c
->x11_variant
= mfree(c
->x11_variant
);
60 static void context_free_vconsole(Context
*c
) {
61 c
->vc_keymap
= mfree(c
->vc_keymap
);
62 c
->vc_keymap_toggle
= mfree(c
->vc_keymap_toggle
);
65 static void context_free_locale(Context
*c
) {
68 for (p
= 0; p
< _VARIABLE_LC_MAX
; p
++)
69 c
->locale
[p
] = mfree(c
->locale
[p
]);
72 void context_clear(Context
*c
) {
73 context_free_locale(c
);
75 context_free_vconsole(c
);
77 sd_bus_message_unref(c
->locale_cache
);
78 sd_bus_message_unref(c
->x11_cache
);
79 sd_bus_message_unref(c
->vc_cache
);
81 bus_verify_polkit_async_registry_free(c
->polkit_registry
);
84 void locale_simplify(char *locale
[_VARIABLE_LC_MAX
]) {
87 for (p
= VARIABLE_LANG
+1; p
< _VARIABLE_LC_MAX
; p
++)
88 if (isempty(locale
[p
]) || streq_ptr(locale
[VARIABLE_LANG
], locale
[p
]))
89 locale
[p
] = mfree(locale
[p
]);
92 int locale_read_data(Context
*c
, sd_bus_message
*m
) {
96 /* Do not try to re-read the file within single bus operation. */
98 if (m
== c
->locale_cache
)
101 sd_bus_message_unref(c
->locale_cache
);
102 c
->locale_cache
= sd_bus_message_ref(m
);
105 r
= stat("/etc/locale.conf", &st
);
106 if (r
< 0 && errno
!= ENOENT
)
112 /* If mtime is not changed, then we do not need to re-read the file. */
113 t
= timespec_load(&st
.st_mtim
);
114 if (c
->locale_mtime
!= USEC_INFINITY
&& t
== c
->locale_mtime
)
118 context_free_locale(c
);
120 r
= parse_env_file(NULL
, "/etc/locale.conf",
121 "LANG", &c
->locale
[VARIABLE_LANG
],
122 "LANGUAGE", &c
->locale
[VARIABLE_LANGUAGE
],
123 "LC_CTYPE", &c
->locale
[VARIABLE_LC_CTYPE
],
124 "LC_NUMERIC", &c
->locale
[VARIABLE_LC_NUMERIC
],
125 "LC_TIME", &c
->locale
[VARIABLE_LC_TIME
],
126 "LC_COLLATE", &c
->locale
[VARIABLE_LC_COLLATE
],
127 "LC_MONETARY", &c
->locale
[VARIABLE_LC_MONETARY
],
128 "LC_MESSAGES", &c
->locale
[VARIABLE_LC_MESSAGES
],
129 "LC_PAPER", &c
->locale
[VARIABLE_LC_PAPER
],
130 "LC_NAME", &c
->locale
[VARIABLE_LC_NAME
],
131 "LC_ADDRESS", &c
->locale
[VARIABLE_LC_ADDRESS
],
132 "LC_TELEPHONE", &c
->locale
[VARIABLE_LC_TELEPHONE
],
133 "LC_MEASUREMENT", &c
->locale
[VARIABLE_LC_MEASUREMENT
],
134 "LC_IDENTIFICATION", &c
->locale
[VARIABLE_LC_IDENTIFICATION
]);
140 c
->locale_mtime
= USEC_INFINITY
;
141 context_free_locale(c
);
143 /* Fill in what we got passed from systemd. */
144 for (p
= 0; p
< _VARIABLE_LC_MAX
; p
++) {
147 name
= locale_variable_to_string(p
);
150 r
= free_and_strdup(&c
->locale
[p
], empty_to_null(getenv(name
)));
156 locale_simplify(c
->locale
);
160 int vconsole_read_data(Context
*c
, sd_bus_message
*m
) {
165 /* Do not try to re-read the file within single bus operation. */
167 if (m
== c
->vc_cache
)
170 sd_bus_message_unref(c
->vc_cache
);
171 c
->vc_cache
= sd_bus_message_ref(m
);
174 if (stat("/etc/vconsole.conf", &st
) < 0) {
178 c
->vc_mtime
= USEC_INFINITY
;
179 context_free_vconsole(c
);
183 /* If mtime is not changed, then we do not need to re-read */
184 t
= timespec_load(&st
.st_mtim
);
185 if (c
->vc_mtime
!= USEC_INFINITY
&& t
== c
->vc_mtime
)
189 context_free_vconsole(c
);
191 r
= parse_env_file(NULL
, "/etc/vconsole.conf",
192 "KEYMAP", &c
->vc_keymap
,
193 "KEYMAP_TOGGLE", &c
->vc_keymap_toggle
);
200 int x11_read_data(Context
*c
, sd_bus_message
*m
) {
201 _cleanup_fclose_
FILE *f
= NULL
;
202 bool in_section
= false;
207 /* Do not try to re-read the file within single bus operation. */
209 if (m
== c
->x11_cache
)
212 sd_bus_message_unref(c
->x11_cache
);
213 c
->x11_cache
= sd_bus_message_ref(m
);
216 if (stat("/etc/X11/xorg.conf.d/00-keyboard.conf", &st
) < 0) {
220 c
->x11_mtime
= USEC_INFINITY
;
225 /* If mtime is not changed, then we do not need to re-read */
226 t
= timespec_load(&st
.st_mtim
);
227 if (c
->x11_mtime
!= USEC_INFINITY
&& t
== c
->x11_mtime
)
233 f
= fopen("/etc/X11/xorg.conf.d/00-keyboard.conf", "re");
238 _cleanup_free_
char *line
= NULL
;
241 r
= read_line(f
, LONG_LINE_MAX
, &line
);
248 if (IN_SET(l
[0], 0, '#'))
251 if (in_section
&& first_word(l
, "Option")) {
252 _cleanup_strv_free_
char **a
= NULL
;
254 r
= strv_split_full(&a
, l
, WHITESPACE
, EXTRACT_UNQUOTE
);
258 if (strv_length(a
) == 3) {
261 if (streq(a
[1], "XkbLayout"))
263 else if (streq(a
[1], "XkbModel"))
265 else if (streq(a
[1], "XkbVariant"))
267 else if (streq(a
[1], "XkbOptions"))
271 free_and_replace(*p
, a
[2]);
274 } else if (!in_section
&& first_word(l
, "Section")) {
275 _cleanup_strv_free_
char **a
= NULL
;
277 r
= strv_split_full(&a
, l
, WHITESPACE
, EXTRACT_UNQUOTE
);
281 if (strv_length(a
) == 2 && streq(a
[1], "InputClass"))
284 } else if (in_section
&& first_word(l
, "EndSection"))
291 int locale_write_data(Context
*c
, char ***settings
) {
292 _cleanup_strv_free_
char **l
= NULL
;
296 /* Set values will be returned as strv in *settings on success. */
298 for (p
= 0; p
< _VARIABLE_LC_MAX
; p
++) {
299 _cleanup_free_
char *t
= NULL
;
303 name
= locale_variable_to_string(p
);
306 if (isempty(c
->locale
[p
]))
309 if (asprintf(&t
, "%s=%s", name
, c
->locale
[p
]) < 0)
312 u
= strv_env_set(l
, t
);
316 strv_free_and_replace(l
, u
);
319 if (strv_isempty(l
)) {
320 if (unlink("/etc/locale.conf") < 0)
321 return errno
== ENOENT
? 0 : -errno
;
323 c
->locale_mtime
= USEC_INFINITY
;
327 r
= write_env_file_label("/etc/locale.conf", l
);
331 *settings
= TAKE_PTR(l
);
333 if (stat("/etc/locale.conf", &st
) >= 0)
334 c
->locale_mtime
= timespec_load(&st
.st_mtim
);
339 int vconsole_write_data(Context
*c
) {
340 _cleanup_strv_free_
char **l
= NULL
;
344 r
= load_env_file(NULL
, "/etc/vconsole.conf", &l
);
345 if (r
< 0 && r
!= -ENOENT
)
348 if (isempty(c
->vc_keymap
))
349 l
= strv_env_unset(l
, "KEYMAP");
351 _cleanup_free_
char *s
= NULL
;
354 s
= strjoin("KEYMAP=", c
->vc_keymap
);
358 u
= strv_env_set(l
, s
);
362 strv_free_and_replace(l
, u
);
365 if (isempty(c
->vc_keymap_toggle
))
366 l
= strv_env_unset(l
, "KEYMAP_TOGGLE");
368 _cleanup_free_
char *s
= NULL
;
371 s
= strjoin("KEYMAP_TOGGLE=", c
->vc_keymap_toggle
);
375 u
= strv_env_set(l
, s
);
379 strv_free_and_replace(l
, u
);
382 if (strv_isempty(l
)) {
383 if (unlink("/etc/vconsole.conf") < 0)
384 return errno
== ENOENT
? 0 : -errno
;
386 c
->vc_mtime
= USEC_INFINITY
;
390 r
= write_env_file_label("/etc/vconsole.conf", l
);
394 if (stat("/etc/vconsole.conf", &st
) >= 0)
395 c
->vc_mtime
= timespec_load(&st
.st_mtim
);
400 int x11_write_data(Context
*c
) {
401 _cleanup_fclose_
FILE *f
= NULL
;
402 _cleanup_free_
char *temp_path
= NULL
;
406 if (isempty(c
->x11_layout
) &&
407 isempty(c
->x11_model
) &&
408 isempty(c
->x11_variant
) &&
409 isempty(c
->x11_options
)) {
411 if (unlink("/etc/X11/xorg.conf.d/00-keyboard.conf") < 0)
412 return errno
== ENOENT
? 0 : -errno
;
414 c
->vc_mtime
= USEC_INFINITY
;
418 (void) mkdir_p_label("/etc/X11/xorg.conf.d", 0755);
419 r
= fopen_temporary("/etc/X11/xorg.conf.d/00-keyboard.conf", &f
, &temp_path
);
423 (void) fchmod(fileno(f
), 0644);
425 fputs("# Written by systemd-localed(8), read by systemd-localed and Xorg. It's\n"
426 "# probably wise not to edit this file manually. Use localectl(1) to\n"
427 "# instruct systemd-localed to update it.\n"
428 "Section \"InputClass\"\n"
429 " Identifier \"system-keyboard\"\n"
430 " MatchIsKeyboard \"on\"\n", f
);
432 if (!isempty(c
->x11_layout
))
433 fprintf(f
, " Option \"XkbLayout\" \"%s\"\n", c
->x11_layout
);
435 if (!isempty(c
->x11_model
))
436 fprintf(f
, " Option \"XkbModel\" \"%s\"\n", c
->x11_model
);
438 if (!isempty(c
->x11_variant
))
439 fprintf(f
, " Option \"XkbVariant\" \"%s\"\n", c
->x11_variant
);
441 if (!isempty(c
->x11_options
))
442 fprintf(f
, " Option \"XkbOptions\" \"%s\"\n", c
->x11_options
);
444 fputs("EndSection\n", f
);
446 r
= fflush_sync_and_check(f
);
450 if (rename(temp_path
, "/etc/X11/xorg.conf.d/00-keyboard.conf") < 0) {
455 if (stat("/etc/X11/xorg.conf.d/00-keyboard.conf", &st
) >= 0)
456 c
->x11_mtime
= timespec_load(&st
.st_mtim
);
462 (void) unlink(temp_path
);
467 static int read_next_mapping(const char* filename
,
468 unsigned min_fields
, unsigned max_fields
,
469 FILE *f
, unsigned *n
, char ***a
) {
475 _cleanup_free_
char *line
= NULL
;
480 r
= read_line(f
, LONG_LINE_MAX
, &line
);
489 if (IN_SET(l
[0], 0, '#'))
492 r
= strv_split_full(&b
, l
, WHITESPACE
, EXTRACT_UNQUOTE
);
496 length
= strv_length(b
);
497 if (length
< min_fields
|| length
> max_fields
) {
498 log_error("Invalid line %s:%u, ignoring.", filename
, *n
);
511 int vconsole_convert_to_x11(Context
*c
) {
515 map
= systemd_kbd_model_map();
517 if (isempty(c
->vc_keymap
)) {
519 !isempty(c
->x11_layout
) ||
520 !isempty(c
->x11_model
) ||
521 !isempty(c
->x11_variant
) ||
522 !isempty(c
->x11_options
);
526 _cleanup_fclose_
FILE *f
= NULL
;
529 f
= fopen(map
, "re");
534 _cleanup_strv_free_
char **a
= NULL
;
537 r
= read_next_mapping(map
, 5, UINT_MAX
, f
, &n
, &a
);
543 if (!streq(c
->vc_keymap
, a
[0]))
546 if (!streq_ptr(c
->x11_layout
, empty_or_dash_to_null(a
[1])) ||
547 !streq_ptr(c
->x11_model
, empty_or_dash_to_null(a
[2])) ||
548 !streq_ptr(c
->x11_variant
, empty_or_dash_to_null(a
[3])) ||
549 !streq_ptr(c
->x11_options
, empty_or_dash_to_null(a
[4]))) {
551 if (free_and_strdup(&c
->x11_layout
, empty_or_dash_to_null(a
[1])) < 0 ||
552 free_and_strdup(&c
->x11_model
, empty_or_dash_to_null(a
[2])) < 0 ||
553 free_and_strdup(&c
->x11_variant
, empty_or_dash_to_null(a
[3])) < 0 ||
554 free_and_strdup(&c
->x11_options
, empty_or_dash_to_null(a
[4])) < 0)
565 log_info("Changing X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
566 strempty(c
->x11_layout
),
567 strempty(c
->x11_model
),
568 strempty(c
->x11_variant
),
569 strempty(c
->x11_options
));
570 else if (modified
< 0)
571 log_notice("X11 keyboard layout was not modified: no conversion found for \"%s\".",
574 log_debug("X11 keyboard layout did not need to be modified.");
579 int find_converted_keymap(const char *x11_layout
, const char *x11_variant
, char **new_keymap
) {
581 _cleanup_free_
char *n
;
584 n
= strjoin(x11_layout
, "-", x11_variant
);
586 n
= strdup(x11_layout
);
590 NULSTR_FOREACH(dir
, KBD_KEYMAP_DIRS
) {
591 _cleanup_free_
char *p
= NULL
, *pz
= NULL
;
594 p
= strjoin(dir
, "xkb/", n
, ".map");
595 pz
= strjoin(dir
, "xkb/", n
, ".map.gz");
599 uncompressed
= access(p
, F_OK
) == 0;
600 if (uncompressed
|| access(pz
, F_OK
) == 0) {
601 log_debug("Found converted keymap %s at %s",
602 n
, uncompressed
? p
: pz
);
604 *new_keymap
= TAKE_PTR(n
);
612 int find_legacy_keymap(Context
*c
, char **ret
) {
614 _cleanup_fclose_
FILE *f
= NULL
;
615 _cleanup_free_
char *new_keymap
= NULL
;
617 unsigned best_matching
= 0;
620 assert(!isempty(c
->x11_layout
));
622 map
= systemd_kbd_model_map();
624 f
= fopen(map
, "re");
629 _cleanup_strv_free_
char **a
= NULL
;
630 unsigned matching
= 0;
632 r
= read_next_mapping(map
, 5, UINT_MAX
, f
, &n
, &a
);
638 /* Determine how well matching this entry is */
639 if (streq(c
->x11_layout
, a
[1]))
640 /* If we got an exact match, this is best */
643 /* We have multiple X layouts, look for an
644 * entry that matches our key with everything
645 * but the first layout stripped off. */
646 if (startswith_comma(c
->x11_layout
, a
[1]))
649 _cleanup_free_
char *x
= NULL
;
651 /* If that didn't work, strip off the
652 * other layouts from the entry, too */
653 x
= strndup(a
[1], strcspn(a
[1], ","));
654 if (startswith_comma(c
->x11_layout
, x
))
660 if (isempty(c
->x11_model
) || streq_ptr(c
->x11_model
, a
[2])) {
663 if (streq_ptr(c
->x11_variant
, a
[3])) {
666 if (streq_ptr(c
->x11_options
, a
[4]))
672 /* The best matching entry so far, then let's save that */
673 if (matching
>= MAX(best_matching
, 1u)) {
674 log_debug("Found legacy keymap %s with score %u",
677 if (matching
> best_matching
) {
678 best_matching
= matching
;
680 r
= free_and_strdup(&new_keymap
, a
[0]);
687 if (best_matching
< 10 && c
->x11_layout
) {
688 /* The best match is only the first part of the X11
689 * keymap. Check if we have a converted map which
690 * matches just the first layout.
692 char *l
, *v
= NULL
, *converted
;
694 l
= strndupa(c
->x11_layout
, strcspn(c
->x11_layout
, ","));
696 v
= strndupa(c
->x11_variant
, strcspn(c
->x11_variant
, ","));
697 r
= find_converted_keymap(l
, v
, &converted
);
701 free_and_replace(new_keymap
, converted
);
704 *ret
= TAKE_PTR(new_keymap
);
708 int find_language_fallback(const char *lang
, char **language
) {
710 _cleanup_fclose_
FILE *f
= NULL
;
716 map
= systemd_language_fallback_map();
718 f
= fopen(map
, "re");
723 _cleanup_strv_free_
char **a
= NULL
;
726 r
= read_next_mapping(map
, 2, 2, f
, &n
, &a
);
730 if (streq(lang
, a
[0])) {
731 assert(strv_length(a
) == 2);
732 *language
= TAKE_PTR(a
[1]);
737 assert_not_reached("should not be here");
740 int x11_convert_to_vconsole(Context
*c
) {
741 bool modified
= false;
743 if (isempty(c
->x11_layout
)) {
745 !isempty(c
->vc_keymap
) ||
746 !isempty(c
->vc_keymap_toggle
);
748 context_free_vconsole(c
);
750 _cleanup_free_
char *new_keymap
= NULL
;
753 r
= find_converted_keymap(c
->x11_layout
, c
->x11_variant
, &new_keymap
);
757 r
= find_legacy_keymap(c
, &new_keymap
);
762 /* We search for layout-variant match first, but then we also look
763 * for anything which matches just the layout. So it's accurate to say
764 * that we couldn't find anything which matches the layout. */
765 log_notice("No conversion to virtual console map found for \"%s\".",
768 if (!streq_ptr(c
->vc_keymap
, new_keymap
)) {
769 free_and_replace(c
->vc_keymap
, new_keymap
);
770 c
->vc_keymap_toggle
= mfree(c
->vc_keymap_toggle
);
776 log_info("Changing virtual console keymap to '%s' toggle '%s'",
777 strempty(c
->vc_keymap
), strempty(c
->vc_keymap_toggle
));
779 log_debug("Virtual console keymap was not modified.");