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