1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright 2011 Lennart Poettering
6 Copyright 2013 Kay Sievers
10 #include <stdio_ext.h>
17 #include "fileio-label.h"
19 #include "keymap-util.h"
20 #include "locale-util.h"
23 #include "string-util.h"
26 static bool startswith_comma(const char *s
, const char *prefix
) {
27 s
= startswith(s
, prefix
);
31 return IN_SET(*s
, ',', '\0');
34 static const char* strnulldash(const char *s
) {
35 return isempty(s
) || streq(s
, "-") ? NULL
: s
;
38 static const char* systemd_kbd_model_map(void) {
41 s
= getenv("SYSTEMD_KBD_MODEL_MAP");
45 return SYSTEMD_KBD_MODEL_MAP
;
48 static const char* systemd_language_fallback_map(void) {
51 s
= getenv("SYSTEMD_LANGUAGE_FALLBACK_MAP");
55 return SYSTEMD_LANGUAGE_FALLBACK_MAP
;
58 static void context_free_x11(Context
*c
) {
59 c
->x11_layout
= mfree(c
->x11_layout
);
60 c
->x11_options
= mfree(c
->x11_options
);
61 c
->x11_model
= mfree(c
->x11_model
);
62 c
->x11_variant
= mfree(c
->x11_variant
);
65 static void context_free_vconsole(Context
*c
) {
66 c
->vc_keymap
= mfree(c
->vc_keymap
);
67 c
->vc_keymap_toggle
= mfree(c
->vc_keymap_toggle
);
70 static void context_free_locale(Context
*c
) {
73 for (p
= 0; p
< _VARIABLE_LC_MAX
; p
++)
74 c
->locale
[p
] = mfree(c
->locale
[p
]);
77 void context_free(Context
*c
) {
78 context_free_locale(c
);
80 context_free_vconsole(c
);
83 void locale_simplify(Context
*c
) {
86 for (p
= VARIABLE_LANG
+1; p
< _VARIABLE_LC_MAX
; p
++)
87 if (isempty(c
->locale
[p
]) || streq_ptr(c
->locale
[VARIABLE_LANG
], c
->locale
[p
]))
88 c
->locale
[p
] = mfree(c
->locale
[p
]);
91 static int locale_read_data(Context
*c
) {
94 context_free_locale(c
);
96 r
= parse_env_file("/etc/locale.conf", NEWLINE
,
97 "LANG", &c
->locale
[VARIABLE_LANG
],
98 "LANGUAGE", &c
->locale
[VARIABLE_LANGUAGE
],
99 "LC_CTYPE", &c
->locale
[VARIABLE_LC_CTYPE
],
100 "LC_NUMERIC", &c
->locale
[VARIABLE_LC_NUMERIC
],
101 "LC_TIME", &c
->locale
[VARIABLE_LC_TIME
],
102 "LC_COLLATE", &c
->locale
[VARIABLE_LC_COLLATE
],
103 "LC_MONETARY", &c
->locale
[VARIABLE_LC_MONETARY
],
104 "LC_MESSAGES", &c
->locale
[VARIABLE_LC_MESSAGES
],
105 "LC_PAPER", &c
->locale
[VARIABLE_LC_PAPER
],
106 "LC_NAME", &c
->locale
[VARIABLE_LC_NAME
],
107 "LC_ADDRESS", &c
->locale
[VARIABLE_LC_ADDRESS
],
108 "LC_TELEPHONE", &c
->locale
[VARIABLE_LC_TELEPHONE
],
109 "LC_MEASUREMENT", &c
->locale
[VARIABLE_LC_MEASUREMENT
],
110 "LC_IDENTIFICATION", &c
->locale
[VARIABLE_LC_IDENTIFICATION
],
116 /* Fill in what we got passed from systemd. */
117 for (p
= 0; p
< _VARIABLE_LC_MAX
; p
++) {
120 name
= locale_variable_to_string(p
);
123 r
= free_and_strdup(&c
->locale
[p
], empty_to_null(getenv(name
)));
135 static int vconsole_read_data(Context
*c
) {
138 context_free_vconsole(c
);
140 r
= parse_env_file("/etc/vconsole.conf", NEWLINE
,
141 "KEYMAP", &c
->vc_keymap
,
142 "KEYMAP_TOGGLE", &c
->vc_keymap_toggle
,
145 if (r
< 0 && r
!= -ENOENT
)
151 static int x11_read_data(Context
*c
) {
152 _cleanup_fclose_
FILE *f
;
154 bool in_section
= false;
159 f
= fopen("/etc/X11/xorg.conf.d/00-keyboard.conf", "re");
161 return errno
== ENOENT
? 0 : -errno
;
163 while (fgets(line
, sizeof(line
), f
)) {
169 if (IN_SET(l
[0], 0, '#'))
172 if (in_section
&& first_word(l
, "Option")) {
173 _cleanup_strv_free_
char **a
= NULL
;
175 r
= strv_split_extract(&a
, l
, WHITESPACE
, EXTRACT_QUOTES
);
179 if (strv_length(a
) == 3) {
182 if (streq(a
[1], "XkbLayout"))
184 else if (streq(a
[1], "XkbModel"))
186 else if (streq(a
[1], "XkbVariant"))
188 else if (streq(a
[1], "XkbOptions"))
192 free_and_replace(*p
, a
[2]);
196 } else if (!in_section
&& first_word(l
, "Section")) {
197 _cleanup_strv_free_
char **a
= NULL
;
199 r
= strv_split_extract(&a
, l
, WHITESPACE
, EXTRACT_QUOTES
);
203 if (strv_length(a
) == 2 && streq(a
[1], "InputClass"))
206 } else if (in_section
&& first_word(l
, "EndSection"))
213 int context_read_data(Context
*c
) {
216 r
= locale_read_data(c
);
217 q
= vconsole_read_data(c
);
218 p
= x11_read_data(c
);
220 return r
< 0 ? r
: q
< 0 ? q
: p
;
223 int locale_write_data(Context
*c
, char ***settings
) {
225 _cleanup_strv_free_
char **l
= NULL
;
227 /* Set values will be returned as strv in *settings on success. */
229 r
= load_env_file(NULL
, "/etc/locale.conf", NULL
, &l
);
230 if (r
< 0 && r
!= -ENOENT
)
233 for (p
= 0; p
< _VARIABLE_LC_MAX
; p
++) {
234 _cleanup_free_
char *t
= NULL
;
238 name
= locale_variable_to_string(p
);
241 if (isempty(c
->locale
[p
])) {
242 l
= strv_env_unset(l
, name
);
246 if (asprintf(&t
, "%s=%s", name
, c
->locale
[p
]) < 0)
249 u
= strv_env_set(l
, t
);
253 strv_free_and_replace(l
, u
);
256 if (strv_isempty(l
)) {
257 if (unlink("/etc/locale.conf") < 0)
258 return errno
== ENOENT
? 0 : -errno
;
263 r
= write_env_file_label("/etc/locale.conf", l
);
267 *settings
= TAKE_PTR(l
);
271 int vconsole_write_data(Context
*c
) {
273 _cleanup_strv_free_
char **l
= NULL
;
275 r
= load_env_file(NULL
, "/etc/vconsole.conf", NULL
, &l
);
276 if (r
< 0 && r
!= -ENOENT
)
279 if (isempty(c
->vc_keymap
))
280 l
= strv_env_unset(l
, "KEYMAP");
282 _cleanup_free_
char *s
= NULL
;
285 s
= strappend("KEYMAP=", c
->vc_keymap
);
289 u
= strv_env_set(l
, s
);
293 strv_free_and_replace(l
, u
);
296 if (isempty(c
->vc_keymap_toggle
))
297 l
= strv_env_unset(l
, "KEYMAP_TOGGLE");
299 _cleanup_free_
char *s
= NULL
;
302 s
= strappend("KEYMAP_TOGGLE=", c
->vc_keymap_toggle
);
306 u
= strv_env_set(l
, s
);
310 strv_free_and_replace(l
, u
);
313 if (strv_isempty(l
)) {
314 if (unlink("/etc/vconsole.conf") < 0)
315 return errno
== ENOENT
? 0 : -errno
;
320 return write_env_file_label("/etc/vconsole.conf", l
);
323 int x11_write_data(Context
*c
) {
324 _cleanup_fclose_
FILE *f
= NULL
;
325 _cleanup_free_
char *temp_path
= NULL
;
328 if (isempty(c
->x11_layout
) &&
329 isempty(c
->x11_model
) &&
330 isempty(c
->x11_variant
) &&
331 isempty(c
->x11_options
)) {
333 if (unlink("/etc/X11/xorg.conf.d/00-keyboard.conf") < 0)
334 return errno
== ENOENT
? 0 : -errno
;
339 mkdir_p_label("/etc/X11/xorg.conf.d", 0755);
341 r
= fopen_temporary("/etc/X11/xorg.conf.d/00-keyboard.conf", &f
, &temp_path
);
345 (void) __fsetlocking(f
, FSETLOCKING_BYCALLER
);
346 (void) fchmod(fileno(f
), 0644);
348 fputs("# Written by systemd-localed(8), read by systemd-localed and Xorg. It's\n"
349 "# probably wise not to edit this file manually. Use localectl(1) to\n"
350 "# instruct systemd-localed to update it.\n"
351 "Section \"InputClass\"\n"
352 " Identifier \"system-keyboard\"\n"
353 " MatchIsKeyboard \"on\"\n", f
);
355 if (!isempty(c
->x11_layout
))
356 fprintf(f
, " Option \"XkbLayout\" \"%s\"\n", c
->x11_layout
);
358 if (!isempty(c
->x11_model
))
359 fprintf(f
, " Option \"XkbModel\" \"%s\"\n", c
->x11_model
);
361 if (!isempty(c
->x11_variant
))
362 fprintf(f
, " Option \"XkbVariant\" \"%s\"\n", c
->x11_variant
);
364 if (!isempty(c
->x11_options
))
365 fprintf(f
, " Option \"XkbOptions\" \"%s\"\n", c
->x11_options
);
367 fputs("EndSection\n", f
);
369 r
= fflush_sync_and_check(f
);
373 if (rename(temp_path
, "/etc/X11/xorg.conf.d/00-keyboard.conf") < 0) {
382 (void) unlink(temp_path
);
387 static int read_next_mapping(const char* filename
,
388 unsigned min_fields
, unsigned max_fields
,
389 FILE *f
, unsigned *n
, char ***a
) {
401 if (!fgets(line
, sizeof(line
), f
)) {
404 return errno
> 0 ? -errno
: -EIO
;
412 if (IN_SET(l
[0], 0, '#'))
415 r
= strv_split_extract(&b
, l
, WHITESPACE
, EXTRACT_QUOTES
);
419 length
= strv_length(b
);
420 if (length
< min_fields
|| length
> max_fields
) {
421 log_error("Invalid line %s:%u, ignoring.", filename
, *n
);
432 int vconsole_convert_to_x11(Context
*c
) {
436 map
= systemd_kbd_model_map();
438 if (isempty(c
->vc_keymap
)) {
440 !isempty(c
->x11_layout
) ||
441 !isempty(c
->x11_model
) ||
442 !isempty(c
->x11_variant
) ||
443 !isempty(c
->x11_options
);
447 _cleanup_fclose_
FILE *f
= NULL
;
450 f
= fopen(map
, "re");
455 _cleanup_strv_free_
char **a
= NULL
;
458 r
= read_next_mapping(map
, 5, UINT_MAX
, f
, &n
, &a
);
464 if (!streq(c
->vc_keymap
, a
[0]))
467 if (!streq_ptr(c
->x11_layout
, strnulldash(a
[1])) ||
468 !streq_ptr(c
->x11_model
, strnulldash(a
[2])) ||
469 !streq_ptr(c
->x11_variant
, strnulldash(a
[3])) ||
470 !streq_ptr(c
->x11_options
, strnulldash(a
[4]))) {
472 if (free_and_strdup(&c
->x11_layout
, strnulldash(a
[1])) < 0 ||
473 free_and_strdup(&c
->x11_model
, strnulldash(a
[2])) < 0 ||
474 free_and_strdup(&c
->x11_variant
, strnulldash(a
[3])) < 0 ||
475 free_and_strdup(&c
->x11_options
, strnulldash(a
[4])) < 0)
486 log_info("Changing X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
487 strempty(c
->x11_layout
),
488 strempty(c
->x11_model
),
489 strempty(c
->x11_variant
),
490 strempty(c
->x11_options
));
491 else if (modified
< 0)
492 log_notice("X11 keyboard layout was not modified: no conversion found for \"%s\".",
495 log_debug("X11 keyboard layout did not need to be modified.");
500 int find_converted_keymap(const char *x11_layout
, const char *x11_variant
, char **new_keymap
) {
502 _cleanup_free_
char *n
;
505 n
= strjoin(x11_layout
, "-", x11_variant
);
507 n
= strdup(x11_layout
);
511 NULSTR_FOREACH(dir
, KBD_KEYMAP_DIRS
) {
512 _cleanup_free_
char *p
= NULL
, *pz
= NULL
;
515 p
= strjoin(dir
, "xkb/", n
, ".map");
516 pz
= strjoin(dir
, "xkb/", n
, ".map.gz");
520 uncompressed
= access(p
, F_OK
) == 0;
521 if (uncompressed
|| access(pz
, F_OK
) == 0) {
522 log_debug("Found converted keymap %s at %s",
523 n
, uncompressed
? p
: pz
);
525 *new_keymap
= TAKE_PTR(n
);
533 int find_legacy_keymap(Context
*c
, char **ret
) {
535 _cleanup_fclose_
FILE *f
= NULL
;
536 _cleanup_free_
char *new_keymap
= NULL
;
538 unsigned best_matching
= 0;
541 assert(!isempty(c
->x11_layout
));
543 map
= systemd_kbd_model_map();
545 f
= fopen(map
, "re");
550 _cleanup_strv_free_
char **a
= NULL
;
551 unsigned matching
= 0;
553 r
= read_next_mapping(map
, 5, UINT_MAX
, f
, &n
, &a
);
559 /* Determine how well matching this entry is */
560 if (streq(c
->x11_layout
, a
[1]))
561 /* If we got an exact match, this is best */
564 /* We have multiple X layouts, look for an
565 * entry that matches our key with everything
566 * but the first layout stripped off. */
567 if (startswith_comma(c
->x11_layout
, a
[1]))
572 /* If that didn't work, strip off the
573 * other layouts from the entry, too */
574 x
= strndupa(a
[1], strcspn(a
[1], ","));
575 if (startswith_comma(c
->x11_layout
, x
))
581 if (isempty(c
->x11_model
) || streq_ptr(c
->x11_model
, a
[2])) {
584 if (streq_ptr(c
->x11_variant
, a
[3])) {
587 if (streq_ptr(c
->x11_options
, a
[4]))
593 /* The best matching entry so far, then let's save that */
594 if (matching
>= MAX(best_matching
, 1u)) {
595 log_debug("Found legacy keymap %s with score %u",
598 if (matching
> best_matching
) {
599 best_matching
= matching
;
601 r
= free_and_strdup(&new_keymap
, a
[0]);
608 if (best_matching
< 10 && c
->x11_layout
) {
609 /* The best match is only the first part of the X11
610 * keymap. Check if we have a converted map which
611 * matches just the first layout.
613 char *l
, *v
= NULL
, *converted
;
615 l
= strndupa(c
->x11_layout
, strcspn(c
->x11_layout
, ","));
617 v
= strndupa(c
->x11_variant
, strcspn(c
->x11_variant
, ","));
618 r
= find_converted_keymap(l
, v
, &converted
);
622 free_and_replace(new_keymap
, converted
);
625 *ret
= TAKE_PTR(new_keymap
);
629 int find_language_fallback(const char *lang
, char **language
) {
631 _cleanup_fclose_
FILE *f
= NULL
;
637 map
= systemd_language_fallback_map();
639 f
= fopen(map
, "re");
644 _cleanup_strv_free_
char **a
= NULL
;
647 r
= read_next_mapping(map
, 2, 2, f
, &n
, &a
);
651 if (streq(lang
, a
[0])) {
652 assert(strv_length(a
) == 2);
653 *language
= TAKE_PTR(a
[1]);
658 assert_not_reached("should not be here");
661 int x11_convert_to_vconsole(Context
*c
) {
662 bool modified
= false;
664 if (isempty(c
->x11_layout
)) {
666 !isempty(c
->vc_keymap
) ||
667 !isempty(c
->vc_keymap_toggle
);
669 context_free_vconsole(c
);
671 char *new_keymap
= NULL
;
674 r
= find_converted_keymap(c
->x11_layout
, c
->x11_variant
, &new_keymap
);
678 r
= find_legacy_keymap(c
, &new_keymap
);
683 /* We search for layout-variant match first, but then we also look
684 * for anything which matches just the layout. So it's accurate to say
685 * that we couldn't find anything which matches the layout. */
686 log_notice("No conversion to virtual console map found for \"%s\".",
689 if (!streq_ptr(c
->vc_keymap
, new_keymap
)) {
691 c
->vc_keymap
= new_keymap
;
692 c
->vc_keymap_toggle
= mfree(c
->vc_keymap_toggle
);
699 log_info("Changing virtual console keymap to '%s' toggle '%s'",
700 strempty(c
->vc_keymap
), strempty(c
->vc_keymap_toggle
));
702 log_debug("Virtual console keymap was not modified.");