]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/locale/keymap-util.c
man/udevadm: remove superfluous --version from subcommands (#8549)
[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>
0d536673 23#include <stdio_ext.h>
4897d1dc
ZJS
24#include <string.h>
25#include <unistd.h>
26
27#include "def.h"
28#include "env-util.h"
29#include "fd-util.h"
30#include "fileio-label.h"
31#include "fileio.h"
32#include "keymap-util.h"
33#include "locale-util.h"
34#include "macro.h"
35#include "mkdir.h"
36#include "string-util.h"
37#include "strv.h"
38
39static bool startswith_comma(const char *s, const char *prefix) {
5ad327dd
ZJS
40 s = startswith(s, prefix);
41 if (!s)
42 return false;
4897d1dc 43
4c701096 44 return IN_SET(*s, ',', '\0');
4897d1dc
ZJS
45}
46
47static const char* strnulldash(const char *s) {
48 return isempty(s) || streq(s, "-") ? NULL : s;
49}
50
cabffaf8
ZJS
51static const char* systemd_kbd_model_map(void) {
52 const char* s;
53
54 s = getenv("SYSTEMD_KBD_MODEL_MAP");
55 if (s)
56 return s;
57
58 return SYSTEMD_KBD_MODEL_MAP;
59}
60
61static const char* systemd_language_fallback_map(void) {
62 const char* s;
63
64 s = getenv("SYSTEMD_LANGUAGE_FALLBACK_MAP");
65 if (s)
66 return s;
67
68 return SYSTEMD_LANGUAGE_FALLBACK_MAP;
69}
70
4897d1dc
ZJS
71static void context_free_x11(Context *c) {
72 c->x11_layout = mfree(c->x11_layout);
73 c->x11_options = mfree(c->x11_options);
74 c->x11_model = mfree(c->x11_model);
75 c->x11_variant = mfree(c->x11_variant);
76}
77
78static void context_free_vconsole(Context *c) {
79 c->vc_keymap = mfree(c->vc_keymap);
80 c->vc_keymap_toggle = mfree(c->vc_keymap_toggle);
81}
82
83static void context_free_locale(Context *c) {
84 int p;
85
86 for (p = 0; p < _VARIABLE_LC_MAX; p++)
87 c->locale[p] = mfree(c->locale[p]);
88}
89
90void context_free(Context *c) {
91 context_free_locale(c);
92 context_free_x11(c);
93 context_free_vconsole(c);
94};
95
96void locale_simplify(Context *c) {
97 int p;
98
99 for (p = VARIABLE_LANG+1; p < _VARIABLE_LC_MAX; p++)
100 if (isempty(c->locale[p]) || streq_ptr(c->locale[VARIABLE_LANG], c->locale[p]))
101 c->locale[p] = mfree(c->locale[p]);
102}
103
104static int locale_read_data(Context *c) {
105 int r;
106
107 context_free_locale(c);
108
109 r = parse_env_file("/etc/locale.conf", NEWLINE,
110 "LANG", &c->locale[VARIABLE_LANG],
111 "LANGUAGE", &c->locale[VARIABLE_LANGUAGE],
112 "LC_CTYPE", &c->locale[VARIABLE_LC_CTYPE],
113 "LC_NUMERIC", &c->locale[VARIABLE_LC_NUMERIC],
114 "LC_TIME", &c->locale[VARIABLE_LC_TIME],
115 "LC_COLLATE", &c->locale[VARIABLE_LC_COLLATE],
116 "LC_MONETARY", &c->locale[VARIABLE_LC_MONETARY],
117 "LC_MESSAGES", &c->locale[VARIABLE_LC_MESSAGES],
118 "LC_PAPER", &c->locale[VARIABLE_LC_PAPER],
119 "LC_NAME", &c->locale[VARIABLE_LC_NAME],
120 "LC_ADDRESS", &c->locale[VARIABLE_LC_ADDRESS],
121 "LC_TELEPHONE", &c->locale[VARIABLE_LC_TELEPHONE],
122 "LC_MEASUREMENT", &c->locale[VARIABLE_LC_MEASUREMENT],
123 "LC_IDENTIFICATION", &c->locale[VARIABLE_LC_IDENTIFICATION],
124 NULL);
125
126 if (r == -ENOENT) {
127 int p;
128
129 /* Fill in what we got passed from systemd. */
130 for (p = 0; p < _VARIABLE_LC_MAX; p++) {
131 const char *name;
132
133 name = locale_variable_to_string(p);
134 assert(name);
135
136 r = free_and_strdup(&c->locale[p], empty_to_null(getenv(name)));
137 if (r < 0)
138 return r;
139 }
140
141 r = 0;
142 }
143
144 locale_simplify(c);
145 return r;
146}
147
148static int vconsole_read_data(Context *c) {
149 int r;
150
151 context_free_vconsole(c);
152
153 r = parse_env_file("/etc/vconsole.conf", NEWLINE,
154 "KEYMAP", &c->vc_keymap,
155 "KEYMAP_TOGGLE", &c->vc_keymap_toggle,
156 NULL);
157
158 if (r < 0 && r != -ENOENT)
159 return r;
160
161 return 0;
162}
163
164static int x11_read_data(Context *c) {
165 _cleanup_fclose_ FILE *f;
166 char line[LINE_MAX];
167 bool in_section = false;
168 int r;
169
170 context_free_x11(c);
171
172 f = fopen("/etc/X11/xorg.conf.d/00-keyboard.conf", "re");
173 if (!f)
174 return errno == ENOENT ? 0 : -errno;
175
176 while (fgets(line, sizeof(line), f)) {
177 char *l;
178
179 char_array_0(line);
180 l = strstrip(line);
181
4c701096 182 if (IN_SET(l[0], 0, '#'))
4897d1dc
ZJS
183 continue;
184
185 if (in_section && first_word(l, "Option")) {
186 _cleanup_strv_free_ char **a = NULL;
187
188 r = strv_split_extract(&a, l, WHITESPACE, EXTRACT_QUOTES);
189 if (r < 0)
190 return r;
191
192 if (strv_length(a) == 3) {
193 char **p = NULL;
194
195 if (streq(a[1], "XkbLayout"))
196 p = &c->x11_layout;
197 else if (streq(a[1], "XkbModel"))
198 p = &c->x11_model;
199 else if (streq(a[1], "XkbVariant"))
200 p = &c->x11_variant;
201 else if (streq(a[1], "XkbOptions"))
202 p = &c->x11_options;
203
204 if (p) {
f9ecfd3b 205 free_and_replace(*p, a[2]);
4897d1dc
ZJS
206 }
207 }
208
209 } else if (!in_section && first_word(l, "Section")) {
210 _cleanup_strv_free_ char **a = NULL;
211
212 r = strv_split_extract(&a, l, WHITESPACE, EXTRACT_QUOTES);
213 if (r < 0)
214 return -ENOMEM;
215
216 if (strv_length(a) == 2 && streq(a[1], "InputClass"))
217 in_section = true;
218
219 } else if (in_section && first_word(l, "EndSection"))
220 in_section = false;
221 }
222
223 return 0;
224}
225
226int context_read_data(Context *c) {
227 int r, q, p;
228
229 r = locale_read_data(c);
230 q = vconsole_read_data(c);
231 p = x11_read_data(c);
232
233 return r < 0 ? r : q < 0 ? q : p;
234}
235
236int locale_write_data(Context *c, char ***settings) {
237 int r, p;
238 _cleanup_strv_free_ char **l = NULL;
239
240 /* Set values will be returned as strv in *settings on success. */
241
242 r = load_env_file(NULL, "/etc/locale.conf", NULL, &l);
243 if (r < 0 && r != -ENOENT)
244 return r;
245
246 for (p = 0; p < _VARIABLE_LC_MAX; p++) {
247 _cleanup_free_ char *t = NULL;
248 char **u;
249 const char *name;
250
251 name = locale_variable_to_string(p);
252 assert(name);
253
254 if (isempty(c->locale[p])) {
255 l = strv_env_unset(l, name);
256 continue;
257 }
258
259 if (asprintf(&t, "%s=%s", name, c->locale[p]) < 0)
260 return -ENOMEM;
261
262 u = strv_env_set(l, t);
263 if (!u)
264 return -ENOMEM;
265
266 strv_free(l);
267 l = u;
268 }
269
270 if (strv_isempty(l)) {
271 if (unlink("/etc/locale.conf") < 0)
272 return errno == ENOENT ? 0 : -errno;
273
274 return 0;
275 }
276
277 r = write_env_file_label("/etc/locale.conf", l);
278 if (r < 0)
279 return r;
280
281 *settings = l;
282 l = NULL;
283 return 0;
284}
285
286int vconsole_write_data(Context *c) {
287 int r;
288 _cleanup_strv_free_ char **l = NULL;
289
290 r = load_env_file(NULL, "/etc/vconsole.conf", NULL, &l);
291 if (r < 0 && r != -ENOENT)
292 return r;
293
294 if (isempty(c->vc_keymap))
295 l = strv_env_unset(l, "KEYMAP");
296 else {
297 _cleanup_free_ char *s = NULL;
298 char **u;
299
300 s = strappend("KEYMAP=", c->vc_keymap);
301 if (!s)
302 return -ENOMEM;
303
304 u = strv_env_set(l, s);
305 if (!u)
306 return -ENOMEM;
307
308 strv_free(l);
309 l = u;
310 }
311
312 if (isempty(c->vc_keymap_toggle))
313 l = strv_env_unset(l, "KEYMAP_TOGGLE");
314 else {
315 _cleanup_free_ char *s = NULL;
316 char **u;
317
318 s = strappend("KEYMAP_TOGGLE=", c->vc_keymap_toggle);
319 if (!s)
320 return -ENOMEM;
321
322 u = strv_env_set(l, s);
323 if (!u)
324 return -ENOMEM;
325
326 strv_free(l);
327 l = u;
328 }
329
330 if (strv_isempty(l)) {
331 if (unlink("/etc/vconsole.conf") < 0)
332 return errno == ENOENT ? 0 : -errno;
333
334 return 0;
335 }
336
337 return write_env_file_label("/etc/vconsole.conf", l);
338}
339
340int x11_write_data(Context *c) {
341 _cleanup_fclose_ FILE *f = NULL;
342 _cleanup_free_ char *temp_path = NULL;
343 int r;
344
345 if (isempty(c->x11_layout) &&
346 isempty(c->x11_model) &&
347 isempty(c->x11_variant) &&
348 isempty(c->x11_options)) {
349
350 if (unlink("/etc/X11/xorg.conf.d/00-keyboard.conf") < 0)
351 return errno == ENOENT ? 0 : -errno;
352
353 return 0;
354 }
355
356 mkdir_p_label("/etc/X11/xorg.conf.d", 0755);
357
358 r = fopen_temporary("/etc/X11/xorg.conf.d/00-keyboard.conf", &f, &temp_path);
359 if (r < 0)
360 return r;
361
0d536673
LP
362 (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
363 (void) fchmod(fileno(f), 0644);
4897d1dc 364
0d536673
LP
365 fputs("# 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);
4897d1dc
ZJS
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
0d536673 384 fputs("EndSection\n", f);
4897d1dc 385
0675e94a 386 r = fflush_sync_and_check(f);
4897d1dc
ZJS
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
397fail:
4897d1dc
ZJS
398 if (temp_path)
399 (void) unlink(temp_path);
400
401 return r;
402}
403
404static 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);
4c701096 429 if (IN_SET(l[0], 0, '#'))
4897d1dc
ZJS
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
449int vconsole_convert_to_x11(Context *c) {
cabffaf8 450 const char *map;
6f3287b3 451 int modified = -1;
4897d1dc 452
cabffaf8
ZJS
453 map = systemd_kbd_model_map();
454
4897d1dc 455 if (isempty(c->vc_keymap)) {
4897d1dc
ZJS
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
cabffaf8 467 f = fopen(map, "re");
4897d1dc
ZJS
468 if (!f)
469 return -errno;
470
471 for (;;) {
472 _cleanup_strv_free_ char **a = NULL;
473 int r;
474
cabffaf8 475 r = read_next_mapping(map, 5, UINT_MAX, f, &n, &a);
4897d1dc
ZJS
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
6f3287b3 502 if (modified > 0)
4897d1dc
ZJS
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));
6f3287b3
ZJS
508 else if (modified < 0)
509 log_notice("X11 keyboard layout was not modified: no conversion found for \"%s\".",
510 c->vc_keymap);
4897d1dc 511 else
6f3287b3 512 log_debug("X11 keyboard layout did not need to be modified.");
4897d1dc 513
6f3287b3 514 return modified > 0;
4897d1dc
ZJS
515}
516
517int 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)
605405c6 522 n = strjoin(x11_layout, "-", x11_variant);
4897d1dc
ZJS
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
605405c6
ZJS
532 p = strjoin(dir, "xkb/", n, ".map");
533 pz = strjoin(dir, "xkb/", n, ".map.gz");
4897d1dc
ZJS
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
aa63b56f 551int find_legacy_keymap(Context *c, char **new_keymap) {
cabffaf8
ZJS
552 const char *map;
553 _cleanup_fclose_ FILE *f = NULL;
4897d1dc
ZJS
554 unsigned n = 0;
555 unsigned best_matching = 0;
556 int r;
557
5ad327dd
ZJS
558 assert(!isempty(c->x11_layout));
559
cabffaf8
ZJS
560 map = systemd_kbd_model_map();
561
562 f = fopen(map, "re");
4897d1dc
ZJS
563 if (!f)
564 return -errno;
565
566 for (;;) {
567 _cleanup_strv_free_ char **a = NULL;
568 unsigned matching = 0;
569
cabffaf8 570 r = read_next_mapping(map, 5, UINT_MAX, f, &n, &a);
4897d1dc
ZJS
571 if (r < 0)
572 return r;
573 if (r == 0)
574 break;
575
576 /* Determine how well matching this entry is */
5ad327dd 577 if (streq(c->x11_layout, a[1]))
4897d1dc
ZJS
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
5ad327dd 644 return (bool) *new_keymap;
4897d1dc
ZJS
645}
646
647int find_language_fallback(const char *lang, char **language) {
cabffaf8 648 const char *map;
4897d1dc
ZJS
649 _cleanup_fclose_ FILE *f = NULL;
650 unsigned n = 0;
651
aa63b56f 652 assert(lang);
4897d1dc
ZJS
653 assert(language);
654
cabffaf8
ZJS
655 map = systemd_language_fallback_map();
656
657 f = fopen(map, "re");
4897d1dc
ZJS
658 if (!f)
659 return -errno;
660
661 for (;;) {
662 _cleanup_strv_free_ char **a = NULL;
663 int r;
664
cabffaf8 665 r = read_next_mapping(map, 2, 2, f, &n, &a);
4897d1dc
ZJS
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
680int x11_convert_to_vconsole(Context *c) {
681 bool modified = false;
682
683 if (isempty(c->x11_layout)) {
4897d1dc
ZJS
684 modified =
685 !isempty(c->vc_keymap) ||
686 !isempty(c->vc_keymap_toggle);
687
aa63b56f 688 context_free_vconsole(c);
4897d1dc
ZJS
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 }
5ad327dd
ZJS
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);
4897d1dc
ZJS
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}