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