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