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