]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/locale/localed.c
tmpfiles: add new line type 'v' for creating btrfs subvolumes
[thirdparty/systemd.git] / src / locale / localed.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4 This file is part of systemd.
5
6 Copyright 2011 Lennart Poettering
7 Copyright 2013 Kay Sievers
8
9 systemd is free software; you can redistribute it and/or modify it
10 under the terms of the GNU Lesser General Public License as published by
11 the Free Software Foundation; either version 2.1 of the License, or
12 (at your option) any later version.
13
14 systemd is distributed in the hope that it will be useful, but
15 WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 Lesser General Public License for more details.
18
19 You should have received a copy of the GNU Lesser General Public License
20 along with systemd; If not, see <http://www.gnu.org/licenses/>.
21 ***/
22
23 #include <errno.h>
24 #include <string.h>
25 #include <unistd.h>
26
27 #include "sd-bus.h"
28
29 #include "util.h"
30 #include "mkdir.h"
31 #include "strv.h"
32 #include "def.h"
33 #include "env-util.h"
34 #include "fileio.h"
35 #include "fileio-label.h"
36 #include "label.h"
37 #include "bus-util.h"
38 #include "bus-error.h"
39 #include "bus-message.h"
40 #include "event-util.h"
41 #include "locale-util.h"
42 #include "selinux-util.h"
43
44 #ifdef HAVE_XKBCOMMON
45 #include <xkbcommon/xkbcommon.h>
46 #endif
47
48 enum {
49 /* We don't list LC_ALL here on purpose. People should be
50 * using LANG instead. */
51 LOCALE_LANG,
52 LOCALE_LANGUAGE,
53 LOCALE_LC_CTYPE,
54 LOCALE_LC_NUMERIC,
55 LOCALE_LC_TIME,
56 LOCALE_LC_COLLATE,
57 LOCALE_LC_MONETARY,
58 LOCALE_LC_MESSAGES,
59 LOCALE_LC_PAPER,
60 LOCALE_LC_NAME,
61 LOCALE_LC_ADDRESS,
62 LOCALE_LC_TELEPHONE,
63 LOCALE_LC_MEASUREMENT,
64 LOCALE_LC_IDENTIFICATION,
65 _LOCALE_MAX
66 };
67
68 static const char * const names[_LOCALE_MAX] = {
69 [LOCALE_LANG] = "LANG",
70 [LOCALE_LANGUAGE] = "LANGUAGE",
71 [LOCALE_LC_CTYPE] = "LC_CTYPE",
72 [LOCALE_LC_NUMERIC] = "LC_NUMERIC",
73 [LOCALE_LC_TIME] = "LC_TIME",
74 [LOCALE_LC_COLLATE] = "LC_COLLATE",
75 [LOCALE_LC_MONETARY] = "LC_MONETARY",
76 [LOCALE_LC_MESSAGES] = "LC_MESSAGES",
77 [LOCALE_LC_PAPER] = "LC_PAPER",
78 [LOCALE_LC_NAME] = "LC_NAME",
79 [LOCALE_LC_ADDRESS] = "LC_ADDRESS",
80 [LOCALE_LC_TELEPHONE] = "LC_TELEPHONE",
81 [LOCALE_LC_MEASUREMENT] = "LC_MEASUREMENT",
82 [LOCALE_LC_IDENTIFICATION] = "LC_IDENTIFICATION"
83 };
84
85 typedef struct Context {
86 char *locale[_LOCALE_MAX];
87
88 char *x11_layout;
89 char *x11_model;
90 char *x11_variant;
91 char *x11_options;
92
93 char *vc_keymap;
94 char *vc_keymap_toggle;
95
96 Hashmap *polkit_registry;
97 } Context;
98
99 static const char* nonempty(const char *s) {
100 return isempty(s) ? NULL : s;
101 }
102
103 static void free_and_replace(char **s, char *v) {
104 free(*s);
105 *s = v;
106 }
107
108 static bool startswith_comma(const char *s, const char *prefix) {
109 const char *t;
110
111 return s && (t = startswith(s, prefix)) && (*t == ',');
112 }
113
114 static void context_free_x11(Context *c) {
115 free_and_replace(&c->x11_layout, NULL);
116 free_and_replace(&c->x11_model, NULL);
117 free_and_replace(&c->x11_variant, NULL);
118 free_and_replace(&c->x11_options, NULL);
119 }
120
121 static void context_free_vconsole(Context *c) {
122 free_and_replace(&c->vc_keymap, NULL);
123 free_and_replace(&c->vc_keymap_toggle, NULL);
124 }
125
126 static void context_free_locale(Context *c) {
127 int p;
128
129 for (p = 0; p < _LOCALE_MAX; p++)
130 free_and_replace(&c->locale[p], NULL);
131 }
132
133 static void context_free(Context *c) {
134 context_free_locale(c);
135 context_free_x11(c);
136 context_free_vconsole(c);
137
138 bus_verify_polkit_async_registry_free(c->polkit_registry);
139 };
140
141 static void locale_simplify(Context *c) {
142 int p;
143
144 for (p = LOCALE_LANG+1; p < _LOCALE_MAX; p++)
145 if (isempty(c->locale[p]) || streq_ptr(c->locale[LOCALE_LANG], c->locale[p]))
146 free_and_replace(&c->locale[p], NULL);
147 }
148
149 static int locale_read_data(Context *c) {
150 int r;
151
152 context_free_locale(c);
153
154 r = parse_env_file("/etc/locale.conf", NEWLINE,
155 "LANG", &c->locale[LOCALE_LANG],
156 "LANGUAGE", &c->locale[LOCALE_LANGUAGE],
157 "LC_CTYPE", &c->locale[LOCALE_LC_CTYPE],
158 "LC_NUMERIC", &c->locale[LOCALE_LC_NUMERIC],
159 "LC_TIME", &c->locale[LOCALE_LC_TIME],
160 "LC_COLLATE", &c->locale[LOCALE_LC_COLLATE],
161 "LC_MONETARY", &c->locale[LOCALE_LC_MONETARY],
162 "LC_MESSAGES", &c->locale[LOCALE_LC_MESSAGES],
163 "LC_PAPER", &c->locale[LOCALE_LC_PAPER],
164 "LC_NAME", &c->locale[LOCALE_LC_NAME],
165 "LC_ADDRESS", &c->locale[LOCALE_LC_ADDRESS],
166 "LC_TELEPHONE", &c->locale[LOCALE_LC_TELEPHONE],
167 "LC_MEASUREMENT", &c->locale[LOCALE_LC_MEASUREMENT],
168 "LC_IDENTIFICATION", &c->locale[LOCALE_LC_IDENTIFICATION],
169 NULL);
170
171 if (r == -ENOENT) {
172 int p;
173
174 /* Fill in what we got passed from systemd. */
175 for (p = 0; p < _LOCALE_MAX; p++) {
176 assert(names[p]);
177
178 r = free_and_strdup(&c->locale[p],
179 nonempty(getenv(names[p])));
180 if (r < 0)
181 return r;
182 }
183
184 r = 0;
185 }
186
187 locale_simplify(c);
188 return r;
189 }
190
191 static int vconsole_read_data(Context *c) {
192 int r;
193
194 context_free_vconsole(c);
195
196 r = parse_env_file("/etc/vconsole.conf", NEWLINE,
197 "KEYMAP", &c->vc_keymap,
198 "KEYMAP_TOGGLE", &c->vc_keymap_toggle,
199 NULL);
200
201 if (r < 0 && r != -ENOENT)
202 return r;
203
204 return 0;
205 }
206
207 static int x11_read_data(Context *c) {
208 _cleanup_fclose_ FILE *f;
209 char line[LINE_MAX];
210 bool in_section = false;
211 int r;
212
213 context_free_x11(c);
214
215 f = fopen("/etc/X11/xorg.conf.d/00-keyboard.conf", "re");
216 if (!f)
217 return errno == ENOENT ? 0 : -errno;
218
219 while (fgets(line, sizeof(line), f)) {
220 char *l;
221
222 char_array_0(line);
223 l = strstrip(line);
224
225 if (l[0] == 0 || l[0] == '#')
226 continue;
227
228 if (in_section && first_word(l, "Option")) {
229 _cleanup_strv_free_ char **a = NULL;
230
231 r = strv_split_quoted(&a, l, false);
232 if (r < 0)
233 return r;
234
235 if (strv_length(a) == 3) {
236 if (streq(a[1], "XkbLayout")) {
237 free_and_replace(&c->x11_layout, a[2]);
238 a[2] = NULL;
239 } else if (streq(a[1], "XkbModel")) {
240 free_and_replace(&c->x11_model, a[2]);
241 a[2] = NULL;
242 } else if (streq(a[1], "XkbVariant")) {
243 free_and_replace(&c->x11_variant, a[2]);
244 a[2] = NULL;
245 } else if (streq(a[1], "XkbOptions")) {
246 free_and_replace(&c->x11_options, 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_quoted(&a, l, false);
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_bus_message_unref_ 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 fflush(f);
481
482 if (ferror(f) || rename(temp_path, "/etc/X11/xorg.conf.d/00-keyboard.conf") < 0) {
483 r = -errno;
484 unlink("/etc/X11/xorg.conf.d/00-keyboard.conf");
485 unlink(temp_path);
486 return r;
487 } else
488 return 0;
489 }
490
491 static int vconsole_reload(sd_bus *bus) {
492 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
493 int r;
494
495 assert(bus);
496
497 r = sd_bus_call_method(bus,
498 "org.freedesktop.systemd1",
499 "/org/freedesktop/systemd1",
500 "org.freedesktop.systemd1.Manager",
501 "RestartUnit",
502 &error,
503 NULL,
504 "ss", "systemd-vconsole-setup.service", "replace");
505
506 if (r < 0)
507 log_error("Failed to issue method call: %s", bus_error_message(&error, -r));
508 return r;
509 }
510
511 static const char* strnulldash(const char *s) {
512 return isempty(s) || streq(s, "-") ? NULL : s;
513 }
514
515 static int read_next_mapping(FILE *f, unsigned *n, char ***a) {
516 assert(f);
517 assert(n);
518 assert(a);
519
520 for (;;) {
521 char line[LINE_MAX];
522 char *l, **b;
523 int r;
524
525 errno = 0;
526 if (!fgets(line, sizeof(line), f)) {
527
528 if (ferror(f))
529 return errno ? -errno : -EIO;
530
531 return 0;
532 }
533
534 (*n) ++;
535
536 l = strstrip(line);
537 if (l[0] == 0 || l[0] == '#')
538 continue;
539
540 r = strv_split_quoted(&b, l, false);
541 if (r < 0)
542 return r;
543
544 if (strv_length(b) < 5) {
545 log_error("Invalid line "SYSTEMD_KBD_MODEL_MAP":%u, ignoring.", *n);
546 strv_free(b);
547 continue;
548
549 }
550
551 *a = b;
552 return 1;
553 }
554 }
555
556 static int vconsole_convert_to_x11(Context *c, sd_bus *bus) {
557 bool modified = false;
558
559 assert(bus);
560
561 if (isempty(c->vc_keymap)) {
562
563 modified =
564 !isempty(c->x11_layout) ||
565 !isempty(c->x11_model) ||
566 !isempty(c->x11_variant) ||
567 !isempty(c->x11_options);
568
569 context_free_x11(c);
570 } else {
571 _cleanup_fclose_ FILE *f = NULL;
572 unsigned n = 0;
573
574 f = fopen(SYSTEMD_KBD_MODEL_MAP, "re");
575 if (!f)
576 return -errno;
577
578 for (;;) {
579 _cleanup_strv_free_ char **a = NULL;
580 int r;
581
582 r = read_next_mapping(f, &n, &a);
583 if (r < 0)
584 return r;
585 if (r == 0)
586 break;
587
588 if (!streq(c->vc_keymap, a[0]))
589 continue;
590
591 if (!streq_ptr(c->x11_layout, strnulldash(a[1])) ||
592 !streq_ptr(c->x11_model, strnulldash(a[2])) ||
593 !streq_ptr(c->x11_variant, strnulldash(a[3])) ||
594 !streq_ptr(c->x11_options, strnulldash(a[4]))) {
595
596 if (free_and_strdup(&c->x11_layout, strnulldash(a[1])) < 0 ||
597 free_and_strdup(&c->x11_model, strnulldash(a[2])) < 0 ||
598 free_and_strdup(&c->x11_variant, strnulldash(a[3])) < 0 ||
599 free_and_strdup(&c->x11_options, strnulldash(a[4])) < 0)
600 return -ENOMEM;
601
602 modified = true;
603 }
604
605 break;
606 }
607 }
608
609 if (modified) {
610 int r;
611
612 r = x11_write_data(c);
613 if (r < 0)
614 return log_error_errno(r, "Failed to set X11 keyboard layout: %m");
615
616 log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
617 strempty(c->x11_layout),
618 strempty(c->x11_model),
619 strempty(c->x11_variant),
620 strempty(c->x11_options));
621
622 sd_bus_emit_properties_changed(bus,
623 "/org/freedesktop/locale1",
624 "org.freedesktop.locale1",
625 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL);
626 } else
627 log_debug("X11 keyboard layout was not modified.");
628
629 return 0;
630 }
631
632 static int find_converted_keymap(const char *x11_layout, const char *x11_variant, char **new_keymap) {
633 const char *dir;
634 _cleanup_free_ char *n;
635
636 if (x11_variant)
637 n = strjoin(x11_layout, "-", x11_variant, NULL);
638 else
639 n = strdup(x11_layout);
640 if (!n)
641 return -ENOMEM;
642
643 NULSTR_FOREACH(dir, KBD_KEYMAP_DIRS) {
644 _cleanup_free_ char *p = NULL, *pz = NULL;
645 bool uncompressed;
646
647 p = strjoin(dir, "xkb/", n, ".map", NULL);
648 pz = strjoin(dir, "xkb/", n, ".map.gz", NULL);
649 if (!p || !pz)
650 return -ENOMEM;
651
652 uncompressed = access(p, F_OK) == 0;
653 if (uncompressed || access(pz, F_OK) == 0) {
654 log_debug("Found converted keymap %s at %s",
655 n, uncompressed ? p : pz);
656
657 *new_keymap = n;
658 n = NULL;
659 return 1;
660 }
661 }
662
663 return 0;
664 }
665
666 static int find_legacy_keymap(Context *c, char **new_keymap) {
667 _cleanup_fclose_ FILE *f;
668 unsigned n = 0;
669 unsigned best_matching = 0;
670 int r;
671
672 f = fopen(SYSTEMD_KBD_MODEL_MAP, "re");
673 if (!f)
674 return -errno;
675
676 for (;;) {
677 _cleanup_strv_free_ char **a = NULL;
678 unsigned matching = 0;
679
680 r = read_next_mapping(f, &n, &a);
681 if (r < 0)
682 return r;
683 if (r == 0)
684 break;
685
686 /* Determine how well matching this entry is */
687 if (streq_ptr(c->x11_layout, a[1]))
688 /* If we got an exact match, this is best */
689 matching = 10;
690 else {
691 /* We have multiple X layouts, look for an
692 * entry that matches our key with everything
693 * but the first layout stripped off. */
694 if (startswith_comma(c->x11_layout, a[1]))
695 matching = 5;
696 else {
697 char *x;
698
699 /* If that didn't work, strip off the
700 * other layouts from the entry, too */
701 x = strndupa(a[1], strcspn(a[1], ","));
702 if (startswith_comma(c->x11_layout, x))
703 matching = 1;
704 }
705 }
706
707 if (matching > 0) {
708 if (isempty(c->x11_model) || streq_ptr(c->x11_model, a[2])) {
709 matching++;
710
711 if (streq_ptr(c->x11_variant, a[3])) {
712 matching++;
713
714 if (streq_ptr(c->x11_options, a[4]))
715 matching++;
716 }
717 }
718 }
719
720 /* The best matching entry so far, then let's save that */
721 if (matching >= MAX(best_matching, 1u)) {
722 log_debug("Found legacy keymap %s with score %u",
723 a[0], matching);
724
725 if (matching > best_matching) {
726 best_matching = matching;
727
728 r = free_and_strdup(new_keymap, a[0]);
729 if (r < 0)
730 return r;
731 }
732 }
733 }
734
735 if (best_matching < 10 && c->x11_layout) {
736 /* The best match is only the first part of the X11
737 * keymap. Check if we have a converted map which
738 * matches just the first layout.
739 */
740 char *l, *v = NULL, *converted;
741
742 l = strndupa(c->x11_layout, strcspn(c->x11_layout, ","));
743 if (c->x11_variant)
744 v = strndupa(c->x11_variant, strcspn(c->x11_variant, ","));
745 r = find_converted_keymap(l, v, &converted);
746 if (r < 0)
747 return r;
748 if (r > 0)
749 free_and_replace(new_keymap, converted);
750 }
751
752 return 0;
753 }
754
755 static int x11_convert_to_vconsole(Context *c, sd_bus *bus) {
756 bool modified = false;
757 int r;
758
759 assert(bus);
760
761 if (isempty(c->x11_layout)) {
762
763 modified =
764 !isempty(c->vc_keymap) ||
765 !isempty(c->vc_keymap_toggle);
766
767 context_free_x11(c);
768 } else {
769 char *new_keymap = NULL;
770
771 r = find_converted_keymap(c->x11_layout, c->x11_variant, &new_keymap);
772 if (r < 0)
773 return r;
774 else if (r == 0) {
775 r = find_legacy_keymap(c, &new_keymap);
776 if (r < 0)
777 return r;
778 }
779
780 if (!streq_ptr(c->vc_keymap, new_keymap)) {
781 free_and_replace(&c->vc_keymap, new_keymap);
782 free_and_replace(&c->vc_keymap_toggle, NULL);
783 modified = true;
784 } else
785 free(new_keymap);
786 }
787
788 if (modified) {
789 r = vconsole_write_data(c);
790 if (r < 0)
791 log_error_errno(r, "Failed to set virtual console keymap: %m");
792
793 log_info("Changed virtual console keymap to '%s' toggle '%s'",
794 strempty(c->vc_keymap), strempty(c->vc_keymap_toggle));
795
796 sd_bus_emit_properties_changed(bus,
797 "/org/freedesktop/locale1",
798 "org.freedesktop.locale1",
799 "VConsoleKeymap", "VConsoleKeymapToggle", NULL);
800
801 return vconsole_reload(bus);
802 } else
803 log_debug("Virtual console keymap was not modified.");
804
805 return 0;
806 }
807
808 static int property_get_locale(
809 sd_bus *bus,
810 const char *path,
811 const char *interface,
812 const char *property,
813 sd_bus_message *reply,
814 void *userdata,
815 sd_bus_error *error) {
816
817 Context *c = userdata;
818 _cleanup_strv_free_ char **l = NULL;
819 int p, q;
820
821 l = new0(char*, _LOCALE_MAX+1);
822 if (!l)
823 return -ENOMEM;
824
825 for (p = 0, q = 0; p < _LOCALE_MAX; p++) {
826 char *t;
827
828 if (isempty(c->locale[p]))
829 continue;
830
831 if (asprintf(&t, "%s=%s", names[p], c->locale[p]) < 0)
832 return -ENOMEM;
833
834 l[q++] = t;
835 }
836
837 return sd_bus_message_append_strv(reply, l);
838 }
839
840 static int method_set_locale(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
841 Context *c = userdata;
842 _cleanup_strv_free_ char **l = NULL;
843 char **i;
844 int interactive;
845 bool modified = false;
846 bool passed[_LOCALE_MAX] = {};
847 int p;
848 int r;
849
850 r = bus_message_read_strv_extend(m, &l);
851 if (r < 0)
852 return r;
853
854 r = sd_bus_message_read_basic(m, 'b', &interactive);
855 if (r < 0)
856 return r;
857
858 /* Check whether a variable changed and if it is valid */
859 STRV_FOREACH(i, l) {
860 bool valid = false;
861
862 for (p = 0; p < _LOCALE_MAX; p++) {
863 size_t k;
864
865 k = strlen(names[p]);
866 if (startswith(*i, names[p]) &&
867 (*i)[k] == '=' &&
868 locale_is_valid((*i) + k + 1)) {
869 valid = true;
870 passed[p] = true;
871
872 if (!streq_ptr(*i + k + 1, c->locale[p]))
873 modified = true;
874
875 break;
876 }
877 }
878
879 if (!valid)
880 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid Locale data.");
881 }
882
883 /* Check whether a variable is unset */
884 if (!modified)
885 for (p = 0; p < _LOCALE_MAX; p++)
886 if (!isempty(c->locale[p]) && !passed[p]) {
887 modified = true;
888 break;
889 }
890
891 if (modified) {
892 _cleanup_strv_free_ char **settings = NULL;
893
894 r = bus_verify_polkit_async(m, CAP_SYS_ADMIN, "org.freedesktop.locale1.set-locale", interactive, &c->polkit_registry, error);
895 if (r < 0)
896 return r;
897 if (r == 0)
898 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
899
900 STRV_FOREACH(i, l)
901 for (p = 0; p < _LOCALE_MAX; p++) {
902 size_t k;
903
904 k = strlen(names[p]);
905 if (startswith(*i, names[p]) && (*i)[k] == '=') {
906 r = free_and_strdup(&c->locale[p], *i + k + 1);
907 if (r < 0)
908 return r;
909 break;
910 }
911 }
912
913 for (p = 0; p < _LOCALE_MAX; p++) {
914 if (passed[p])
915 continue;
916
917 free_and_replace(&c->locale[p], NULL);
918 }
919
920 locale_simplify(c);
921
922 r = locale_write_data(c, &settings);
923 if (r < 0) {
924 log_error_errno(r, "Failed to set locale: %m");
925 return sd_bus_error_set_errnof(error, r, "Failed to set locale: %s", strerror(-r));
926 }
927
928 locale_update_system_manager(c, bus);
929
930 if (settings) {
931 _cleanup_free_ char *line;
932
933 line = strv_join(settings, ", ");
934 log_info("Changed locale to %s.", strnull(line));
935 } else
936 log_info("Changed locale to unset.");
937
938 sd_bus_emit_properties_changed(bus,
939 "/org/freedesktop/locale1",
940 "org.freedesktop.locale1",
941 "Locale", NULL);
942 } else
943 log_debug("Locale settings were not modified.");
944
945
946 return sd_bus_reply_method_return(m, NULL);
947 }
948
949 static int method_set_vc_keyboard(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
950 Context *c = userdata;
951 const char *keymap, *keymap_toggle;
952 int convert, interactive;
953 int r;
954
955 r = sd_bus_message_read(m, "ssbb", &keymap, &keymap_toggle, &convert, &interactive);
956 if (r < 0)
957 return r;
958
959 if (isempty(keymap))
960 keymap = NULL;
961
962 if (isempty(keymap_toggle))
963 keymap_toggle = NULL;
964
965 if (!streq_ptr(keymap, c->vc_keymap) ||
966 !streq_ptr(keymap_toggle, c->vc_keymap_toggle)) {
967
968 if ((keymap && (!filename_is_valid(keymap) || !string_is_safe(keymap))) ||
969 (keymap_toggle && (!filename_is_valid(keymap_toggle) || !string_is_safe(keymap_toggle))))
970 return sd_bus_error_set_errnof(error, -EINVAL, "Received invalid keymap data");
971
972 r = bus_verify_polkit_async(m, CAP_SYS_ADMIN, "org.freedesktop.locale1.set-keyboard", interactive, &c->polkit_registry, error);
973 if (r < 0)
974 return r;
975 if (r == 0)
976 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
977
978 if (free_and_strdup(&c->vc_keymap, keymap) < 0 ||
979 free_and_strdup(&c->vc_keymap_toggle, keymap_toggle) < 0)
980 return -ENOMEM;
981
982 r = vconsole_write_data(c);
983 if (r < 0) {
984 log_error_errno(r, "Failed to set virtual console keymap: %m");
985 return sd_bus_error_set_errnof(error, r, "Failed to set virtual console keymap: %s", strerror(-r));
986 }
987
988 log_info("Changed virtual console keymap to '%s' toggle '%s'",
989 strempty(c->vc_keymap), strempty(c->vc_keymap_toggle));
990
991 r = vconsole_reload(bus);
992 if (r < 0)
993 log_error_errno(r, "Failed to request keymap reload: %m");
994
995 sd_bus_emit_properties_changed(bus,
996 "/org/freedesktop/locale1",
997 "org.freedesktop.locale1",
998 "VConsoleKeymap", "VConsoleKeymapToggle", NULL);
999
1000 if (convert) {
1001 r = vconsole_convert_to_x11(c, bus);
1002 if (r < 0)
1003 log_error_errno(r, "Failed to convert keymap data: %m");
1004 }
1005 }
1006
1007 return sd_bus_reply_method_return(m, NULL);
1008 }
1009
1010 #ifdef HAVE_XKBCOMMON
1011 static void log_xkb(struct xkb_context *ctx, enum xkb_log_level lvl, const char *format, va_list args) {
1012 const char *fmt;
1013
1014 fmt = strappenda("libxkbcommon: ", format);
1015 log_internalv(LOG_DEBUG, 0, __FILE__, __LINE__, __func__, fmt, args);
1016 }
1017
1018 static int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, const char *options) {
1019 const struct xkb_rule_names rmlvo = {
1020 .model = model,
1021 .layout = layout,
1022 .variant = variant,
1023 .options = options,
1024 };
1025 struct xkb_context *ctx = NULL;
1026 struct xkb_keymap *km = NULL;
1027 int r;
1028
1029 /* compile keymap from RMLVO information to check out its validity */
1030
1031 ctx = xkb_context_new(XKB_CONTEXT_NO_ENVIRONMENT_NAMES);
1032 if (!ctx) {
1033 r = -ENOMEM;
1034 goto exit;
1035 }
1036
1037 xkb_context_set_log_fn(ctx, log_xkb);
1038
1039 km = xkb_keymap_new_from_names(ctx, &rmlvo, XKB_KEYMAP_COMPILE_NO_FLAGS);
1040 if (!km) {
1041 r = -EINVAL;
1042 goto exit;
1043 }
1044
1045 r = 0;
1046
1047 exit:
1048 xkb_keymap_unref(km);
1049 xkb_context_unref(ctx);
1050 return r;
1051 }
1052 #else
1053 static int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, const char *options) {
1054 return 0;
1055 }
1056 #endif
1057
1058 static int method_set_x11_keyboard(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
1059 Context *c = userdata;
1060 const char *layout, *model, *variant, *options;
1061 int convert, interactive;
1062 int r;
1063
1064 r = sd_bus_message_read(m, "ssssbb", &layout, &model, &variant, &options, &convert, &interactive);
1065 if (r < 0)
1066 return r;
1067
1068 if (isempty(layout))
1069 layout = NULL;
1070
1071 if (isempty(model))
1072 model = NULL;
1073
1074 if (isempty(variant))
1075 variant = NULL;
1076
1077 if (isempty(options))
1078 options = NULL;
1079
1080 if (!streq_ptr(layout, c->x11_layout) ||
1081 !streq_ptr(model, c->x11_model) ||
1082 !streq_ptr(variant, c->x11_variant) ||
1083 !streq_ptr(options, c->x11_options)) {
1084
1085 if ((layout && !string_is_safe(layout)) ||
1086 (model && !string_is_safe(model)) ||
1087 (variant && !string_is_safe(variant)) ||
1088 (options && !string_is_safe(options)))
1089 return sd_bus_error_set_errnof(error, -EINVAL, "Received invalid keyboard data");
1090
1091 r = bus_verify_polkit_async(m, CAP_SYS_ADMIN, "org.freedesktop.locale1.set-keyboard", interactive, &c->polkit_registry, error);
1092 if (r < 0)
1093 return r;
1094 if (r == 0)
1095 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
1096
1097 r = verify_xkb_rmlvo(model, layout, variant, options);
1098 if (r < 0) {
1099 log_error_errno(r, "Cannot compile XKB keymap for new x11 keyboard layout ('%s' / '%s' / '%s' / '%s'): %m",
1100 strempty(model), strempty(layout), strempty(variant), strempty(options));
1101 return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Cannot compile XKB keymap, refusing");
1102 }
1103
1104 if (free_and_strdup(&c->x11_layout, layout) < 0 ||
1105 free_and_strdup(&c->x11_model, model) < 0 ||
1106 free_and_strdup(&c->x11_variant, variant) < 0 ||
1107 free_and_strdup(&c->x11_options, options) < 0)
1108 return -ENOMEM;
1109
1110 r = x11_write_data(c);
1111 if (r < 0) {
1112 log_error_errno(r, "Failed to set X11 keyboard layout: %m");
1113 return sd_bus_error_set_errnof(error, r, "Failed to set X11 keyboard layout: %s", strerror(-r));
1114 }
1115
1116 log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
1117 strempty(c->x11_layout),
1118 strempty(c->x11_model),
1119 strempty(c->x11_variant),
1120 strempty(c->x11_options));
1121
1122 sd_bus_emit_properties_changed(bus,
1123 "/org/freedesktop/locale1",
1124 "org.freedesktop.locale1",
1125 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL);
1126
1127 if (convert) {
1128 r = x11_convert_to_vconsole(c, bus);
1129 if (r < 0)
1130 log_error_errno(r, "Failed to convert keymap data: %m");
1131 }
1132 }
1133
1134 return sd_bus_reply_method_return(m, NULL);
1135 }
1136
1137 static const sd_bus_vtable locale_vtable[] = {
1138 SD_BUS_VTABLE_START(0),
1139 SD_BUS_PROPERTY("Locale", "as", property_get_locale, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1140 SD_BUS_PROPERTY("X11Layout", "s", NULL, offsetof(Context, x11_layout), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1141 SD_BUS_PROPERTY("X11Model", "s", NULL, offsetof(Context, x11_model), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1142 SD_BUS_PROPERTY("X11Variant", "s", NULL, offsetof(Context, x11_variant), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1143 SD_BUS_PROPERTY("X11Options", "s", NULL, offsetof(Context, x11_options), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1144 SD_BUS_PROPERTY("VConsoleKeymap", "s", NULL, offsetof(Context, vc_keymap), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1145 SD_BUS_PROPERTY("VConsoleKeymapToggle", "s", NULL, offsetof(Context, vc_keymap_toggle), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1146 SD_BUS_METHOD("SetLocale", "asb", NULL, method_set_locale, SD_BUS_VTABLE_UNPRIVILEGED),
1147 SD_BUS_METHOD("SetVConsoleKeyboard", "ssbb", NULL, method_set_vc_keyboard, SD_BUS_VTABLE_UNPRIVILEGED),
1148 SD_BUS_METHOD("SetX11Keyboard", "ssssbb", NULL, method_set_x11_keyboard, SD_BUS_VTABLE_UNPRIVILEGED),
1149 SD_BUS_VTABLE_END
1150 };
1151
1152 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
1153 _cleanup_bus_close_unref_ sd_bus *bus = NULL;
1154 int r;
1155
1156 assert(c);
1157 assert(event);
1158 assert(_bus);
1159
1160 r = sd_bus_default_system(&bus);
1161 if (r < 0)
1162 return log_error_errno(r, "Failed to get system bus connection: %m");
1163
1164 r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/locale1", "org.freedesktop.locale1", locale_vtable, c);
1165 if (r < 0)
1166 return log_error_errno(r, "Failed to register object: %m");
1167
1168 r = sd_bus_request_name(bus, "org.freedesktop.locale1", 0);
1169 if (r < 0)
1170 return log_error_errno(r, "Failed to register name: %m");
1171
1172 r = sd_bus_attach_event(bus, event, 0);
1173 if (r < 0)
1174 return log_error_errno(r, "Failed to attach bus to event loop: %m");
1175
1176 *_bus = bus;
1177 bus = NULL;
1178
1179 return 0;
1180 }
1181
1182 int main(int argc, char *argv[]) {
1183 _cleanup_(context_free) Context context = {};
1184 _cleanup_event_unref_ sd_event *event = NULL;
1185 _cleanup_bus_close_unref_ sd_bus *bus = NULL;
1186 int r;
1187
1188 log_set_target(LOG_TARGET_AUTO);
1189 log_parse_environment();
1190 log_open();
1191
1192 umask(0022);
1193 mac_selinux_init("/etc");
1194
1195 if (argc != 1) {
1196 log_error("This program takes no arguments.");
1197 r = -EINVAL;
1198 goto finish;
1199 }
1200
1201 r = sd_event_default(&event);
1202 if (r < 0) {
1203 log_error_errno(r, "Failed to allocate event loop: %m");
1204 goto finish;
1205 }
1206
1207 sd_event_set_watchdog(event, true);
1208
1209 r = connect_bus(&context, event, &bus);
1210 if (r < 0)
1211 goto finish;
1212
1213 r = context_read_data(&context);
1214 if (r < 0) {
1215 log_error_errno(r, "Failed to read locale data: %m");
1216 goto finish;
1217 }
1218
1219 r = bus_event_loop_with_idle(event, bus, "org.freedesktop.locale1", DEFAULT_EXIT_USEC, NULL, NULL);
1220 if (r < 0) {
1221 log_error_errno(r, "Failed to run event loop: %m");
1222 goto finish;
1223 }
1224
1225 finish:
1226 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
1227 }