]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/locale/localed.c
Merge pull request #1159 from AnchorCat/polkit-details/v2
[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 NULL,
964 interactive,
965 UID_INVALID,
966 &c->polkit_registry,
967 error);
968 if (r < 0)
969 return r;
970 if (r == 0)
971 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
972
973 STRV_FOREACH(i, l)
974 for (p = 0; p < _LOCALE_MAX; p++) {
975 size_t k;
976
977 k = strlen(names[p]);
978 if (startswith(*i, names[p]) && (*i)[k] == '=') {
979 r = free_and_strdup(&c->locale[p], *i + k + 1);
980 if (r < 0)
981 return r;
982 break;
983 }
984 }
985
986 for (p = 0; p < _LOCALE_MAX; p++) {
987 if (have[p])
988 continue;
989
990 free_and_replace(&c->locale[p], NULL);
991 }
992
993 locale_simplify(c);
994
995 r = locale_write_data(c, &settings);
996 if (r < 0) {
997 log_error_errno(r, "Failed to set locale: %m");
998 return sd_bus_error_set_errnof(error, r, "Failed to set locale: %s", strerror(-r));
999 }
1000
1001 locale_update_system_manager(c, sd_bus_message_get_bus(m));
1002
1003 if (settings) {
1004 _cleanup_free_ char *line;
1005
1006 line = strv_join(settings, ", ");
1007 log_info("Changed locale to %s.", strnull(line));
1008 } else
1009 log_info("Changed locale to unset.");
1010
1011 (void) sd_bus_emit_properties_changed(
1012 sd_bus_message_get_bus(m),
1013 "/org/freedesktop/locale1",
1014 "org.freedesktop.locale1",
1015 "Locale", NULL);
1016 } else
1017 log_debug("Locale settings were not modified.");
1018
1019
1020 return sd_bus_reply_method_return(m, NULL);
1021 }
1022
1023 static int method_set_vc_keyboard(sd_bus_message *m, void *userdata, sd_bus_error *error) {
1024 Context *c = userdata;
1025 const char *keymap, *keymap_toggle;
1026 int convert, interactive;
1027 int r;
1028
1029 assert(m);
1030 assert(c);
1031
1032 r = sd_bus_message_read(m, "ssbb", &keymap, &keymap_toggle, &convert, &interactive);
1033 if (r < 0)
1034 return r;
1035
1036 if (isempty(keymap))
1037 keymap = NULL;
1038
1039 if (isempty(keymap_toggle))
1040 keymap_toggle = NULL;
1041
1042 if (!streq_ptr(keymap, c->vc_keymap) ||
1043 !streq_ptr(keymap_toggle, c->vc_keymap_toggle)) {
1044
1045 if ((keymap && (!filename_is_valid(keymap) || !string_is_safe(keymap))) ||
1046 (keymap_toggle && (!filename_is_valid(keymap_toggle) || !string_is_safe(keymap_toggle))))
1047 return sd_bus_error_set_errnof(error, -EINVAL, "Received invalid keymap data");
1048
1049 r = bus_verify_polkit_async(
1050 m,
1051 CAP_SYS_ADMIN,
1052 "org.freedesktop.locale1.set-keyboard",
1053 NULL,
1054 interactive,
1055 UID_INVALID,
1056 &c->polkit_registry,
1057 error);
1058 if (r < 0)
1059 return r;
1060 if (r == 0)
1061 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
1062
1063 if (free_and_strdup(&c->vc_keymap, keymap) < 0 ||
1064 free_and_strdup(&c->vc_keymap_toggle, keymap_toggle) < 0)
1065 return -ENOMEM;
1066
1067 r = vconsole_write_data(c);
1068 if (r < 0) {
1069 log_error_errno(r, "Failed to set virtual console keymap: %m");
1070 return sd_bus_error_set_errnof(error, r, "Failed to set virtual console keymap: %s", strerror(-r));
1071 }
1072
1073 log_info("Changed virtual console keymap to '%s' toggle '%s'",
1074 strempty(c->vc_keymap), strempty(c->vc_keymap_toggle));
1075
1076 r = vconsole_reload(sd_bus_message_get_bus(m));
1077 if (r < 0)
1078 log_error_errno(r, "Failed to request keymap reload: %m");
1079
1080 (void) sd_bus_emit_properties_changed(
1081 sd_bus_message_get_bus(m),
1082 "/org/freedesktop/locale1",
1083 "org.freedesktop.locale1",
1084 "VConsoleKeymap", "VConsoleKeymapToggle", NULL);
1085
1086 if (convert) {
1087 r = vconsole_convert_to_x11(c, sd_bus_message_get_bus(m));
1088 if (r < 0)
1089 log_error_errno(r, "Failed to convert keymap data: %m");
1090 }
1091 }
1092
1093 return sd_bus_reply_method_return(m, NULL);
1094 }
1095
1096 #ifdef HAVE_XKBCOMMON
1097 _printf_(3, 0)
1098 static void log_xkb(struct xkb_context *ctx, enum xkb_log_level lvl, const char *format, va_list args) {
1099 const char *fmt;
1100
1101 fmt = strjoina("libxkbcommon: ", format);
1102 log_internalv(LOG_DEBUG, 0, __FILE__, __LINE__, __func__, fmt, args);
1103 }
1104
1105 static int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, const char *options) {
1106 const struct xkb_rule_names rmlvo = {
1107 .model = model,
1108 .layout = layout,
1109 .variant = variant,
1110 .options = options,
1111 };
1112 struct xkb_context *ctx = NULL;
1113 struct xkb_keymap *km = NULL;
1114 int r;
1115
1116 /* compile keymap from RMLVO information to check out its validity */
1117
1118 ctx = xkb_context_new(XKB_CONTEXT_NO_ENVIRONMENT_NAMES);
1119 if (!ctx) {
1120 r = -ENOMEM;
1121 goto exit;
1122 }
1123
1124 xkb_context_set_log_fn(ctx, log_xkb);
1125
1126 km = xkb_keymap_new_from_names(ctx, &rmlvo, XKB_KEYMAP_COMPILE_NO_FLAGS);
1127 if (!km) {
1128 r = -EINVAL;
1129 goto exit;
1130 }
1131
1132 r = 0;
1133
1134 exit:
1135 xkb_keymap_unref(km);
1136 xkb_context_unref(ctx);
1137 return r;
1138 }
1139 #else
1140 static int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, const char *options) {
1141 return 0;
1142 }
1143 #endif
1144
1145 static int method_set_x11_keyboard(sd_bus_message *m, void *userdata, sd_bus_error *error) {
1146 Context *c = userdata;
1147 const char *layout, *model, *variant, *options;
1148 int convert, interactive;
1149 int r;
1150
1151 assert(m);
1152 assert(c);
1153
1154 r = sd_bus_message_read(m, "ssssbb", &layout, &model, &variant, &options, &convert, &interactive);
1155 if (r < 0)
1156 return r;
1157
1158 if (isempty(layout))
1159 layout = NULL;
1160
1161 if (isempty(model))
1162 model = NULL;
1163
1164 if (isempty(variant))
1165 variant = NULL;
1166
1167 if (isempty(options))
1168 options = NULL;
1169
1170 if (!streq_ptr(layout, c->x11_layout) ||
1171 !streq_ptr(model, c->x11_model) ||
1172 !streq_ptr(variant, c->x11_variant) ||
1173 !streq_ptr(options, c->x11_options)) {
1174
1175 if ((layout && !string_is_safe(layout)) ||
1176 (model && !string_is_safe(model)) ||
1177 (variant && !string_is_safe(variant)) ||
1178 (options && !string_is_safe(options)))
1179 return sd_bus_error_set_errnof(error, -EINVAL, "Received invalid keyboard data");
1180
1181 r = bus_verify_polkit_async(
1182 m,
1183 CAP_SYS_ADMIN,
1184 "org.freedesktop.locale1.set-keyboard",
1185 NULL,
1186 interactive,
1187 UID_INVALID,
1188 &c->polkit_registry,
1189 error);
1190 if (r < 0)
1191 return r;
1192 if (r == 0)
1193 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
1194
1195 r = verify_xkb_rmlvo(model, layout, variant, options);
1196 if (r < 0) {
1197 log_error_errno(r, "Cannot compile XKB keymap for new x11 keyboard layout ('%s' / '%s' / '%s' / '%s'): %m",
1198 strempty(model), strempty(layout), strempty(variant), strempty(options));
1199 return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Cannot compile XKB keymap, refusing");
1200 }
1201
1202 if (free_and_strdup(&c->x11_layout, layout) < 0 ||
1203 free_and_strdup(&c->x11_model, model) < 0 ||
1204 free_and_strdup(&c->x11_variant, variant) < 0 ||
1205 free_and_strdup(&c->x11_options, options) < 0)
1206 return -ENOMEM;
1207
1208 r = x11_write_data(c);
1209 if (r < 0) {
1210 log_error_errno(r, "Failed to set X11 keyboard layout: %m");
1211 return sd_bus_error_set_errnof(error, r, "Failed to set X11 keyboard layout: %s", strerror(-r));
1212 }
1213
1214 log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
1215 strempty(c->x11_layout),
1216 strempty(c->x11_model),
1217 strempty(c->x11_variant),
1218 strempty(c->x11_options));
1219
1220 (void) sd_bus_emit_properties_changed(
1221 sd_bus_message_get_bus(m),
1222 "/org/freedesktop/locale1",
1223 "org.freedesktop.locale1",
1224 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL);
1225
1226 if (convert) {
1227 r = x11_convert_to_vconsole(c, sd_bus_message_get_bus(m));
1228 if (r < 0)
1229 log_error_errno(r, "Failed to convert keymap data: %m");
1230 }
1231 }
1232
1233 return sd_bus_reply_method_return(m, NULL);
1234 }
1235
1236 static const sd_bus_vtable locale_vtable[] = {
1237 SD_BUS_VTABLE_START(0),
1238 SD_BUS_PROPERTY("Locale", "as", property_get_locale, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1239 SD_BUS_PROPERTY("X11Layout", "s", NULL, offsetof(Context, x11_layout), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1240 SD_BUS_PROPERTY("X11Model", "s", NULL, offsetof(Context, x11_model), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1241 SD_BUS_PROPERTY("X11Variant", "s", NULL, offsetof(Context, x11_variant), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1242 SD_BUS_PROPERTY("X11Options", "s", NULL, offsetof(Context, x11_options), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1243 SD_BUS_PROPERTY("VConsoleKeymap", "s", NULL, offsetof(Context, vc_keymap), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1244 SD_BUS_PROPERTY("VConsoleKeymapToggle", "s", NULL, offsetof(Context, vc_keymap_toggle), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1245 SD_BUS_METHOD("SetLocale", "asb", NULL, method_set_locale, SD_BUS_VTABLE_UNPRIVILEGED),
1246 SD_BUS_METHOD("SetVConsoleKeyboard", "ssbb", NULL, method_set_vc_keyboard, SD_BUS_VTABLE_UNPRIVILEGED),
1247 SD_BUS_METHOD("SetX11Keyboard", "ssssbb", NULL, method_set_x11_keyboard, SD_BUS_VTABLE_UNPRIVILEGED),
1248 SD_BUS_VTABLE_END
1249 };
1250
1251 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
1252 _cleanup_bus_flush_close_unref_ sd_bus *bus = NULL;
1253 int r;
1254
1255 assert(c);
1256 assert(event);
1257 assert(_bus);
1258
1259 r = sd_bus_default_system(&bus);
1260 if (r < 0)
1261 return log_error_errno(r, "Failed to get system bus connection: %m");
1262
1263 r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/locale1", "org.freedesktop.locale1", locale_vtable, c);
1264 if (r < 0)
1265 return log_error_errno(r, "Failed to register object: %m");
1266
1267 r = sd_bus_request_name(bus, "org.freedesktop.locale1", 0);
1268 if (r < 0)
1269 return log_error_errno(r, "Failed to register name: %m");
1270
1271 r = sd_bus_attach_event(bus, event, 0);
1272 if (r < 0)
1273 return log_error_errno(r, "Failed to attach bus to event loop: %m");
1274
1275 *_bus = bus;
1276 bus = NULL;
1277
1278 return 0;
1279 }
1280
1281 int main(int argc, char *argv[]) {
1282 _cleanup_(context_free) Context context = {};
1283 _cleanup_event_unref_ sd_event *event = NULL;
1284 _cleanup_bus_flush_close_unref_ sd_bus *bus = NULL;
1285 int r;
1286
1287 log_set_target(LOG_TARGET_AUTO);
1288 log_parse_environment();
1289 log_open();
1290
1291 umask(0022);
1292 mac_selinux_init("/etc");
1293
1294 if (argc != 1) {
1295 log_error("This program takes no arguments.");
1296 r = -EINVAL;
1297 goto finish;
1298 }
1299
1300 r = sd_event_default(&event);
1301 if (r < 0) {
1302 log_error_errno(r, "Failed to allocate event loop: %m");
1303 goto finish;
1304 }
1305
1306 sd_event_set_watchdog(event, true);
1307
1308 r = connect_bus(&context, event, &bus);
1309 if (r < 0)
1310 goto finish;
1311
1312 r = context_read_data(&context);
1313 if (r < 0) {
1314 log_error_errno(r, "Failed to read locale data: %m");
1315 goto finish;
1316 }
1317
1318 r = bus_event_loop_with_idle(event, bus, "org.freedesktop.locale1", DEFAULT_EXIT_USEC, NULL, NULL);
1319 if (r < 0) {
1320 log_error_errno(r, "Failed to run event loop: %m");
1321 goto finish;
1322 }
1323
1324 finish:
1325 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
1326 }