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
);
281 *settings
= TAKE_PTR(l
);
285 int vconsole_write_data(Context
*c
) {
287 _cleanup_strv_free_
char **l
= NULL
;
289 r
= load_env_file(NULL
, "/etc/vconsole.conf", NULL
, &l
);
290 if (r
< 0 && r
!= -ENOENT
)
293 if (isempty(c
->vc_keymap
))
294 l
= strv_env_unset(l
, "KEYMAP");
296 _cleanup_free_
char *s
= NULL
;
299 s
= strappend("KEYMAP=", c
->vc_keymap
);
303 u
= strv_env_set(l
, s
);
311 if (isempty(c
->vc_keymap_toggle
))
312 l
= strv_env_unset(l
, "KEYMAP_TOGGLE");
314 _cleanup_free_
char *s
= NULL
;
317 s
= strappend("KEYMAP_TOGGLE=", c
->vc_keymap_toggle
);
321 u
= strv_env_set(l
, s
);
329 if (strv_isempty(l
)) {
330 if (unlink("/etc/vconsole.conf") < 0)
331 return errno
== ENOENT
? 0 : -errno
;
336 return write_env_file_label("/etc/vconsole.conf", l
);
339 int x11_write_data(Context
*c
) {
340 _cleanup_fclose_
FILE *f
= NULL
;
341 _cleanup_free_
char *temp_path
= NULL
;
344 if (isempty(c
->x11_layout
) &&
345 isempty(c
->x11_model
) &&
346 isempty(c
->x11_variant
) &&
347 isempty(c
->x11_options
)) {
349 if (unlink("/etc/X11/xorg.conf.d/00-keyboard.conf") < 0)
350 return errno
== ENOENT
? 0 : -errno
;
355 mkdir_p_label("/etc/X11/xorg.conf.d", 0755);
357 r
= fopen_temporary("/etc/X11/xorg.conf.d/00-keyboard.conf", &f
, &temp_path
);
361 (void) __fsetlocking(f
, FSETLOCKING_BYCALLER
);
362 (void) fchmod(fileno(f
), 0644);
364 fputs("# Written by systemd-localed(8), read by systemd-localed and Xorg. It's\n"
365 "# probably wise not to edit this file manually. Use localectl(1) to\n"
366 "# instruct systemd-localed to update it.\n"
367 "Section \"InputClass\"\n"
368 " Identifier \"system-keyboard\"\n"
369 " MatchIsKeyboard \"on\"\n", f
);
371 if (!isempty(c
->x11_layout
))
372 fprintf(f
, " Option \"XkbLayout\" \"%s\"\n", c
->x11_layout
);
374 if (!isempty(c
->x11_model
))
375 fprintf(f
, " Option \"XkbModel\" \"%s\"\n", c
->x11_model
);
377 if (!isempty(c
->x11_variant
))
378 fprintf(f
, " Option \"XkbVariant\" \"%s\"\n", c
->x11_variant
);
380 if (!isempty(c
->x11_options
))
381 fprintf(f
, " Option \"XkbOptions\" \"%s\"\n", c
->x11_options
);
383 fputs("EndSection\n", f
);
385 r
= fflush_sync_and_check(f
);
389 if (rename(temp_path
, "/etc/X11/xorg.conf.d/00-keyboard.conf") < 0) {
398 (void) unlink(temp_path
);
403 static int read_next_mapping(const char* filename
,
404 unsigned min_fields
, unsigned max_fields
,
405 FILE *f
, unsigned *n
, char ***a
) {
417 if (!fgets(line
, sizeof(line
), f
)) {
420 return errno
> 0 ? -errno
: -EIO
;
428 if (IN_SET(l
[0], 0, '#'))
431 r
= strv_split_extract(&b
, l
, WHITESPACE
, EXTRACT_QUOTES
);
435 length
= strv_length(b
);
436 if (length
< min_fields
|| length
> max_fields
) {
437 log_error("Invalid line %s:%u, ignoring.", filename
, *n
);
448 int vconsole_convert_to_x11(Context
*c
) {
452 map
= systemd_kbd_model_map();
454 if (isempty(c
->vc_keymap
)) {
456 !isempty(c
->x11_layout
) ||
457 !isempty(c
->x11_model
) ||
458 !isempty(c
->x11_variant
) ||
459 !isempty(c
->x11_options
);
463 _cleanup_fclose_
FILE *f
= NULL
;
466 f
= fopen(map
, "re");
471 _cleanup_strv_free_
char **a
= NULL
;
474 r
= read_next_mapping(map
, 5, UINT_MAX
, f
, &n
, &a
);
480 if (!streq(c
->vc_keymap
, a
[0]))
483 if (!streq_ptr(c
->x11_layout
, strnulldash(a
[1])) ||
484 !streq_ptr(c
->x11_model
, strnulldash(a
[2])) ||
485 !streq_ptr(c
->x11_variant
, strnulldash(a
[3])) ||
486 !streq_ptr(c
->x11_options
, strnulldash(a
[4]))) {
488 if (free_and_strdup(&c
->x11_layout
, strnulldash(a
[1])) < 0 ||
489 free_and_strdup(&c
->x11_model
, strnulldash(a
[2])) < 0 ||
490 free_and_strdup(&c
->x11_variant
, strnulldash(a
[3])) < 0 ||
491 free_and_strdup(&c
->x11_options
, strnulldash(a
[4])) < 0)
502 log_info("Changing X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
503 strempty(c
->x11_layout
),
504 strempty(c
->x11_model
),
505 strempty(c
->x11_variant
),
506 strempty(c
->x11_options
));
507 else if (modified
< 0)
508 log_notice("X11 keyboard layout was not modified: no conversion found for \"%s\".",
511 log_debug("X11 keyboard layout did not need to be modified.");
516 int find_converted_keymap(const char *x11_layout
, const char *x11_variant
, char **new_keymap
) {
518 _cleanup_free_
char *n
;
521 n
= strjoin(x11_layout
, "-", x11_variant
);
523 n
= strdup(x11_layout
);
527 NULSTR_FOREACH(dir
, KBD_KEYMAP_DIRS
) {
528 _cleanup_free_
char *p
= NULL
, *pz
= NULL
;
531 p
= strjoin(dir
, "xkb/", n
, ".map");
532 pz
= strjoin(dir
, "xkb/", n
, ".map.gz");
536 uncompressed
= access(p
, F_OK
) == 0;
537 if (uncompressed
|| access(pz
, F_OK
) == 0) {
538 log_debug("Found converted keymap %s at %s",
539 n
, uncompressed
? p
: pz
);
541 *new_keymap
= TAKE_PTR(n
);
549 int find_legacy_keymap(Context
*c
, char **new_keymap
) {
551 _cleanup_fclose_
FILE *f
= NULL
;
553 unsigned best_matching
= 0;
556 assert(!isempty(c
->x11_layout
));
558 map
= systemd_kbd_model_map();
560 f
= fopen(map
, "re");
565 _cleanup_strv_free_
char **a
= NULL
;
566 unsigned matching
= 0;
568 r
= read_next_mapping(map
, 5, UINT_MAX
, f
, &n
, &a
);
574 /* Determine how well matching this entry is */
575 if (streq(c
->x11_layout
, a
[1]))
576 /* If we got an exact match, this is best */
579 /* We have multiple X layouts, look for an
580 * entry that matches our key with everything
581 * but the first layout stripped off. */
582 if (startswith_comma(c
->x11_layout
, a
[1]))
587 /* If that didn't work, strip off the
588 * other layouts from the entry, too */
589 x
= strndupa(a
[1], strcspn(a
[1], ","));
590 if (startswith_comma(c
->x11_layout
, x
))
596 if (isempty(c
->x11_model
) || streq_ptr(c
->x11_model
, a
[2])) {
599 if (streq_ptr(c
->x11_variant
, a
[3])) {
602 if (streq_ptr(c
->x11_options
, a
[4]))
608 /* The best matching entry so far, then let's save that */
609 if (matching
>= MAX(best_matching
, 1u)) {
610 log_debug("Found legacy keymap %s with score %u",
613 if (matching
> best_matching
) {
614 best_matching
= matching
;
616 r
= free_and_strdup(new_keymap
, a
[0]);
623 if (best_matching
< 10 && c
->x11_layout
) {
624 /* The best match is only the first part of the X11
625 * keymap. Check if we have a converted map which
626 * matches just the first layout.
628 char *l
, *v
= NULL
, *converted
;
630 l
= strndupa(c
->x11_layout
, strcspn(c
->x11_layout
, ","));
632 v
= strndupa(c
->x11_variant
, strcspn(c
->x11_variant
, ","));
633 r
= find_converted_keymap(l
, v
, &converted
);
638 *new_keymap
= converted
;
642 return (bool) *new_keymap
;
645 int find_language_fallback(const char *lang
, char **language
) {
647 _cleanup_fclose_
FILE *f
= NULL
;
653 map
= systemd_language_fallback_map();
655 f
= fopen(map
, "re");
660 _cleanup_strv_free_
char **a
= NULL
;
663 r
= read_next_mapping(map
, 2, 2, f
, &n
, &a
);
667 if (streq(lang
, a
[0])) {
668 assert(strv_length(a
) == 2);
675 assert_not_reached("should not be here");
678 int x11_convert_to_vconsole(Context
*c
) {
679 bool modified
= false;
681 if (isempty(c
->x11_layout
)) {
683 !isempty(c
->vc_keymap
) ||
684 !isempty(c
->vc_keymap_toggle
);
686 context_free_vconsole(c
);
688 char *new_keymap
= NULL
;
691 r
= find_converted_keymap(c
->x11_layout
, c
->x11_variant
, &new_keymap
);
695 r
= find_legacy_keymap(c
, &new_keymap
);
700 /* We search for layout-variant match first, but then we also look
701 * for anything which matches just the layout. So it's accurate to say
702 * that we couldn't find anything which matches the layout. */
703 log_notice("No conversion to virtual console map found for \"%s\".",
706 if (!streq_ptr(c
->vc_keymap
, new_keymap
)) {
708 c
->vc_keymap
= new_keymap
;
709 c
->vc_keymap_toggle
= mfree(c
->vc_keymap_toggle
);
716 log_info("Changing virtual console keymap to '%s' toggle '%s'",
717 strempty(c
->vc_keymap
), strempty(c
->vc_keymap_toggle
));
719 log_debug("Virtual console keymap was not modified.");