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