]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/locale/localed.c
treewide: auto-convert the simple cases to log_*_errno()
[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 #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 log_error_errno(-r, "Failed to set X11 keyboard layout: %m");
615 return r;
616 }
617
618 log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
619 strempty(c->x11_layout),
620 strempty(c->x11_model),
621 strempty(c->x11_variant),
622 strempty(c->x11_options));
623
624 sd_bus_emit_properties_changed(bus,
625 "/org/freedesktop/locale1",
626 "org.freedesktop.locale1",
627 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL);
628 } else
629 log_debug("X11 keyboard layout was not modified.");
630
631 return 0;
632 }
633
634 static int find_converted_keymap(const char *x11_layout, const char *x11_variant, char **new_keymap) {
635 const char *dir;
636 _cleanup_free_ char *n;
637
638 if (x11_variant)
639 n = strjoin(x11_layout, "-", x11_variant, NULL);
640 else
641 n = strdup(x11_layout);
642 if (!n)
643 return -ENOMEM;
644
645 NULSTR_FOREACH(dir, KBD_KEYMAP_DIRS) {
646 _cleanup_free_ char *p = NULL, *pz = NULL;
647 bool uncompressed;
648
649 p = strjoin(dir, "xkb/", n, ".map", NULL);
650 pz = strjoin(dir, "xkb/", n, ".map.gz", NULL);
651 if (!p || !pz)
652 return -ENOMEM;
653
654 uncompressed = access(p, F_OK) == 0;
655 if (uncompressed || access(pz, F_OK) == 0) {
656 log_debug("Found converted keymap %s at %s",
657 n, uncompressed ? p : pz);
658
659 *new_keymap = n;
660 n = NULL;
661 return 1;
662 }
663 }
664
665 return 0;
666 }
667
668 static int find_legacy_keymap(Context *c, char **new_keymap) {
669 _cleanup_fclose_ FILE *f;
670 unsigned n = 0;
671 unsigned best_matching = 0;
672 int r;
673
674 f = fopen(SYSTEMD_KBD_MODEL_MAP, "re");
675 if (!f)
676 return -errno;
677
678 for (;;) {
679 _cleanup_strv_free_ char **a = NULL;
680 unsigned matching = 0;
681
682 r = read_next_mapping(f, &n, &a);
683 if (r < 0)
684 return r;
685 if (r == 0)
686 break;
687
688 /* Determine how well matching this entry is */
689 if (streq_ptr(c->x11_layout, a[1]))
690 /* If we got an exact match, this is best */
691 matching = 10;
692 else {
693 /* We have multiple X layouts, look for an
694 * entry that matches our key with everything
695 * but the first layout stripped off. */
696 if (startswith_comma(c->x11_layout, a[1]))
697 matching = 5;
698 else {
699 char *x;
700
701 /* If that didn't work, strip off the
702 * other layouts from the entry, too */
703 x = strndupa(a[1], strcspn(a[1], ","));
704 if (startswith_comma(c->x11_layout, x))
705 matching = 1;
706 }
707 }
708
709 if (matching > 0) {
710 if (isempty(c->x11_model) || streq_ptr(c->x11_model, a[2])) {
711 matching++;
712
713 if (streq_ptr(c->x11_variant, a[3])) {
714 matching++;
715
716 if (streq_ptr(c->x11_options, a[4]))
717 matching++;
718 }
719 }
720 }
721
722 /* The best matching entry so far, then let's save that */
723 if (matching >= MAX(best_matching, 1u)) {
724 log_debug("Found legacy keymap %s with score %u",
725 a[0], matching);
726
727 if (matching > best_matching) {
728 best_matching = matching;
729
730 r = free_and_strdup(new_keymap, a[0]);
731 if (r < 0)
732 return r;
733 }
734 }
735 }
736
737 if (best_matching < 10 && c->x11_layout) {
738 /* The best match is only the first part of the X11
739 * keymap. Check if we have a converted map which
740 * matches just the first layout.
741 */
742 char *l, *v = NULL, *converted;
743
744 l = strndupa(c->x11_layout, strcspn(c->x11_layout, ","));
745 if (c->x11_variant)
746 v = strndupa(c->x11_variant, strcspn(c->x11_variant, ","));
747 r = find_converted_keymap(l, v, &converted);
748 if (r < 0)
749 return r;
750 if (r > 0)
751 free_and_replace(new_keymap, converted);
752 }
753
754 return 0;
755 }
756
757 static int x11_convert_to_vconsole(Context *c, sd_bus *bus) {
758 bool modified = false;
759 int r;
760
761 assert(bus);
762
763 if (isempty(c->x11_layout)) {
764
765 modified =
766 !isempty(c->vc_keymap) ||
767 !isempty(c->vc_keymap_toggle);
768
769 context_free_x11(c);
770 } else {
771 char *new_keymap = NULL;
772
773 r = find_converted_keymap(c->x11_layout, c->x11_variant, &new_keymap);
774 if (r < 0)
775 return r;
776 else if (r == 0) {
777 r = find_legacy_keymap(c, &new_keymap);
778 if (r < 0)
779 return r;
780 }
781
782 if (!streq_ptr(c->vc_keymap, new_keymap)) {
783 free_and_replace(&c->vc_keymap, new_keymap);
784 free_and_replace(&c->vc_keymap_toggle, NULL);
785 modified = true;
786 } else
787 free(new_keymap);
788 }
789
790 if (modified) {
791 r = vconsole_write_data(c);
792 if (r < 0)
793 log_error_errno(-r, "Failed to set virtual console keymap: %m");
794
795 log_info("Changed virtual console keymap to '%s' toggle '%s'",
796 strempty(c->vc_keymap), strempty(c->vc_keymap_toggle));
797
798 sd_bus_emit_properties_changed(bus,
799 "/org/freedesktop/locale1",
800 "org.freedesktop.locale1",
801 "VConsoleKeymap", "VConsoleKeymapToggle", NULL);
802
803 return vconsole_reload(bus);
804 } else
805 log_debug("Virtual console keymap was not modified.");
806
807 return 0;
808 }
809
810 static int property_get_locale(
811 sd_bus *bus,
812 const char *path,
813 const char *interface,
814 const char *property,
815 sd_bus_message *reply,
816 void *userdata,
817 sd_bus_error *error) {
818
819 Context *c = userdata;
820 _cleanup_strv_free_ char **l = NULL;
821 int p, q;
822
823 l = new0(char*, _LOCALE_MAX+1);
824 if (!l)
825 return -ENOMEM;
826
827 for (p = 0, q = 0; p < _LOCALE_MAX; p++) {
828 char *t;
829
830 if (isempty(c->locale[p]))
831 continue;
832
833 if (asprintf(&t, "%s=%s", names[p], c->locale[p]) < 0)
834 return -ENOMEM;
835
836 l[q++] = t;
837 }
838
839 return sd_bus_message_append_strv(reply, l);
840 }
841
842 static int method_set_locale(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
843 Context *c = userdata;
844 _cleanup_strv_free_ char **l = NULL;
845 char **i;
846 int interactive;
847 bool modified = false;
848 bool passed[_LOCALE_MAX] = {};
849 int p;
850 int r;
851
852 r = bus_message_read_strv_extend(m, &l);
853 if (r < 0)
854 return r;
855
856 r = sd_bus_message_read_basic(m, 'b', &interactive);
857 if (r < 0)
858 return r;
859
860 /* Check whether a variable changed and if it is valid */
861 STRV_FOREACH(i, l) {
862 bool valid = false;
863
864 for (p = 0; p < _LOCALE_MAX; p++) {
865 size_t k;
866
867 k = strlen(names[p]);
868 if (startswith(*i, names[p]) &&
869 (*i)[k] == '=' &&
870 locale_is_valid((*i) + k + 1)) {
871 valid = true;
872 passed[p] = true;
873
874 if (!streq_ptr(*i + k + 1, c->locale[p]))
875 modified = true;
876
877 break;
878 }
879 }
880
881 if (!valid)
882 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid Locale data.");
883 }
884
885 /* Check whether a variable is unset */
886 if (!modified)
887 for (p = 0; p < _LOCALE_MAX; p++)
888 if (!isempty(c->locale[p]) && !passed[p]) {
889 modified = true;
890 break;
891 }
892
893 if (modified) {
894 _cleanup_strv_free_ char **settings = NULL;
895
896 r = bus_verify_polkit_async(m, CAP_SYS_ADMIN, "org.freedesktop.locale1.set-locale", interactive, &c->polkit_registry, error);
897 if (r < 0)
898 return r;
899 if (r == 0)
900 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
901
902 STRV_FOREACH(i, l)
903 for (p = 0; p < _LOCALE_MAX; p++) {
904 size_t k;
905
906 k = strlen(names[p]);
907 if (startswith(*i, names[p]) && (*i)[k] == '=') {
908 r = free_and_strdup(&c->locale[p], *i + k + 1);
909 if (r < 0)
910 return r;
911 break;
912 }
913 }
914
915 for (p = 0; p < _LOCALE_MAX; p++) {
916 if (passed[p])
917 continue;
918
919 free_and_replace(&c->locale[p], NULL);
920 }
921
922 locale_simplify(c);
923
924 r = locale_write_data(c, &settings);
925 if (r < 0) {
926 log_error_errno(-r, "Failed to set locale: %m");
927 return sd_bus_error_set_errnof(error, r, "Failed to set locale: %s", strerror(-r));
928 }
929
930 locale_update_system_manager(c, bus);
931
932 if (settings) {
933 _cleanup_free_ char *line;
934
935 line = strv_join(settings, ", ");
936 log_info("Changed locale to %s.", strnull(line));
937 } else
938 log_info("Changed locale to unset.");
939
940 sd_bus_emit_properties_changed(bus,
941 "/org/freedesktop/locale1",
942 "org.freedesktop.locale1",
943 "Locale", NULL);
944 } else
945 log_debug("Locale settings were not modified.");
946
947
948 return sd_bus_reply_method_return(m, NULL);
949 }
950
951 static int method_set_vc_keyboard(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
952 Context *c = userdata;
953 const char *keymap, *keymap_toggle;
954 int convert, interactive;
955 int r;
956
957 r = sd_bus_message_read(m, "ssbb", &keymap, &keymap_toggle, &convert, &interactive);
958 if (r < 0)
959 return r;
960
961 if (isempty(keymap))
962 keymap = NULL;
963
964 if (isempty(keymap_toggle))
965 keymap_toggle = NULL;
966
967 if (!streq_ptr(keymap, c->vc_keymap) ||
968 !streq_ptr(keymap_toggle, c->vc_keymap_toggle)) {
969
970 if ((keymap && (!filename_is_safe(keymap) || !string_is_safe(keymap))) ||
971 (keymap_toggle && (!filename_is_safe(keymap_toggle) || !string_is_safe(keymap_toggle))))
972 return sd_bus_error_set_errnof(error, -EINVAL, "Received invalid keymap data");
973
974 r = bus_verify_polkit_async(m, CAP_SYS_ADMIN, "org.freedesktop.locale1.set-keyboard", interactive, &c->polkit_registry, error);
975 if (r < 0)
976 return r;
977 if (r == 0)
978 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
979
980 if (free_and_strdup(&c->vc_keymap, keymap) < 0 ||
981 free_and_strdup(&c->vc_keymap_toggle, keymap_toggle) < 0)
982 return -ENOMEM;
983
984 r = vconsole_write_data(c);
985 if (r < 0) {
986 log_error_errno(-r, "Failed to set virtual console keymap: %m");
987 return sd_bus_error_set_errnof(error, r, "Failed to set virtual console keymap: %s", strerror(-r));
988 }
989
990 log_info("Changed virtual console keymap to '%s' toggle '%s'",
991 strempty(c->vc_keymap), strempty(c->vc_keymap_toggle));
992
993 r = vconsole_reload(bus);
994 if (r < 0)
995 log_error_errno(-r, "Failed to request keymap reload: %m");
996
997 sd_bus_emit_properties_changed(bus,
998 "/org/freedesktop/locale1",
999 "org.freedesktop.locale1",
1000 "VConsoleKeymap", "VConsoleKeymapToggle", NULL);
1001
1002 if (convert) {
1003 r = vconsole_convert_to_x11(c, bus);
1004 if (r < 0)
1005 log_error_errno(-r, "Failed to convert keymap data: %m");
1006 }
1007 }
1008
1009 return sd_bus_reply_method_return(m, NULL);
1010 }
1011
1012 #ifdef HAVE_XKBCOMMON
1013 static void log_xkb(struct xkb_context *ctx, enum xkb_log_level lvl, const char *format, va_list args) {
1014 /* suppress xkb messages for now */
1015 }
1016
1017 static int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, const char *options) {
1018 const struct xkb_rule_names rmlvo = {
1019 .model = model,
1020 .layout = layout,
1021 .variant = variant,
1022 .options = options,
1023 };
1024 struct xkb_context *ctx = NULL;
1025 struct xkb_keymap *km = NULL;
1026 int r;
1027
1028 /* compile keymap from RMLVO information to check out its validity */
1029
1030 ctx = xkb_context_new(XKB_CONTEXT_NO_ENVIRONMENT_NAMES);
1031 if (!ctx) {
1032 r = -ENOMEM;
1033 goto exit;
1034 }
1035
1036 xkb_context_set_log_fn(ctx, log_xkb);
1037
1038 km = xkb_keymap_new_from_names(ctx, &rmlvo, XKB_KEYMAP_COMPILE_NO_FLAGS);
1039 if (!km) {
1040 r = -EINVAL;
1041 goto exit;
1042 }
1043
1044 r = 0;
1045
1046 exit:
1047 xkb_keymap_unref(km);
1048 xkb_context_unref(ctx);
1049 return r;
1050 }
1051 #else
1052 static int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, const char *options) {
1053 return 0;
1054 }
1055 #endif
1056
1057 static int method_set_x11_keyboard(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
1058 Context *c = userdata;
1059 const char *layout, *model, *variant, *options;
1060 int convert, interactive;
1061 int r;
1062
1063 r = sd_bus_message_read(m, "ssssbb", &layout, &model, &variant, &options, &convert, &interactive);
1064 if (r < 0)
1065 return r;
1066
1067 if (isempty(layout))
1068 layout = NULL;
1069
1070 if (isempty(model))
1071 model = NULL;
1072
1073 if (isempty(variant))
1074 variant = NULL;
1075
1076 if (isempty(options))
1077 options = NULL;
1078
1079 if (!streq_ptr(layout, c->x11_layout) ||
1080 !streq_ptr(model, c->x11_model) ||
1081 !streq_ptr(variant, c->x11_variant) ||
1082 !streq_ptr(options, c->x11_options)) {
1083
1084 if ((layout && !string_is_safe(layout)) ||
1085 (model && !string_is_safe(model)) ||
1086 (variant && !string_is_safe(variant)) ||
1087 (options && !string_is_safe(options)))
1088 return sd_bus_error_set_errnof(error, -EINVAL, "Received invalid keyboard data");
1089
1090 r = bus_verify_polkit_async(m, CAP_SYS_ADMIN, "org.freedesktop.locale1.set-keyboard", interactive, &c->polkit_registry, error);
1091 if (r < 0)
1092 return r;
1093 if (r == 0)
1094 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
1095
1096 r = verify_xkb_rmlvo(model, layout, variant, options);
1097 if (r < 0)
1098 log_warning("Cannot compile XKB keymap for new x11 keyboard layout ('%s' / '%s' / '%s' / '%s'): %s",
1099 strempty(model), strempty(layout), strempty(variant), strempty(options), strerror(-r));
1100
1101 if (free_and_strdup(&c->x11_layout, layout) < 0 ||
1102 free_and_strdup(&c->x11_model, model) < 0 ||
1103 free_and_strdup(&c->x11_variant, variant) < 0 ||
1104 free_and_strdup(&c->x11_options, options) < 0)
1105 return -ENOMEM;
1106
1107 r = x11_write_data(c);
1108 if (r < 0) {
1109 log_error_errno(-r, "Failed to set X11 keyboard layout: %m");
1110 return sd_bus_error_set_errnof(error, r, "Failed to set X11 keyboard layout: %s", strerror(-r));
1111 }
1112
1113 log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
1114 strempty(c->x11_layout),
1115 strempty(c->x11_model),
1116 strempty(c->x11_variant),
1117 strempty(c->x11_options));
1118
1119 sd_bus_emit_properties_changed(bus,
1120 "/org/freedesktop/locale1",
1121 "org.freedesktop.locale1",
1122 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL);
1123
1124 if (convert) {
1125 r = x11_convert_to_vconsole(c, bus);
1126 if (r < 0)
1127 log_error_errno(-r, "Failed to convert keymap data: %m");
1128 }
1129 }
1130
1131 return sd_bus_reply_method_return(m, NULL);
1132 }
1133
1134 static const sd_bus_vtable locale_vtable[] = {
1135 SD_BUS_VTABLE_START(0),
1136 SD_BUS_PROPERTY("Locale", "as", property_get_locale, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1137 SD_BUS_PROPERTY("X11Layout", "s", NULL, offsetof(Context, x11_layout), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1138 SD_BUS_PROPERTY("X11Model", "s", NULL, offsetof(Context, x11_model), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1139 SD_BUS_PROPERTY("X11Variant", "s", NULL, offsetof(Context, x11_variant), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1140 SD_BUS_PROPERTY("X11Options", "s", NULL, offsetof(Context, x11_options), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1141 SD_BUS_PROPERTY("VConsoleKeymap", "s", NULL, offsetof(Context, vc_keymap), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1142 SD_BUS_PROPERTY("VConsoleKeymapToggle", "s", NULL, offsetof(Context, vc_keymap_toggle), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1143 SD_BUS_METHOD("SetLocale", "asb", NULL, method_set_locale, SD_BUS_VTABLE_UNPRIVILEGED),
1144 SD_BUS_METHOD("SetVConsoleKeyboard", "ssbb", NULL, method_set_vc_keyboard, SD_BUS_VTABLE_UNPRIVILEGED),
1145 SD_BUS_METHOD("SetX11Keyboard", "ssssbb", NULL, method_set_x11_keyboard, SD_BUS_VTABLE_UNPRIVILEGED),
1146 SD_BUS_VTABLE_END
1147 };
1148
1149 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
1150 _cleanup_bus_close_unref_ sd_bus *bus = NULL;
1151 int r;
1152
1153 assert(c);
1154 assert(event);
1155 assert(_bus);
1156
1157 r = sd_bus_default_system(&bus);
1158 if (r < 0) {
1159 log_error_errno(-r, "Failed to get system bus connection: %m");
1160 return r;
1161 }
1162
1163 r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/locale1", "org.freedesktop.locale1", locale_vtable, c);
1164 if (r < 0) {
1165 log_error_errno(-r, "Failed to register object: %m");
1166 return r;
1167 }
1168
1169 r = sd_bus_request_name(bus, "org.freedesktop.locale1", 0);
1170 if (r < 0) {
1171 log_error_errno(-r, "Failed to register name: %m");
1172 return r;
1173 }
1174
1175 r = sd_bus_attach_event(bus, event, 0);
1176 if (r < 0) {
1177 log_error_errno(-r, "Failed to attach bus to event loop: %m");
1178 return r;
1179 }
1180
1181 *_bus = bus;
1182 bus = NULL;
1183
1184 return 0;
1185 }
1186
1187 int main(int argc, char *argv[]) {
1188 _cleanup_(context_free) Context context = {};
1189 _cleanup_event_unref_ sd_event *event = NULL;
1190 _cleanup_bus_close_unref_ sd_bus *bus = NULL;
1191 int r;
1192
1193 log_set_target(LOG_TARGET_AUTO);
1194 log_parse_environment();
1195 log_open();
1196
1197 umask(0022);
1198 mac_selinux_init("/etc");
1199
1200 if (argc != 1) {
1201 log_error("This program takes no arguments.");
1202 r = -EINVAL;
1203 goto finish;
1204 }
1205
1206 r = sd_event_default(&event);
1207 if (r < 0) {
1208 log_error_errno(-r, "Failed to allocate event loop: %m");
1209 goto finish;
1210 }
1211
1212 sd_event_set_watchdog(event, true);
1213
1214 r = connect_bus(&context, event, &bus);
1215 if (r < 0)
1216 goto finish;
1217
1218 r = context_read_data(&context);
1219 if (r < 0) {
1220 log_error_errno(-r, "Failed to read locale data: %m");
1221 goto finish;
1222 }
1223
1224 r = bus_event_loop_with_idle(event, bus, "org.freedesktop.locale1", DEFAULT_EXIT_USEC, NULL, NULL);
1225 if (r < 0) {
1226 log_error_errno(-r, "Failed to run event loop: %m");
1227 goto finish;
1228 }
1229
1230 finish:
1231 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
1232 }