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