]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/locale/keymap-util.c
Add SPDX license identifiers to source files under the LGPL
[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(*p);
205 *p = a[2];
206 a[2] = NULL;
207 }
208 }
209
210 } else if (!in_section && first_word(l, "Section")) {
211 _cleanup_strv_free_ char **a = NULL;
212
213 r = strv_split_extract(&a, l, WHITESPACE, EXTRACT_QUOTES);
214 if (r < 0)
215 return -ENOMEM;
216
217 if (strv_length(a) == 2 && streq(a[1], "InputClass"))
218 in_section = true;
219
220 } else if (in_section && first_word(l, "EndSection"))
221 in_section = false;
222 }
223
224 return 0;
225 }
226
227 int context_read_data(Context *c) {
228 int r, q, p;
229
230 r = locale_read_data(c);
231 q = vconsole_read_data(c);
232 p = x11_read_data(c);
233
234 return r < 0 ? r : q < 0 ? q : p;
235 }
236
237 int locale_write_data(Context *c, char ***settings) {
238 int r, p;
239 _cleanup_strv_free_ char **l = NULL;
240
241 /* Set values will be returned as strv in *settings on success. */
242
243 r = load_env_file(NULL, "/etc/locale.conf", NULL, &l);
244 if (r < 0 && r != -ENOENT)
245 return r;
246
247 for (p = 0; p < _VARIABLE_LC_MAX; p++) {
248 _cleanup_free_ char *t = NULL;
249 char **u;
250 const char *name;
251
252 name = locale_variable_to_string(p);
253 assert(name);
254
255 if (isempty(c->locale[p])) {
256 l = strv_env_unset(l, name);
257 continue;
258 }
259
260 if (asprintf(&t, "%s=%s", name, c->locale[p]) < 0)
261 return -ENOMEM;
262
263 u = strv_env_set(l, t);
264 if (!u)
265 return -ENOMEM;
266
267 strv_free(l);
268 l = u;
269 }
270
271 if (strv_isempty(l)) {
272 if (unlink("/etc/locale.conf") < 0)
273 return errno == ENOENT ? 0 : -errno;
274
275 return 0;
276 }
277
278 r = write_env_file_label("/etc/locale.conf", l);
279 if (r < 0)
280 return r;
281
282 *settings = l;
283 l = NULL;
284 return 0;
285 }
286
287 int vconsole_write_data(Context *c) {
288 int r;
289 _cleanup_strv_free_ char **l = NULL;
290
291 r = load_env_file(NULL, "/etc/vconsole.conf", NULL, &l);
292 if (r < 0 && r != -ENOENT)
293 return r;
294
295 if (isempty(c->vc_keymap))
296 l = strv_env_unset(l, "KEYMAP");
297 else {
298 _cleanup_free_ char *s = NULL;
299 char **u;
300
301 s = strappend("KEYMAP=", c->vc_keymap);
302 if (!s)
303 return -ENOMEM;
304
305 u = strv_env_set(l, s);
306 if (!u)
307 return -ENOMEM;
308
309 strv_free(l);
310 l = u;
311 }
312
313 if (isempty(c->vc_keymap_toggle))
314 l = strv_env_unset(l, "KEYMAP_TOGGLE");
315 else {
316 _cleanup_free_ char *s = NULL;
317 char **u;
318
319 s = strappend("KEYMAP_TOGGLE=", c->vc_keymap_toggle);
320 if (!s)
321 return -ENOMEM;
322
323 u = strv_env_set(l, s);
324 if (!u)
325 return -ENOMEM;
326
327 strv_free(l);
328 l = u;
329 }
330
331 if (strv_isempty(l)) {
332 if (unlink("/etc/vconsole.conf") < 0)
333 return errno == ENOENT ? 0 : -errno;
334
335 return 0;
336 }
337
338 return write_env_file_label("/etc/vconsole.conf", l);
339 }
340
341 int x11_write_data(Context *c) {
342 _cleanup_fclose_ FILE *f = NULL;
343 _cleanup_free_ char *temp_path = NULL;
344 int r;
345
346 if (isempty(c->x11_layout) &&
347 isempty(c->x11_model) &&
348 isempty(c->x11_variant) &&
349 isempty(c->x11_options)) {
350
351 if (unlink("/etc/X11/xorg.conf.d/00-keyboard.conf") < 0)
352 return errno == ENOENT ? 0 : -errno;
353
354 return 0;
355 }
356
357 mkdir_p_label("/etc/X11/xorg.conf.d", 0755);
358
359 r = fopen_temporary("/etc/X11/xorg.conf.d/00-keyboard.conf", &f, &temp_path);
360 if (r < 0)
361 return r;
362
363 fchmod(fileno(f), 0644);
364
365 fputs_unlocked("# 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);
371
372 if (!isempty(c->x11_layout))
373 fprintf(f, " Option \"XkbLayout\" \"%s\"\n", c->x11_layout);
374
375 if (!isempty(c->x11_model))
376 fprintf(f, " Option \"XkbModel\" \"%s\"\n", c->x11_model);
377
378 if (!isempty(c->x11_variant))
379 fprintf(f, " Option \"XkbVariant\" \"%s\"\n", c->x11_variant);
380
381 if (!isempty(c->x11_options))
382 fprintf(f, " Option \"XkbOptions\" \"%s\"\n", c->x11_options);
383
384 fputs_unlocked("EndSection\n", f);
385
386 r = fflush_sync_and_check(f);
387 if (r < 0)
388 goto fail;
389
390 if (rename(temp_path, "/etc/X11/xorg.conf.d/00-keyboard.conf") < 0) {
391 r = -errno;
392 goto fail;
393 }
394
395 return 0;
396
397 fail:
398 if (temp_path)
399 (void) unlink(temp_path);
400
401 return r;
402 }
403
404 static int read_next_mapping(const char* filename,
405 unsigned min_fields, unsigned max_fields,
406 FILE *f, unsigned *n, char ***a) {
407 assert(f);
408 assert(n);
409 assert(a);
410
411 for (;;) {
412 char line[LINE_MAX];
413 char *l, **b;
414 int r;
415 size_t length;
416
417 errno = 0;
418 if (!fgets(line, sizeof(line), f)) {
419
420 if (ferror(f))
421 return errno > 0 ? -errno : -EIO;
422
423 return 0;
424 }
425
426 (*n)++;
427
428 l = strstrip(line);
429 if (IN_SET(l[0], 0, '#'))
430 continue;
431
432 r = strv_split_extract(&b, l, WHITESPACE, EXTRACT_QUOTES);
433 if (r < 0)
434 return r;
435
436 length = strv_length(b);
437 if (length < min_fields || length > max_fields) {
438 log_error("Invalid line %s:%u, ignoring.", filename, *n);
439 strv_free(b);
440 continue;
441
442 }
443
444 *a = b;
445 return 1;
446 }
447 }
448
449 int vconsole_convert_to_x11(Context *c) {
450 const char *map;
451 int modified = -1;
452
453 map = systemd_kbd_model_map();
454
455 if (isempty(c->vc_keymap)) {
456 modified =
457 !isempty(c->x11_layout) ||
458 !isempty(c->x11_model) ||
459 !isempty(c->x11_variant) ||
460 !isempty(c->x11_options);
461
462 context_free_x11(c);
463 } else {
464 _cleanup_fclose_ FILE *f = NULL;
465 unsigned n = 0;
466
467 f = fopen(map, "re");
468 if (!f)
469 return -errno;
470
471 for (;;) {
472 _cleanup_strv_free_ char **a = NULL;
473 int r;
474
475 r = read_next_mapping(map, 5, UINT_MAX, f, &n, &a);
476 if (r < 0)
477 return r;
478 if (r == 0)
479 break;
480
481 if (!streq(c->vc_keymap, a[0]))
482 continue;
483
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]))) {
488
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)
493 return -ENOMEM;
494
495 modified = true;
496 }
497
498 break;
499 }
500 }
501
502 if (modified > 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\".",
510 c->vc_keymap);
511 else
512 log_debug("X11 keyboard layout did not need to be modified.");
513
514 return modified > 0;
515 }
516
517 int find_converted_keymap(const char *x11_layout, const char *x11_variant, char **new_keymap) {
518 const char *dir;
519 _cleanup_free_ char *n;
520
521 if (x11_variant)
522 n = strjoin(x11_layout, "-", x11_variant);
523 else
524 n = strdup(x11_layout);
525 if (!n)
526 return -ENOMEM;
527
528 NULSTR_FOREACH(dir, KBD_KEYMAP_DIRS) {
529 _cleanup_free_ char *p = NULL, *pz = NULL;
530 bool uncompressed;
531
532 p = strjoin(dir, "xkb/", n, ".map");
533 pz = strjoin(dir, "xkb/", n, ".map.gz");
534 if (!p || !pz)
535 return -ENOMEM;
536
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);
541
542 *new_keymap = n;
543 n = NULL;
544 return 1;
545 }
546 }
547
548 return 0;
549 }
550
551 int find_legacy_keymap(Context *c, char **new_keymap) {
552 const char *map;
553 _cleanup_fclose_ FILE *f = NULL;
554 unsigned n = 0;
555 unsigned best_matching = 0;
556 int r;
557
558 assert(!isempty(c->x11_layout));
559
560 map = systemd_kbd_model_map();
561
562 f = fopen(map, "re");
563 if (!f)
564 return -errno;
565
566 for (;;) {
567 _cleanup_strv_free_ char **a = NULL;
568 unsigned matching = 0;
569
570 r = read_next_mapping(map, 5, UINT_MAX, f, &n, &a);
571 if (r < 0)
572 return r;
573 if (r == 0)
574 break;
575
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 */
579 matching = 10;
580 else {
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]))
585 matching = 5;
586 else {
587 char *x;
588
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))
593 matching = 1;
594 }
595 }
596
597 if (matching > 0) {
598 if (isempty(c->x11_model) || streq_ptr(c->x11_model, a[2])) {
599 matching++;
600
601 if (streq_ptr(c->x11_variant, a[3])) {
602 matching++;
603
604 if (streq_ptr(c->x11_options, a[4]))
605 matching++;
606 }
607 }
608 }
609
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",
613 a[0], matching);
614
615 if (matching > best_matching) {
616 best_matching = matching;
617
618 r = free_and_strdup(new_keymap, a[0]);
619 if (r < 0)
620 return r;
621 }
622 }
623 }
624
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.
629 */
630 char *l, *v = NULL, *converted;
631
632 l = strndupa(c->x11_layout, strcspn(c->x11_layout, ","));
633 if (c->x11_variant)
634 v = strndupa(c->x11_variant, strcspn(c->x11_variant, ","));
635 r = find_converted_keymap(l, v, &converted);
636 if (r < 0)
637 return r;
638 if (r > 0) {
639 free(*new_keymap);
640 *new_keymap = converted;
641 }
642 }
643
644 return (bool) *new_keymap;
645 }
646
647 int find_language_fallback(const char *lang, char **language) {
648 const char *map;
649 _cleanup_fclose_ FILE *f = NULL;
650 unsigned n = 0;
651
652 assert(lang);
653 assert(language);
654
655 map = systemd_language_fallback_map();
656
657 f = fopen(map, "re");
658 if (!f)
659 return -errno;
660
661 for (;;) {
662 _cleanup_strv_free_ char **a = NULL;
663 int r;
664
665 r = read_next_mapping(map, 2, 2, f, &n, &a);
666 if (r <= 0)
667 return r;
668
669 if (streq(lang, a[0])) {
670 assert(strv_length(a) == 2);
671 *language = a[1];
672 a[1] = NULL;
673 return 1;
674 }
675 }
676
677 assert_not_reached("should not be here");
678 }
679
680 int x11_convert_to_vconsole(Context *c) {
681 bool modified = false;
682
683 if (isempty(c->x11_layout)) {
684 modified =
685 !isempty(c->vc_keymap) ||
686 !isempty(c->vc_keymap_toggle);
687
688 context_free_vconsole(c);
689 } else {
690 char *new_keymap = NULL;
691 int r;
692
693 r = find_converted_keymap(c->x11_layout, c->x11_variant, &new_keymap);
694 if (r < 0)
695 return r;
696 else if (r == 0) {
697 r = find_legacy_keymap(c, &new_keymap);
698 if (r < 0)
699 return r;
700 }
701 if (r == 0)
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\".",
706 c->x11_layout);
707
708 if (!streq_ptr(c->vc_keymap, new_keymap)) {
709 free(c->vc_keymap);
710 c->vc_keymap = new_keymap;
711 c->vc_keymap_toggle = mfree(c->vc_keymap_toggle);
712 modified = true;
713 } else
714 free(new_keymap);
715 }
716
717 if (modified)
718 log_info("Changing virtual console keymap to '%s' toggle '%s'",
719 strempty(c->vc_keymap), strempty(c->vc_keymap_toggle));
720 else
721 log_debug("Virtual console keymap was not modified.");
722
723 return modified;
724 }