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