]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/locale/keymap-util.c
tree-wide: drop NULL sentinel from strjoin
[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 *s == ',' || *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 (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
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("# Read and parsed by systemd-localed. It's probably wise not to edit this file\n"
365 "# manually too freely.\n"
366 "Section \"InputClass\"\n"
367 " Identifier \"system-keyboard\"\n"
368 " MatchIsKeyboard \"on\"\n", f);
369
370 if (!isempty(c->x11_layout))
371 fprintf(f, " Option \"XkbLayout\" \"%s\"\n", c->x11_layout);
372
373 if (!isempty(c->x11_model))
374 fprintf(f, " Option \"XkbModel\" \"%s\"\n", c->x11_model);
375
376 if (!isempty(c->x11_variant))
377 fprintf(f, " Option \"XkbVariant\" \"%s\"\n", c->x11_variant);
378
379 if (!isempty(c->x11_options))
380 fprintf(f, " Option \"XkbOptions\" \"%s\"\n", c->x11_options);
381
382 fputs("EndSection\n", f);
383
384 r = fflush_and_check(f);
385 if (r < 0)
386 goto fail;
387
388 if (rename(temp_path, "/etc/X11/xorg.conf.d/00-keyboard.conf") < 0) {
389 r = -errno;
390 goto fail;
391 }
392
393 return 0;
394
395 fail:
396 (void) unlink("/etc/X11/xorg.conf.d/00-keyboard.conf");
397
398 if (temp_path)
399 (void) unlink(temp_path);
400
401 return r;
402 }
403
404 static 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);
429 if (l[0] == 0 || l[0] == '#')
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
449 int vconsole_convert_to_x11(Context *c) {
450 const char *map;
451 int modified = -1;
452
453 map = systemd_kbd_model_map();
454
455 if (isempty(c->vc_keymap)) {
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
467 f = fopen(map, "re");
468 if (!f)
469 return -errno;
470
471 for (;;) {
472 _cleanup_strv_free_ char **a = NULL;
473 int r;
474
475 r = read_next_mapping(map, 5, UINT_MAX, f, &n, &a);
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
502 if (modified > 0)
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));
508 else if (modified < 0)
509 log_notice("X11 keyboard layout was not modified: no conversion found for \"%s\".",
510 c->vc_keymap);
511 else
512 log_debug("X11 keyboard layout did not need to be modified.");
513
514 return modified > 0;
515 }
516
517 int 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)
522 n = strjoin(x11_layout, "-", x11_variant);
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
532 p = strjoin(dir, "xkb/", n, ".map");
533 pz = strjoin(dir, "xkb/", n, ".map.gz");
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
551 int find_legacy_keymap(Context *c, char **new_keymap) {
552 const char *map;
553 _cleanup_fclose_ FILE *f = NULL;
554 unsigned n = 0;
555 unsigned best_matching = 0;
556 int r;
557
558 assert(!isempty(c->x11_layout));
559
560 map = systemd_kbd_model_map();
561
562 f = fopen(map, "re");
563 if (!f)
564 return -errno;
565
566 for (;;) {
567 _cleanup_strv_free_ char **a = NULL;
568 unsigned matching = 0;
569
570 r = read_next_mapping(map, 5, UINT_MAX, f, &n, &a);
571 if (r < 0)
572 return r;
573 if (r == 0)
574 break;
575
576 /* Determine how well matching this entry is */
577 if (streq(c->x11_layout, a[1]))
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
644 return (bool) *new_keymap;
645 }
646
647 int find_language_fallback(const char *lang, char **language) {
648 const char *map;
649 _cleanup_fclose_ FILE *f = NULL;
650 unsigned n = 0;
651
652 assert(lang);
653 assert(language);
654
655 map = systemd_language_fallback_map();
656
657 f = fopen(map, "re");
658 if (!f)
659 return -errno;
660
661 for (;;) {
662 _cleanup_strv_free_ char **a = NULL;
663 int r;
664
665 r = read_next_mapping(map, 2, 2, f, &n, &a);
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
680 int x11_convert_to_vconsole(Context *c) {
681 bool modified = false;
682
683 if (isempty(c->x11_layout)) {
684 modified =
685 !isempty(c->vc_keymap) ||
686 !isempty(c->vc_keymap_toggle);
687
688 context_free_vconsole(c);
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 }
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);
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 }