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