1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 Copyright 2011 Lennart Poettering
4 Copyright 2013 Kay Sievers
15 #include "fileio-label.h"
17 #include "keymap-util.h"
18 #include "locale-util.h"
21 #include "string-util.h"
24 static bool startswith_comma(const char *s
, const char *prefix
) {
25 s
= startswith(s
, prefix
);
29 return IN_SET(*s
, ',', '\0');
32 static const char* strnulldash(const char *s
) {
33 return isempty(s
) || streq(s
, "-") ? NULL
: s
;
36 static const char* systemd_kbd_model_map(void) {
39 s
= getenv("SYSTEMD_KBD_MODEL_MAP");
43 return SYSTEMD_KBD_MODEL_MAP
;
46 static const char* systemd_language_fallback_map(void) {
49 s
= getenv("SYSTEMD_LANGUAGE_FALLBACK_MAP");
53 return SYSTEMD_LANGUAGE_FALLBACK_MAP
;
56 static void context_free_x11(Context
*c
) {
57 c
->x11_layout
= mfree(c
->x11_layout
);
58 c
->x11_options
= mfree(c
->x11_options
);
59 c
->x11_model
= mfree(c
->x11_model
);
60 c
->x11_variant
= mfree(c
->x11_variant
);
63 static void context_free_vconsole(Context
*c
) {
64 c
->vc_keymap
= mfree(c
->vc_keymap
);
65 c
->vc_keymap_toggle
= mfree(c
->vc_keymap_toggle
);
68 static void context_free_locale(Context
*c
) {
71 for (p
= 0; p
< _VARIABLE_LC_MAX
; p
++)
72 c
->locale
[p
] = mfree(c
->locale
[p
]);
75 void context_free(Context
*c
) {
76 context_free_locale(c
);
78 context_free_vconsole(c
);
81 void locale_simplify(char *locale
[_VARIABLE_LC_MAX
]) {
84 for (p
= VARIABLE_LANG
+1; p
< _VARIABLE_LC_MAX
; p
++)
85 if (isempty(locale
[p
]) || streq_ptr(locale
[VARIABLE_LANG
], locale
[p
]))
86 locale
[p
] = mfree(locale
[p
]);
89 int locale_read_data(Context
*c
, sd_bus_message
*m
) {
93 /* Do not try to re-read the file within single bus operation. */
94 if (m
&& m
== c
->locale_cache
)
97 /* To suppress multiple call of stat(), store the message to cache here. */
100 r
= stat("/etc/locale.conf", &st
);
101 if (r
< 0 && errno
!= ENOENT
)
107 /* If mtime is not changed, then we do not need to re-read the file. */
108 t
= timespec_load(&st
.st_mtim
);
109 if (c
->locale_mtime
!= USEC_INFINITY
&& t
== c
->locale_mtime
)
113 context_free_locale(c
);
115 r
= parse_env_file(NULL
, "/etc/locale.conf", NEWLINE
,
116 "LANG", &c
->locale
[VARIABLE_LANG
],
117 "LANGUAGE", &c
->locale
[VARIABLE_LANGUAGE
],
118 "LC_CTYPE", &c
->locale
[VARIABLE_LC_CTYPE
],
119 "LC_NUMERIC", &c
->locale
[VARIABLE_LC_NUMERIC
],
120 "LC_TIME", &c
->locale
[VARIABLE_LC_TIME
],
121 "LC_COLLATE", &c
->locale
[VARIABLE_LC_COLLATE
],
122 "LC_MONETARY", &c
->locale
[VARIABLE_LC_MONETARY
],
123 "LC_MESSAGES", &c
->locale
[VARIABLE_LC_MESSAGES
],
124 "LC_PAPER", &c
->locale
[VARIABLE_LC_PAPER
],
125 "LC_NAME", &c
->locale
[VARIABLE_LC_NAME
],
126 "LC_ADDRESS", &c
->locale
[VARIABLE_LC_ADDRESS
],
127 "LC_TELEPHONE", &c
->locale
[VARIABLE_LC_TELEPHONE
],
128 "LC_MEASUREMENT", &c
->locale
[VARIABLE_LC_MEASUREMENT
],
129 "LC_IDENTIFICATION", &c
->locale
[VARIABLE_LC_IDENTIFICATION
],
136 c
->locale_mtime
= USEC_INFINITY
;
137 context_free_locale(c
);
139 /* Fill in what we got passed from systemd. */
140 for (p
= 0; p
< _VARIABLE_LC_MAX
; p
++) {
143 name
= locale_variable_to_string(p
);
146 r
= free_and_strdup(&c
->locale
[p
], empty_to_null(getenv(name
)));
152 locale_simplify(c
->locale
);
156 int vconsole_read_data(Context
*c
, sd_bus_message
*m
) {
161 /* Do not try to re-read the file within single bus operation. */
162 if (m
&& m
== c
->vc_cache
)
165 /* To suppress multiple call of stat(), store the message to cache here. */
168 if (stat("/etc/vconsole.conf", &st
) < 0) {
172 c
->vc_mtime
= USEC_INFINITY
;
173 context_free_vconsole(c
);
177 /* If mtime is not changed, then we do not need to re-read */
178 t
= timespec_load(&st
.st_mtim
);
179 if (c
->vc_mtime
!= USEC_INFINITY
&& t
== c
->vc_mtime
)
183 context_free_vconsole(c
);
185 r
= parse_env_file(NULL
, "/etc/vconsole.conf", NEWLINE
,
186 "KEYMAP", &c
->vc_keymap
,
187 "KEYMAP_TOGGLE", &c
->vc_keymap_toggle
,
195 int x11_read_data(Context
*c
, sd_bus_message
*m
) {
196 _cleanup_fclose_
FILE *f
= NULL
;
197 bool in_section
= false;
203 /* Do not try to re-read the file within single bus operation. */
204 if (m
&& m
== c
->x11_cache
)
207 /* To suppress multiple call of stat(), store the message to cache here. */
210 if (stat("/etc/X11/xorg.conf.d/00-keyboard.conf", &st
) < 0) {
214 c
->x11_mtime
= USEC_INFINITY
;
219 /* If mtime is not changed, then we do not need to re-read */
220 t
= timespec_load(&st
.st_mtim
);
221 if (c
->x11_mtime
!= USEC_INFINITY
&& t
== c
->x11_mtime
)
227 f
= fopen("/etc/X11/xorg.conf.d/00-keyboard.conf", "re");
231 while (fgets(line
, sizeof(line
), f
)) {
237 if (IN_SET(l
[0], 0, '#'))
240 if (in_section
&& first_word(l
, "Option")) {
241 _cleanup_strv_free_
char **a
= NULL
;
243 r
= strv_split_extract(&a
, l
, WHITESPACE
, EXTRACT_QUOTES
);
247 if (strv_length(a
) == 3) {
250 if (streq(a
[1], "XkbLayout"))
252 else if (streq(a
[1], "XkbModel"))
254 else if (streq(a
[1], "XkbVariant"))
256 else if (streq(a
[1], "XkbOptions"))
260 free_and_replace(*p
, a
[2]);
264 } else if (!in_section
&& first_word(l
, "Section")) {
265 _cleanup_strv_free_
char **a
= NULL
;
267 r
= strv_split_extract(&a
, l
, WHITESPACE
, EXTRACT_QUOTES
);
271 if (strv_length(a
) == 2 && streq(a
[1], "InputClass"))
274 } else if (in_section
&& first_word(l
, "EndSection"))
281 int locale_write_data(Context
*c
, char ***settings
) {
282 _cleanup_strv_free_
char **l
= NULL
;
286 /* Set values will be returned as strv in *settings on success. */
288 for (p
= 0; p
< _VARIABLE_LC_MAX
; p
++) {
289 _cleanup_free_
char *t
= NULL
;
293 name
= locale_variable_to_string(p
);
296 if (isempty(c
->locale
[p
]))
299 if (asprintf(&t
, "%s=%s", name
, c
->locale
[p
]) < 0)
302 u
= strv_env_set(l
, t
);
306 strv_free_and_replace(l
, u
);
309 if (strv_isempty(l
)) {
310 if (unlink("/etc/locale.conf") < 0)
311 return errno
== ENOENT
? 0 : -errno
;
313 c
->locale_mtime
= USEC_INFINITY
;
317 r
= write_env_file_label("/etc/locale.conf", l
);
321 *settings
= TAKE_PTR(l
);
323 if (stat("/etc/locale.conf", &st
) >= 0)
324 c
->locale_mtime
= timespec_load(&st
.st_mtim
);
329 int vconsole_write_data(Context
*c
) {
330 _cleanup_strv_free_
char **l
= NULL
;
334 r
= load_env_file(NULL
, "/etc/vconsole.conf", NULL
, &l
);
335 if (r
< 0 && r
!= -ENOENT
)
338 if (isempty(c
->vc_keymap
))
339 l
= strv_env_unset(l
, "KEYMAP");
341 _cleanup_free_
char *s
= NULL
;
344 s
= strappend("KEYMAP=", c
->vc_keymap
);
348 u
= strv_env_set(l
, s
);
352 strv_free_and_replace(l
, u
);
355 if (isempty(c
->vc_keymap_toggle
))
356 l
= strv_env_unset(l
, "KEYMAP_TOGGLE");
358 _cleanup_free_
char *s
= NULL
;
361 s
= strappend("KEYMAP_TOGGLE=", c
->vc_keymap_toggle
);
365 u
= strv_env_set(l
, s
);
369 strv_free_and_replace(l
, u
);
372 if (strv_isempty(l
)) {
373 if (unlink("/etc/vconsole.conf") < 0)
374 return errno
== ENOENT
? 0 : -errno
;
376 c
->vc_mtime
= USEC_INFINITY
;
380 r
= write_env_file_label("/etc/vconsole.conf", l
);
384 if (stat("/etc/vconsole.conf", &st
) >= 0)
385 c
->vc_mtime
= timespec_load(&st
.st_mtim
);
390 int x11_write_data(Context
*c
) {
391 _cleanup_fclose_
FILE *f
= NULL
;
392 _cleanup_free_
char *temp_path
= NULL
;
396 if (isempty(c
->x11_layout
) &&
397 isempty(c
->x11_model
) &&
398 isempty(c
->x11_variant
) &&
399 isempty(c
->x11_options
)) {
401 if (unlink("/etc/X11/xorg.conf.d/00-keyboard.conf") < 0)
402 return errno
== ENOENT
? 0 : -errno
;
404 c
->vc_mtime
= USEC_INFINITY
;
408 mkdir_p_label("/etc/X11/xorg.conf.d", 0755);
410 r
= fopen_temporary("/etc/X11/xorg.conf.d/00-keyboard.conf", &f
, &temp_path
);
414 (void) __fsetlocking(f
, FSETLOCKING_BYCALLER
);
415 (void) fchmod(fileno(f
), 0644);
417 fputs("# Written by systemd-localed(8), read by systemd-localed and Xorg. It's\n"
418 "# probably wise not to edit this file manually. Use localectl(1) to\n"
419 "# instruct systemd-localed to update it.\n"
420 "Section \"InputClass\"\n"
421 " Identifier \"system-keyboard\"\n"
422 " MatchIsKeyboard \"on\"\n", f
);
424 if (!isempty(c
->x11_layout
))
425 fprintf(f
, " Option \"XkbLayout\" \"%s\"\n", c
->x11_layout
);
427 if (!isempty(c
->x11_model
))
428 fprintf(f
, " Option \"XkbModel\" \"%s\"\n", c
->x11_model
);
430 if (!isempty(c
->x11_variant
))
431 fprintf(f
, " Option \"XkbVariant\" \"%s\"\n", c
->x11_variant
);
433 if (!isempty(c
->x11_options
))
434 fprintf(f
, " Option \"XkbOptions\" \"%s\"\n", c
->x11_options
);
436 fputs("EndSection\n", f
);
438 r
= fflush_sync_and_check(f
);
442 if (rename(temp_path
, "/etc/X11/xorg.conf.d/00-keyboard.conf") < 0) {
447 if (stat("/etc/X11/xorg.conf.d/00-keyboard.conf", &st
) >= 0)
448 c
->x11_mtime
= timespec_load(&st
.st_mtim
);
454 (void) unlink(temp_path
);
459 static int read_next_mapping(const char* filename
,
460 unsigned min_fields
, unsigned max_fields
,
461 FILE *f
, unsigned *n
, char ***a
) {
473 if (!fgets(line
, sizeof(line
), f
)) {
476 return errno
> 0 ? -errno
: -EIO
;
484 if (IN_SET(l
[0], 0, '#'))
487 r
= strv_split_extract(&b
, l
, WHITESPACE
, EXTRACT_QUOTES
);
491 length
= strv_length(b
);
492 if (length
< min_fields
|| length
> max_fields
) {
493 log_error("Invalid line %s:%u, ignoring.", filename
, *n
);
504 int vconsole_convert_to_x11(Context
*c
) {
508 map
= systemd_kbd_model_map();
510 if (isempty(c
->vc_keymap
)) {
512 !isempty(c
->x11_layout
) ||
513 !isempty(c
->x11_model
) ||
514 !isempty(c
->x11_variant
) ||
515 !isempty(c
->x11_options
);
519 _cleanup_fclose_
FILE *f
= NULL
;
522 f
= fopen(map
, "re");
527 _cleanup_strv_free_
char **a
= NULL
;
530 r
= read_next_mapping(map
, 5, UINT_MAX
, f
, &n
, &a
);
536 if (!streq(c
->vc_keymap
, a
[0]))
539 if (!streq_ptr(c
->x11_layout
, strnulldash(a
[1])) ||
540 !streq_ptr(c
->x11_model
, strnulldash(a
[2])) ||
541 !streq_ptr(c
->x11_variant
, strnulldash(a
[3])) ||
542 !streq_ptr(c
->x11_options
, strnulldash(a
[4]))) {
544 if (free_and_strdup(&c
->x11_layout
, strnulldash(a
[1])) < 0 ||
545 free_and_strdup(&c
->x11_model
, strnulldash(a
[2])) < 0 ||
546 free_and_strdup(&c
->x11_variant
, strnulldash(a
[3])) < 0 ||
547 free_and_strdup(&c
->x11_options
, strnulldash(a
[4])) < 0)
558 log_info("Changing X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
559 strempty(c
->x11_layout
),
560 strempty(c
->x11_model
),
561 strempty(c
->x11_variant
),
562 strempty(c
->x11_options
));
563 else if (modified
< 0)
564 log_notice("X11 keyboard layout was not modified: no conversion found for \"%s\".",
567 log_debug("X11 keyboard layout did not need to be modified.");
572 int find_converted_keymap(const char *x11_layout
, const char *x11_variant
, char **new_keymap
) {
574 _cleanup_free_
char *n
;
577 n
= strjoin(x11_layout
, "-", x11_variant
);
579 n
= strdup(x11_layout
);
583 NULSTR_FOREACH(dir
, KBD_KEYMAP_DIRS
) {
584 _cleanup_free_
char *p
= NULL
, *pz
= NULL
;
587 p
= strjoin(dir
, "xkb/", n
, ".map");
588 pz
= strjoin(dir
, "xkb/", n
, ".map.gz");
592 uncompressed
= access(p
, F_OK
) == 0;
593 if (uncompressed
|| access(pz
, F_OK
) == 0) {
594 log_debug("Found converted keymap %s at %s",
595 n
, uncompressed
? p
: pz
);
597 *new_keymap
= TAKE_PTR(n
);
605 int find_legacy_keymap(Context
*c
, char **ret
) {
607 _cleanup_fclose_
FILE *f
= NULL
;
608 _cleanup_free_
char *new_keymap
= NULL
;
610 unsigned best_matching
= 0;
613 assert(!isempty(c
->x11_layout
));
615 map
= systemd_kbd_model_map();
617 f
= fopen(map
, "re");
622 _cleanup_strv_free_
char **a
= NULL
;
623 unsigned matching
= 0;
625 r
= read_next_mapping(map
, 5, UINT_MAX
, f
, &n
, &a
);
631 /* Determine how well matching this entry is */
632 if (streq(c
->x11_layout
, a
[1]))
633 /* If we got an exact match, this is best */
636 /* We have multiple X layouts, look for an
637 * entry that matches our key with everything
638 * but the first layout stripped off. */
639 if (startswith_comma(c
->x11_layout
, a
[1]))
644 /* If that didn't work, strip off the
645 * other layouts from the entry, too */
646 x
= strndupa(a
[1], strcspn(a
[1], ","));
647 if (startswith_comma(c
->x11_layout
, x
))
653 if (isempty(c
->x11_model
) || streq_ptr(c
->x11_model
, a
[2])) {
656 if (streq_ptr(c
->x11_variant
, a
[3])) {
659 if (streq_ptr(c
->x11_options
, a
[4]))
665 /* The best matching entry so far, then let's save that */
666 if (matching
>= MAX(best_matching
, 1u)) {
667 log_debug("Found legacy keymap %s with score %u",
670 if (matching
> best_matching
) {
671 best_matching
= matching
;
673 r
= free_and_strdup(&new_keymap
, a
[0]);
680 if (best_matching
< 10 && c
->x11_layout
) {
681 /* The best match is only the first part of the X11
682 * keymap. Check if we have a converted map which
683 * matches just the first layout.
685 char *l
, *v
= NULL
, *converted
;
687 l
= strndupa(c
->x11_layout
, strcspn(c
->x11_layout
, ","));
689 v
= strndupa(c
->x11_variant
, strcspn(c
->x11_variant
, ","));
690 r
= find_converted_keymap(l
, v
, &converted
);
694 free_and_replace(new_keymap
, converted
);
697 *ret
= TAKE_PTR(new_keymap
);
701 int find_language_fallback(const char *lang
, char **language
) {
703 _cleanup_fclose_
FILE *f
= NULL
;
709 map
= systemd_language_fallback_map();
711 f
= fopen(map
, "re");
716 _cleanup_strv_free_
char **a
= NULL
;
719 r
= read_next_mapping(map
, 2, 2, f
, &n
, &a
);
723 if (streq(lang
, a
[0])) {
724 assert(strv_length(a
) == 2);
725 *language
= TAKE_PTR(a
[1]);
730 assert_not_reached("should not be here");
733 int x11_convert_to_vconsole(Context
*c
) {
734 bool modified
= false;
736 if (isempty(c
->x11_layout
)) {
738 !isempty(c
->vc_keymap
) ||
739 !isempty(c
->vc_keymap_toggle
);
741 context_free_vconsole(c
);
743 _cleanup_free_
char *new_keymap
= NULL
;
746 r
= find_converted_keymap(c
->x11_layout
, c
->x11_variant
, &new_keymap
);
750 r
= find_legacy_keymap(c
, &new_keymap
);
755 /* We search for layout-variant match first, but then we also look
756 * for anything which matches just the layout. So it's accurate to say
757 * that we couldn't find anything which matches the layout. */
758 log_notice("No conversion to virtual console map found for \"%s\".",
761 if (!streq_ptr(c
->vc_keymap
, new_keymap
)) {
762 free_and_replace(c
->vc_keymap
, new_keymap
);
763 c
->vc_keymap_toggle
= mfree(c
->vc_keymap_toggle
);
769 log_info("Changing virtual console keymap to '%s' toggle '%s'",
770 strempty(c
->vc_keymap
), strempty(c
->vc_keymap_toggle
));
772 log_debug("Virtual console keymap was not modified.");