]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/locale/keymap-util.c
Merge pull request #7446 from poettering/efi-firmware-boot-fixes
[thirdparty/systemd.git] / src / locale / keymap-util.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3 This file is part of systemd.
4
5 Copyright 2011 Lennart Poettering
6 Copyright 2013 Kay Sievers
7
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.
12
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.
17
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/>.
20 ***/
21
22 #include <errno.h>
23 #include <string.h>
24 #include <unistd.h>
25
26 #include "def.h"
27 #include "env-util.h"
28 #include "fd-util.h"
29 #include "fileio-label.h"
30 #include "fileio.h"
31 #include "keymap-util.h"
32 #include "locale-util.h"
33 #include "macro.h"
34 #include "mkdir.h"
35 #include "string-util.h"
36 #include "strv.h"
37
38 static bool startswith_comma(const char *s, const char *prefix) {
39 s = startswith(s, prefix);
40 if (!s)
41 return false;
42
43 return IN_SET(*s, ',', '\0');
44 }
45
46 static const char* strnulldash(const char *s) {
47 return isempty(s) || streq(s, "-") ? NULL : s;
48 }
49
50 static const char* systemd_kbd_model_map(void) {
51 const char* s;
52
53 s = getenv("SYSTEMD_KBD_MODEL_MAP");
54 if (s)
55 return s;
56
57 return SYSTEMD_KBD_MODEL_MAP;
58 }
59
60 static const char* systemd_language_fallback_map(void) {
61 const char* s;
62
63 s = getenv("SYSTEMD_LANGUAGE_FALLBACK_MAP");
64 if (s)
65 return s;
66
67 return SYSTEMD_LANGUAGE_FALLBACK_MAP;
68 }
69
70 static void context_free_x11(Context *c) {
71 c->x11_layout = mfree(c->x11_layout);
72 c->x11_options = mfree(c->x11_options);
73 c->x11_model = mfree(c->x11_model);
74 c->x11_variant = mfree(c->x11_variant);
75 }
76
77 static void context_free_vconsole(Context *c) {
78 c->vc_keymap = mfree(c->vc_keymap);
79 c->vc_keymap_toggle = mfree(c->vc_keymap_toggle);
80 }
81
82 static void context_free_locale(Context *c) {
83 int p;
84
85 for (p = 0; p < _VARIABLE_LC_MAX; p++)
86 c->locale[p] = mfree(c->locale[p]);
87 }
88
89 void context_free(Context *c) {
90 context_free_locale(c);
91 context_free_x11(c);
92 context_free_vconsole(c);
93 };
94
95 void locale_simplify(Context *c) {
96 int p;
97
98 for (p = VARIABLE_LANG+1; p < _VARIABLE_LC_MAX; p++)
99 if (isempty(c->locale[p]) || streq_ptr(c->locale[VARIABLE_LANG], c->locale[p]))
100 c->locale[p] = mfree(c->locale[p]);
101 }
102
103 static int locale_read_data(Context *c) {
104 int r;
105
106 context_free_locale(c);
107
108 r = parse_env_file("/etc/locale.conf", NEWLINE,
109 "LANG", &c->locale[VARIABLE_LANG],
110 "LANGUAGE", &c->locale[VARIABLE_LANGUAGE],
111 "LC_CTYPE", &c->locale[VARIABLE_LC_CTYPE],
112 "LC_NUMERIC", &c->locale[VARIABLE_LC_NUMERIC],
113 "LC_TIME", &c->locale[VARIABLE_LC_TIME],
114 "LC_COLLATE", &c->locale[VARIABLE_LC_COLLATE],
115 "LC_MONETARY", &c->locale[VARIABLE_LC_MONETARY],
116 "LC_MESSAGES", &c->locale[VARIABLE_LC_MESSAGES],
117 "LC_PAPER", &c->locale[VARIABLE_LC_PAPER],
118 "LC_NAME", &c->locale[VARIABLE_LC_NAME],
119 "LC_ADDRESS", &c->locale[VARIABLE_LC_ADDRESS],
120 "LC_TELEPHONE", &c->locale[VARIABLE_LC_TELEPHONE],
121 "LC_MEASUREMENT", &c->locale[VARIABLE_LC_MEASUREMENT],
122 "LC_IDENTIFICATION", &c->locale[VARIABLE_LC_IDENTIFICATION],
123 NULL);
124
125 if (r == -ENOENT) {
126 int p;
127
128 /* Fill in what we got passed from systemd. */
129 for (p = 0; p < _VARIABLE_LC_MAX; p++) {
130 const char *name;
131
132 name = locale_variable_to_string(p);
133 assert(name);
134
135 r = free_and_strdup(&c->locale[p], empty_to_null(getenv(name)));
136 if (r < 0)
137 return r;
138 }
139
140 r = 0;
141 }
142
143 locale_simplify(c);
144 return r;
145 }
146
147 static int vconsole_read_data(Context *c) {
148 int r;
149
150 context_free_vconsole(c);
151
152 r = parse_env_file("/etc/vconsole.conf", NEWLINE,
153 "KEYMAP", &c->vc_keymap,
154 "KEYMAP_TOGGLE", &c->vc_keymap_toggle,
155 NULL);
156
157 if (r < 0 && r != -ENOENT)
158 return r;
159
160 return 0;
161 }
162
163 static int x11_read_data(Context *c) {
164 _cleanup_fclose_ FILE *f;
165 char line[LINE_MAX];
166 bool in_section = false;
167 int r;
168
169 context_free_x11(c);
170
171 f = fopen("/etc/X11/xorg.conf.d/00-keyboard.conf", "re");
172 if (!f)
173 return errno == ENOENT ? 0 : -errno;
174
175 while (fgets(line, sizeof(line), f)) {
176 char *l;
177
178 char_array_0(line);
179 l = strstrip(line);
180
181 if (IN_SET(l[0], 0, '#'))
182 continue;
183
184 if (in_section && first_word(l, "Option")) {
185 _cleanup_strv_free_ char **a = NULL;
186
187 r = strv_split_extract(&a, l, WHITESPACE, EXTRACT_QUOTES);
188 if (r < 0)
189 return r;
190
191 if (strv_length(a) == 3) {
192 char **p = NULL;
193
194 if (streq(a[1], "XkbLayout"))
195 p = &c->x11_layout;
196 else if (streq(a[1], "XkbModel"))
197 p = &c->x11_model;
198 else if (streq(a[1], "XkbVariant"))
199 p = &c->x11_variant;
200 else if (streq(a[1], "XkbOptions"))
201 p = &c->x11_options;
202
203 if (p) {
204 free_and_replace(*p, a[2]);
205 }
206 }
207
208 } else if (!in_section && first_word(l, "Section")) {
209 _cleanup_strv_free_ char **a = NULL;
210
211 r = strv_split_extract(&a, l, WHITESPACE, EXTRACT_QUOTES);
212 if (r < 0)
213 return -ENOMEM;
214
215 if (strv_length(a) == 2 && streq(a[1], "InputClass"))
216 in_section = true;
217
218 } else if (in_section && first_word(l, "EndSection"))
219 in_section = false;
220 }
221
222 return 0;
223 }
224
225 int context_read_data(Context *c) {
226 int r, q, p;
227
228 r = locale_read_data(c);
229 q = vconsole_read_data(c);
230 p = x11_read_data(c);
231
232 return r < 0 ? r : q < 0 ? q : p;
233 }
234
235 int locale_write_data(Context *c, char ***settings) {
236 int r, p;
237 _cleanup_strv_free_ char **l = NULL;
238
239 /* Set values will be returned as strv in *settings on success. */
240
241 r = load_env_file(NULL, "/etc/locale.conf", NULL, &l);
242 if (r < 0 && r != -ENOENT)
243 return r;
244
245 for (p = 0; p < _VARIABLE_LC_MAX; p++) {
246 _cleanup_free_ char *t = NULL;
247 char **u;
248 const char *name;
249
250 name = locale_variable_to_string(p);
251 assert(name);
252
253 if (isempty(c->locale[p])) {
254 l = strv_env_unset(l, name);
255 continue;
256 }
257
258 if (asprintf(&t, "%s=%s", name, c->locale[p]) < 0)
259 return -ENOMEM;
260
261 u = strv_env_set(l, t);
262 if (!u)
263 return -ENOMEM;
264
265 strv_free(l);
266 l = u;
267 }
268
269 if (strv_isempty(l)) {
270 if (unlink("/etc/locale.conf") < 0)
271 return errno == ENOENT ? 0 : -errno;
272
273 return 0;
274 }
275
276 r = write_env_file_label("/etc/locale.conf", l);
277 if (r < 0)
278 return r;
279
280 *settings = l;
281 l = NULL;
282 return 0;
283 }
284
285 int vconsole_write_data(Context *c) {
286 int r;
287 _cleanup_strv_free_ char **l = NULL;
288
289 r = load_env_file(NULL, "/etc/vconsole.conf", NULL, &l);
290 if (r < 0 && r != -ENOENT)
291 return r;
292
293 if (isempty(c->vc_keymap))
294 l = strv_env_unset(l, "KEYMAP");
295 else {
296 _cleanup_free_ char *s = NULL;
297 char **u;
298
299 s = strappend("KEYMAP=", c->vc_keymap);
300 if (!s)
301 return -ENOMEM;
302
303 u = strv_env_set(l, s);
304 if (!u)
305 return -ENOMEM;
306
307 strv_free(l);
308 l = u;
309 }
310
311 if (isempty(c->vc_keymap_toggle))
312 l = strv_env_unset(l, "KEYMAP_TOGGLE");
313 else {
314 _cleanup_free_ char *s = NULL;
315 char **u;
316
317 s = strappend("KEYMAP_TOGGLE=", c->vc_keymap_toggle);
318 if (!s)
319 return -ENOMEM;
320
321 u = strv_env_set(l, s);
322 if (!u)
323 return -ENOMEM;
324
325 strv_free(l);
326 l = u;
327 }
328
329 if (strv_isempty(l)) {
330 if (unlink("/etc/vconsole.conf") < 0)
331 return errno == ENOENT ? 0 : -errno;
332
333 return 0;
334 }
335
336 return write_env_file_label("/etc/vconsole.conf", l);
337 }
338
339 int x11_write_data(Context *c) {
340 _cleanup_fclose_ FILE *f = NULL;
341 _cleanup_free_ char *temp_path = NULL;
342 int r;
343
344 if (isempty(c->x11_layout) &&
345 isempty(c->x11_model) &&
346 isempty(c->x11_variant) &&
347 isempty(c->x11_options)) {
348
349 if (unlink("/etc/X11/xorg.conf.d/00-keyboard.conf") < 0)
350 return errno == ENOENT ? 0 : -errno;
351
352 return 0;
353 }
354
355 mkdir_p_label("/etc/X11/xorg.conf.d", 0755);
356
357 r = fopen_temporary("/etc/X11/xorg.conf.d/00-keyboard.conf", &f, &temp_path);
358 if (r < 0)
359 return r;
360
361 fchmod(fileno(f), 0644);
362
363 fputs_unlocked("# Written by systemd-localed(8), read by systemd-localed and Xorg. It's\n"
364 "# probably wise not to edit this file manually. Use localectl(1) to\n"
365 "# instruct systemd-localed to update it.\n"
366 "Section \"InputClass\"\n"
367 " Identifier \"system-keyboard\"\n"
368 " MatchIsKeyboard \"on\"\n", f);
369
370 if (!isempty(c->x11_layout))
371 fprintf(f, " Option \"XkbLayout\" \"%s\"\n", c->x11_layout);
372
373 if (!isempty(c->x11_model))
374 fprintf(f, " Option \"XkbModel\" \"%s\"\n", c->x11_model);
375
376 if (!isempty(c->x11_variant))
377 fprintf(f, " Option \"XkbVariant\" \"%s\"\n", c->x11_variant);
378
379 if (!isempty(c->x11_options))
380 fprintf(f, " Option \"XkbOptions\" \"%s\"\n", c->x11_options);
381
382 fputs_unlocked("EndSection\n", f);
383
384 r = fflush_sync_and_check(f);
385 if (r < 0)
386 goto fail;
387
388 if (rename(temp_path, "/etc/X11/xorg.conf.d/00-keyboard.conf") < 0) {
389 r = -errno;
390 goto fail;
391 }
392
393 return 0;
394
395 fail:
396 if (temp_path)
397 (void) unlink(temp_path);
398
399 return r;
400 }
401
402 static int read_next_mapping(const char* filename,
403 unsigned min_fields, unsigned max_fields,
404 FILE *f, unsigned *n, char ***a) {
405 assert(f);
406 assert(n);
407 assert(a);
408
409 for (;;) {
410 char line[LINE_MAX];
411 char *l, **b;
412 int r;
413 size_t length;
414
415 errno = 0;
416 if (!fgets(line, sizeof(line), f)) {
417
418 if (ferror(f))
419 return errno > 0 ? -errno : -EIO;
420
421 return 0;
422 }
423
424 (*n)++;
425
426 l = strstrip(line);
427 if (IN_SET(l[0], 0, '#'))
428 continue;
429
430 r = strv_split_extract(&b, l, WHITESPACE, EXTRACT_QUOTES);
431 if (r < 0)
432 return r;
433
434 length = strv_length(b);
435 if (length < min_fields || length > max_fields) {
436 log_error("Invalid line %s:%u, ignoring.", filename, *n);
437 strv_free(b);
438 continue;
439
440 }
441
442 *a = b;
443 return 1;
444 }
445 }
446
447 int vconsole_convert_to_x11(Context *c) {
448 const char *map;
449 int modified = -1;
450
451 map = systemd_kbd_model_map();
452
453 if (isempty(c->vc_keymap)) {
454 modified =
455 !isempty(c->x11_layout) ||
456 !isempty(c->x11_model) ||
457 !isempty(c->x11_variant) ||
458 !isempty(c->x11_options);
459
460 context_free_x11(c);
461 } else {
462 _cleanup_fclose_ FILE *f = NULL;
463 unsigned n = 0;
464
465 f = fopen(map, "re");
466 if (!f)
467 return -errno;
468
469 for (;;) {
470 _cleanup_strv_free_ char **a = NULL;
471 int r;
472
473 r = read_next_mapping(map, 5, UINT_MAX, f, &n, &a);
474 if (r < 0)
475 return r;
476 if (r == 0)
477 break;
478
479 if (!streq(c->vc_keymap, a[0]))
480 continue;
481
482 if (!streq_ptr(c->x11_layout, strnulldash(a[1])) ||
483 !streq_ptr(c->x11_model, strnulldash(a[2])) ||
484 !streq_ptr(c->x11_variant, strnulldash(a[3])) ||
485 !streq_ptr(c->x11_options, strnulldash(a[4]))) {
486
487 if (free_and_strdup(&c->x11_layout, strnulldash(a[1])) < 0 ||
488 free_and_strdup(&c->x11_model, strnulldash(a[2])) < 0 ||
489 free_and_strdup(&c->x11_variant, strnulldash(a[3])) < 0 ||
490 free_and_strdup(&c->x11_options, strnulldash(a[4])) < 0)
491 return -ENOMEM;
492
493 modified = true;
494 }
495
496 break;
497 }
498 }
499
500 if (modified > 0)
501 log_info("Changing X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
502 strempty(c->x11_layout),
503 strempty(c->x11_model),
504 strempty(c->x11_variant),
505 strempty(c->x11_options));
506 else if (modified < 0)
507 log_notice("X11 keyboard layout was not modified: no conversion found for \"%s\".",
508 c->vc_keymap);
509 else
510 log_debug("X11 keyboard layout did not need to be modified.");
511
512 return modified > 0;
513 }
514
515 int find_converted_keymap(const char *x11_layout, const char *x11_variant, char **new_keymap) {
516 const char *dir;
517 _cleanup_free_ char *n;
518
519 if (x11_variant)
520 n = strjoin(x11_layout, "-", x11_variant);
521 else
522 n = strdup(x11_layout);
523 if (!n)
524 return -ENOMEM;
525
526 NULSTR_FOREACH(dir, KBD_KEYMAP_DIRS) {
527 _cleanup_free_ char *p = NULL, *pz = NULL;
528 bool uncompressed;
529
530 p = strjoin(dir, "xkb/", n, ".map");
531 pz = strjoin(dir, "xkb/", n, ".map.gz");
532 if (!p || !pz)
533 return -ENOMEM;
534
535 uncompressed = access(p, F_OK) == 0;
536 if (uncompressed || access(pz, F_OK) == 0) {
537 log_debug("Found converted keymap %s at %s",
538 n, uncompressed ? p : pz);
539
540 *new_keymap = n;
541 n = NULL;
542 return 1;
543 }
544 }
545
546 return 0;
547 }
548
549 int find_legacy_keymap(Context *c, char **new_keymap) {
550 const char *map;
551 _cleanup_fclose_ FILE *f = NULL;
552 unsigned n = 0;
553 unsigned best_matching = 0;
554 int r;
555
556 assert(!isempty(c->x11_layout));
557
558 map = systemd_kbd_model_map();
559
560 f = fopen(map, "re");
561 if (!f)
562 return -errno;
563
564 for (;;) {
565 _cleanup_strv_free_ char **a = NULL;
566 unsigned matching = 0;
567
568 r = read_next_mapping(map, 5, UINT_MAX, f, &n, &a);
569 if (r < 0)
570 return r;
571 if (r == 0)
572 break;
573
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 */
577 matching = 10;
578 else {
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]))
583 matching = 5;
584 else {
585 char *x;
586
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))
591 matching = 1;
592 }
593 }
594
595 if (matching > 0) {
596 if (isempty(c->x11_model) || streq_ptr(c->x11_model, a[2])) {
597 matching++;
598
599 if (streq_ptr(c->x11_variant, a[3])) {
600 matching++;
601
602 if (streq_ptr(c->x11_options, a[4]))
603 matching++;
604 }
605 }
606 }
607
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",
611 a[0], matching);
612
613 if (matching > best_matching) {
614 best_matching = matching;
615
616 r = free_and_strdup(new_keymap, a[0]);
617 if (r < 0)
618 return r;
619 }
620 }
621 }
622
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.
627 */
628 char *l, *v = NULL, *converted;
629
630 l = strndupa(c->x11_layout, strcspn(c->x11_layout, ","));
631 if (c->x11_variant)
632 v = strndupa(c->x11_variant, strcspn(c->x11_variant, ","));
633 r = find_converted_keymap(l, v, &converted);
634 if (r < 0)
635 return r;
636 if (r > 0) {
637 free(*new_keymap);
638 *new_keymap = converted;
639 }
640 }
641
642 return (bool) *new_keymap;
643 }
644
645 int find_language_fallback(const char *lang, char **language) {
646 const char *map;
647 _cleanup_fclose_ FILE *f = NULL;
648 unsigned n = 0;
649
650 assert(lang);
651 assert(language);
652
653 map = systemd_language_fallback_map();
654
655 f = fopen(map, "re");
656 if (!f)
657 return -errno;
658
659 for (;;) {
660 _cleanup_strv_free_ char **a = NULL;
661 int r;
662
663 r = read_next_mapping(map, 2, 2, f, &n, &a);
664 if (r <= 0)
665 return r;
666
667 if (streq(lang, a[0])) {
668 assert(strv_length(a) == 2);
669 *language = a[1];
670 a[1] = NULL;
671 return 1;
672 }
673 }
674
675 assert_not_reached("should not be here");
676 }
677
678 int x11_convert_to_vconsole(Context *c) {
679 bool modified = false;
680
681 if (isempty(c->x11_layout)) {
682 modified =
683 !isempty(c->vc_keymap) ||
684 !isempty(c->vc_keymap_toggle);
685
686 context_free_vconsole(c);
687 } else {
688 char *new_keymap = NULL;
689 int r;
690
691 r = find_converted_keymap(c->x11_layout, c->x11_variant, &new_keymap);
692 if (r < 0)
693 return r;
694 else if (r == 0) {
695 r = find_legacy_keymap(c, &new_keymap);
696 if (r < 0)
697 return r;
698 }
699 if (r == 0)
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\".",
704 c->x11_layout);
705
706 if (!streq_ptr(c->vc_keymap, new_keymap)) {
707 free(c->vc_keymap);
708 c->vc_keymap = new_keymap;
709 c->vc_keymap_toggle = mfree(c->vc_keymap_toggle);
710 modified = true;
711 } else
712 free(new_keymap);
713 }
714
715 if (modified)
716 log_info("Changing virtual console keymap to '%s' toggle '%s'",
717 strempty(c->vc_keymap), strempty(c->vc_keymap_toggle));
718 else
719 log_debug("Virtual console keymap was not modified.");
720
721 return modified;
722 }