]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/locale/keymap-util.c
Merge pull request #6975 from sourcejedi/logind_pid_0_v2
[thirdparty/systemd.git] / src / locale / keymap-util.c
1 /***
2 This file is part of systemd.
3
4 Copyright 2011 Lennart Poettering
5 Copyright 2013 Kay Sievers
6
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19 ***/
20
21 #include <errno.h>
22 #include <string.h>
23 #include <unistd.h>
24
25 #include "def.h"
26 #include "env-util.h"
27 #include "fd-util.h"
28 #include "fileio-label.h"
29 #include "fileio.h"
30 #include "keymap-util.h"
31 #include "locale-util.h"
32 #include "macro.h"
33 #include "mkdir.h"
34 #include "string-util.h"
35 #include "strv.h"
36
37 static bool startswith_comma(const char *s, const char *prefix) {
38 s = startswith(s, prefix);
39 if (!s)
40 return false;
41
42 return IN_SET(*s, ',', '\0');
43 }
44
45 static const char* strnulldash(const char *s) {
46 return isempty(s) || streq(s, "-") ? NULL : s;
47 }
48
49 static const char* systemd_kbd_model_map(void) {
50 const char* s;
51
52 s = getenv("SYSTEMD_KBD_MODEL_MAP");
53 if (s)
54 return s;
55
56 return SYSTEMD_KBD_MODEL_MAP;
57 }
58
59 static const char* systemd_language_fallback_map(void) {
60 const char* s;
61
62 s = getenv("SYSTEMD_LANGUAGE_FALLBACK_MAP");
63 if (s)
64 return s;
65
66 return SYSTEMD_LANGUAGE_FALLBACK_MAP;
67 }
68
69 static void context_free_x11(Context *c) {
70 c->x11_layout = mfree(c->x11_layout);
71 c->x11_options = mfree(c->x11_options);
72 c->x11_model = mfree(c->x11_model);
73 c->x11_variant = mfree(c->x11_variant);
74 }
75
76 static void context_free_vconsole(Context *c) {
77 c->vc_keymap = mfree(c->vc_keymap);
78 c->vc_keymap_toggle = mfree(c->vc_keymap_toggle);
79 }
80
81 static void context_free_locale(Context *c) {
82 int p;
83
84 for (p = 0; p < _VARIABLE_LC_MAX; p++)
85 c->locale[p] = mfree(c->locale[p]);
86 }
87
88 void context_free(Context *c) {
89 context_free_locale(c);
90 context_free_x11(c);
91 context_free_vconsole(c);
92 };
93
94 void locale_simplify(Context *c) {
95 int p;
96
97 for (p = VARIABLE_LANG+1; p < _VARIABLE_LC_MAX; p++)
98 if (isempty(c->locale[p]) || streq_ptr(c->locale[VARIABLE_LANG], c->locale[p]))
99 c->locale[p] = mfree(c->locale[p]);
100 }
101
102 static int locale_read_data(Context *c) {
103 int r;
104
105 context_free_locale(c);
106
107 r = parse_env_file("/etc/locale.conf", NEWLINE,
108 "LANG", &c->locale[VARIABLE_LANG],
109 "LANGUAGE", &c->locale[VARIABLE_LANGUAGE],
110 "LC_CTYPE", &c->locale[VARIABLE_LC_CTYPE],
111 "LC_NUMERIC", &c->locale[VARIABLE_LC_NUMERIC],
112 "LC_TIME", &c->locale[VARIABLE_LC_TIME],
113 "LC_COLLATE", &c->locale[VARIABLE_LC_COLLATE],
114 "LC_MONETARY", &c->locale[VARIABLE_LC_MONETARY],
115 "LC_MESSAGES", &c->locale[VARIABLE_LC_MESSAGES],
116 "LC_PAPER", &c->locale[VARIABLE_LC_PAPER],
117 "LC_NAME", &c->locale[VARIABLE_LC_NAME],
118 "LC_ADDRESS", &c->locale[VARIABLE_LC_ADDRESS],
119 "LC_TELEPHONE", &c->locale[VARIABLE_LC_TELEPHONE],
120 "LC_MEASUREMENT", &c->locale[VARIABLE_LC_MEASUREMENT],
121 "LC_IDENTIFICATION", &c->locale[VARIABLE_LC_IDENTIFICATION],
122 NULL);
123
124 if (r == -ENOENT) {
125 int p;
126
127 /* Fill in what we got passed from systemd. */
128 for (p = 0; p < _VARIABLE_LC_MAX; p++) {
129 const char *name;
130
131 name = locale_variable_to_string(p);
132 assert(name);
133
134 r = free_and_strdup(&c->locale[p], empty_to_null(getenv(name)));
135 if (r < 0)
136 return r;
137 }
138
139 r = 0;
140 }
141
142 locale_simplify(c);
143 return r;
144 }
145
146 static int vconsole_read_data(Context *c) {
147 int r;
148
149 context_free_vconsole(c);
150
151 r = parse_env_file("/etc/vconsole.conf", NEWLINE,
152 "KEYMAP", &c->vc_keymap,
153 "KEYMAP_TOGGLE", &c->vc_keymap_toggle,
154 NULL);
155
156 if (r < 0 && r != -ENOENT)
157 return r;
158
159 return 0;
160 }
161
162 static int x11_read_data(Context *c) {
163 _cleanup_fclose_ FILE *f;
164 char line[LINE_MAX];
165 bool in_section = false;
166 int r;
167
168 context_free_x11(c);
169
170 f = fopen("/etc/X11/xorg.conf.d/00-keyboard.conf", "re");
171 if (!f)
172 return errno == ENOENT ? 0 : -errno;
173
174 while (fgets(line, sizeof(line), f)) {
175 char *l;
176
177 char_array_0(line);
178 l = strstrip(line);
179
180 if (IN_SET(l[0], 0, '#'))
181 continue;
182
183 if (in_section && first_word(l, "Option")) {
184 _cleanup_strv_free_ char **a = NULL;
185
186 r = strv_split_extract(&a, l, WHITESPACE, EXTRACT_QUOTES);
187 if (r < 0)
188 return r;
189
190 if (strv_length(a) == 3) {
191 char **p = NULL;
192
193 if (streq(a[1], "XkbLayout"))
194 p = &c->x11_layout;
195 else if (streq(a[1], "XkbModel"))
196 p = &c->x11_model;
197 else if (streq(a[1], "XkbVariant"))
198 p = &c->x11_variant;
199 else if (streq(a[1], "XkbOptions"))
200 p = &c->x11_options;
201
202 if (p) {
203 free(*p);
204 *p = a[2];
205 a[2] = NULL;
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
226 int 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
236 int 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
286 int 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
340 int 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
362 fchmod(fileno(f), 0644);
363
364 fputs_unlocked("# Written by systemd-localed(8), read by systemd-localed and Xorg. It's\n"
365 "# probably wise not to edit this file manually. Use localectl(1) to\n"
366 "# instruct systemd-localed to update it.\n"
367 "Section \"InputClass\"\n"
368 " Identifier \"system-keyboard\"\n"
369 " MatchIsKeyboard \"on\"\n", f);
370
371 if (!isempty(c->x11_layout))
372 fprintf(f, " Option \"XkbLayout\" \"%s\"\n", c->x11_layout);
373
374 if (!isempty(c->x11_model))
375 fprintf(f, " Option \"XkbModel\" \"%s\"\n", c->x11_model);
376
377 if (!isempty(c->x11_variant))
378 fprintf(f, " Option \"XkbVariant\" \"%s\"\n", c->x11_variant);
379
380 if (!isempty(c->x11_options))
381 fprintf(f, " Option \"XkbOptions\" \"%s\"\n", c->x11_options);
382
383 fputs_unlocked("EndSection\n", f);
384
385 r = fflush_sync_and_check(f);
386 if (r < 0)
387 goto fail;
388
389 if (rename(temp_path, "/etc/X11/xorg.conf.d/00-keyboard.conf") < 0) {
390 r = -errno;
391 goto fail;
392 }
393
394 return 0;
395
396 fail:
397 if (temp_path)
398 (void) unlink(temp_path);
399
400 return r;
401 }
402
403 static int read_next_mapping(const char* filename,
404 unsigned min_fields, unsigned max_fields,
405 FILE *f, unsigned *n, char ***a) {
406 assert(f);
407 assert(n);
408 assert(a);
409
410 for (;;) {
411 char line[LINE_MAX];
412 char *l, **b;
413 int r;
414 size_t length;
415
416 errno = 0;
417 if (!fgets(line, sizeof(line), f)) {
418
419 if (ferror(f))
420 return errno > 0 ? -errno : -EIO;
421
422 return 0;
423 }
424
425 (*n)++;
426
427 l = strstrip(line);
428 if (IN_SET(l[0], 0, '#'))
429 continue;
430
431 r = strv_split_extract(&b, l, WHITESPACE, EXTRACT_QUOTES);
432 if (r < 0)
433 return r;
434
435 length = strv_length(b);
436 if (length < min_fields || length > max_fields) {
437 log_error("Invalid line %s:%u, ignoring.", filename, *n);
438 strv_free(b);
439 continue;
440
441 }
442
443 *a = b;
444 return 1;
445 }
446 }
447
448 int vconsole_convert_to_x11(Context *c) {
449 const char *map;
450 int modified = -1;
451
452 map = systemd_kbd_model_map();
453
454 if (isempty(c->vc_keymap)) {
455 modified =
456 !isempty(c->x11_layout) ||
457 !isempty(c->x11_model) ||
458 !isempty(c->x11_variant) ||
459 !isempty(c->x11_options);
460
461 context_free_x11(c);
462 } else {
463 _cleanup_fclose_ FILE *f = NULL;
464 unsigned n = 0;
465
466 f = fopen(map, "re");
467 if (!f)
468 return -errno;
469
470 for (;;) {
471 _cleanup_strv_free_ char **a = NULL;
472 int r;
473
474 r = read_next_mapping(map, 5, UINT_MAX, f, &n, &a);
475 if (r < 0)
476 return r;
477 if (r == 0)
478 break;
479
480 if (!streq(c->vc_keymap, a[0]))
481 continue;
482
483 if (!streq_ptr(c->x11_layout, strnulldash(a[1])) ||
484 !streq_ptr(c->x11_model, strnulldash(a[2])) ||
485 !streq_ptr(c->x11_variant, strnulldash(a[3])) ||
486 !streq_ptr(c->x11_options, strnulldash(a[4]))) {
487
488 if (free_and_strdup(&c->x11_layout, strnulldash(a[1])) < 0 ||
489 free_and_strdup(&c->x11_model, strnulldash(a[2])) < 0 ||
490 free_and_strdup(&c->x11_variant, strnulldash(a[3])) < 0 ||
491 free_and_strdup(&c->x11_options, strnulldash(a[4])) < 0)
492 return -ENOMEM;
493
494 modified = true;
495 }
496
497 break;
498 }
499 }
500
501 if (modified > 0)
502 log_info("Changing X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
503 strempty(c->x11_layout),
504 strempty(c->x11_model),
505 strempty(c->x11_variant),
506 strempty(c->x11_options));
507 else if (modified < 0)
508 log_notice("X11 keyboard layout was not modified: no conversion found for \"%s\".",
509 c->vc_keymap);
510 else
511 log_debug("X11 keyboard layout did not need to be modified.");
512
513 return modified > 0;
514 }
515
516 int find_converted_keymap(const char *x11_layout, const char *x11_variant, char **new_keymap) {
517 const char *dir;
518 _cleanup_free_ char *n;
519
520 if (x11_variant)
521 n = strjoin(x11_layout, "-", x11_variant);
522 else
523 n = strdup(x11_layout);
524 if (!n)
525 return -ENOMEM;
526
527 NULSTR_FOREACH(dir, KBD_KEYMAP_DIRS) {
528 _cleanup_free_ char *p = NULL, *pz = NULL;
529 bool uncompressed;
530
531 p = strjoin(dir, "xkb/", n, ".map");
532 pz = strjoin(dir, "xkb/", n, ".map.gz");
533 if (!p || !pz)
534 return -ENOMEM;
535
536 uncompressed = access(p, F_OK) == 0;
537 if (uncompressed || access(pz, F_OK) == 0) {
538 log_debug("Found converted keymap %s at %s",
539 n, uncompressed ? p : pz);
540
541 *new_keymap = n;
542 n = NULL;
543 return 1;
544 }
545 }
546
547 return 0;
548 }
549
550 int find_legacy_keymap(Context *c, char **new_keymap) {
551 const char *map;
552 _cleanup_fclose_ FILE *f = NULL;
553 unsigned n = 0;
554 unsigned best_matching = 0;
555 int r;
556
557 assert(!isempty(c->x11_layout));
558
559 map = systemd_kbd_model_map();
560
561 f = fopen(map, "re");
562 if (!f)
563 return -errno;
564
565 for (;;) {
566 _cleanup_strv_free_ char **a = NULL;
567 unsigned matching = 0;
568
569 r = read_next_mapping(map, 5, UINT_MAX, f, &n, &a);
570 if (r < 0)
571 return r;
572 if (r == 0)
573 break;
574
575 /* Determine how well matching this entry is */
576 if (streq(c->x11_layout, a[1]))
577 /* If we got an exact match, this is best */
578 matching = 10;
579 else {
580 /* We have multiple X layouts, look for an
581 * entry that matches our key with everything
582 * but the first layout stripped off. */
583 if (startswith_comma(c->x11_layout, a[1]))
584 matching = 5;
585 else {
586 char *x;
587
588 /* If that didn't work, strip off the
589 * other layouts from the entry, too */
590 x = strndupa(a[1], strcspn(a[1], ","));
591 if (startswith_comma(c->x11_layout, x))
592 matching = 1;
593 }
594 }
595
596 if (matching > 0) {
597 if (isempty(c->x11_model) || streq_ptr(c->x11_model, a[2])) {
598 matching++;
599
600 if (streq_ptr(c->x11_variant, a[3])) {
601 matching++;
602
603 if (streq_ptr(c->x11_options, a[4]))
604 matching++;
605 }
606 }
607 }
608
609 /* The best matching entry so far, then let's save that */
610 if (matching >= MAX(best_matching, 1u)) {
611 log_debug("Found legacy keymap %s with score %u",
612 a[0], matching);
613
614 if (matching > best_matching) {
615 best_matching = matching;
616
617 r = free_and_strdup(new_keymap, a[0]);
618 if (r < 0)
619 return r;
620 }
621 }
622 }
623
624 if (best_matching < 10 && c->x11_layout) {
625 /* The best match is only the first part of the X11
626 * keymap. Check if we have a converted map which
627 * matches just the first layout.
628 */
629 char *l, *v = NULL, *converted;
630
631 l = strndupa(c->x11_layout, strcspn(c->x11_layout, ","));
632 if (c->x11_variant)
633 v = strndupa(c->x11_variant, strcspn(c->x11_variant, ","));
634 r = find_converted_keymap(l, v, &converted);
635 if (r < 0)
636 return r;
637 if (r > 0) {
638 free(*new_keymap);
639 *new_keymap = converted;
640 }
641 }
642
643 return (bool) *new_keymap;
644 }
645
646 int find_language_fallback(const char *lang, char **language) {
647 const char *map;
648 _cleanup_fclose_ FILE *f = NULL;
649 unsigned n = 0;
650
651 assert(lang);
652 assert(language);
653
654 map = systemd_language_fallback_map();
655
656 f = fopen(map, "re");
657 if (!f)
658 return -errno;
659
660 for (;;) {
661 _cleanup_strv_free_ char **a = NULL;
662 int r;
663
664 r = read_next_mapping(map, 2, 2, f, &n, &a);
665 if (r <= 0)
666 return r;
667
668 if (streq(lang, a[0])) {
669 assert(strv_length(a) == 2);
670 *language = a[1];
671 a[1] = NULL;
672 return 1;
673 }
674 }
675
676 assert_not_reached("should not be here");
677 }
678
679 int x11_convert_to_vconsole(Context *c) {
680 bool modified = false;
681
682 if (isempty(c->x11_layout)) {
683 modified =
684 !isempty(c->vc_keymap) ||
685 !isempty(c->vc_keymap_toggle);
686
687 context_free_vconsole(c);
688 } else {
689 char *new_keymap = NULL;
690 int r;
691
692 r = find_converted_keymap(c->x11_layout, c->x11_variant, &new_keymap);
693 if (r < 0)
694 return r;
695 else if (r == 0) {
696 r = find_legacy_keymap(c, &new_keymap);
697 if (r < 0)
698 return r;
699 }
700 if (r == 0)
701 /* We search for layout-variant match first, but then we also look
702 * for anything which matches just the layout. So it's accurate to say
703 * that we couldn't find anything which matches the layout. */
704 log_notice("No conversion to virtual console map found for \"%s\".",
705 c->x11_layout);
706
707 if (!streq_ptr(c->vc_keymap, new_keymap)) {
708 free(c->vc_keymap);
709 c->vc_keymap = new_keymap;
710 c->vc_keymap_toggle = mfree(c->vc_keymap_toggle);
711 modified = true;
712 } else
713 free(new_keymap);
714 }
715
716 if (modified)
717 log_info("Changing virtual console keymap to '%s' toggle '%s'",
718 strempty(c->vc_keymap), strempty(c->vc_keymap_toggle));
719 else
720 log_debug("Virtual console keymap was not modified.");
721
722 return modified;
723 }