]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/locale/keymap-util.c
localed: don't remove xorg.conf.d/00-keyboard.conf on failures
[thirdparty/systemd.git] / src / locale / keymap-util.c
CommitLineData
4897d1dc
ZJS
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
37static bool startswith_comma(const char *s, const char *prefix) {
5ad327dd
ZJS
38 s = startswith(s, prefix);
39 if (!s)
40 return false;
4897d1dc 41
03a44125 42 return *s == ',' || *s == '\0';
4897d1dc
ZJS
43}
44
45static const char* strnulldash(const char *s) {
46 return isempty(s) || streq(s, "-") ? NULL : s;
47}
48
cabffaf8
ZJS
49static 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
59static 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
4897d1dc
ZJS
69static 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
76static 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
81static 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
88void context_free(Context *c) {
89 context_free_locale(c);
90 context_free_x11(c);
91 context_free_vconsole(c);
92};
93
94void 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
102static 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
146static 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
162static 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 (l[0] == 0 || l[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
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
362 fchmod(fileno(f), 0644);
363
4b61c875
LP
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);
4897d1dc
ZJS
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
4b61c875 383 fputs_unlocked("EndSection\n", f);
4897d1dc
ZJS
384
385 r = fflush_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
396fail:
4897d1dc
ZJS
397 if (temp_path)
398 (void) unlink(temp_path);
399
400 return r;
401}
402
403static 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 (l[0] == 0 || l[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
448int vconsole_convert_to_x11(Context *c) {
cabffaf8 449 const char *map;
6f3287b3 450 int modified = -1;
4897d1dc 451
cabffaf8
ZJS
452 map = systemd_kbd_model_map();
453
4897d1dc 454 if (isempty(c->vc_keymap)) {
4897d1dc
ZJS
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
cabffaf8 466 f = fopen(map, "re");
4897d1dc
ZJS
467 if (!f)
468 return -errno;
469
470 for (;;) {
471 _cleanup_strv_free_ char **a = NULL;
472 int r;
473
cabffaf8 474 r = read_next_mapping(map, 5, UINT_MAX, f, &n, &a);
4897d1dc
ZJS
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
6f3287b3 501 if (modified > 0)
4897d1dc
ZJS
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));
6f3287b3
ZJS
507 else if (modified < 0)
508 log_notice("X11 keyboard layout was not modified: no conversion found for \"%s\".",
509 c->vc_keymap);
4897d1dc 510 else
6f3287b3 511 log_debug("X11 keyboard layout did not need to be modified.");
4897d1dc 512
6f3287b3 513 return modified > 0;
4897d1dc
ZJS
514}
515
516int 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)
605405c6 521 n = strjoin(x11_layout, "-", x11_variant);
4897d1dc
ZJS
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
605405c6
ZJS
531 p = strjoin(dir, "xkb/", n, ".map");
532 pz = strjoin(dir, "xkb/", n, ".map.gz");
4897d1dc
ZJS
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
aa63b56f 550int find_legacy_keymap(Context *c, char **new_keymap) {
cabffaf8
ZJS
551 const char *map;
552 _cleanup_fclose_ FILE *f = NULL;
4897d1dc
ZJS
553 unsigned n = 0;
554 unsigned best_matching = 0;
555 int r;
556
5ad327dd
ZJS
557 assert(!isempty(c->x11_layout));
558
cabffaf8
ZJS
559 map = systemd_kbd_model_map();
560
561 f = fopen(map, "re");
4897d1dc
ZJS
562 if (!f)
563 return -errno;
564
565 for (;;) {
566 _cleanup_strv_free_ char **a = NULL;
567 unsigned matching = 0;
568
cabffaf8 569 r = read_next_mapping(map, 5, UINT_MAX, f, &n, &a);
4897d1dc
ZJS
570 if (r < 0)
571 return r;
572 if (r == 0)
573 break;
574
575 /* Determine how well matching this entry is */
5ad327dd 576 if (streq(c->x11_layout, a[1]))
4897d1dc
ZJS
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
5ad327dd 643 return (bool) *new_keymap;
4897d1dc
ZJS
644}
645
646int find_language_fallback(const char *lang, char **language) {
cabffaf8 647 const char *map;
4897d1dc
ZJS
648 _cleanup_fclose_ FILE *f = NULL;
649 unsigned n = 0;
650
aa63b56f 651 assert(lang);
4897d1dc
ZJS
652 assert(language);
653
cabffaf8
ZJS
654 map = systemd_language_fallback_map();
655
656 f = fopen(map, "re");
4897d1dc
ZJS
657 if (!f)
658 return -errno;
659
660 for (;;) {
661 _cleanup_strv_free_ char **a = NULL;
662 int r;
663
cabffaf8 664 r = read_next_mapping(map, 2, 2, f, &n, &a);
4897d1dc
ZJS
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
679int x11_convert_to_vconsole(Context *c) {
680 bool modified = false;
681
682 if (isempty(c->x11_layout)) {
4897d1dc
ZJS
683 modified =
684 !isempty(c->vc_keymap) ||
685 !isempty(c->vc_keymap_toggle);
686
aa63b56f 687 context_free_vconsole(c);
4897d1dc
ZJS
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 }
5ad327dd
ZJS
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);
4897d1dc
ZJS
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}