1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright 2011 Lennart Poettering
6 Copyright 2013 Kay Sievers
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
23 #include <stdio_ext.h>
30 #include "fileio-label.h"
32 #include "keymap-util.h"
33 #include "locale-util.h"
36 #include "string-util.h"
39 static bool startswith_comma(const char *s
, const char *prefix
) {
40 s
= startswith(s
, prefix
);
44 return IN_SET(*s
, ',', '\0');
47 static const char* strnulldash(const char *s
) {
48 return isempty(s
) || streq(s
, "-") ? NULL
: s
;
51 static const char* systemd_kbd_model_map(void) {
54 s
= getenv("SYSTEMD_KBD_MODEL_MAP");
58 return SYSTEMD_KBD_MODEL_MAP
;
61 static const char* systemd_language_fallback_map(void) {
64 s
= getenv("SYSTEMD_LANGUAGE_FALLBACK_MAP");
68 return SYSTEMD_LANGUAGE_FALLBACK_MAP
;
71 static void context_free_x11(Context
*c
) {
72 c
->x11_layout
= mfree(c
->x11_layout
);
73 c
->x11_options
= mfree(c
->x11_options
);
74 c
->x11_model
= mfree(c
->x11_model
);
75 c
->x11_variant
= mfree(c
->x11_variant
);
78 static void context_free_vconsole(Context
*c
) {
79 c
->vc_keymap
= mfree(c
->vc_keymap
);
80 c
->vc_keymap_toggle
= mfree(c
->vc_keymap_toggle
);
83 static void context_free_locale(Context
*c
) {
86 for (p
= 0; p
< _VARIABLE_LC_MAX
; p
++)
87 c
->locale
[p
] = mfree(c
->locale
[p
]);
90 void context_free(Context
*c
) {
91 context_free_locale(c
);
93 context_free_vconsole(c
);
96 void locale_simplify(Context
*c
) {
99 for (p
= VARIABLE_LANG
+1; p
< _VARIABLE_LC_MAX
; p
++)
100 if (isempty(c
->locale
[p
]) || streq_ptr(c
->locale
[VARIABLE_LANG
], c
->locale
[p
]))
101 c
->locale
[p
] = mfree(c
->locale
[p
]);
104 static int locale_read_data(Context
*c
) {
107 context_free_locale(c
);
109 r
= parse_env_file("/etc/locale.conf", NEWLINE
,
110 "LANG", &c
->locale
[VARIABLE_LANG
],
111 "LANGUAGE", &c
->locale
[VARIABLE_LANGUAGE
],
112 "LC_CTYPE", &c
->locale
[VARIABLE_LC_CTYPE
],
113 "LC_NUMERIC", &c
->locale
[VARIABLE_LC_NUMERIC
],
114 "LC_TIME", &c
->locale
[VARIABLE_LC_TIME
],
115 "LC_COLLATE", &c
->locale
[VARIABLE_LC_COLLATE
],
116 "LC_MONETARY", &c
->locale
[VARIABLE_LC_MONETARY
],
117 "LC_MESSAGES", &c
->locale
[VARIABLE_LC_MESSAGES
],
118 "LC_PAPER", &c
->locale
[VARIABLE_LC_PAPER
],
119 "LC_NAME", &c
->locale
[VARIABLE_LC_NAME
],
120 "LC_ADDRESS", &c
->locale
[VARIABLE_LC_ADDRESS
],
121 "LC_TELEPHONE", &c
->locale
[VARIABLE_LC_TELEPHONE
],
122 "LC_MEASUREMENT", &c
->locale
[VARIABLE_LC_MEASUREMENT
],
123 "LC_IDENTIFICATION", &c
->locale
[VARIABLE_LC_IDENTIFICATION
],
129 /* Fill in what we got passed from systemd. */
130 for (p
= 0; p
< _VARIABLE_LC_MAX
; p
++) {
133 name
= locale_variable_to_string(p
);
136 r
= free_and_strdup(&c
->locale
[p
], empty_to_null(getenv(name
)));
148 static int vconsole_read_data(Context
*c
) {
151 context_free_vconsole(c
);
153 r
= parse_env_file("/etc/vconsole.conf", NEWLINE
,
154 "KEYMAP", &c
->vc_keymap
,
155 "KEYMAP_TOGGLE", &c
->vc_keymap_toggle
,
158 if (r
< 0 && r
!= -ENOENT
)
164 static int x11_read_data(Context
*c
) {
165 _cleanup_fclose_
FILE *f
;
167 bool in_section
= false;
172 f
= fopen("/etc/X11/xorg.conf.d/00-keyboard.conf", "re");
174 return errno
== ENOENT
? 0 : -errno
;
176 while (fgets(line
, sizeof(line
), f
)) {
182 if (IN_SET(l
[0], 0, '#'))
185 if (in_section
&& first_word(l
, "Option")) {
186 _cleanup_strv_free_
char **a
= NULL
;
188 r
= strv_split_extract(&a
, l
, WHITESPACE
, EXTRACT_QUOTES
);
192 if (strv_length(a
) == 3) {
195 if (streq(a
[1], "XkbLayout"))
197 else if (streq(a
[1], "XkbModel"))
199 else if (streq(a
[1], "XkbVariant"))
201 else if (streq(a
[1], "XkbOptions"))
205 free_and_replace(*p
, a
[2]);
209 } else if (!in_section
&& first_word(l
, "Section")) {
210 _cleanup_strv_free_
char **a
= NULL
;
212 r
= strv_split_extract(&a
, l
, WHITESPACE
, EXTRACT_QUOTES
);
216 if (strv_length(a
) == 2 && streq(a
[1], "InputClass"))
219 } else if (in_section
&& first_word(l
, "EndSection"))
226 int context_read_data(Context
*c
) {
229 r
= locale_read_data(c
);
230 q
= vconsole_read_data(c
);
231 p
= x11_read_data(c
);
233 return r
< 0 ? r
: q
< 0 ? q
: p
;
236 int locale_write_data(Context
*c
, char ***settings
) {
238 _cleanup_strv_free_
char **l
= NULL
;
240 /* Set values will be returned as strv in *settings on success. */
242 r
= load_env_file(NULL
, "/etc/locale.conf", NULL
, &l
);
243 if (r
< 0 && r
!= -ENOENT
)
246 for (p
= 0; p
< _VARIABLE_LC_MAX
; p
++) {
247 _cleanup_free_
char *t
= NULL
;
251 name
= locale_variable_to_string(p
);
254 if (isempty(c
->locale
[p
])) {
255 l
= strv_env_unset(l
, name
);
259 if (asprintf(&t
, "%s=%s", name
, c
->locale
[p
]) < 0)
262 u
= strv_env_set(l
, t
);
270 if (strv_isempty(l
)) {
271 if (unlink("/etc/locale.conf") < 0)
272 return errno
== ENOENT
? 0 : -errno
;
277 r
= write_env_file_label("/etc/locale.conf", l
);
286 int vconsole_write_data(Context
*c
) {
288 _cleanup_strv_free_
char **l
= NULL
;
290 r
= load_env_file(NULL
, "/etc/vconsole.conf", NULL
, &l
);
291 if (r
< 0 && r
!= -ENOENT
)
294 if (isempty(c
->vc_keymap
))
295 l
= strv_env_unset(l
, "KEYMAP");
297 _cleanup_free_
char *s
= NULL
;
300 s
= strappend("KEYMAP=", c
->vc_keymap
);
304 u
= strv_env_set(l
, s
);
312 if (isempty(c
->vc_keymap_toggle
))
313 l
= strv_env_unset(l
, "KEYMAP_TOGGLE");
315 _cleanup_free_
char *s
= NULL
;
318 s
= strappend("KEYMAP_TOGGLE=", c
->vc_keymap_toggle
);
322 u
= strv_env_set(l
, s
);
330 if (strv_isempty(l
)) {
331 if (unlink("/etc/vconsole.conf") < 0)
332 return errno
== ENOENT
? 0 : -errno
;
337 return write_env_file_label("/etc/vconsole.conf", l
);
340 int x11_write_data(Context
*c
) {
341 _cleanup_fclose_
FILE *f
= NULL
;
342 _cleanup_free_
char *temp_path
= NULL
;
345 if (isempty(c
->x11_layout
) &&
346 isempty(c
->x11_model
) &&
347 isempty(c
->x11_variant
) &&
348 isempty(c
->x11_options
)) {
350 if (unlink("/etc/X11/xorg.conf.d/00-keyboard.conf") < 0)
351 return errno
== ENOENT
? 0 : -errno
;
356 mkdir_p_label("/etc/X11/xorg.conf.d", 0755);
358 r
= fopen_temporary("/etc/X11/xorg.conf.d/00-keyboard.conf", &f
, &temp_path
);
362 (void) __fsetlocking(f
, FSETLOCKING_BYCALLER
);
363 (void) fchmod(fileno(f
), 0644);
365 fputs("# Written by systemd-localed(8), read by systemd-localed and Xorg. It's\n"
366 "# probably wise not to edit this file manually. Use localectl(1) to\n"
367 "# instruct systemd-localed to update it.\n"
368 "Section \"InputClass\"\n"
369 " Identifier \"system-keyboard\"\n"
370 " MatchIsKeyboard \"on\"\n", f
);
372 if (!isempty(c
->x11_layout
))
373 fprintf(f
, " Option \"XkbLayout\" \"%s\"\n", c
->x11_layout
);
375 if (!isempty(c
->x11_model
))
376 fprintf(f
, " Option \"XkbModel\" \"%s\"\n", c
->x11_model
);
378 if (!isempty(c
->x11_variant
))
379 fprintf(f
, " Option \"XkbVariant\" \"%s\"\n", c
->x11_variant
);
381 if (!isempty(c
->x11_options
))
382 fprintf(f
, " Option \"XkbOptions\" \"%s\"\n", c
->x11_options
);
384 fputs("EndSection\n", f
);
386 r
= fflush_sync_and_check(f
);
390 if (rename(temp_path
, "/etc/X11/xorg.conf.d/00-keyboard.conf") < 0) {
399 (void) unlink(temp_path
);
404 static int read_next_mapping(const char* filename
,
405 unsigned min_fields
, unsigned max_fields
,
406 FILE *f
, unsigned *n
, char ***a
) {
418 if (!fgets(line
, sizeof(line
), f
)) {
421 return errno
> 0 ? -errno
: -EIO
;
429 if (IN_SET(l
[0], 0, '#'))
432 r
= strv_split_extract(&b
, l
, WHITESPACE
, EXTRACT_QUOTES
);
436 length
= strv_length(b
);
437 if (length
< min_fields
|| length
> max_fields
) {
438 log_error("Invalid line %s:%u, ignoring.", filename
, *n
);
449 int vconsole_convert_to_x11(Context
*c
) {
453 map
= systemd_kbd_model_map();
455 if (isempty(c
->vc_keymap
)) {
457 !isempty(c
->x11_layout
) ||
458 !isempty(c
->x11_model
) ||
459 !isempty(c
->x11_variant
) ||
460 !isempty(c
->x11_options
);
464 _cleanup_fclose_
FILE *f
= NULL
;
467 f
= fopen(map
, "re");
472 _cleanup_strv_free_
char **a
= NULL
;
475 r
= read_next_mapping(map
, 5, UINT_MAX
, f
, &n
, &a
);
481 if (!streq(c
->vc_keymap
, a
[0]))
484 if (!streq_ptr(c
->x11_layout
, strnulldash(a
[1])) ||
485 !streq_ptr(c
->x11_model
, strnulldash(a
[2])) ||
486 !streq_ptr(c
->x11_variant
, strnulldash(a
[3])) ||
487 !streq_ptr(c
->x11_options
, strnulldash(a
[4]))) {
489 if (free_and_strdup(&c
->x11_layout
, strnulldash(a
[1])) < 0 ||
490 free_and_strdup(&c
->x11_model
, strnulldash(a
[2])) < 0 ||
491 free_and_strdup(&c
->x11_variant
, strnulldash(a
[3])) < 0 ||
492 free_and_strdup(&c
->x11_options
, strnulldash(a
[4])) < 0)
503 log_info("Changing X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
504 strempty(c
->x11_layout
),
505 strempty(c
->x11_model
),
506 strempty(c
->x11_variant
),
507 strempty(c
->x11_options
));
508 else if (modified
< 0)
509 log_notice("X11 keyboard layout was not modified: no conversion found for \"%s\".",
512 log_debug("X11 keyboard layout did not need to be modified.");
517 int find_converted_keymap(const char *x11_layout
, const char *x11_variant
, char **new_keymap
) {
519 _cleanup_free_
char *n
;
522 n
= strjoin(x11_layout
, "-", x11_variant
);
524 n
= strdup(x11_layout
);
528 NULSTR_FOREACH(dir
, KBD_KEYMAP_DIRS
) {
529 _cleanup_free_
char *p
= NULL
, *pz
= NULL
;
532 p
= strjoin(dir
, "xkb/", n
, ".map");
533 pz
= strjoin(dir
, "xkb/", n
, ".map.gz");
537 uncompressed
= access(p
, F_OK
) == 0;
538 if (uncompressed
|| access(pz
, F_OK
) == 0) {
539 log_debug("Found converted keymap %s at %s",
540 n
, uncompressed
? p
: pz
);
551 int find_legacy_keymap(Context
*c
, char **new_keymap
) {
553 _cleanup_fclose_
FILE *f
= NULL
;
555 unsigned best_matching
= 0;
558 assert(!isempty(c
->x11_layout
));
560 map
= systemd_kbd_model_map();
562 f
= fopen(map
, "re");
567 _cleanup_strv_free_
char **a
= NULL
;
568 unsigned matching
= 0;
570 r
= read_next_mapping(map
, 5, UINT_MAX
, f
, &n
, &a
);
576 /* Determine how well matching this entry is */
577 if (streq(c
->x11_layout
, a
[1]))
578 /* If we got an exact match, this is best */
581 /* We have multiple X layouts, look for an
582 * entry that matches our key with everything
583 * but the first layout stripped off. */
584 if (startswith_comma(c
->x11_layout
, a
[1]))
589 /* If that didn't work, strip off the
590 * other layouts from the entry, too */
591 x
= strndupa(a
[1], strcspn(a
[1], ","));
592 if (startswith_comma(c
->x11_layout
, x
))
598 if (isempty(c
->x11_model
) || streq_ptr(c
->x11_model
, a
[2])) {
601 if (streq_ptr(c
->x11_variant
, a
[3])) {
604 if (streq_ptr(c
->x11_options
, a
[4]))
610 /* The best matching entry so far, then let's save that */
611 if (matching
>= MAX(best_matching
, 1u)) {
612 log_debug("Found legacy keymap %s with score %u",
615 if (matching
> best_matching
) {
616 best_matching
= matching
;
618 r
= free_and_strdup(new_keymap
, a
[0]);
625 if (best_matching
< 10 && c
->x11_layout
) {
626 /* The best match is only the first part of the X11
627 * keymap. Check if we have a converted map which
628 * matches just the first layout.
630 char *l
, *v
= NULL
, *converted
;
632 l
= strndupa(c
->x11_layout
, strcspn(c
->x11_layout
, ","));
634 v
= strndupa(c
->x11_variant
, strcspn(c
->x11_variant
, ","));
635 r
= find_converted_keymap(l
, v
, &converted
);
640 *new_keymap
= converted
;
644 return (bool) *new_keymap
;
647 int find_language_fallback(const char *lang
, char **language
) {
649 _cleanup_fclose_
FILE *f
= NULL
;
655 map
= systemd_language_fallback_map();
657 f
= fopen(map
, "re");
662 _cleanup_strv_free_
char **a
= NULL
;
665 r
= read_next_mapping(map
, 2, 2, f
, &n
, &a
);
669 if (streq(lang
, a
[0])) {
670 assert(strv_length(a
) == 2);
677 assert_not_reached("should not be here");
680 int x11_convert_to_vconsole(Context
*c
) {
681 bool modified
= false;
683 if (isempty(c
->x11_layout
)) {
685 !isempty(c
->vc_keymap
) ||
686 !isempty(c
->vc_keymap_toggle
);
688 context_free_vconsole(c
);
690 char *new_keymap
= NULL
;
693 r
= find_converted_keymap(c
->x11_layout
, c
->x11_variant
, &new_keymap
);
697 r
= find_legacy_keymap(c
, &new_keymap
);
702 /* We search for layout-variant match first, but then we also look
703 * for anything which matches just the layout. So it's accurate to say
704 * that we couldn't find anything which matches the layout. */
705 log_notice("No conversion to virtual console map found for \"%s\".",
708 if (!streq_ptr(c
->vc_keymap
, new_keymap
)) {
710 c
->vc_keymap
= new_keymap
;
711 c
->vc_keymap_toggle
= mfree(c
->vc_keymap_toggle
);
718 log_info("Changing virtual console keymap to '%s' toggle '%s'",
719 strempty(c
->vc_keymap
), strempty(c
->vc_keymap_toggle
));
721 log_debug("Virtual console keymap was not modified.");