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