]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/locale/localed.c
bus: let's simplify things by getting rid of unnecessary bus parameters
[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
42 enum {
43 /* We don't list LC_ALL here on purpose. People should be
44 * using LANG instead. */
45 LOCALE_LANG,
46 LOCALE_LANGUAGE,
47 LOCALE_LC_CTYPE,
48 LOCALE_LC_NUMERIC,
49 LOCALE_LC_TIME,
50 LOCALE_LC_COLLATE,
51 LOCALE_LC_MONETARY,
52 LOCALE_LC_MESSAGES,
53 LOCALE_LC_PAPER,
54 LOCALE_LC_NAME,
55 LOCALE_LC_ADDRESS,
56 LOCALE_LC_TELEPHONE,
57 LOCALE_LC_MEASUREMENT,
58 LOCALE_LC_IDENTIFICATION,
59 _LOCALE_MAX
60 };
61
62 static const char * const names[_LOCALE_MAX] = {
63 [LOCALE_LANG] = "LANG",
64 [LOCALE_LANGUAGE] = "LANGUAGE",
65 [LOCALE_LC_CTYPE] = "LC_CTYPE",
66 [LOCALE_LC_NUMERIC] = "LC_NUMERIC",
67 [LOCALE_LC_TIME] = "LC_TIME",
68 [LOCALE_LC_COLLATE] = "LC_COLLATE",
69 [LOCALE_LC_MONETARY] = "LC_MONETARY",
70 [LOCALE_LC_MESSAGES] = "LC_MESSAGES",
71 [LOCALE_LC_PAPER] = "LC_PAPER",
72 [LOCALE_LC_NAME] = "LC_NAME",
73 [LOCALE_LC_ADDRESS] = "LC_ADDRESS",
74 [LOCALE_LC_TELEPHONE] = "LC_TELEPHONE",
75 [LOCALE_LC_MEASUREMENT] = "LC_MEASUREMENT",
76 [LOCALE_LC_IDENTIFICATION] = "LC_IDENTIFICATION"
77 };
78
79 typedef struct Context {
80 char *locale[_LOCALE_MAX];
81
82 char *x11_layout;
83 char *x11_model;
84 char *x11_variant;
85 char *x11_options;
86
87 char *vc_keymap;
88 char *vc_keymap_toggle;
89
90 Hashmap *polkit_registry;
91 } Context;
92
93 static int free_and_copy(char **s, const char *v) {
94 int r;
95 char *t;
96
97 assert(s);
98
99 r = strdup_or_null(isempty(v) ? NULL : v, &t);
100 if (r < 0)
101 return r;
102
103 free(*s);
104 *s = t;
105
106 return 0;
107 }
108
109 static void free_and_replace(char **s, char *v) {
110 free(*s);
111 *s = v;
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, sd_bus *bus) {
134 context_free_locale(c);
135 context_free_x11(c);
136 context_free_vconsole(c);
137
138 bus_verify_polkit_async_registry_free(bus, 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(c->locale[p]);
147 c->locale[p] = NULL;
148 }
149 }
150
151 static int locale_read_data(Context *c) {
152 int r;
153
154 context_free_locale(c);
155
156 r = parse_env_file("/etc/locale.conf", NEWLINE,
157 "LANG", &c->locale[LOCALE_LANG],
158 "LANGUAGE", &c->locale[LOCALE_LANGUAGE],
159 "LC_CTYPE", &c->locale[LOCALE_LC_CTYPE],
160 "LC_NUMERIC", &c->locale[LOCALE_LC_NUMERIC],
161 "LC_TIME", &c->locale[LOCALE_LC_TIME],
162 "LC_COLLATE", &c->locale[LOCALE_LC_COLLATE],
163 "LC_MONETARY", &c->locale[LOCALE_LC_MONETARY],
164 "LC_MESSAGES", &c->locale[LOCALE_LC_MESSAGES],
165 "LC_PAPER", &c->locale[LOCALE_LC_PAPER],
166 "LC_NAME", &c->locale[LOCALE_LC_NAME],
167 "LC_ADDRESS", &c->locale[LOCALE_LC_ADDRESS],
168 "LC_TELEPHONE", &c->locale[LOCALE_LC_TELEPHONE],
169 "LC_MEASUREMENT", &c->locale[LOCALE_LC_MEASUREMENT],
170 "LC_IDENTIFICATION", &c->locale[LOCALE_LC_IDENTIFICATION],
171 NULL);
172
173 if (r == -ENOENT) {
174 int p;
175
176 /* Fill in what we got passed from systemd. */
177 for (p = 0; p < _LOCALE_MAX; p++) {
178 assert(names[p]);
179
180 r = free_and_copy(&c->locale[p], getenv(names[p]));
181 if (r < 0)
182 return r;
183 }
184
185 r = 0;
186 }
187
188 locale_simplify(c);
189 return r;
190 }
191
192 static int vconsole_read_data(Context *c) {
193 int r;
194
195 context_free_vconsole(c);
196
197 r = parse_env_file("/etc/vconsole.conf", NEWLINE,
198 "KEYMAP", &c->vc_keymap,
199 "KEYMAP_TOGGLE", &c->vc_keymap_toggle,
200 NULL);
201
202 if (r < 0 && r != -ENOENT)
203 return r;
204
205 return 0;
206 }
207
208 static int x11_read_data(Context *c) {
209 FILE *f;
210 char line[LINE_MAX];
211 bool in_section = false;
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 char **a;
230
231 a = strv_split_quoted(l);
232 if (!a) {
233 fclose(f);
234 return -ENOMEM;
235 }
236
237 if (strv_length(a) == 3) {
238 if (streq(a[1], "XkbLayout")) {
239 free_and_replace(&c->x11_layout, a[2]);
240 a[2] = NULL;
241 } else if (streq(a[1], "XkbModel")) {
242 free_and_replace(&c->x11_model, a[2]);
243 a[2] = NULL;
244 } else if (streq(a[1], "XkbVariant")) {
245 free_and_replace(&c->x11_variant, a[2]);
246 a[2] = NULL;
247 } else if (streq(a[1], "XkbOptions")) {
248 free_and_replace(&c->x11_options, a[2]);
249 a[2] = NULL;
250 }
251 }
252
253 strv_free(a);
254
255 } else if (!in_section && first_word(l, "Section")) {
256 char **a;
257
258 a = strv_split_quoted(l);
259 if (!a) {
260 fclose(f);
261 return -ENOMEM;
262 }
263
264 if (strv_length(a) == 2 && streq(a[1], "InputClass"))
265 in_section = true;
266
267 strv_free(a);
268 } else if (in_section && first_word(l, "EndSection"))
269 in_section = false;
270 }
271
272 fclose(f);
273
274 return 0;
275 }
276
277 static int context_read_data(Context *c) {
278 int r, q, p;
279
280 r = locale_read_data(c);
281 q = vconsole_read_data(c);
282 p = x11_read_data(c);
283
284 return r < 0 ? r : q < 0 ? q : p;
285 }
286
287 static int locale_write_data(Context *c) {
288 int r, p;
289 char **l = NULL;
290
291 r = load_env_file("/etc/locale.conf", NULL, &l);
292 if (r < 0 && r != -ENOENT)
293 return r;
294
295 for (p = 0; p < _LOCALE_MAX; p++) {
296 char *t, **u;
297
298 assert(names[p]);
299
300 if (isempty(c->locale[p])) {
301 l = strv_env_unset(l, names[p]);
302 continue;
303 }
304
305 if (asprintf(&t, "%s=%s", names[p], c->locale[p]) < 0) {
306 strv_free(l);
307 return -ENOMEM;
308 }
309
310 u = strv_env_set(l, t);
311 free(t);
312 strv_free(l);
313
314 if (!u)
315 return -ENOMEM;
316
317 l = u;
318 }
319
320 if (strv_isempty(l)) {
321 strv_free(l);
322
323 if (unlink("/etc/locale.conf") < 0)
324 return errno == ENOENT ? 0 : -errno;
325
326 return 0;
327 }
328
329 r = write_env_file_label("/etc/locale.conf", l);
330 strv_free(l);
331
332 return r;
333 }
334
335 static int locale_update_system_manager(Context *c, sd_bus *bus) {
336 _cleanup_free_ char **l_unset = NULL;
337 _cleanup_strv_free_ char **l_set = NULL;
338 _cleanup_bus_message_unref_ sd_bus_message *m = NULL;
339 sd_bus_error error = SD_BUS_ERROR_NULL;
340 unsigned c_set, c_unset, p;
341 int r;
342
343 assert(bus);
344
345 l_unset = new0(char*, _LOCALE_MAX);
346 if (!l_unset)
347 return -ENOMEM;
348
349 l_set = new0(char*, _LOCALE_MAX);
350 if (!l_set)
351 return -ENOMEM;
352
353 for (p = 0, c_set = 0, c_unset = 0; p < _LOCALE_MAX; p++) {
354 assert(names[p]);
355
356 if (isempty(c->locale[p]))
357 l_unset[c_set++] = (char*) names[p];
358 else {
359 char *s;
360
361 if (asprintf(&s, "%s=%s", names[p], c->locale[p]) < 0)
362 return -ENOMEM;
363
364 l_set[c_unset++] = s;
365 }
366 }
367
368 assert(c_set + c_unset == _LOCALE_MAX);
369 r = sd_bus_message_new_method_call(bus,
370 "org.freedesktop.systemd1",
371 "/org/freedesktop/systemd1",
372 "org.freedesktop.systemd1.Manager",
373 "UnsetAndSetEnvironment", &m);
374 if (r < 0)
375 return r;
376
377 r = sd_bus_message_append_strv(m, l_unset);
378 if (r < 0)
379 return r;
380
381 r = sd_bus_message_append_strv(m, l_set);
382 if (r < 0)
383 return r;
384
385 r = sd_bus_call(bus, m, 0, &error, NULL);
386 if (r < 0)
387 log_error("Failed to update the manager environment: %s", strerror(-r));
388
389 return 0;
390 }
391
392 static int vconsole_write_data(Context *c) {
393 int r;
394 _cleanup_strv_free_ char **l = NULL;
395
396 r = load_env_file("/etc/vconsole.conf", NULL, &l);
397 if (r < 0 && r != -ENOENT)
398 return r;
399
400 if (isempty(c->vc_keymap))
401 l = strv_env_unset(l, "KEYMAP");
402 else {
403 char *s, **u;
404
405 s = strappend("KEYMAP=", c->vc_keymap);
406 if (!s)
407 return -ENOMEM;
408
409 u = strv_env_set(l, s);
410 free(s);
411 strv_free(l);
412
413 if (!u)
414 return -ENOMEM;
415
416 l = u;
417 }
418
419 if (isempty(c->vc_keymap_toggle))
420 l = strv_env_unset(l, "KEYMAP_TOGGLE");
421 else {
422 char *s, **u;
423
424 s = strappend("KEYMAP_TOGGLE=", c->vc_keymap_toggle);
425 if (!s)
426 return -ENOMEM;
427
428 u = strv_env_set(l, s);
429 free(s);
430 strv_free(l);
431
432 if (!u)
433 return -ENOMEM;
434
435 l = u;
436 }
437
438 if (strv_isempty(l)) {
439 if (unlink("/etc/vconsole.conf") < 0)
440 return errno == ENOENT ? 0 : -errno;
441
442 return 0;
443 }
444
445 r = write_env_file_label("/etc/vconsole.conf", l);
446 return r;
447 }
448
449 static int write_data_x11(Context *c) {
450 _cleanup_fclose_ FILE *f = NULL;
451 _cleanup_free_ char *temp_path = NULL;
452 int r;
453
454 if (isempty(c->x11_layout) &&
455 isempty(c->x11_model) &&
456 isempty(c->x11_variant) &&
457 isempty(c->x11_options)) {
458
459 if (unlink("/etc/X11/xorg.conf.d/00-keyboard.conf") < 0)
460 return errno == ENOENT ? 0 : -errno;
461
462 return 0;
463 }
464
465 mkdir_p_label("/etc/X11/xorg.conf.d", 0755);
466
467 r = fopen_temporary("/etc/X11/xorg.conf.d/00-keyboard.conf", &f, &temp_path);
468 if (r < 0)
469 return r;
470
471 fchmod(fileno(f), 0644);
472
473 fputs("# Read and parsed by systemd-localed. It's probably wise not to edit this file\n"
474 "# manually too freely.\n"
475 "Section \"InputClass\"\n"
476 " Identifier \"system-keyboard\"\n"
477 " MatchIsKeyboard \"on\"\n", f);
478
479 if (!isempty(c->x11_layout))
480 fprintf(f, " Option \"XkbLayout\" \"%s\"\n", c->x11_layout);
481
482 if (!isempty(c->x11_model))
483 fprintf(f, " Option \"XkbModel\" \"%s\"\n", c->x11_model);
484
485 if (!isempty(c->x11_variant))
486 fprintf(f, " Option \"XkbVariant\" \"%s\"\n", c->x11_variant);
487
488 if (!isempty(c->x11_options))
489 fprintf(f, " Option \"XkbOptions\" \"%s\"\n", c->x11_options);
490
491 fputs("EndSection\n", f);
492 fflush(f);
493
494 if (ferror(f) || rename(temp_path, "/etc/X11/xorg.conf.d/00-keyboard.conf") < 0) {
495 r = -errno;
496 unlink("/etc/X11/xorg.conf.d/00-keyboard.conf");
497 unlink(temp_path);
498 return r;
499 } else
500 return 0;
501 }
502
503 static int vconsole_reload(sd_bus *bus) {
504 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
505 int r;
506
507 assert(bus);
508
509 r = sd_bus_call_method(bus,
510 "org.freedesktop.systemd1",
511 "/org/freedesktop/systemd1",
512 "org.freedesktop.systemd1.Manager",
513 "RestartUnit",
514 &error,
515 NULL,
516 "ss", "systemd-vconsole-setup.service", "replace");
517
518 if (r < 0)
519 log_error("Failed to issue method call: %s", bus_error_message(&error, -r));
520 return r;
521 }
522
523 static char *strnulldash(const char *s) {
524 return s == NULL || *s == 0 || (s[0] == '-' && s[1] == 0) ? NULL : (char*) s;
525 }
526
527 static int read_next_mapping(FILE *f, unsigned *n, char ***a) {
528 assert(f);
529 assert(n);
530 assert(a);
531
532 for (;;) {
533 char line[LINE_MAX];
534 char *l, **b;
535
536 errno = 0;
537 if (!fgets(line, sizeof(line), f)) {
538
539 if (ferror(f))
540 return errno ? -errno : -EIO;
541
542 return 0;
543 }
544
545 (*n) ++;
546
547 l = strstrip(line);
548 if (l[0] == 0 || l[0] == '#')
549 continue;
550
551 b = strv_split_quoted(l);
552 if (!b)
553 return -ENOMEM;
554
555 if (strv_length(b) < 5) {
556 log_error("Invalid line "SYSTEMD_KBD_MODEL_MAP":%u, ignoring.", *n);
557 strv_free(b);
558 continue;
559
560 }
561
562 *a = b;
563 return 1;
564 }
565 }
566
567 static int vconsole_convert_to_x11(Context *c, sd_bus *bus) {
568 bool modified = false;
569
570 assert(bus);
571
572 if (isempty(c->vc_keymap)) {
573
574 modified =
575 !isempty(c->x11_layout) ||
576 !isempty(c->x11_model) ||
577 !isempty(c->x11_variant) ||
578 !isempty(c->x11_options);
579
580 context_free_x11(c);
581 } else {
582 _cleanup_fclose_ FILE *f = NULL;
583 unsigned n = 0;
584
585 f = fopen(SYSTEMD_KBD_MODEL_MAP, "re");
586 if (!f)
587 return -errno;
588
589 for (;;) {
590 _cleanup_strv_free_ char **a = NULL;
591 int r;
592
593 r = read_next_mapping(f, &n, &a);
594 if (r < 0)
595 return r;
596 if (r == 0)
597 break;
598
599 if (!streq(c->vc_keymap, a[0]))
600 continue;
601
602 if (!streq_ptr(c->x11_layout, strnulldash(a[1])) ||
603 !streq_ptr(c->x11_model, strnulldash(a[2])) ||
604 !streq_ptr(c->x11_variant, strnulldash(a[3])) ||
605 !streq_ptr(c->x11_options, strnulldash(a[4]))) {
606
607 if (free_and_copy(&c->x11_layout, strnulldash(a[1])) < 0 ||
608 free_and_copy(&c->x11_model, strnulldash(a[2])) < 0 ||
609 free_and_copy(&c->x11_variant, strnulldash(a[3])) < 0 ||
610 free_and_copy(&c->x11_options, strnulldash(a[4])) < 0)
611 return -ENOMEM;
612
613 modified = true;
614 }
615
616 break;
617 }
618 }
619
620 if (modified) {
621 int r;
622
623 r = write_data_x11(c);
624 if (r < 0)
625 log_error("Failed to set X11 keyboard layout: %s", strerror(-r));
626
627 sd_bus_emit_properties_changed(bus,
628 "/org/freedesktop/locale1",
629 "org.freedesktop.locale1",
630 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL);
631 }
632
633 return 0;
634 }
635
636 static int find_converted_keymap(Context *c, char **new_keymap) {
637 const char *dir;
638 _cleanup_free_ char *n;
639
640 if (c->x11_variant)
641 n = strjoin(c->x11_layout, "-", c->x11_variant, NULL);
642 else
643 n = strdup(c->x11_layout);
644 if (!n)
645 return -ENOMEM;
646
647 NULSTR_FOREACH(dir, KBD_KEYMAP_DIRS) {
648 _cleanup_free_ char *p = NULL, *pz = NULL;
649
650 p = strjoin(dir, "xkb/", n, ".map", NULL);
651 pz = strjoin(dir, "xkb/", n, ".map.gz", NULL);
652 if (!p || !pz)
653 return -ENOMEM;
654
655 if (access(p, F_OK) == 0 || access(pz, F_OK) == 0) {
656 *new_keymap = n;
657 n = NULL;
658 return 1;
659 }
660 }
661
662 return 0;
663 }
664
665 static int find_legacy_keymap(Context *c, char **new_keymap) {
666 _cleanup_fclose_ FILE *f;
667 unsigned n = 0;
668 unsigned best_matching = 0;
669
670
671 f = fopen(SYSTEMD_KBD_MODEL_MAP, "re");
672 if (!f)
673 return -errno;
674
675 for (;;) {
676 _cleanup_strv_free_ char **a = NULL;
677 unsigned matching = 0;
678 int r;
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 size_t x;
692
693 x = strcspn(c->x11_layout, ",");
694
695 /* We have multiple X layouts, look for an
696 * entry that matches our key with everything
697 * but the first layout stripped off. */
698 if (x > 0 &&
699 strlen(a[1]) == x &&
700 strneq(c->x11_layout, a[1], x))
701 matching = 5;
702 else {
703 size_t w;
704
705 /* If that didn't work, strip off the
706 * other layouts from the entry, too */
707 w = strcspn(a[1], ",");
708
709 if (x > 0 && x == w &&
710 memcmp(c->x11_layout, a[1], x) == 0)
711 matching = 1;
712 }
713 }
714
715 if (matching > 0 &&
716 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 /* The best matching entry so far, then let's save that */
728 if (matching > best_matching) {
729 best_matching = matching;
730
731 free(*new_keymap);
732 *new_keymap = strdup(a[0]);
733 if (!*new_keymap)
734 return -ENOMEM;
735 }
736 }
737
738 return 0;
739 }
740
741 static int x11_convert_to_vconsole(Context *c, sd_bus *bus) {
742 bool modified = false;
743 int r;
744
745 assert(bus);
746
747 if (isempty(c->x11_layout)) {
748
749 modified =
750 !isempty(c->vc_keymap) ||
751 !isempty(c->vc_keymap_toggle);
752
753 context_free_x11(c);
754 } else {
755 char *new_keymap = NULL;
756
757 r = find_converted_keymap(c, &new_keymap);
758 if (r < 0)
759 return r;
760 else if (r == 0) {
761 r = find_legacy_keymap(c, &new_keymap);
762 if (r < 0)
763 return r;
764 }
765
766 if (!streq_ptr(c->vc_keymap, new_keymap)) {
767 free_and_replace(&c->vc_keymap, new_keymap);
768 free_and_replace(&c->vc_keymap_toggle, NULL);
769 modified = true;
770 } else
771 free(new_keymap);
772 }
773
774 if (modified) {
775 r = vconsole_write_data(c);
776 if (r < 0)
777 log_error("Failed to set virtual console keymap: %s", strerror(-r));
778
779 sd_bus_emit_properties_changed(bus,
780 "/org/freedesktop/locale1",
781 "org.freedesktop.locale1",
782 "VConsoleKeymap", "VConsoleKeymapToggle", NULL);
783
784 return vconsole_reload(bus);
785 }
786
787 return 0;
788 }
789
790 static int property_get_locale(sd_bus *bus, const char *path, const char *interface,
791 const char *property, sd_bus_message *reply, sd_bus_error *error, void *userdata) {
792 Context *c = userdata;
793 _cleanup_strv_free_ char **l = NULL;
794 int p, q;
795
796 l = new0(char*, _LOCALE_MAX+1);
797 if (!l)
798 return -ENOMEM;
799
800 for (p = 0, q = 0; p < _LOCALE_MAX; p++) {
801 char *t;
802
803 if (isempty(c->locale[p]))
804 continue;
805
806 if (asprintf(&t, "%s=%s", names[p], c->locale[p]) < 0)
807 return -ENOMEM;
808
809 l[q++] = t;
810 }
811
812 return sd_bus_message_append_strv(reply, l);
813 }
814
815 static int method_set_locale(sd_bus *bus, sd_bus_message *m, void *userdata) {
816 Context *c = userdata;
817 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
818 _cleanup_strv_free_ char **l = NULL;
819 char **i;
820 int interactive;
821 bool modified = false;
822 bool passed[_LOCALE_MAX] = {};
823 int p;
824 int r;
825
826 r = bus_message_read_strv_extend(m, &l);
827 if (r < 0)
828 return sd_bus_reply_method_errno(m, r, NULL);
829
830 r = sd_bus_message_read_basic(m, 'b', &interactive);
831 if (r < 0)
832 return sd_bus_reply_method_errno(m, r, NULL);
833
834 /* Check whether a variable changed and if so valid */
835 STRV_FOREACH(i, l) {
836 bool valid = false;
837
838 for (p = 0; p < _LOCALE_MAX; p++) {
839 size_t k;
840
841 k = strlen(names[p]);
842 if (startswith(*i, names[p]) &&
843 (*i)[k] == '=' &&
844 string_is_safe((*i) + k + 1)) {
845 valid = true;
846 passed[p] = true;
847
848 if (!streq_ptr(*i + k + 1, c->locale[p]))
849 modified = true;
850
851 break;
852 }
853 }
854
855 if (!valid)
856 sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "Invalid Locale data.");
857 }
858
859 /* Check whether a variable is unset */
860 if (!modified) {
861 for (p = 0; p < _LOCALE_MAX; p++)
862 if (!isempty(c->locale[p]) && !passed[p]) {
863 modified = true;
864 break;
865 }
866 }
867
868 if (modified) {
869 r = bus_verify_polkit_async(bus, &c->polkit_registry, m,
870 "org.freedesktop.locale1.set-locale", interactive,
871 &error, method_set_locale, c);
872 if (r < 0)
873 return sd_bus_reply_method_errno(m, r, &error);
874 if (r == 0)
875 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
876
877 STRV_FOREACH(i, l) {
878 for (p = 0; p < _LOCALE_MAX; p++) {
879 size_t k;
880
881 k = strlen(names[p]);
882 if (startswith(*i, names[p]) && (*i)[k] == '=') {
883 char *t;
884
885 t = strdup(*i + k + 1);
886 if (!t)
887 return -ENOMEM;
888
889 free(c->locale[p]);
890 c->locale[p] = t;
891 break;
892 }
893 }
894 }
895
896 for (p = 0; p < _LOCALE_MAX; p++) {
897 if (passed[p])
898 continue;
899
900 free_and_replace(&c->locale[p], NULL);
901 }
902
903 locale_simplify(c);
904
905 r = locale_write_data(c);
906 if (r < 0) {
907 log_error("Failed to set locale: %s", strerror(-r));
908 return sd_bus_reply_method_errnof(m, r, "Failed to set locale: %s", strerror(-r));
909 }
910
911 locale_update_system_manager(c, bus);
912
913 log_info("Changed locale information.");
914
915 sd_bus_emit_properties_changed(bus,
916 "/org/freedesktop/locale1",
917 "org.freedesktop.locale1",
918 "Locale", NULL);
919 }
920
921 return sd_bus_reply_method_return(m, NULL);
922 }
923
924 static int method_set_vc_keyboard(sd_bus *bus, sd_bus_message *m, void *userdata) {
925 Context *c = userdata;
926 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
927 const char *keymap, *keymap_toggle;
928 int convert, interactive;
929 int r;
930
931 r = sd_bus_message_read(m, "ssbb", &keymap, &keymap_toggle, &convert, &interactive);
932 if (r < 0)
933 return sd_bus_reply_method_errno(m, r, NULL);
934
935 if (isempty(keymap))
936 keymap = NULL;
937
938 if (isempty(keymap_toggle))
939 keymap_toggle = NULL;
940
941 if (!streq_ptr(keymap, c->vc_keymap) ||
942 !streq_ptr(keymap_toggle, c->vc_keymap_toggle)) {
943
944 if ((keymap && (!filename_is_safe(keymap) || !string_is_safe(keymap))) ||
945 (keymap_toggle && (!filename_is_safe(keymap_toggle) || !string_is_safe(keymap_toggle))))
946 return sd_bus_reply_method_errnof(m, r, "Received invalid keymap data: %s", -EINVAL);
947
948 r = bus_verify_polkit_async(bus, &c->polkit_registry, m,
949 "org.freedesktop.locale1.set-keyboard",
950 interactive, &error, method_set_vc_keyboard, c);
951 if (r < 0)
952 return sd_bus_reply_method_errno(m, r, &error);
953 if (r == 0)
954 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
955
956 if (free_and_copy(&c->vc_keymap, keymap) < 0 ||
957 free_and_copy(&c->vc_keymap_toggle, keymap_toggle) < 0)
958 return -ENOMEM;
959
960 r = vconsole_write_data(c);
961 if (r < 0) {
962 log_error("Failed to set virtual console keymap: %s", strerror(-r));
963 return sd_bus_reply_method_errnof(m, r, "Failed to set virtual console keymap: %s", strerror(-r));
964 }
965
966 log_info("Changed virtual console keymap to '%s'", strempty(c->vc_keymap));
967
968 r = vconsole_reload(bus);
969 if (r < 0)
970 log_error("Failed to request keymap reload: %s", strerror(-r));
971
972 sd_bus_emit_properties_changed(bus,
973 "/org/freedesktop/locale1",
974 "org.freedesktop.locale1",
975 "VConsoleKeymap", "VConsoleKeymapToggle", NULL);
976
977 if (convert) {
978 r = vconsole_convert_to_x11(c, bus);
979 if (r < 0)
980 log_error("Failed to convert keymap data: %s", strerror(-r));
981 }
982 }
983
984 return sd_bus_reply_method_return(m, NULL);
985 }
986
987 static int method_set_x11_keyboard(sd_bus *bus, sd_bus_message *m, void *userdata) {
988 Context *c = userdata;
989 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
990 const char *layout, *model, *variant, *options;
991 int convert, interactive;
992 int r;
993
994 r = sd_bus_message_read(m, "ssssbb", &layout, &model, &variant, &options, &convert, &interactive);
995 if (r < 0)
996 return sd_bus_reply_method_errno(m, r, NULL);
997
998 if (isempty(layout))
999 layout = NULL;
1000
1001 if (isempty(model))
1002 model = NULL;
1003
1004 if (isempty(variant))
1005 variant = NULL;
1006
1007 if (isempty(options))
1008 options = NULL;
1009
1010 if (!streq_ptr(layout, c->x11_layout) ||
1011 !streq_ptr(model, c->x11_model) ||
1012 !streq_ptr(variant, c->x11_variant) ||
1013 !streq_ptr(options, c->x11_options)) {
1014
1015 if ((layout && !string_is_safe(layout)) ||
1016 (model && !string_is_safe(model)) ||
1017 (variant && !string_is_safe(variant)) ||
1018 (options && !string_is_safe(options)))
1019 return sd_bus_reply_method_errnof(m, r, "Received invalid keyboard data: %s", -EINVAL);
1020
1021 r = bus_verify_polkit_async(bus, &c->polkit_registry, m,
1022 "org.freedesktop.locale1.set-keyboard",
1023 interactive, &error, method_set_x11_keyboard, c);
1024 if (r < 0)
1025 return sd_bus_reply_method_errno(m, r, &error);
1026 if (r == 0)
1027 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
1028
1029 if (free_and_copy(&c->x11_layout, layout) < 0 ||
1030 free_and_copy(&c->x11_model, model) < 0 ||
1031 free_and_copy(&c->x11_variant, variant) < 0 ||
1032 free_and_copy(&c->x11_options, options) < 0)
1033 return -ENOMEM;
1034
1035 r = write_data_x11(c);
1036 if (r < 0) {
1037 log_error("Failed to set X11 keyboard layout: %s", strerror(-r));
1038 return sd_bus_reply_method_errnof(m, r, "Failed to set X11 keyboard layout: %s", strerror(-r));
1039 }
1040
1041 log_info("Changed X11 keyboard layout to '%s'", strempty(c->x11_layout));
1042
1043 sd_bus_emit_properties_changed(bus,
1044 "/org/freedesktop/locale1",
1045 "org.freedesktop.locale1",
1046 "X11Layout" "X11Model" "X11Variant" "X11Options", NULL);
1047
1048 if (convert) {
1049 r = x11_convert_to_vconsole(c, bus);
1050 if (r < 0)
1051 log_error("Failed to convert keymap data: %s", strerror(-r));
1052 }
1053 }
1054
1055 return sd_bus_reply_method_return(m, NULL);
1056 }
1057
1058 static const sd_bus_vtable locale_vtable[] = {
1059 SD_BUS_VTABLE_START(0),
1060 SD_BUS_PROPERTY("Locale", "as", property_get_locale, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1061 SD_BUS_PROPERTY("X11Layout", "s", NULL, offsetof(Context, x11_layout), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1062 SD_BUS_PROPERTY("X11Model", "s", NULL, offsetof(Context, x11_model), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1063 SD_BUS_PROPERTY("X11Variant", "s", NULL, offsetof(Context, x11_variant), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1064 SD_BUS_PROPERTY("X11Options", "s", NULL, offsetof(Context, x11_options), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1065 SD_BUS_PROPERTY("VConsoleKeymap", "s", NULL, offsetof(Context, vc_keymap), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1066 SD_BUS_PROPERTY("VConsoleKeymapToggle", "s", NULL, offsetof(Context, vc_keymap_toggle), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1067 SD_BUS_METHOD("SetLocale", "asb", NULL, method_set_locale, 0),
1068 SD_BUS_METHOD("SetVConsoleKeyboard", "ssbb", NULL, method_set_vc_keyboard, 0),
1069 SD_BUS_METHOD("SetX11Keyboard", "ssssbb", NULL, method_set_x11_keyboard, 0),
1070 SD_BUS_VTABLE_END
1071 };
1072
1073 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
1074 _cleanup_bus_unref_ sd_bus *bus = NULL;
1075 int r;
1076
1077 assert(c);
1078 assert(event);
1079 assert(_bus);
1080
1081 r = sd_bus_default_system(&bus);
1082 if (r < 0) {
1083 log_error("Failed to get system bus connection: %s", strerror(-r));
1084 return r;
1085 }
1086
1087 r = sd_bus_add_object_vtable(bus, "/org/freedesktop/locale1", "org.freedesktop.locale1", locale_vtable, c);
1088 if (r < 0) {
1089 log_error("Failed to register object: %s", strerror(-r));
1090 return r;
1091 }
1092
1093 r = sd_bus_request_name(bus, "org.freedesktop.locale1", SD_BUS_NAME_DO_NOT_QUEUE);
1094 if (r < 0) {
1095 log_error("Failed to register name: %s", strerror(-r));
1096 return r;
1097 }
1098
1099 if (r != SD_BUS_NAME_PRIMARY_OWNER) {
1100 log_error("Failed to acquire name.");
1101 return -EEXIST;
1102 }
1103
1104 r = sd_bus_attach_event(bus, event, 0);
1105 if (r < 0) {
1106 log_error("Failed to attach bus to event loop: %s", strerror(-r));
1107 return r;
1108 }
1109
1110 *_bus = bus;
1111 bus = NULL;
1112
1113 return 0;
1114 }
1115
1116 int main(int argc, char *argv[]) {
1117 Context context = {};
1118 _cleanup_event_unref_ sd_event *event = NULL;
1119 _cleanup_bus_unref_ sd_bus *bus = NULL;
1120 int r;
1121
1122 log_set_target(LOG_TARGET_AUTO);
1123 log_parse_environment();
1124 log_open();
1125
1126 umask(0022);
1127 label_init("/etc");
1128
1129 if (argc != 1) {
1130 log_error("This program takes no arguments.");
1131 r = -EINVAL;
1132 goto finish;
1133 }
1134
1135 r = sd_event_default(&event);
1136 if (r < 0) {
1137 log_error("Failed to allocate event loop: %s", strerror(-r));
1138 goto finish;
1139 }
1140
1141 r = connect_bus(&context, event, &bus);
1142 if (r < 0)
1143 goto finish;
1144
1145 r = context_read_data(&context);
1146 if (r < 0) {
1147 log_error("Failed to read locale data: %s", strerror(-r));
1148 goto finish;
1149 }
1150
1151 r = bus_event_loop_with_idle(event, bus, "org.freedesktop.locale1", DEFAULT_EXIT_USEC);
1152 if (r < 0) {
1153 log_error("Failed to run event loop: %s", strerror(-r));
1154 goto finish;
1155 }
1156
1157 r = 0;
1158
1159 finish:
1160 context_free(&context, bus);
1161
1162 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
1163 }