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