]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/locale/keymap-util.c
Merge pull request #7625 from thom311/th/const-strlen
[thirdparty/systemd.git] / src / locale / keymap-util.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
4897d1dc
ZJS
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
38static bool startswith_comma(const char *s, const char *prefix) {
5ad327dd
ZJS
39 s = startswith(s, prefix);
40 if (!s)
41 return false;
4897d1dc 42
4c701096 43 return IN_SET(*s, ',', '\0');
4897d1dc
ZJS
44}
45
46static const char* strnulldash(const char *s) {
47 return isempty(s) || streq(s, "-") ? NULL : s;
48}
49
cabffaf8
ZJS
50static 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
60static 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
4897d1dc
ZJS
70static 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
77static 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
82static 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
89void context_free(Context *c) {
90 context_free_locale(c);
91 context_free_x11(c);
92 context_free_vconsole(c);
93};
94
95void 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
103static 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
147static 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
163static 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
4c701096 181 if (IN_SET(l[0], 0, '#'))
4897d1dc
ZJS
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) {
f9ecfd3b 204 free_and_replace(*p, a[2]);
4897d1dc
ZJS
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
225int 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
235int 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
285int 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
339int 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
4b61c875
LP
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);
4897d1dc
ZJS
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
4b61c875 382 fputs_unlocked("EndSection\n", f);
4897d1dc 383
0675e94a 384 r = fflush_sync_and_check(f);
4897d1dc
ZJS
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
395fail:
4897d1dc
ZJS
396 if (temp_path)
397 (void) unlink(temp_path);
398
399 return r;
400}
401
402static 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);
4c701096 427 if (IN_SET(l[0], 0, '#'))
4897d1dc
ZJS
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
447int vconsole_convert_to_x11(Context *c) {
cabffaf8 448 const char *map;
6f3287b3 449 int modified = -1;
4897d1dc 450
cabffaf8
ZJS
451 map = systemd_kbd_model_map();
452
4897d1dc 453 if (isempty(c->vc_keymap)) {
4897d1dc
ZJS
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
cabffaf8 465 f = fopen(map, "re");
4897d1dc
ZJS
466 if (!f)
467 return -errno;
468
469 for (;;) {
470 _cleanup_strv_free_ char **a = NULL;
471 int r;
472
cabffaf8 473 r = read_next_mapping(map, 5, UINT_MAX, f, &n, &a);
4897d1dc
ZJS
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
6f3287b3 500 if (modified > 0)
4897d1dc
ZJS
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));
6f3287b3
ZJS
506 else if (modified < 0)
507 log_notice("X11 keyboard layout was not modified: no conversion found for \"%s\".",
508 c->vc_keymap);
4897d1dc 509 else
6f3287b3 510 log_debug("X11 keyboard layout did not need to be modified.");
4897d1dc 511
6f3287b3 512 return modified > 0;
4897d1dc
ZJS
513}
514
515int 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)
605405c6 520 n = strjoin(x11_layout, "-", x11_variant);
4897d1dc
ZJS
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
605405c6
ZJS
530 p = strjoin(dir, "xkb/", n, ".map");
531 pz = strjoin(dir, "xkb/", n, ".map.gz");
4897d1dc
ZJS
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
aa63b56f 549int find_legacy_keymap(Context *c, char **new_keymap) {
cabffaf8
ZJS
550 const char *map;
551 _cleanup_fclose_ FILE *f = NULL;
4897d1dc
ZJS
552 unsigned n = 0;
553 unsigned best_matching = 0;
554 int r;
555
5ad327dd
ZJS
556 assert(!isempty(c->x11_layout));
557
cabffaf8
ZJS
558 map = systemd_kbd_model_map();
559
560 f = fopen(map, "re");
4897d1dc
ZJS
561 if (!f)
562 return -errno;
563
564 for (;;) {
565 _cleanup_strv_free_ char **a = NULL;
566 unsigned matching = 0;
567
cabffaf8 568 r = read_next_mapping(map, 5, UINT_MAX, f, &n, &a);
4897d1dc
ZJS
569 if (r < 0)
570 return r;
571 if (r == 0)
572 break;
573
574 /* Determine how well matching this entry is */
5ad327dd 575 if (streq(c->x11_layout, a[1]))
4897d1dc
ZJS
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
5ad327dd 642 return (bool) *new_keymap;
4897d1dc
ZJS
643}
644
645int find_language_fallback(const char *lang, char **language) {
cabffaf8 646 const char *map;
4897d1dc
ZJS
647 _cleanup_fclose_ FILE *f = NULL;
648 unsigned n = 0;
649
aa63b56f 650 assert(lang);
4897d1dc
ZJS
651 assert(language);
652
cabffaf8
ZJS
653 map = systemd_language_fallback_map();
654
655 f = fopen(map, "re");
4897d1dc
ZJS
656 if (!f)
657 return -errno;
658
659 for (;;) {
660 _cleanup_strv_free_ char **a = NULL;
661 int r;
662
cabffaf8 663 r = read_next_mapping(map, 2, 2, f, &n, &a);
4897d1dc
ZJS
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
678int x11_convert_to_vconsole(Context *c) {
679 bool modified = false;
680
681 if (isempty(c->x11_layout)) {
4897d1dc
ZJS
682 modified =
683 !isempty(c->vc_keymap) ||
684 !isempty(c->vc_keymap_toggle);
685
aa63b56f 686 context_free_vconsole(c);
4897d1dc
ZJS
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 }
5ad327dd
ZJS
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);
4897d1dc
ZJS
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}