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