]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/locale/localed.c
localed: add LANGUAGE= fallback when LANG= is specified
[thirdparty/systemd.git] / src / locale / localed.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4 This file is part of systemd.
5
6 Copyright 2011 Lennart Poettering
7 Copyright 2013 Kay Sievers
8
9 systemd is free software; you can redistribute it and/or modify it
10 under the terms of the GNU Lesser General Public License as published by
11 the Free Software Foundation; either version 2.1 of the License, or
12 (at your option) any later version.
13
14 systemd is distributed in the hope that it will be useful, but
15 WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 Lesser General Public License for more details.
18
19 You should have received a copy of the GNU Lesser General Public License
20 along with systemd; If not, see <http://www.gnu.org/licenses/>.
21 ***/
22
23 #include <errno.h>
24 #include <string.h>
25 #include <unistd.h>
26
27 #include "sd-bus.h"
28
29 #include "util.h"
30 #include "mkdir.h"
31 #include "strv.h"
32 #include "def.h"
33 #include "env-util.h"
34 #include "fileio.h"
35 #include "fileio-label.h"
36 #include "label.h"
37 #include "bus-util.h"
38 #include "bus-error.h"
39 #include "bus-message.h"
40 #include "event-util.h"
41 #include "locale-util.h"
42 #include "selinux-util.h"
43
44 #ifdef HAVE_XKBCOMMON
45 #include <xkbcommon/xkbcommon.h>
46 #endif
47
48 enum {
49 /* We don't list LC_ALL here on purpose. People should be
50 * using LANG instead. */
51 LOCALE_LANG,
52 LOCALE_LANGUAGE,
53 LOCALE_LC_CTYPE,
54 LOCALE_LC_NUMERIC,
55 LOCALE_LC_TIME,
56 LOCALE_LC_COLLATE,
57 LOCALE_LC_MONETARY,
58 LOCALE_LC_MESSAGES,
59 LOCALE_LC_PAPER,
60 LOCALE_LC_NAME,
61 LOCALE_LC_ADDRESS,
62 LOCALE_LC_TELEPHONE,
63 LOCALE_LC_MEASUREMENT,
64 LOCALE_LC_IDENTIFICATION,
65 _LOCALE_MAX
66 };
67
68 static const char * const names[_LOCALE_MAX] = {
69 [LOCALE_LANG] = "LANG",
70 [LOCALE_LANGUAGE] = "LANGUAGE",
71 [LOCALE_LC_CTYPE] = "LC_CTYPE",
72 [LOCALE_LC_NUMERIC] = "LC_NUMERIC",
73 [LOCALE_LC_TIME] = "LC_TIME",
74 [LOCALE_LC_COLLATE] = "LC_COLLATE",
75 [LOCALE_LC_MONETARY] = "LC_MONETARY",
76 [LOCALE_LC_MESSAGES] = "LC_MESSAGES",
77 [LOCALE_LC_PAPER] = "LC_PAPER",
78 [LOCALE_LC_NAME] = "LC_NAME",
79 [LOCALE_LC_ADDRESS] = "LC_ADDRESS",
80 [LOCALE_LC_TELEPHONE] = "LC_TELEPHONE",
81 [LOCALE_LC_MEASUREMENT] = "LC_MEASUREMENT",
82 [LOCALE_LC_IDENTIFICATION] = "LC_IDENTIFICATION"
83 };
84
85 typedef struct Context {
86 char *locale[_LOCALE_MAX];
87
88 char *x11_layout;
89 char *x11_model;
90 char *x11_variant;
91 char *x11_options;
92
93 char *vc_keymap;
94 char *vc_keymap_toggle;
95
96 Hashmap *polkit_registry;
97 } Context;
98
99 static const char* nonempty(const char *s) {
100 return isempty(s) ? NULL : s;
101 }
102
103 static void free_and_replace(char **s, char *v) {
104 free(*s);
105 *s = v;
106 }
107
108 static bool startswith_comma(const char *s, const char *prefix) {
109 const char *t;
110
111 return s && (t = startswith(s, prefix)) && (*t == ',');
112 }
113
114 static void context_free_x11(Context *c) {
115 free_and_replace(&c->x11_layout, NULL);
116 free_and_replace(&c->x11_model, NULL);
117 free_and_replace(&c->x11_variant, NULL);
118 free_and_replace(&c->x11_options, NULL);
119 }
120
121 static void context_free_vconsole(Context *c) {
122 free_and_replace(&c->vc_keymap, NULL);
123 free_and_replace(&c->vc_keymap_toggle, NULL);
124 }
125
126 static void context_free_locale(Context *c) {
127 int p;
128
129 for (p = 0; p < _LOCALE_MAX; p++)
130 free_and_replace(&c->locale[p], NULL);
131 }
132
133 static void context_free(Context *c) {
134 context_free_locale(c);
135 context_free_x11(c);
136 context_free_vconsole(c);
137
138 bus_verify_polkit_async_registry_free(c->polkit_registry);
139 };
140
141 static void locale_simplify(Context *c) {
142 int p;
143
144 for (p = LOCALE_LANG+1; p < _LOCALE_MAX; p++)
145 if (isempty(c->locale[p]) || streq_ptr(c->locale[LOCALE_LANG], c->locale[p]))
146 free_and_replace(&c->locale[p], NULL);
147 }
148
149 static int locale_read_data(Context *c) {
150 int r;
151
152 context_free_locale(c);
153
154 r = parse_env_file("/etc/locale.conf", NEWLINE,
155 "LANG", &c->locale[LOCALE_LANG],
156 "LANGUAGE", &c->locale[LOCALE_LANGUAGE],
157 "LC_CTYPE", &c->locale[LOCALE_LC_CTYPE],
158 "LC_NUMERIC", &c->locale[LOCALE_LC_NUMERIC],
159 "LC_TIME", &c->locale[LOCALE_LC_TIME],
160 "LC_COLLATE", &c->locale[LOCALE_LC_COLLATE],
161 "LC_MONETARY", &c->locale[LOCALE_LC_MONETARY],
162 "LC_MESSAGES", &c->locale[LOCALE_LC_MESSAGES],
163 "LC_PAPER", &c->locale[LOCALE_LC_PAPER],
164 "LC_NAME", &c->locale[LOCALE_LC_NAME],
165 "LC_ADDRESS", &c->locale[LOCALE_LC_ADDRESS],
166 "LC_TELEPHONE", &c->locale[LOCALE_LC_TELEPHONE],
167 "LC_MEASUREMENT", &c->locale[LOCALE_LC_MEASUREMENT],
168 "LC_IDENTIFICATION", &c->locale[LOCALE_LC_IDENTIFICATION],
169 NULL);
170
171 if (r == -ENOENT) {
172 int p;
173
174 /* Fill in what we got passed from systemd. */
175 for (p = 0; p < _LOCALE_MAX; p++) {
176 assert(names[p]);
177
178 r = free_and_strdup(&c->locale[p],
179 nonempty(getenv(names[p])));
180 if (r < 0)
181 return r;
182 }
183
184 r = 0;
185 }
186
187 locale_simplify(c);
188 return r;
189 }
190
191 static int vconsole_read_data(Context *c) {
192 int r;
193
194 context_free_vconsole(c);
195
196 r = parse_env_file("/etc/vconsole.conf", NEWLINE,
197 "KEYMAP", &c->vc_keymap,
198 "KEYMAP_TOGGLE", &c->vc_keymap_toggle,
199 NULL);
200
201 if (r < 0 && r != -ENOENT)
202 return r;
203
204 return 0;
205 }
206
207 static int x11_read_data(Context *c) {
208 _cleanup_fclose_ FILE *f;
209 char line[LINE_MAX];
210 bool in_section = false;
211 int r;
212
213 context_free_x11(c);
214
215 f = fopen("/etc/X11/xorg.conf.d/00-keyboard.conf", "re");
216 if (!f)
217 return errno == ENOENT ? 0 : -errno;
218
219 while (fgets(line, sizeof(line), f)) {
220 char *l;
221
222 char_array_0(line);
223 l = strstrip(line);
224
225 if (l[0] == 0 || l[0] == '#')
226 continue;
227
228 if (in_section && first_word(l, "Option")) {
229 _cleanup_strv_free_ char **a = NULL;
230
231 r = strv_split_quoted(&a, l, false);
232 if (r < 0)
233 return r;
234
235 if (strv_length(a) == 3) {
236 if (streq(a[1], "XkbLayout")) {
237 free_and_replace(&c->x11_layout, a[2]);
238 a[2] = NULL;
239 } else if (streq(a[1], "XkbModel")) {
240 free_and_replace(&c->x11_model, a[2]);
241 a[2] = NULL;
242 } else if (streq(a[1], "XkbVariant")) {
243 free_and_replace(&c->x11_variant, a[2]);
244 a[2] = NULL;
245 } else if (streq(a[1], "XkbOptions")) {
246 free_and_replace(&c->x11_options, a[2]);
247 a[2] = NULL;
248 }
249 }
250
251 } else if (!in_section && first_word(l, "Section")) {
252 _cleanup_strv_free_ char **a = NULL;
253
254 r = strv_split_quoted(&a, l, false);
255 if (r < 0)
256 return -ENOMEM;
257
258 if (strv_length(a) == 2 && streq(a[1], "InputClass"))
259 in_section = true;
260
261 } else if (in_section && first_word(l, "EndSection"))
262 in_section = false;
263 }
264
265 return 0;
266 }
267
268 static int context_read_data(Context *c) {
269 int r, q, p;
270
271 r = locale_read_data(c);
272 q = vconsole_read_data(c);
273 p = x11_read_data(c);
274
275 return r < 0 ? r : q < 0 ? q : p;
276 }
277
278 static int locale_write_data(Context *c, char ***settings) {
279 int r, p;
280 _cleanup_strv_free_ char **l = NULL;
281
282 /* Set values will be returned as strv in *settings on success. */
283
284 r = load_env_file(NULL, "/etc/locale.conf", NULL, &l);
285 if (r < 0 && r != -ENOENT)
286 return r;
287
288 for (p = 0; p < _LOCALE_MAX; p++) {
289 _cleanup_free_ char *t = NULL;
290 char **u;
291
292 assert(names[p]);
293
294 if (isempty(c->locale[p])) {
295 l = strv_env_unset(l, names[p]);
296 continue;
297 }
298
299 if (asprintf(&t, "%s=%s", names[p], c->locale[p]) < 0)
300 return -ENOMEM;
301
302 u = strv_env_set(l, t);
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/locale.conf") < 0)
312 return errno == ENOENT ? 0 : -errno;
313
314 return 0;
315 }
316
317 r = write_env_file_label("/etc/locale.conf", l);
318 if (r < 0)
319 return r;
320
321 *settings = l;
322 l = NULL;
323 return 0;
324 }
325
326 static int locale_update_system_manager(Context *c, sd_bus *bus) {
327 _cleanup_free_ char **l_unset = NULL;
328 _cleanup_strv_free_ char **l_set = NULL;
329 _cleanup_bus_message_unref_ sd_bus_message *m = NULL;
330 sd_bus_error error = SD_BUS_ERROR_NULL;
331 unsigned c_set, c_unset, p;
332 int r;
333
334 assert(bus);
335
336 l_unset = new0(char*, _LOCALE_MAX);
337 if (!l_unset)
338 return -ENOMEM;
339
340 l_set = new0(char*, _LOCALE_MAX);
341 if (!l_set)
342 return -ENOMEM;
343
344 for (p = 0, c_set = 0, c_unset = 0; p < _LOCALE_MAX; p++) {
345 assert(names[p]);
346
347 if (isempty(c->locale[p]))
348 l_unset[c_set++] = (char*) names[p];
349 else {
350 char *s;
351
352 if (asprintf(&s, "%s=%s", names[p], c->locale[p]) < 0)
353 return -ENOMEM;
354
355 l_set[c_unset++] = s;
356 }
357 }
358
359 assert(c_set + c_unset == _LOCALE_MAX);
360 r = sd_bus_message_new_method_call(bus, &m,
361 "org.freedesktop.systemd1",
362 "/org/freedesktop/systemd1",
363 "org.freedesktop.systemd1.Manager",
364 "UnsetAndSetEnvironment");
365 if (r < 0)
366 return r;
367
368 r = sd_bus_message_append_strv(m, l_unset);
369 if (r < 0)
370 return r;
371
372 r = sd_bus_message_append_strv(m, l_set);
373 if (r < 0)
374 return r;
375
376 r = sd_bus_call(bus, m, 0, &error, NULL);
377 if (r < 0)
378 log_error_errno(r, "Failed to update the manager environment: %m");
379
380 return 0;
381 }
382
383 static int vconsole_write_data(Context *c) {
384 int r;
385 _cleanup_strv_free_ char **l = NULL;
386
387 r = load_env_file(NULL, "/etc/vconsole.conf", NULL, &l);
388 if (r < 0 && r != -ENOENT)
389 return r;
390
391 if (isempty(c->vc_keymap))
392 l = strv_env_unset(l, "KEYMAP");
393 else {
394 _cleanup_free_ char *s = NULL;
395 char **u;
396
397 s = strappend("KEYMAP=", c->vc_keymap);
398 if (!s)
399 return -ENOMEM;
400
401 u = strv_env_set(l, s);
402 if (!u)
403 return -ENOMEM;
404
405 strv_free(l);
406 l = u;
407 }
408
409 if (isempty(c->vc_keymap_toggle))
410 l = strv_env_unset(l, "KEYMAP_TOGGLE");
411 else {
412 _cleanup_free_ char *s = NULL;
413 char **u;
414
415 s = strappend("KEYMAP_TOGGLE=", c->vc_keymap_toggle);
416 if (!s)
417 return -ENOMEM;
418
419 u = strv_env_set(l, s);
420 if (!u)
421 return -ENOMEM;
422
423 strv_free(l);
424 l = u;
425 }
426
427 if (strv_isempty(l)) {
428 if (unlink("/etc/vconsole.conf") < 0)
429 return errno == ENOENT ? 0 : -errno;
430
431 return 0;
432 }
433
434 return write_env_file_label("/etc/vconsole.conf", l);
435 }
436
437 static int x11_write_data(Context *c) {
438 _cleanup_fclose_ FILE *f = NULL;
439 _cleanup_free_ char *temp_path = NULL;
440 int r;
441
442 if (isempty(c->x11_layout) &&
443 isempty(c->x11_model) &&
444 isempty(c->x11_variant) &&
445 isempty(c->x11_options)) {
446
447 if (unlink("/etc/X11/xorg.conf.d/00-keyboard.conf") < 0)
448 return errno == ENOENT ? 0 : -errno;
449
450 return 0;
451 }
452
453 mkdir_p_label("/etc/X11/xorg.conf.d", 0755);
454
455 r = fopen_temporary("/etc/X11/xorg.conf.d/00-keyboard.conf", &f, &temp_path);
456 if (r < 0)
457 return r;
458
459 fchmod(fileno(f), 0644);
460
461 fputs("# Read and parsed by systemd-localed. It's probably wise not to edit this file\n"
462 "# manually too freely.\n"
463 "Section \"InputClass\"\n"
464 " Identifier \"system-keyboard\"\n"
465 " MatchIsKeyboard \"on\"\n", f);
466
467 if (!isempty(c->x11_layout))
468 fprintf(f, " Option \"XkbLayout\" \"%s\"\n", c->x11_layout);
469
470 if (!isempty(c->x11_model))
471 fprintf(f, " Option \"XkbModel\" \"%s\"\n", c->x11_model);
472
473 if (!isempty(c->x11_variant))
474 fprintf(f, " Option \"XkbVariant\" \"%s\"\n", c->x11_variant);
475
476 if (!isempty(c->x11_options))
477 fprintf(f, " Option \"XkbOptions\" \"%s\"\n", c->x11_options);
478
479 fputs("EndSection\n", f);
480 fflush(f);
481
482 if (ferror(f) || rename(temp_path, "/etc/X11/xorg.conf.d/00-keyboard.conf") < 0) {
483 r = -errno;
484 unlink("/etc/X11/xorg.conf.d/00-keyboard.conf");
485 unlink(temp_path);
486 return r;
487 } else
488 return 0;
489 }
490
491 static int vconsole_reload(sd_bus *bus) {
492 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
493 int r;
494
495 assert(bus);
496
497 r = sd_bus_call_method(bus,
498 "org.freedesktop.systemd1",
499 "/org/freedesktop/systemd1",
500 "org.freedesktop.systemd1.Manager",
501 "RestartUnit",
502 &error,
503 NULL,
504 "ss", "systemd-vconsole-setup.service", "replace");
505
506 if (r < 0)
507 log_error("Failed to issue method call: %s", bus_error_message(&error, -r));
508 return r;
509 }
510
511 static const char* strnulldash(const char *s) {
512 return isempty(s) || streq(s, "-") ? NULL : s;
513 }
514
515 static int read_next_mapping(const char* filename,
516 unsigned min_fields, unsigned max_fields,
517 FILE *f, unsigned *n, char ***a) {
518 assert(f);
519 assert(n);
520 assert(a);
521
522 for (;;) {
523 char line[LINE_MAX];
524 char *l, **b;
525 int r;
526 size_t length;
527
528 errno = 0;
529 if (!fgets(line, sizeof(line), f)) {
530
531 if (ferror(f))
532 return errno ? -errno : -EIO;
533
534 return 0;
535 }
536
537 (*n) ++;
538
539 l = strstrip(line);
540 if (l[0] == 0 || l[0] == '#')
541 continue;
542
543 r = strv_split_quoted(&b, l, false);
544 if (r < 0)
545 return r;
546
547 length = strv_length(b);
548 if (length < min_fields || length > max_fields) {
549 log_error("Invalid line %s:%u, ignoring.", filename, *n);
550 strv_free(b);
551 continue;
552
553 }
554
555 *a = b;
556 return 1;
557 }
558 }
559
560 static int vconsole_convert_to_x11(Context *c, sd_bus *bus) {
561 bool modified = false;
562
563 assert(bus);
564
565 if (isempty(c->vc_keymap)) {
566
567 modified =
568 !isempty(c->x11_layout) ||
569 !isempty(c->x11_model) ||
570 !isempty(c->x11_variant) ||
571 !isempty(c->x11_options);
572
573 context_free_x11(c);
574 } else {
575 _cleanup_fclose_ FILE *f = NULL;
576 unsigned n = 0;
577
578 f = fopen(SYSTEMD_KBD_MODEL_MAP, "re");
579 if (!f)
580 return -errno;
581
582 for (;;) {
583 _cleanup_strv_free_ char **a = NULL;
584 int r;
585
586 r = read_next_mapping(SYSTEMD_KBD_MODEL_MAP, 5, UINT_MAX, f, &n, &a);
587 if (r < 0)
588 return r;
589 if (r == 0)
590 break;
591
592 if (!streq(c->vc_keymap, a[0]))
593 continue;
594
595 if (!streq_ptr(c->x11_layout, strnulldash(a[1])) ||
596 !streq_ptr(c->x11_model, strnulldash(a[2])) ||
597 !streq_ptr(c->x11_variant, strnulldash(a[3])) ||
598 !streq_ptr(c->x11_options, strnulldash(a[4]))) {
599
600 if (free_and_strdup(&c->x11_layout, strnulldash(a[1])) < 0 ||
601 free_and_strdup(&c->x11_model, strnulldash(a[2])) < 0 ||
602 free_and_strdup(&c->x11_variant, strnulldash(a[3])) < 0 ||
603 free_and_strdup(&c->x11_options, strnulldash(a[4])) < 0)
604 return -ENOMEM;
605
606 modified = true;
607 }
608
609 break;
610 }
611 }
612
613 if (modified) {
614 int r;
615
616 r = x11_write_data(c);
617 if (r < 0)
618 return log_error_errno(r, "Failed to set X11 keyboard layout: %m");
619
620 log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
621 strempty(c->x11_layout),
622 strempty(c->x11_model),
623 strempty(c->x11_variant),
624 strempty(c->x11_options));
625
626 sd_bus_emit_properties_changed(bus,
627 "/org/freedesktop/locale1",
628 "org.freedesktop.locale1",
629 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL);
630 } else
631 log_debug("X11 keyboard layout was not modified.");
632
633 return 0;
634 }
635
636 static int find_converted_keymap(const char *x11_layout, const char *x11_variant, char **new_keymap) {
637 const char *dir;
638 _cleanup_free_ char *n;
639
640 if (x11_variant)
641 n = strjoin(x11_layout, "-", x11_variant, NULL);
642 else
643 n = strdup(x11_layout);
644 if (!n)
645 return -ENOMEM;
646
647 NULSTR_FOREACH(dir, KBD_KEYMAP_DIRS) {
648 _cleanup_free_ char *p = NULL, *pz = NULL;
649 bool uncompressed;
650
651 p = strjoin(dir, "xkb/", n, ".map", NULL);
652 pz = strjoin(dir, "xkb/", n, ".map.gz", NULL);
653 if (!p || !pz)
654 return -ENOMEM;
655
656 uncompressed = access(p, F_OK) == 0;
657 if (uncompressed || access(pz, F_OK) == 0) {
658 log_debug("Found converted keymap %s at %s",
659 n, uncompressed ? p : pz);
660
661 *new_keymap = n;
662 n = NULL;
663 return 1;
664 }
665 }
666
667 return 0;
668 }
669
670 static int find_legacy_keymap(Context *c, char **new_keymap) {
671 _cleanup_fclose_ FILE *f;
672 unsigned n = 0;
673 unsigned best_matching = 0;
674 int r;
675
676 f = fopen(SYSTEMD_KBD_MODEL_MAP, "re");
677 if (!f)
678 return -errno;
679
680 for (;;) {
681 _cleanup_strv_free_ char **a = NULL;
682 unsigned matching = 0;
683
684 r = read_next_mapping(SYSTEMD_KBD_MODEL_MAP, 5, UINT_MAX, f, &n, &a);
685 if (r < 0)
686 return r;
687 if (r == 0)
688 break;
689
690 /* Determine how well matching this entry is */
691 if (streq_ptr(c->x11_layout, a[1]))
692 /* If we got an exact match, this is best */
693 matching = 10;
694 else {
695 /* We have multiple X layouts, look for an
696 * entry that matches our key with everything
697 * but the first layout stripped off. */
698 if (startswith_comma(c->x11_layout, a[1]))
699 matching = 5;
700 else {
701 char *x;
702
703 /* If that didn't work, strip off the
704 * other layouts from the entry, too */
705 x = strndupa(a[1], strcspn(a[1], ","));
706 if (startswith_comma(c->x11_layout, x))
707 matching = 1;
708 }
709 }
710
711 if (matching > 0) {
712 if (isempty(c->x11_model) || streq_ptr(c->x11_model, a[2])) {
713 matching++;
714
715 if (streq_ptr(c->x11_variant, a[3])) {
716 matching++;
717
718 if (streq_ptr(c->x11_options, a[4]))
719 matching++;
720 }
721 }
722 }
723
724 /* The best matching entry so far, then let's save that */
725 if (matching >= MAX(best_matching, 1u)) {
726 log_debug("Found legacy keymap %s with score %u",
727 a[0], matching);
728
729 if (matching > best_matching) {
730 best_matching = matching;
731
732 r = free_and_strdup(new_keymap, a[0]);
733 if (r < 0)
734 return r;
735 }
736 }
737 }
738
739 if (best_matching < 10 && c->x11_layout) {
740 /* The best match is only the first part of the X11
741 * keymap. Check if we have a converted map which
742 * matches just the first layout.
743 */
744 char *l, *v = NULL, *converted;
745
746 l = strndupa(c->x11_layout, strcspn(c->x11_layout, ","));
747 if (c->x11_variant)
748 v = strndupa(c->x11_variant, strcspn(c->x11_variant, ","));
749 r = find_converted_keymap(l, v, &converted);
750 if (r < 0)
751 return r;
752 if (r > 0)
753 free_and_replace(new_keymap, converted);
754 }
755
756 return 0;
757 }
758
759 static int find_language_fallback(const char *lang, char **language) {
760 _cleanup_fclose_ FILE *f = NULL;
761 unsigned n = 0;
762
763 assert(language);
764
765 f = fopen(SYSTEMD_LANGUAGE_FALLBACK_MAP, "re");
766 if (!f)
767 return -errno;
768
769 for (;;) {
770 _cleanup_strv_free_ char **a = NULL;
771 int r;
772
773 r = read_next_mapping(SYSTEMD_LANGUAGE_FALLBACK_MAP, 2, 2, f, &n, &a);
774 if (r <= 0)
775 return r;
776
777 if (streq(lang, a[0])) {
778 assert(strv_length(a) == 2);
779 *language = a[1];
780 a[1] = NULL;
781 return 1;
782 }
783 }
784
785 assert_not_reached("should not be here");
786 }
787
788 static int x11_convert_to_vconsole(Context *c, sd_bus *bus) {
789 bool modified = false;
790 int r;
791
792 assert(bus);
793
794 if (isempty(c->x11_layout)) {
795
796 modified =
797 !isempty(c->vc_keymap) ||
798 !isempty(c->vc_keymap_toggle);
799
800 context_free_x11(c);
801 } else {
802 char *new_keymap = NULL;
803
804 r = find_converted_keymap(c->x11_layout, c->x11_variant, &new_keymap);
805 if (r < 0)
806 return r;
807 else if (r == 0) {
808 r = find_legacy_keymap(c, &new_keymap);
809 if (r < 0)
810 return r;
811 }
812
813 if (!streq_ptr(c->vc_keymap, new_keymap)) {
814 free_and_replace(&c->vc_keymap, new_keymap);
815 free_and_replace(&c->vc_keymap_toggle, NULL);
816 modified = true;
817 } else
818 free(new_keymap);
819 }
820
821 if (modified) {
822 r = vconsole_write_data(c);
823 if (r < 0)
824 log_error_errno(r, "Failed to set virtual console keymap: %m");
825
826 log_info("Changed virtual console keymap to '%s' toggle '%s'",
827 strempty(c->vc_keymap), strempty(c->vc_keymap_toggle));
828
829 sd_bus_emit_properties_changed(bus,
830 "/org/freedesktop/locale1",
831 "org.freedesktop.locale1",
832 "VConsoleKeymap", "VConsoleKeymapToggle", NULL);
833
834 return vconsole_reload(bus);
835 } else
836 log_debug("Virtual console keymap was not modified.");
837
838 return 0;
839 }
840
841 static int property_get_locale(
842 sd_bus *bus,
843 const char *path,
844 const char *interface,
845 const char *property,
846 sd_bus_message *reply,
847 void *userdata,
848 sd_bus_error *error) {
849
850 Context *c = userdata;
851 _cleanup_strv_free_ char **l = NULL;
852 int p, q;
853
854 l = new0(char*, _LOCALE_MAX+1);
855 if (!l)
856 return -ENOMEM;
857
858 for (p = 0, q = 0; p < _LOCALE_MAX; p++) {
859 char *t;
860
861 if (isempty(c->locale[p]))
862 continue;
863
864 if (asprintf(&t, "%s=%s", names[p], c->locale[p]) < 0)
865 return -ENOMEM;
866
867 l[q++] = t;
868 }
869
870 return sd_bus_message_append_strv(reply, l);
871 }
872
873 static int method_set_locale(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
874 Context *c = userdata;
875 _cleanup_strv_free_ char **l = NULL;
876 char **i;
877 const char *lang = NULL;
878 int interactive;
879 bool modified = false;
880 bool have[_LOCALE_MAX] = {};
881 int p;
882 int r;
883
884 r = bus_message_read_strv_extend(m, &l);
885 if (r < 0)
886 return r;
887
888 r = sd_bus_message_read_basic(m, 'b', &interactive);
889 if (r < 0)
890 return r;
891
892 /* Check whether a variable changed and if it is valid */
893 STRV_FOREACH(i, l) {
894 bool valid = false;
895
896 for (p = 0; p < _LOCALE_MAX; p++) {
897 size_t k;
898
899 k = strlen(names[p]);
900 if (startswith(*i, names[p]) &&
901 (*i)[k] == '=' &&
902 locale_is_valid((*i) + k + 1)) {
903 valid = true;
904 have[p] = true;
905
906 if (p == LOCALE_LANG)
907 lang = (*i) + k + 1;
908
909 if (!streq_ptr(*i + k + 1, c->locale[p]))
910 modified = true;
911
912 break;
913 }
914 }
915
916 if (!valid)
917 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid Locale data.");
918 }
919
920 /* If LANG was specified, but not LANGUAGE, check if we should
921 * set it based on the language fallback table. */
922 if (have[LOCALE_LANG] && !have[LOCALE_LANGUAGE]) {
923 _cleanup_free_ char *language = NULL;
924
925 assert(lang);
926
927 (void) find_language_fallback(lang, &language);
928 if (language) {
929 log_debug("Converted LANG=%s to LANGUAGE=%s", lang, language);
930 if (!streq_ptr(language, c->locale[LOCALE_LANGUAGE])) {
931 r = strv_extendf(&l, "LANGUAGE=%s", language);
932 if (r < 0)
933 return r;
934
935 have[LOCALE_LANGUAGE] = true;
936 modified = true;
937 }
938 }
939 }
940
941 /* Check whether a variable is unset */
942 if (!modified)
943 for (p = 0; p < _LOCALE_MAX; p++)
944 if (!isempty(c->locale[p]) && !have[p]) {
945 modified = true;
946 break;
947 }
948
949 if (modified) {
950 _cleanup_strv_free_ char **settings = NULL;
951
952 r = bus_verify_polkit_async(m, CAP_SYS_ADMIN, "org.freedesktop.locale1.set-locale", interactive, &c->polkit_registry, error);
953 if (r < 0)
954 return r;
955 if (r == 0)
956 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
957
958 STRV_FOREACH(i, l)
959 for (p = 0; p < _LOCALE_MAX; p++) {
960 size_t k;
961
962 k = strlen(names[p]);
963 if (startswith(*i, names[p]) && (*i)[k] == '=') {
964 r = free_and_strdup(&c->locale[p], *i + k + 1);
965 if (r < 0)
966 return r;
967 break;
968 }
969 }
970
971 for (p = 0; p < _LOCALE_MAX; p++) {
972 if (have[p])
973 continue;
974
975 free_and_replace(&c->locale[p], NULL);
976 }
977
978 locale_simplify(c);
979
980 r = locale_write_data(c, &settings);
981 if (r < 0) {
982 log_error_errno(r, "Failed to set locale: %m");
983 return sd_bus_error_set_errnof(error, r, "Failed to set locale: %s", strerror(-r));
984 }
985
986 locale_update_system_manager(c, bus);
987
988 if (settings) {
989 _cleanup_free_ char *line;
990
991 line = strv_join(settings, ", ");
992 log_info("Changed locale to %s.", strnull(line));
993 } else
994 log_info("Changed locale to unset.");
995
996 sd_bus_emit_properties_changed(bus,
997 "/org/freedesktop/locale1",
998 "org.freedesktop.locale1",
999 "Locale", NULL);
1000 } else
1001 log_debug("Locale settings were not modified.");
1002
1003
1004 return sd_bus_reply_method_return(m, NULL);
1005 }
1006
1007 static int method_set_vc_keyboard(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
1008 Context *c = userdata;
1009 const char *keymap, *keymap_toggle;
1010 int convert, interactive;
1011 int r;
1012
1013 r = sd_bus_message_read(m, "ssbb", &keymap, &keymap_toggle, &convert, &interactive);
1014 if (r < 0)
1015 return r;
1016
1017 if (isempty(keymap))
1018 keymap = NULL;
1019
1020 if (isempty(keymap_toggle))
1021 keymap_toggle = NULL;
1022
1023 if (!streq_ptr(keymap, c->vc_keymap) ||
1024 !streq_ptr(keymap_toggle, c->vc_keymap_toggle)) {
1025
1026 if ((keymap && (!filename_is_valid(keymap) || !string_is_safe(keymap))) ||
1027 (keymap_toggle && (!filename_is_valid(keymap_toggle) || !string_is_safe(keymap_toggle))))
1028 return sd_bus_error_set_errnof(error, -EINVAL, "Received invalid keymap data");
1029
1030 r = bus_verify_polkit_async(m, CAP_SYS_ADMIN, "org.freedesktop.locale1.set-keyboard", interactive, &c->polkit_registry, error);
1031 if (r < 0)
1032 return r;
1033 if (r == 0)
1034 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
1035
1036 if (free_and_strdup(&c->vc_keymap, keymap) < 0 ||
1037 free_and_strdup(&c->vc_keymap_toggle, keymap_toggle) < 0)
1038 return -ENOMEM;
1039
1040 r = vconsole_write_data(c);
1041 if (r < 0) {
1042 log_error_errno(r, "Failed to set virtual console keymap: %m");
1043 return sd_bus_error_set_errnof(error, r, "Failed to set virtual console keymap: %s", strerror(-r));
1044 }
1045
1046 log_info("Changed virtual console keymap to '%s' toggle '%s'",
1047 strempty(c->vc_keymap), strempty(c->vc_keymap_toggle));
1048
1049 r = vconsole_reload(bus);
1050 if (r < 0)
1051 log_error_errno(r, "Failed to request keymap reload: %m");
1052
1053 sd_bus_emit_properties_changed(bus,
1054 "/org/freedesktop/locale1",
1055 "org.freedesktop.locale1",
1056 "VConsoleKeymap", "VConsoleKeymapToggle", NULL);
1057
1058 if (convert) {
1059 r = vconsole_convert_to_x11(c, bus);
1060 if (r < 0)
1061 log_error_errno(r, "Failed to convert keymap data: %m");
1062 }
1063 }
1064
1065 return sd_bus_reply_method_return(m, NULL);
1066 }
1067
1068 #ifdef HAVE_XKBCOMMON
1069 static void log_xkb(struct xkb_context *ctx, enum xkb_log_level lvl, const char *format, va_list args) {
1070 const char *fmt;
1071
1072 fmt = strjoina("libxkbcommon: ", format);
1073 log_internalv(LOG_DEBUG, 0, __FILE__, __LINE__, __func__, fmt, args);
1074 }
1075
1076 static int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, const char *options) {
1077 const struct xkb_rule_names rmlvo = {
1078 .model = model,
1079 .layout = layout,
1080 .variant = variant,
1081 .options = options,
1082 };
1083 struct xkb_context *ctx = NULL;
1084 struct xkb_keymap *km = NULL;
1085 int r;
1086
1087 /* compile keymap from RMLVO information to check out its validity */
1088
1089 ctx = xkb_context_new(XKB_CONTEXT_NO_ENVIRONMENT_NAMES);
1090 if (!ctx) {
1091 r = -ENOMEM;
1092 goto exit;
1093 }
1094
1095 xkb_context_set_log_fn(ctx, log_xkb);
1096
1097 km = xkb_keymap_new_from_names(ctx, &rmlvo, XKB_KEYMAP_COMPILE_NO_FLAGS);
1098 if (!km) {
1099 r = -EINVAL;
1100 goto exit;
1101 }
1102
1103 r = 0;
1104
1105 exit:
1106 xkb_keymap_unref(km);
1107 xkb_context_unref(ctx);
1108 return r;
1109 }
1110 #else
1111 static int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, const char *options) {
1112 return 0;
1113 }
1114 #endif
1115
1116 static int method_set_x11_keyboard(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
1117 Context *c = userdata;
1118 const char *layout, *model, *variant, *options;
1119 int convert, interactive;
1120 int r;
1121
1122 r = sd_bus_message_read(m, "ssssbb", &layout, &model, &variant, &options, &convert, &interactive);
1123 if (r < 0)
1124 return r;
1125
1126 if (isempty(layout))
1127 layout = NULL;
1128
1129 if (isempty(model))
1130 model = NULL;
1131
1132 if (isempty(variant))
1133 variant = NULL;
1134
1135 if (isempty(options))
1136 options = NULL;
1137
1138 if (!streq_ptr(layout, c->x11_layout) ||
1139 !streq_ptr(model, c->x11_model) ||
1140 !streq_ptr(variant, c->x11_variant) ||
1141 !streq_ptr(options, c->x11_options)) {
1142
1143 if ((layout && !string_is_safe(layout)) ||
1144 (model && !string_is_safe(model)) ||
1145 (variant && !string_is_safe(variant)) ||
1146 (options && !string_is_safe(options)))
1147 return sd_bus_error_set_errnof(error, -EINVAL, "Received invalid keyboard data");
1148
1149 r = bus_verify_polkit_async(m, CAP_SYS_ADMIN, "org.freedesktop.locale1.set-keyboard", interactive, &c->polkit_registry, error);
1150 if (r < 0)
1151 return r;
1152 if (r == 0)
1153 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
1154
1155 r = verify_xkb_rmlvo(model, layout, variant, options);
1156 if (r < 0) {
1157 log_error_errno(r, "Cannot compile XKB keymap for new x11 keyboard layout ('%s' / '%s' / '%s' / '%s'): %m",
1158 strempty(model), strempty(layout), strempty(variant), strempty(options));
1159 return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Cannot compile XKB keymap, refusing");
1160 }
1161
1162 if (free_and_strdup(&c->x11_layout, layout) < 0 ||
1163 free_and_strdup(&c->x11_model, model) < 0 ||
1164 free_and_strdup(&c->x11_variant, variant) < 0 ||
1165 free_and_strdup(&c->x11_options, options) < 0)
1166 return -ENOMEM;
1167
1168 r = x11_write_data(c);
1169 if (r < 0) {
1170 log_error_errno(r, "Failed to set X11 keyboard layout: %m");
1171 return sd_bus_error_set_errnof(error, r, "Failed to set X11 keyboard layout: %s", strerror(-r));
1172 }
1173
1174 log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
1175 strempty(c->x11_layout),
1176 strempty(c->x11_model),
1177 strempty(c->x11_variant),
1178 strempty(c->x11_options));
1179
1180 sd_bus_emit_properties_changed(bus,
1181 "/org/freedesktop/locale1",
1182 "org.freedesktop.locale1",
1183 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL);
1184
1185 if (convert) {
1186 r = x11_convert_to_vconsole(c, bus);
1187 if (r < 0)
1188 log_error_errno(r, "Failed to convert keymap data: %m");
1189 }
1190 }
1191
1192 return sd_bus_reply_method_return(m, NULL);
1193 }
1194
1195 static const sd_bus_vtable locale_vtable[] = {
1196 SD_BUS_VTABLE_START(0),
1197 SD_BUS_PROPERTY("Locale", "as", property_get_locale, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1198 SD_BUS_PROPERTY("X11Layout", "s", NULL, offsetof(Context, x11_layout), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1199 SD_BUS_PROPERTY("X11Model", "s", NULL, offsetof(Context, x11_model), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1200 SD_BUS_PROPERTY("X11Variant", "s", NULL, offsetof(Context, x11_variant), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1201 SD_BUS_PROPERTY("X11Options", "s", NULL, offsetof(Context, x11_options), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1202 SD_BUS_PROPERTY("VConsoleKeymap", "s", NULL, offsetof(Context, vc_keymap), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1203 SD_BUS_PROPERTY("VConsoleKeymapToggle", "s", NULL, offsetof(Context, vc_keymap_toggle), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1204 SD_BUS_METHOD("SetLocale", "asb", NULL, method_set_locale, SD_BUS_VTABLE_UNPRIVILEGED),
1205 SD_BUS_METHOD("SetVConsoleKeyboard", "ssbb", NULL, method_set_vc_keyboard, SD_BUS_VTABLE_UNPRIVILEGED),
1206 SD_BUS_METHOD("SetX11Keyboard", "ssssbb", NULL, method_set_x11_keyboard, SD_BUS_VTABLE_UNPRIVILEGED),
1207 SD_BUS_VTABLE_END
1208 };
1209
1210 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
1211 _cleanup_bus_close_unref_ sd_bus *bus = NULL;
1212 int r;
1213
1214 assert(c);
1215 assert(event);
1216 assert(_bus);
1217
1218 r = sd_bus_default_system(&bus);
1219 if (r < 0)
1220 return log_error_errno(r, "Failed to get system bus connection: %m");
1221
1222 r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/locale1", "org.freedesktop.locale1", locale_vtable, c);
1223 if (r < 0)
1224 return log_error_errno(r, "Failed to register object: %m");
1225
1226 r = sd_bus_request_name(bus, "org.freedesktop.locale1", 0);
1227 if (r < 0)
1228 return log_error_errno(r, "Failed to register name: %m");
1229
1230 r = sd_bus_attach_event(bus, event, 0);
1231 if (r < 0)
1232 return log_error_errno(r, "Failed to attach bus to event loop: %m");
1233
1234 *_bus = bus;
1235 bus = NULL;
1236
1237 return 0;
1238 }
1239
1240 int main(int argc, char *argv[]) {
1241 _cleanup_(context_free) Context context = {};
1242 _cleanup_event_unref_ sd_event *event = NULL;
1243 _cleanup_bus_close_unref_ sd_bus *bus = NULL;
1244 int r;
1245
1246 log_set_target(LOG_TARGET_AUTO);
1247 log_parse_environment();
1248 log_open();
1249
1250 umask(0022);
1251 mac_selinux_init("/etc");
1252
1253 if (argc != 1) {
1254 log_error("This program takes no arguments.");
1255 r = -EINVAL;
1256 goto finish;
1257 }
1258
1259 r = sd_event_default(&event);
1260 if (r < 0) {
1261 log_error_errno(r, "Failed to allocate event loop: %m");
1262 goto finish;
1263 }
1264
1265 sd_event_set_watchdog(event, true);
1266
1267 r = connect_bus(&context, event, &bus);
1268 if (r < 0)
1269 goto finish;
1270
1271 r = context_read_data(&context);
1272 if (r < 0) {
1273 log_error_errno(r, "Failed to read locale data: %m");
1274 goto finish;
1275 }
1276
1277 r = bus_event_loop_with_idle(event, bus, "org.freedesktop.locale1", DEFAULT_EXIT_USEC, NULL, NULL);
1278 if (r < 0) {
1279 log_error_errno(r, "Failed to run event loop: %m");
1280 goto finish;
1281 }
1282
1283 finish:
1284 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
1285 }