]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/locale/localed.c
Merge pull request #3392 from poettering/assorted-stuff
[thirdparty/systemd.git] / src / locale / localed.c
1 /***
2 This file is part of systemd.
3
4 Copyright 2011 Lennart Poettering
5 Copyright 2013 Kay Sievers
6
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19 ***/
20
21 #include <errno.h>
22 #include <string.h>
23 #include <unistd.h>
24
25 #ifdef HAVE_XKBCOMMON
26 #include <xkbcommon/xkbcommon.h>
27 #include <dlfcn.h>
28 #endif
29
30 #include "sd-bus.h"
31
32 #include "alloc-util.h"
33 #include "bus-error.h"
34 #include "bus-message.h"
35 #include "bus-util.h"
36 #include "def.h"
37 #include "env-util.h"
38 #include "fd-util.h"
39 #include "fileio-label.h"
40 #include "fileio.h"
41 #include "locale-util.h"
42 #include "mkdir.h"
43 #include "path-util.h"
44 #include "selinux-util.h"
45 #include "strv.h"
46 #include "user-util.h"
47 #include "util.h"
48
49 enum {
50 /* We don't list LC_ALL here on purpose. People should be
51 * using LANG instead. */
52 LOCALE_LANG,
53 LOCALE_LANGUAGE,
54 LOCALE_LC_CTYPE,
55 LOCALE_LC_NUMERIC,
56 LOCALE_LC_TIME,
57 LOCALE_LC_COLLATE,
58 LOCALE_LC_MONETARY,
59 LOCALE_LC_MESSAGES,
60 LOCALE_LC_PAPER,
61 LOCALE_LC_NAME,
62 LOCALE_LC_ADDRESS,
63 LOCALE_LC_TELEPHONE,
64 LOCALE_LC_MEASUREMENT,
65 LOCALE_LC_IDENTIFICATION,
66 _LOCALE_MAX
67 };
68
69 static const char * const names[_LOCALE_MAX] = {
70 [LOCALE_LANG] = "LANG",
71 [LOCALE_LANGUAGE] = "LANGUAGE",
72 [LOCALE_LC_CTYPE] = "LC_CTYPE",
73 [LOCALE_LC_NUMERIC] = "LC_NUMERIC",
74 [LOCALE_LC_TIME] = "LC_TIME",
75 [LOCALE_LC_COLLATE] = "LC_COLLATE",
76 [LOCALE_LC_MONETARY] = "LC_MONETARY",
77 [LOCALE_LC_MESSAGES] = "LC_MESSAGES",
78 [LOCALE_LC_PAPER] = "LC_PAPER",
79 [LOCALE_LC_NAME] = "LC_NAME",
80 [LOCALE_LC_ADDRESS] = "LC_ADDRESS",
81 [LOCALE_LC_TELEPHONE] = "LC_TELEPHONE",
82 [LOCALE_LC_MEASUREMENT] = "LC_MEASUREMENT",
83 [LOCALE_LC_IDENTIFICATION] = "LC_IDENTIFICATION"
84 };
85
86 typedef struct Context {
87 char *locale[_LOCALE_MAX];
88
89 char *x11_layout;
90 char *x11_model;
91 char *x11_variant;
92 char *x11_options;
93
94 char *vc_keymap;
95 char *vc_keymap_toggle;
96
97 Hashmap *polkit_registry;
98 } Context;
99
100 static bool startswith_comma(const char *s, const char *prefix) {
101 const char *t;
102
103 return s && (t = startswith(s, prefix)) && (*t == ',');
104 }
105
106 static void context_free_x11(Context *c) {
107 c->x11_layout = mfree(c->x11_layout);
108 c->x11_options = mfree(c->x11_options);
109 c->x11_model = mfree(c->x11_model);
110 c->x11_variant = mfree(c->x11_variant);
111 }
112
113 static void context_free_vconsole(Context *c) {
114 c->vc_keymap = mfree(c->vc_keymap);
115 c->vc_keymap_toggle = mfree(c->vc_keymap_toggle);
116 }
117
118 static void context_free_locale(Context *c) {
119 int p;
120
121 for (p = 0; p < _LOCALE_MAX; p++)
122 c->locale[p] = mfree(c->locale[p]);
123 }
124
125 static void context_free(Context *c) {
126 context_free_locale(c);
127 context_free_x11(c);
128 context_free_vconsole(c);
129
130 bus_verify_polkit_async_registry_free(c->polkit_registry);
131 };
132
133 static void locale_simplify(Context *c) {
134 int p;
135
136 for (p = LOCALE_LANG+1; p < _LOCALE_MAX; p++)
137 if (isempty(c->locale[p]) || streq_ptr(c->locale[LOCALE_LANG], c->locale[p]))
138 c->locale[p] = mfree(c->locale[p]);
139 }
140
141 static int locale_read_data(Context *c) {
142 int r;
143
144 context_free_locale(c);
145
146 r = parse_env_file("/etc/locale.conf", NEWLINE,
147 "LANG", &c->locale[LOCALE_LANG],
148 "LANGUAGE", &c->locale[LOCALE_LANGUAGE],
149 "LC_CTYPE", &c->locale[LOCALE_LC_CTYPE],
150 "LC_NUMERIC", &c->locale[LOCALE_LC_NUMERIC],
151 "LC_TIME", &c->locale[LOCALE_LC_TIME],
152 "LC_COLLATE", &c->locale[LOCALE_LC_COLLATE],
153 "LC_MONETARY", &c->locale[LOCALE_LC_MONETARY],
154 "LC_MESSAGES", &c->locale[LOCALE_LC_MESSAGES],
155 "LC_PAPER", &c->locale[LOCALE_LC_PAPER],
156 "LC_NAME", &c->locale[LOCALE_LC_NAME],
157 "LC_ADDRESS", &c->locale[LOCALE_LC_ADDRESS],
158 "LC_TELEPHONE", &c->locale[LOCALE_LC_TELEPHONE],
159 "LC_MEASUREMENT", &c->locale[LOCALE_LC_MEASUREMENT],
160 "LC_IDENTIFICATION", &c->locale[LOCALE_LC_IDENTIFICATION],
161 NULL);
162
163 if (r == -ENOENT) {
164 int p;
165
166 /* Fill in what we got passed from systemd. */
167 for (p = 0; p < _LOCALE_MAX; p++) {
168 assert(names[p]);
169
170 r = free_and_strdup(&c->locale[p], empty_to_null(getenv(names[p])));
171 if (r < 0)
172 return r;
173 }
174
175 r = 0;
176 }
177
178 locale_simplify(c);
179 return r;
180 }
181
182 static int vconsole_read_data(Context *c) {
183 int r;
184
185 context_free_vconsole(c);
186
187 r = parse_env_file("/etc/vconsole.conf", NEWLINE,
188 "KEYMAP", &c->vc_keymap,
189 "KEYMAP_TOGGLE", &c->vc_keymap_toggle,
190 NULL);
191
192 if (r < 0 && r != -ENOENT)
193 return r;
194
195 return 0;
196 }
197
198 static int x11_read_data(Context *c) {
199 _cleanup_fclose_ FILE *f;
200 char line[LINE_MAX];
201 bool in_section = false;
202 int r;
203
204 context_free_x11(c);
205
206 f = fopen("/etc/X11/xorg.conf.d/00-keyboard.conf", "re");
207 if (!f)
208 return errno == ENOENT ? 0 : -errno;
209
210 while (fgets(line, sizeof(line), f)) {
211 char *l;
212
213 char_array_0(line);
214 l = strstrip(line);
215
216 if (l[0] == 0 || l[0] == '#')
217 continue;
218
219 if (in_section && first_word(l, "Option")) {
220 _cleanup_strv_free_ char **a = NULL;
221
222 r = strv_split_extract(&a, l, WHITESPACE, EXTRACT_QUOTES);
223 if (r < 0)
224 return r;
225
226 if (strv_length(a) == 3) {
227 char **p = NULL;
228
229 if (streq(a[1], "XkbLayout"))
230 p = &c->x11_layout;
231 else if (streq(a[1], "XkbModel"))
232 p = &c->x11_model;
233 else if (streq(a[1], "XkbVariant"))
234 p = &c->x11_variant;
235 else if (streq(a[1], "XkbOptions"))
236 p = &c->x11_options;
237
238 if (p) {
239 free(*p);
240 *p = 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_(sd_bus_message_unrefp) 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_(sd_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 > 0 ? -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(*new_keymap);
758 *new_keymap = converted;
759 }
760 }
761
762 return 0;
763 }
764
765 static int find_language_fallback(const char *lang, char **language) {
766 _cleanup_fclose_ FILE *f = NULL;
767 unsigned n = 0;
768
769 assert(language);
770
771 f = fopen(SYSTEMD_LANGUAGE_FALLBACK_MAP, "re");
772 if (!f)
773 return -errno;
774
775 for (;;) {
776 _cleanup_strv_free_ char **a = NULL;
777 int r;
778
779 r = read_next_mapping(SYSTEMD_LANGUAGE_FALLBACK_MAP, 2, 2, f, &n, &a);
780 if (r <= 0)
781 return r;
782
783 if (streq(lang, a[0])) {
784 assert(strv_length(a) == 2);
785 *language = a[1];
786 a[1] = NULL;
787 return 1;
788 }
789 }
790
791 assert_not_reached("should not be here");
792 }
793
794 static int x11_convert_to_vconsole(Context *c, sd_bus *bus) {
795 bool modified = false;
796 int r;
797
798 assert(bus);
799
800 if (isempty(c->x11_layout)) {
801
802 modified =
803 !isempty(c->vc_keymap) ||
804 !isempty(c->vc_keymap_toggle);
805
806 context_free_x11(c);
807 } else {
808 char *new_keymap = NULL;
809
810 r = find_converted_keymap(c->x11_layout, c->x11_variant, &new_keymap);
811 if (r < 0)
812 return r;
813 else if (r == 0) {
814 r = find_legacy_keymap(c, &new_keymap);
815 if (r < 0)
816 return r;
817 }
818
819 if (!streq_ptr(c->vc_keymap, new_keymap)) {
820 free(c->vc_keymap);
821 c->vc_keymap = new_keymap;
822 c->vc_keymap_toggle = mfree(c->vc_keymap_toggle);
823 modified = true;
824 } else
825 free(new_keymap);
826 }
827
828 if (modified) {
829 r = vconsole_write_data(c);
830 if (r < 0)
831 log_error_errno(r, "Failed to set virtual console keymap: %m");
832
833 log_info("Changed virtual console keymap to '%s' toggle '%s'",
834 strempty(c->vc_keymap), strempty(c->vc_keymap_toggle));
835
836 sd_bus_emit_properties_changed(bus,
837 "/org/freedesktop/locale1",
838 "org.freedesktop.locale1",
839 "VConsoleKeymap", "VConsoleKeymapToggle", NULL);
840
841 return vconsole_reload(bus);
842 } else
843 log_debug("Virtual console keymap was not modified.");
844
845 return 0;
846 }
847
848 static int property_get_locale(
849 sd_bus *bus,
850 const char *path,
851 const char *interface,
852 const char *property,
853 sd_bus_message *reply,
854 void *userdata,
855 sd_bus_error *error) {
856
857 Context *c = userdata;
858 _cleanup_strv_free_ char **l = NULL;
859 int p, q;
860
861 l = new0(char*, _LOCALE_MAX+1);
862 if (!l)
863 return -ENOMEM;
864
865 for (p = 0, q = 0; p < _LOCALE_MAX; p++) {
866 char *t;
867
868 if (isempty(c->locale[p]))
869 continue;
870
871 if (asprintf(&t, "%s=%s", names[p], c->locale[p]) < 0)
872 return -ENOMEM;
873
874 l[q++] = t;
875 }
876
877 return sd_bus_message_append_strv(reply, l);
878 }
879
880 static int method_set_locale(sd_bus_message *m, void *userdata, sd_bus_error *error) {
881 Context *c = userdata;
882 _cleanup_strv_free_ char **l = NULL;
883 char **i;
884 const char *lang = NULL;
885 int interactive;
886 bool modified = false;
887 bool have[_LOCALE_MAX] = {};
888 int p;
889 int r;
890
891 assert(m);
892 assert(c);
893
894 r = bus_message_read_strv_extend(m, &l);
895 if (r < 0)
896 return r;
897
898 r = sd_bus_message_read_basic(m, 'b', &interactive);
899 if (r < 0)
900 return r;
901
902 /* Check whether a variable changed and if it is valid */
903 STRV_FOREACH(i, l) {
904 bool valid = false;
905
906 for (p = 0; p < _LOCALE_MAX; p++) {
907 size_t k;
908
909 k = strlen(names[p]);
910 if (startswith(*i, names[p]) &&
911 (*i)[k] == '=' &&
912 locale_is_valid((*i) + k + 1)) {
913 valid = true;
914 have[p] = true;
915
916 if (p == LOCALE_LANG)
917 lang = (*i) + k + 1;
918
919 if (!streq_ptr(*i + k + 1, c->locale[p]))
920 modified = true;
921
922 break;
923 }
924 }
925
926 if (!valid)
927 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid Locale data.");
928 }
929
930 /* If LANG was specified, but not LANGUAGE, check if we should
931 * set it based on the language fallback table. */
932 if (have[LOCALE_LANG] && !have[LOCALE_LANGUAGE]) {
933 _cleanup_free_ char *language = NULL;
934
935 assert(lang);
936
937 (void) find_language_fallback(lang, &language);
938 if (language) {
939 log_debug("Converted LANG=%s to LANGUAGE=%s", lang, language);
940 if (!streq_ptr(language, c->locale[LOCALE_LANGUAGE])) {
941 r = strv_extendf(&l, "LANGUAGE=%s", language);
942 if (r < 0)
943 return r;
944
945 have[LOCALE_LANGUAGE] = true;
946 modified = true;
947 }
948 }
949 }
950
951 /* Check whether a variable is unset */
952 if (!modified)
953 for (p = 0; p < _LOCALE_MAX; p++)
954 if (!isempty(c->locale[p]) && !have[p]) {
955 modified = true;
956 break;
957 }
958
959 if (modified) {
960 _cleanup_strv_free_ char **settings = NULL;
961
962 r = bus_verify_polkit_async(
963 m,
964 CAP_SYS_ADMIN,
965 "org.freedesktop.locale1.set-locale",
966 NULL,
967 interactive,
968 UID_INVALID,
969 &c->polkit_registry,
970 error);
971 if (r < 0)
972 return r;
973 if (r == 0)
974 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
975
976 STRV_FOREACH(i, l)
977 for (p = 0; p < _LOCALE_MAX; p++) {
978 size_t k;
979
980 k = strlen(names[p]);
981 if (startswith(*i, names[p]) && (*i)[k] == '=') {
982 r = free_and_strdup(&c->locale[p], *i + k + 1);
983 if (r < 0)
984 return r;
985 break;
986 }
987 }
988
989 for (p = 0; p < _LOCALE_MAX; p++) {
990 if (have[p])
991 continue;
992
993 c->locale[p] = mfree(c->locale[p]);
994 }
995
996 locale_simplify(c);
997
998 r = locale_write_data(c, &settings);
999 if (r < 0) {
1000 log_error_errno(r, "Failed to set locale: %m");
1001 return sd_bus_error_set_errnof(error, r, "Failed to set locale: %s", strerror(-r));
1002 }
1003
1004 locale_update_system_manager(c, sd_bus_message_get_bus(m));
1005
1006 if (settings) {
1007 _cleanup_free_ char *line;
1008
1009 line = strv_join(settings, ", ");
1010 log_info("Changed locale to %s.", strnull(line));
1011 } else
1012 log_info("Changed locale to unset.");
1013
1014 (void) sd_bus_emit_properties_changed(
1015 sd_bus_message_get_bus(m),
1016 "/org/freedesktop/locale1",
1017 "org.freedesktop.locale1",
1018 "Locale", NULL);
1019 } else
1020 log_debug("Locale settings were not modified.");
1021
1022
1023 return sd_bus_reply_method_return(m, NULL);
1024 }
1025
1026 static int method_set_vc_keyboard(sd_bus_message *m, void *userdata, sd_bus_error *error) {
1027 Context *c = userdata;
1028 const char *keymap, *keymap_toggle;
1029 int convert, interactive;
1030 int r;
1031
1032 assert(m);
1033 assert(c);
1034
1035 r = sd_bus_message_read(m, "ssbb", &keymap, &keymap_toggle, &convert, &interactive);
1036 if (r < 0)
1037 return r;
1038
1039 keymap = empty_to_null(keymap);
1040 keymap_toggle = empty_to_null(keymap_toggle);
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
1098 _printf_(3, 0)
1099 static void log_xkb(struct xkb_context *ctx, enum xkb_log_level lvl, const char *format, va_list args) {
1100 const char *fmt;
1101
1102 fmt = strjoina("libxkbcommon: ", format);
1103 log_internalv(LOG_DEBUG, 0, __FILE__, __LINE__, __func__, fmt, args);
1104 }
1105
1106 #define LOAD_SYMBOL(symbol, dl, name) \
1107 ({ \
1108 (symbol) = (typeof(symbol)) dlvsym((dl), (name), "V_0.5.0"); \
1109 (symbol) ? 0 : -EOPNOTSUPP; \
1110 })
1111
1112 static int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, const char *options) {
1113
1114 /* We dlopen() the library in order to make the dependency soft. The library (and what it pulls in) is huge
1115 * after all, hence let's support XKB maps when the library is around, and refuse otherwise. The function
1116 * pointers to the shared library are below: */
1117
1118 struct xkb_context* (*symbol_xkb_context_new)(enum xkb_context_flags flags) = NULL;
1119 void (*symbol_xkb_context_unref)(struct xkb_context *context) = NULL;
1120 void (*symbol_xkb_context_set_log_fn)(struct xkb_context *context, void (*log_fn)(struct xkb_context *context, enum xkb_log_level level, const char *format, va_list args)) = NULL;
1121 struct xkb_keymap* (*symbol_xkb_keymap_new_from_names)(struct xkb_context *context, const struct xkb_rule_names *names, enum xkb_keymap_compile_flags flags) = NULL;
1122 void (*symbol_xkb_keymap_unref)(struct xkb_keymap *keymap) = NULL;
1123
1124 const struct xkb_rule_names rmlvo = {
1125 .model = model,
1126 .layout = layout,
1127 .variant = variant,
1128 .options = options,
1129 };
1130 struct xkb_context *ctx = NULL;
1131 struct xkb_keymap *km = NULL;
1132 void *dl;
1133 int r;
1134
1135 /* Compile keymap from RMLVO information to check out its validity */
1136
1137 dl = dlopen("libxkbcommon.so.0", RTLD_LAZY);
1138 if (!dl)
1139 return -EOPNOTSUPP;
1140
1141 r = LOAD_SYMBOL(symbol_xkb_context_new, dl, "xkb_context_new");
1142 if (r < 0)
1143 goto finish;
1144
1145 r = LOAD_SYMBOL(symbol_xkb_context_unref, dl, "xkb_context_unref");
1146 if (r < 0)
1147 goto finish;
1148
1149 r = LOAD_SYMBOL(symbol_xkb_context_set_log_fn, dl, "xkb_context_set_log_fn");
1150 if (r < 0)
1151 goto finish;
1152
1153 r = LOAD_SYMBOL(symbol_xkb_keymap_new_from_names, dl, "xkb_keymap_new_from_names");
1154 if (r < 0)
1155 goto finish;
1156
1157 r = LOAD_SYMBOL(symbol_xkb_keymap_unref, dl, "xkb_keymap_unref");
1158 if (r < 0)
1159 goto finish;
1160
1161 ctx = symbol_xkb_context_new(XKB_CONTEXT_NO_ENVIRONMENT_NAMES);
1162 if (!ctx) {
1163 r = -ENOMEM;
1164 goto finish;
1165 }
1166
1167 symbol_xkb_context_set_log_fn(ctx, log_xkb);
1168
1169 km = symbol_xkb_keymap_new_from_names(ctx, &rmlvo, XKB_KEYMAP_COMPILE_NO_FLAGS);
1170 if (!km) {
1171 r = -EINVAL;
1172 goto finish;
1173 }
1174
1175 r = 0;
1176
1177 finish:
1178 if (symbol_xkb_keymap_unref && km)
1179 symbol_xkb_keymap_unref(km);
1180
1181 if (symbol_xkb_context_unref && ctx)
1182 symbol_xkb_context_unref(ctx);
1183
1184 (void) dlclose(dl);
1185 return r;
1186 }
1187
1188 #else
1189
1190 static int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, const char *options) {
1191 return 0;
1192 }
1193
1194 #endif
1195
1196 static int method_set_x11_keyboard(sd_bus_message *m, void *userdata, sd_bus_error *error) {
1197 Context *c = userdata;
1198 const char *layout, *model, *variant, *options;
1199 int convert, interactive;
1200 int r;
1201
1202 assert(m);
1203 assert(c);
1204
1205 r = sd_bus_message_read(m, "ssssbb", &layout, &model, &variant, &options, &convert, &interactive);
1206 if (r < 0)
1207 return r;
1208
1209 layout = empty_to_null(layout);
1210 model = empty_to_null(model);
1211 variant = empty_to_null(variant);
1212 options = empty_to_null(options);
1213
1214 if (!streq_ptr(layout, c->x11_layout) ||
1215 !streq_ptr(model, c->x11_model) ||
1216 !streq_ptr(variant, c->x11_variant) ||
1217 !streq_ptr(options, c->x11_options)) {
1218
1219 if ((layout && !string_is_safe(layout)) ||
1220 (model && !string_is_safe(model)) ||
1221 (variant && !string_is_safe(variant)) ||
1222 (options && !string_is_safe(options)))
1223 return sd_bus_error_set_errnof(error, -EINVAL, "Received invalid keyboard data");
1224
1225 r = bus_verify_polkit_async(
1226 m,
1227 CAP_SYS_ADMIN,
1228 "org.freedesktop.locale1.set-keyboard",
1229 NULL,
1230 interactive,
1231 UID_INVALID,
1232 &c->polkit_registry,
1233 error);
1234 if (r < 0)
1235 return r;
1236 if (r == 0)
1237 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
1238
1239 r = verify_xkb_rmlvo(model, layout, variant, options);
1240 if (r < 0) {
1241 log_error_errno(r, "Cannot compile XKB keymap for new x11 keyboard layout ('%s' / '%s' / '%s' / '%s'): %m",
1242 strempty(model), strempty(layout), strempty(variant), strempty(options));
1243
1244 if (r == -EOPNOTSUPP)
1245 return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Local keyboard configuration not supported on this system.");
1246
1247 return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Specified keymap cannot be compiled, refusing as invalid.");
1248 }
1249
1250 if (free_and_strdup(&c->x11_layout, layout) < 0 ||
1251 free_and_strdup(&c->x11_model, model) < 0 ||
1252 free_and_strdup(&c->x11_variant, variant) < 0 ||
1253 free_and_strdup(&c->x11_options, options) < 0)
1254 return -ENOMEM;
1255
1256 r = x11_write_data(c);
1257 if (r < 0) {
1258 log_error_errno(r, "Failed to set X11 keyboard layout: %m");
1259 return sd_bus_error_set_errnof(error, r, "Failed to set X11 keyboard layout: %s", strerror(-r));
1260 }
1261
1262 log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
1263 strempty(c->x11_layout),
1264 strempty(c->x11_model),
1265 strempty(c->x11_variant),
1266 strempty(c->x11_options));
1267
1268 (void) sd_bus_emit_properties_changed(
1269 sd_bus_message_get_bus(m),
1270 "/org/freedesktop/locale1",
1271 "org.freedesktop.locale1",
1272 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL);
1273
1274 if (convert) {
1275 r = x11_convert_to_vconsole(c, sd_bus_message_get_bus(m));
1276 if (r < 0)
1277 log_error_errno(r, "Failed to convert keymap data: %m");
1278 }
1279 }
1280
1281 return sd_bus_reply_method_return(m, NULL);
1282 }
1283
1284 static const sd_bus_vtable locale_vtable[] = {
1285 SD_BUS_VTABLE_START(0),
1286 SD_BUS_PROPERTY("Locale", "as", property_get_locale, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1287 SD_BUS_PROPERTY("X11Layout", "s", NULL, offsetof(Context, x11_layout), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1288 SD_BUS_PROPERTY("X11Model", "s", NULL, offsetof(Context, x11_model), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1289 SD_BUS_PROPERTY("X11Variant", "s", NULL, offsetof(Context, x11_variant), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1290 SD_BUS_PROPERTY("X11Options", "s", NULL, offsetof(Context, x11_options), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1291 SD_BUS_PROPERTY("VConsoleKeymap", "s", NULL, offsetof(Context, vc_keymap), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1292 SD_BUS_PROPERTY("VConsoleKeymapToggle", "s", NULL, offsetof(Context, vc_keymap_toggle), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1293 SD_BUS_METHOD("SetLocale", "asb", NULL, method_set_locale, SD_BUS_VTABLE_UNPRIVILEGED),
1294 SD_BUS_METHOD("SetVConsoleKeyboard", "ssbb", NULL, method_set_vc_keyboard, SD_BUS_VTABLE_UNPRIVILEGED),
1295 SD_BUS_METHOD("SetX11Keyboard", "ssssbb", NULL, method_set_x11_keyboard, SD_BUS_VTABLE_UNPRIVILEGED),
1296 SD_BUS_VTABLE_END
1297 };
1298
1299 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
1300 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
1301 int r;
1302
1303 assert(c);
1304 assert(event);
1305 assert(_bus);
1306
1307 r = sd_bus_default_system(&bus);
1308 if (r < 0)
1309 return log_error_errno(r, "Failed to get system bus connection: %m");
1310
1311 r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/locale1", "org.freedesktop.locale1", locale_vtable, c);
1312 if (r < 0)
1313 return log_error_errno(r, "Failed to register object: %m");
1314
1315 r = sd_bus_request_name(bus, "org.freedesktop.locale1", 0);
1316 if (r < 0)
1317 return log_error_errno(r, "Failed to register name: %m");
1318
1319 r = sd_bus_attach_event(bus, event, 0);
1320 if (r < 0)
1321 return log_error_errno(r, "Failed to attach bus to event loop: %m");
1322
1323 *_bus = bus;
1324 bus = NULL;
1325
1326 return 0;
1327 }
1328
1329 int main(int argc, char *argv[]) {
1330 _cleanup_(context_free) Context context = {};
1331 _cleanup_(sd_event_unrefp) sd_event *event = NULL;
1332 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
1333 int r;
1334
1335 log_set_target(LOG_TARGET_AUTO);
1336 log_parse_environment();
1337 log_open();
1338
1339 umask(0022);
1340 mac_selinux_init();
1341
1342 if (argc != 1) {
1343 log_error("This program takes no arguments.");
1344 r = -EINVAL;
1345 goto finish;
1346 }
1347
1348 r = sd_event_default(&event);
1349 if (r < 0) {
1350 log_error_errno(r, "Failed to allocate event loop: %m");
1351 goto finish;
1352 }
1353
1354 sd_event_set_watchdog(event, true);
1355
1356 r = connect_bus(&context, event, &bus);
1357 if (r < 0)
1358 goto finish;
1359
1360 r = context_read_data(&context);
1361 if (r < 0) {
1362 log_error_errno(r, "Failed to read locale data: %m");
1363 goto finish;
1364 }
1365
1366 r = bus_event_loop_with_idle(event, bus, "org.freedesktop.locale1", DEFAULT_EXIT_USEC, NULL, NULL);
1367 if (r < 0) {
1368 log_error_errno(r, "Failed to run event loop: %m");
1369 goto finish;
1370 }
1371
1372 finish:
1373 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
1374 }