]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/locale/localed.c
Merge pull request #2709 from vcaputo/normalize_inc_dec_usage
[thirdparty/systemd.git] / src / locale / localed.c
1 /***
2 This file is part of systemd.
3
4 Copyright 2011 Lennart Poettering
5 Copyright 2013 Kay Sievers
6
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19 ***/
20
21 #include <errno.h>
22 #include <string.h>
23 #include <unistd.h>
24
25 #ifdef HAVE_XKBCOMMON
26 #include <xkbcommon/xkbcommon.h>
27 #endif
28
29 #include "sd-bus.h"
30
31 #include "alloc-util.h"
32 #include "bus-error.h"
33 #include "bus-message.h"
34 #include "bus-util.h"
35 #include "def.h"
36 #include "env-util.h"
37 #include "fd-util.h"
38 #include "fileio-label.h"
39 #include "fileio.h"
40 #include "locale-util.h"
41 #include "mkdir.h"
42 #include "path-util.h"
43 #include "selinux-util.h"
44 #include "strv.h"
45 #include "user-util.h"
46 #include "util.h"
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 bool startswith_comma(const char *s, const char *prefix) {
104 const char *t;
105
106 return s && (t = startswith(s, prefix)) && (*t == ',');
107 }
108
109 static void context_free_x11(Context *c) {
110 c->x11_layout = mfree(c->x11_layout);
111 c->x11_options = mfree(c->x11_options);
112 c->x11_model = mfree(c->x11_model);
113 c->x11_variant = mfree(c->x11_variant);
114 }
115
116 static void context_free_vconsole(Context *c) {
117 c->vc_keymap = mfree(c->vc_keymap);
118 c->vc_keymap_toggle = mfree(c->vc_keymap_toggle);
119 }
120
121 static void context_free_locale(Context *c) {
122 int p;
123
124 for (p = 0; p < _LOCALE_MAX; p++)
125 c->locale[p] = mfree(c->locale[p]);
126 }
127
128 static void context_free(Context *c) {
129 context_free_locale(c);
130 context_free_x11(c);
131 context_free_vconsole(c);
132
133 bus_verify_polkit_async_registry_free(c->polkit_registry);
134 };
135
136 static void locale_simplify(Context *c) {
137 int p;
138
139 for (p = LOCALE_LANG+1; p < _LOCALE_MAX; p++)
140 if (isempty(c->locale[p]) || streq_ptr(c->locale[LOCALE_LANG], c->locale[p]))
141 c->locale[p] = mfree(c->locale[p]);
142 }
143
144 static int locale_read_data(Context *c) {
145 int r;
146
147 context_free_locale(c);
148
149 r = parse_env_file("/etc/locale.conf", NEWLINE,
150 "LANG", &c->locale[LOCALE_LANG],
151 "LANGUAGE", &c->locale[LOCALE_LANGUAGE],
152 "LC_CTYPE", &c->locale[LOCALE_LC_CTYPE],
153 "LC_NUMERIC", &c->locale[LOCALE_LC_NUMERIC],
154 "LC_TIME", &c->locale[LOCALE_LC_TIME],
155 "LC_COLLATE", &c->locale[LOCALE_LC_COLLATE],
156 "LC_MONETARY", &c->locale[LOCALE_LC_MONETARY],
157 "LC_MESSAGES", &c->locale[LOCALE_LC_MESSAGES],
158 "LC_PAPER", &c->locale[LOCALE_LC_PAPER],
159 "LC_NAME", &c->locale[LOCALE_LC_NAME],
160 "LC_ADDRESS", &c->locale[LOCALE_LC_ADDRESS],
161 "LC_TELEPHONE", &c->locale[LOCALE_LC_TELEPHONE],
162 "LC_MEASUREMENT", &c->locale[LOCALE_LC_MEASUREMENT],
163 "LC_IDENTIFICATION", &c->locale[LOCALE_LC_IDENTIFICATION],
164 NULL);
165
166 if (r == -ENOENT) {
167 int p;
168
169 /* Fill in what we got passed from systemd. */
170 for (p = 0; p < _LOCALE_MAX; p++) {
171 assert(names[p]);
172
173 r = free_and_strdup(&c->locale[p],
174 nonempty(getenv(names[p])));
175 if (r < 0)
176 return r;
177 }
178
179 r = 0;
180 }
181
182 locale_simplify(c);
183 return r;
184 }
185
186 static int vconsole_read_data(Context *c) {
187 int r;
188
189 context_free_vconsole(c);
190
191 r = parse_env_file("/etc/vconsole.conf", NEWLINE,
192 "KEYMAP", &c->vc_keymap,
193 "KEYMAP_TOGGLE", &c->vc_keymap_toggle,
194 NULL);
195
196 if (r < 0 && r != -ENOENT)
197 return r;
198
199 return 0;
200 }
201
202 static int x11_read_data(Context *c) {
203 _cleanup_fclose_ FILE *f;
204 char line[LINE_MAX];
205 bool in_section = false;
206 int r;
207
208 context_free_x11(c);
209
210 f = fopen("/etc/X11/xorg.conf.d/00-keyboard.conf", "re");
211 if (!f)
212 return errno == ENOENT ? 0 : -errno;
213
214 while (fgets(line, sizeof(line), f)) {
215 char *l;
216
217 char_array_0(line);
218 l = strstrip(line);
219
220 if (l[0] == 0 || l[0] == '#')
221 continue;
222
223 if (in_section && first_word(l, "Option")) {
224 _cleanup_strv_free_ char **a = NULL;
225
226 r = strv_split_extract(&a, l, WHITESPACE, EXTRACT_QUOTES);
227 if (r < 0)
228 return r;
229
230 if (strv_length(a) == 3) {
231 char **p = NULL;
232
233 if (streq(a[1], "XkbLayout"))
234 p = &c->x11_layout;
235 else if (streq(a[1], "XkbModel"))
236 p = &c->x11_model;
237 else if (streq(a[1], "XkbVariant"))
238 p = &c->x11_variant;
239 else if (streq(a[1], "XkbOptions"))
240 p = &c->x11_options;
241
242 if (p) {
243 free(*p);
244 *p = a[2];
245 a[2] = NULL;
246 }
247 }
248
249 } else if (!in_section && first_word(l, "Section")) {
250 _cleanup_strv_free_ char **a = NULL;
251
252 r = strv_split_extract(&a, l, WHITESPACE, EXTRACT_QUOTES);
253 if (r < 0)
254 return -ENOMEM;
255
256 if (strv_length(a) == 2 && streq(a[1], "InputClass"))
257 in_section = true;
258
259 } else if (in_section && first_word(l, "EndSection"))
260 in_section = false;
261 }
262
263 return 0;
264 }
265
266 static int context_read_data(Context *c) {
267 int r, q, p;
268
269 r = locale_read_data(c);
270 q = vconsole_read_data(c);
271 p = x11_read_data(c);
272
273 return r < 0 ? r : q < 0 ? q : p;
274 }
275
276 static int locale_write_data(Context *c, char ***settings) {
277 int r, p;
278 _cleanup_strv_free_ char **l = NULL;
279
280 /* Set values will be returned as strv in *settings on success. */
281
282 r = load_env_file(NULL, "/etc/locale.conf", NULL, &l);
283 if (r < 0 && r != -ENOENT)
284 return r;
285
286 for (p = 0; p < _LOCALE_MAX; p++) {
287 _cleanup_free_ char *t = NULL;
288 char **u;
289
290 assert(names[p]);
291
292 if (isempty(c->locale[p])) {
293 l = strv_env_unset(l, names[p]);
294 continue;
295 }
296
297 if (asprintf(&t, "%s=%s", names[p], c->locale[p]) < 0)
298 return -ENOMEM;
299
300 u = strv_env_set(l, t);
301 if (!u)
302 return -ENOMEM;
303
304 strv_free(l);
305 l = u;
306 }
307
308 if (strv_isempty(l)) {
309 if (unlink("/etc/locale.conf") < 0)
310 return errno == ENOENT ? 0 : -errno;
311
312 return 0;
313 }
314
315 r = write_env_file_label("/etc/locale.conf", l);
316 if (r < 0)
317 return r;
318
319 *settings = l;
320 l = NULL;
321 return 0;
322 }
323
324 static int locale_update_system_manager(Context *c, sd_bus *bus) {
325 _cleanup_free_ char **l_unset = NULL;
326 _cleanup_strv_free_ char **l_set = NULL;
327 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
328 sd_bus_error error = SD_BUS_ERROR_NULL;
329 unsigned c_set, c_unset, p;
330 int r;
331
332 assert(bus);
333
334 l_unset = new0(char*, _LOCALE_MAX);
335 if (!l_unset)
336 return -ENOMEM;
337
338 l_set = new0(char*, _LOCALE_MAX);
339 if (!l_set)
340 return -ENOMEM;
341
342 for (p = 0, c_set = 0, c_unset = 0; p < _LOCALE_MAX; p++) {
343 assert(names[p]);
344
345 if (isempty(c->locale[p]))
346 l_unset[c_set++] = (char*) names[p];
347 else {
348 char *s;
349
350 if (asprintf(&s, "%s=%s", names[p], c->locale[p]) < 0)
351 return -ENOMEM;
352
353 l_set[c_unset++] = s;
354 }
355 }
356
357 assert(c_set + c_unset == _LOCALE_MAX);
358 r = sd_bus_message_new_method_call(bus, &m,
359 "org.freedesktop.systemd1",
360 "/org/freedesktop/systemd1",
361 "org.freedesktop.systemd1.Manager",
362 "UnsetAndSetEnvironment");
363 if (r < 0)
364 return r;
365
366 r = sd_bus_message_append_strv(m, l_unset);
367 if (r < 0)
368 return r;
369
370 r = sd_bus_message_append_strv(m, l_set);
371 if (r < 0)
372 return r;
373
374 r = sd_bus_call(bus, m, 0, &error, NULL);
375 if (r < 0)
376 log_error_errno(r, "Failed to update the manager environment: %m");
377
378 return 0;
379 }
380
381 static int vconsole_write_data(Context *c) {
382 int r;
383 _cleanup_strv_free_ char **l = NULL;
384
385 r = load_env_file(NULL, "/etc/vconsole.conf", NULL, &l);
386 if (r < 0 && r != -ENOENT)
387 return r;
388
389 if (isempty(c->vc_keymap))
390 l = strv_env_unset(l, "KEYMAP");
391 else {
392 _cleanup_free_ char *s = NULL;
393 char **u;
394
395 s = strappend("KEYMAP=", c->vc_keymap);
396 if (!s)
397 return -ENOMEM;
398
399 u = strv_env_set(l, s);
400 if (!u)
401 return -ENOMEM;
402
403 strv_free(l);
404 l = u;
405 }
406
407 if (isempty(c->vc_keymap_toggle))
408 l = strv_env_unset(l, "KEYMAP_TOGGLE");
409 else {
410 _cleanup_free_ char *s = NULL;
411 char **u;
412
413 s = strappend("KEYMAP_TOGGLE=", c->vc_keymap_toggle);
414 if (!s)
415 return -ENOMEM;
416
417 u = strv_env_set(l, s);
418 if (!u)
419 return -ENOMEM;
420
421 strv_free(l);
422 l = u;
423 }
424
425 if (strv_isempty(l)) {
426 if (unlink("/etc/vconsole.conf") < 0)
427 return errno == ENOENT ? 0 : -errno;
428
429 return 0;
430 }
431
432 return write_env_file_label("/etc/vconsole.conf", l);
433 }
434
435 static int x11_write_data(Context *c) {
436 _cleanup_fclose_ FILE *f = NULL;
437 _cleanup_free_ char *temp_path = NULL;
438 int r;
439
440 if (isempty(c->x11_layout) &&
441 isempty(c->x11_model) &&
442 isempty(c->x11_variant) &&
443 isempty(c->x11_options)) {
444
445 if (unlink("/etc/X11/xorg.conf.d/00-keyboard.conf") < 0)
446 return errno == ENOENT ? 0 : -errno;
447
448 return 0;
449 }
450
451 mkdir_p_label("/etc/X11/xorg.conf.d", 0755);
452
453 r = fopen_temporary("/etc/X11/xorg.conf.d/00-keyboard.conf", &f, &temp_path);
454 if (r < 0)
455 return r;
456
457 fchmod(fileno(f), 0644);
458
459 fputs("# Read and parsed by systemd-localed. It's probably wise not to edit this file\n"
460 "# manually too freely.\n"
461 "Section \"InputClass\"\n"
462 " Identifier \"system-keyboard\"\n"
463 " MatchIsKeyboard \"on\"\n", f);
464
465 if (!isempty(c->x11_layout))
466 fprintf(f, " Option \"XkbLayout\" \"%s\"\n", c->x11_layout);
467
468 if (!isempty(c->x11_model))
469 fprintf(f, " Option \"XkbModel\" \"%s\"\n", c->x11_model);
470
471 if (!isempty(c->x11_variant))
472 fprintf(f, " Option \"XkbVariant\" \"%s\"\n", c->x11_variant);
473
474 if (!isempty(c->x11_options))
475 fprintf(f, " Option \"XkbOptions\" \"%s\"\n", c->x11_options);
476
477 fputs("EndSection\n", f);
478
479 r = fflush_and_check(f);
480 if (r < 0)
481 goto fail;
482
483 if (rename(temp_path, "/etc/X11/xorg.conf.d/00-keyboard.conf") < 0) {
484 r = -errno;
485 goto fail;
486 }
487
488 return 0;
489
490 fail:
491 (void) unlink("/etc/X11/xorg.conf.d/00-keyboard.conf");
492
493 if (temp_path)
494 (void) unlink(temp_path);
495
496 return r;
497 }
498
499 static int vconsole_reload(sd_bus *bus) {
500 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
501 int r;
502
503 assert(bus);
504
505 r = sd_bus_call_method(bus,
506 "org.freedesktop.systemd1",
507 "/org/freedesktop/systemd1",
508 "org.freedesktop.systemd1.Manager",
509 "RestartUnit",
510 &error,
511 NULL,
512 "ss", "systemd-vconsole-setup.service", "replace");
513
514 if (r < 0)
515 log_error("Failed to issue method call: %s", bus_error_message(&error, -r));
516 return r;
517 }
518
519 static const char* strnulldash(const char *s) {
520 return isempty(s) || streq(s, "-") ? NULL : s;
521 }
522
523 static int read_next_mapping(const char* filename,
524 unsigned min_fields, unsigned max_fields,
525 FILE *f, unsigned *n, char ***a) {
526 assert(f);
527 assert(n);
528 assert(a);
529
530 for (;;) {
531 char line[LINE_MAX];
532 char *l, **b;
533 int r;
534 size_t length;
535
536 errno = 0;
537 if (!fgets(line, sizeof(line), f)) {
538
539 if (ferror(f))
540 return errno > 0 ? -errno : -EIO;
541
542 return 0;
543 }
544
545 (*n)++;
546
547 l = strstrip(line);
548 if (l[0] == 0 || l[0] == '#')
549 continue;
550
551 r = strv_split_extract(&b, l, WHITESPACE, EXTRACT_QUOTES);
552 if (r < 0)
553 return r;
554
555 length = strv_length(b);
556 if (length < min_fields || length > max_fields) {
557 log_error("Invalid line %s:%u, ignoring.", filename, *n);
558 strv_free(b);
559 continue;
560
561 }
562
563 *a = b;
564 return 1;
565 }
566 }
567
568 static int vconsole_convert_to_x11(Context *c, sd_bus *bus) {
569 bool modified = false;
570
571 assert(bus);
572
573 if (isempty(c->vc_keymap)) {
574
575 modified =
576 !isempty(c->x11_layout) ||
577 !isempty(c->x11_model) ||
578 !isempty(c->x11_variant) ||
579 !isempty(c->x11_options);
580
581 context_free_x11(c);
582 } else {
583 _cleanup_fclose_ FILE *f = NULL;
584 unsigned n = 0;
585
586 f = fopen(SYSTEMD_KBD_MODEL_MAP, "re");
587 if (!f)
588 return -errno;
589
590 for (;;) {
591 _cleanup_strv_free_ char **a = NULL;
592 int r;
593
594 r = read_next_mapping(SYSTEMD_KBD_MODEL_MAP, 5, UINT_MAX, f, &n, &a);
595 if (r < 0)
596 return r;
597 if (r == 0)
598 break;
599
600 if (!streq(c->vc_keymap, a[0]))
601 continue;
602
603 if (!streq_ptr(c->x11_layout, strnulldash(a[1])) ||
604 !streq_ptr(c->x11_model, strnulldash(a[2])) ||
605 !streq_ptr(c->x11_variant, strnulldash(a[3])) ||
606 !streq_ptr(c->x11_options, strnulldash(a[4]))) {
607
608 if (free_and_strdup(&c->x11_layout, strnulldash(a[1])) < 0 ||
609 free_and_strdup(&c->x11_model, strnulldash(a[2])) < 0 ||
610 free_and_strdup(&c->x11_variant, strnulldash(a[3])) < 0 ||
611 free_and_strdup(&c->x11_options, strnulldash(a[4])) < 0)
612 return -ENOMEM;
613
614 modified = true;
615 }
616
617 break;
618 }
619 }
620
621 if (modified) {
622 int r;
623
624 r = x11_write_data(c);
625 if (r < 0)
626 return log_error_errno(r, "Failed to set X11 keyboard layout: %m");
627
628 log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
629 strempty(c->x11_layout),
630 strempty(c->x11_model),
631 strempty(c->x11_variant),
632 strempty(c->x11_options));
633
634 sd_bus_emit_properties_changed(bus,
635 "/org/freedesktop/locale1",
636 "org.freedesktop.locale1",
637 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL);
638 } else
639 log_debug("X11 keyboard layout was not modified.");
640
641 return 0;
642 }
643
644 static int find_converted_keymap(const char *x11_layout, const char *x11_variant, char **new_keymap) {
645 const char *dir;
646 _cleanup_free_ char *n;
647
648 if (x11_variant)
649 n = strjoin(x11_layout, "-", x11_variant, NULL);
650 else
651 n = strdup(x11_layout);
652 if (!n)
653 return -ENOMEM;
654
655 NULSTR_FOREACH(dir, KBD_KEYMAP_DIRS) {
656 _cleanup_free_ char *p = NULL, *pz = NULL;
657 bool uncompressed;
658
659 p = strjoin(dir, "xkb/", n, ".map", NULL);
660 pz = strjoin(dir, "xkb/", n, ".map.gz", NULL);
661 if (!p || !pz)
662 return -ENOMEM;
663
664 uncompressed = access(p, F_OK) == 0;
665 if (uncompressed || access(pz, F_OK) == 0) {
666 log_debug("Found converted keymap %s at %s",
667 n, uncompressed ? p : pz);
668
669 *new_keymap = n;
670 n = NULL;
671 return 1;
672 }
673 }
674
675 return 0;
676 }
677
678 static int find_legacy_keymap(Context *c, char **new_keymap) {
679 _cleanup_fclose_ FILE *f;
680 unsigned n = 0;
681 unsigned best_matching = 0;
682 int r;
683
684 f = fopen(SYSTEMD_KBD_MODEL_MAP, "re");
685 if (!f)
686 return -errno;
687
688 for (;;) {
689 _cleanup_strv_free_ char **a = NULL;
690 unsigned matching = 0;
691
692 r = read_next_mapping(SYSTEMD_KBD_MODEL_MAP, 5, UINT_MAX, f, &n, &a);
693 if (r < 0)
694 return r;
695 if (r == 0)
696 break;
697
698 /* Determine how well matching this entry is */
699 if (streq_ptr(c->x11_layout, a[1]))
700 /* If we got an exact match, this is best */
701 matching = 10;
702 else {
703 /* We have multiple X layouts, look for an
704 * entry that matches our key with everything
705 * but the first layout stripped off. */
706 if (startswith_comma(c->x11_layout, a[1]))
707 matching = 5;
708 else {
709 char *x;
710
711 /* If that didn't work, strip off the
712 * other layouts from the entry, too */
713 x = strndupa(a[1], strcspn(a[1], ","));
714 if (startswith_comma(c->x11_layout, x))
715 matching = 1;
716 }
717 }
718
719 if (matching > 0) {
720 if (isempty(c->x11_model) || streq_ptr(c->x11_model, a[2])) {
721 matching++;
722
723 if (streq_ptr(c->x11_variant, a[3])) {
724 matching++;
725
726 if (streq_ptr(c->x11_options, a[4]))
727 matching++;
728 }
729 }
730 }
731
732 /* The best matching entry so far, then let's save that */
733 if (matching >= MAX(best_matching, 1u)) {
734 log_debug("Found legacy keymap %s with score %u",
735 a[0], matching);
736
737 if (matching > best_matching) {
738 best_matching = matching;
739
740 r = free_and_strdup(new_keymap, a[0]);
741 if (r < 0)
742 return r;
743 }
744 }
745 }
746
747 if (best_matching < 10 && c->x11_layout) {
748 /* The best match is only the first part of the X11
749 * keymap. Check if we have a converted map which
750 * matches just the first layout.
751 */
752 char *l, *v = NULL, *converted;
753
754 l = strndupa(c->x11_layout, strcspn(c->x11_layout, ","));
755 if (c->x11_variant)
756 v = strndupa(c->x11_variant, strcspn(c->x11_variant, ","));
757 r = find_converted_keymap(l, v, &converted);
758 if (r < 0)
759 return r;
760 if (r > 0) {
761 free(*new_keymap);
762 *new_keymap = converted;
763 }
764 }
765
766 return 0;
767 }
768
769 static int find_language_fallback(const char *lang, char **language) {
770 _cleanup_fclose_ FILE *f = NULL;
771 unsigned n = 0;
772
773 assert(language);
774
775 f = fopen(SYSTEMD_LANGUAGE_FALLBACK_MAP, "re");
776 if (!f)
777 return -errno;
778
779 for (;;) {
780 _cleanup_strv_free_ char **a = NULL;
781 int r;
782
783 r = read_next_mapping(SYSTEMD_LANGUAGE_FALLBACK_MAP, 2, 2, f, &n, &a);
784 if (r <= 0)
785 return r;
786
787 if (streq(lang, a[0])) {
788 assert(strv_length(a) == 2);
789 *language = a[1];
790 a[1] = NULL;
791 return 1;
792 }
793 }
794
795 assert_not_reached("should not be here");
796 }
797
798 static int x11_convert_to_vconsole(Context *c, sd_bus *bus) {
799 bool modified = false;
800 int r;
801
802 assert(bus);
803
804 if (isempty(c->x11_layout)) {
805
806 modified =
807 !isempty(c->vc_keymap) ||
808 !isempty(c->vc_keymap_toggle);
809
810 context_free_x11(c);
811 } else {
812 char *new_keymap = NULL;
813
814 r = find_converted_keymap(c->x11_layout, c->x11_variant, &new_keymap);
815 if (r < 0)
816 return r;
817 else if (r == 0) {
818 r = find_legacy_keymap(c, &new_keymap);
819 if (r < 0)
820 return r;
821 }
822
823 if (!streq_ptr(c->vc_keymap, new_keymap)) {
824 free(c->vc_keymap);
825 c->vc_keymap = new_keymap;
826 c->vc_keymap_toggle = mfree(c->vc_keymap_toggle);
827 modified = true;
828 } else
829 free(new_keymap);
830 }
831
832 if (modified) {
833 r = vconsole_write_data(c);
834 if (r < 0)
835 log_error_errno(r, "Failed to set virtual console keymap: %m");
836
837 log_info("Changed virtual console keymap to '%s' toggle '%s'",
838 strempty(c->vc_keymap), strempty(c->vc_keymap_toggle));
839
840 sd_bus_emit_properties_changed(bus,
841 "/org/freedesktop/locale1",
842 "org.freedesktop.locale1",
843 "VConsoleKeymap", "VConsoleKeymapToggle", NULL);
844
845 return vconsole_reload(bus);
846 } else
847 log_debug("Virtual console keymap was not modified.");
848
849 return 0;
850 }
851
852 static int property_get_locale(
853 sd_bus *bus,
854 const char *path,
855 const char *interface,
856 const char *property,
857 sd_bus_message *reply,
858 void *userdata,
859 sd_bus_error *error) {
860
861 Context *c = userdata;
862 _cleanup_strv_free_ char **l = NULL;
863 int p, q;
864
865 l = new0(char*, _LOCALE_MAX+1);
866 if (!l)
867 return -ENOMEM;
868
869 for (p = 0, q = 0; p < _LOCALE_MAX; p++) {
870 char *t;
871
872 if (isempty(c->locale[p]))
873 continue;
874
875 if (asprintf(&t, "%s=%s", names[p], c->locale[p]) < 0)
876 return -ENOMEM;
877
878 l[q++] = t;
879 }
880
881 return sd_bus_message_append_strv(reply, l);
882 }
883
884 static int method_set_locale(sd_bus_message *m, void *userdata, sd_bus_error *error) {
885 Context *c = userdata;
886 _cleanup_strv_free_ char **l = NULL;
887 char **i;
888 const char *lang = NULL;
889 int interactive;
890 bool modified = false;
891 bool have[_LOCALE_MAX] = {};
892 int p;
893 int r;
894
895 assert(m);
896 assert(c);
897
898 r = bus_message_read_strv_extend(m, &l);
899 if (r < 0)
900 return r;
901
902 r = sd_bus_message_read_basic(m, 'b', &interactive);
903 if (r < 0)
904 return r;
905
906 /* Check whether a variable changed and if it is valid */
907 STRV_FOREACH(i, l) {
908 bool valid = false;
909
910 for (p = 0; p < _LOCALE_MAX; p++) {
911 size_t k;
912
913 k = strlen(names[p]);
914 if (startswith(*i, names[p]) &&
915 (*i)[k] == '=' &&
916 locale_is_valid((*i) + k + 1)) {
917 valid = true;
918 have[p] = true;
919
920 if (p == LOCALE_LANG)
921 lang = (*i) + k + 1;
922
923 if (!streq_ptr(*i + k + 1, c->locale[p]))
924 modified = true;
925
926 break;
927 }
928 }
929
930 if (!valid)
931 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid Locale data.");
932 }
933
934 /* If LANG was specified, but not LANGUAGE, check if we should
935 * set it based on the language fallback table. */
936 if (have[LOCALE_LANG] && !have[LOCALE_LANGUAGE]) {
937 _cleanup_free_ char *language = NULL;
938
939 assert(lang);
940
941 (void) find_language_fallback(lang, &language);
942 if (language) {
943 log_debug("Converted LANG=%s to LANGUAGE=%s", lang, language);
944 if (!streq_ptr(language, c->locale[LOCALE_LANGUAGE])) {
945 r = strv_extendf(&l, "LANGUAGE=%s", language);
946 if (r < 0)
947 return r;
948
949 have[LOCALE_LANGUAGE] = true;
950 modified = true;
951 }
952 }
953 }
954
955 /* Check whether a variable is unset */
956 if (!modified)
957 for (p = 0; p < _LOCALE_MAX; p++)
958 if (!isempty(c->locale[p]) && !have[p]) {
959 modified = true;
960 break;
961 }
962
963 if (modified) {
964 _cleanup_strv_free_ char **settings = NULL;
965
966 r = bus_verify_polkit_async(
967 m,
968 CAP_SYS_ADMIN,
969 "org.freedesktop.locale1.set-locale",
970 NULL,
971 interactive,
972 UID_INVALID,
973 &c->polkit_registry,
974 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 STRV_FOREACH(i, l)
981 for (p = 0; p < _LOCALE_MAX; p++) {
982 size_t k;
983
984 k = strlen(names[p]);
985 if (startswith(*i, names[p]) && (*i)[k] == '=') {
986 r = free_and_strdup(&c->locale[p], *i + k + 1);
987 if (r < 0)
988 return r;
989 break;
990 }
991 }
992
993 for (p = 0; p < _LOCALE_MAX; p++) {
994 if (have[p])
995 continue;
996
997 c->locale[p] = mfree(c->locale[p]);
998 }
999
1000 locale_simplify(c);
1001
1002 r = locale_write_data(c, &settings);
1003 if (r < 0) {
1004 log_error_errno(r, "Failed to set locale: %m");
1005 return sd_bus_error_set_errnof(error, r, "Failed to set locale: %s", strerror(-r));
1006 }
1007
1008 locale_update_system_manager(c, sd_bus_message_get_bus(m));
1009
1010 if (settings) {
1011 _cleanup_free_ char *line;
1012
1013 line = strv_join(settings, ", ");
1014 log_info("Changed locale to %s.", strnull(line));
1015 } else
1016 log_info("Changed locale to unset.");
1017
1018 (void) sd_bus_emit_properties_changed(
1019 sd_bus_message_get_bus(m),
1020 "/org/freedesktop/locale1",
1021 "org.freedesktop.locale1",
1022 "Locale", NULL);
1023 } else
1024 log_debug("Locale settings were not modified.");
1025
1026
1027 return sd_bus_reply_method_return(m, NULL);
1028 }
1029
1030 static int method_set_vc_keyboard(sd_bus_message *m, void *userdata, sd_bus_error *error) {
1031 Context *c = userdata;
1032 const char *keymap, *keymap_toggle;
1033 int convert, interactive;
1034 int r;
1035
1036 assert(m);
1037 assert(c);
1038
1039 r = sd_bus_message_read(m, "ssbb", &keymap, &keymap_toggle, &convert, &interactive);
1040 if (r < 0)
1041 return r;
1042
1043 if (isempty(keymap))
1044 keymap = NULL;
1045
1046 if (isempty(keymap_toggle))
1047 keymap_toggle = NULL;
1048
1049 if (!streq_ptr(keymap, c->vc_keymap) ||
1050 !streq_ptr(keymap_toggle, c->vc_keymap_toggle)) {
1051
1052 if ((keymap && (!filename_is_valid(keymap) || !string_is_safe(keymap))) ||
1053 (keymap_toggle && (!filename_is_valid(keymap_toggle) || !string_is_safe(keymap_toggle))))
1054 return sd_bus_error_set_errnof(error, -EINVAL, "Received invalid keymap data");
1055
1056 r = bus_verify_polkit_async(
1057 m,
1058 CAP_SYS_ADMIN,
1059 "org.freedesktop.locale1.set-keyboard",
1060 NULL,
1061 interactive,
1062 UID_INVALID,
1063 &c->polkit_registry,
1064 error);
1065 if (r < 0)
1066 return r;
1067 if (r == 0)
1068 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
1069
1070 if (free_and_strdup(&c->vc_keymap, keymap) < 0 ||
1071 free_and_strdup(&c->vc_keymap_toggle, keymap_toggle) < 0)
1072 return -ENOMEM;
1073
1074 r = vconsole_write_data(c);
1075 if (r < 0) {
1076 log_error_errno(r, "Failed to set virtual console keymap: %m");
1077 return sd_bus_error_set_errnof(error, r, "Failed to set virtual console keymap: %s", strerror(-r));
1078 }
1079
1080 log_info("Changed virtual console keymap to '%s' toggle '%s'",
1081 strempty(c->vc_keymap), strempty(c->vc_keymap_toggle));
1082
1083 r = vconsole_reload(sd_bus_message_get_bus(m));
1084 if (r < 0)
1085 log_error_errno(r, "Failed to request keymap reload: %m");
1086
1087 (void) sd_bus_emit_properties_changed(
1088 sd_bus_message_get_bus(m),
1089 "/org/freedesktop/locale1",
1090 "org.freedesktop.locale1",
1091 "VConsoleKeymap", "VConsoleKeymapToggle", NULL);
1092
1093 if (convert) {
1094 r = vconsole_convert_to_x11(c, sd_bus_message_get_bus(m));
1095 if (r < 0)
1096 log_error_errno(r, "Failed to convert keymap data: %m");
1097 }
1098 }
1099
1100 return sd_bus_reply_method_return(m, NULL);
1101 }
1102
1103 #ifdef HAVE_XKBCOMMON
1104 _printf_(3, 0)
1105 static void log_xkb(struct xkb_context *ctx, enum xkb_log_level lvl, const char *format, va_list args) {
1106 const char *fmt;
1107
1108 fmt = strjoina("libxkbcommon: ", format);
1109 log_internalv(LOG_DEBUG, 0, __FILE__, __LINE__, __func__, fmt, args);
1110 }
1111
1112 static int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, const char *options) {
1113 const struct xkb_rule_names rmlvo = {
1114 .model = model,
1115 .layout = layout,
1116 .variant = variant,
1117 .options = options,
1118 };
1119 struct xkb_context *ctx = NULL;
1120 struct xkb_keymap *km = NULL;
1121 int r;
1122
1123 /* compile keymap from RMLVO information to check out its validity */
1124
1125 ctx = xkb_context_new(XKB_CONTEXT_NO_ENVIRONMENT_NAMES);
1126 if (!ctx) {
1127 r = -ENOMEM;
1128 goto exit;
1129 }
1130
1131 xkb_context_set_log_fn(ctx, log_xkb);
1132
1133 km = xkb_keymap_new_from_names(ctx, &rmlvo, XKB_KEYMAP_COMPILE_NO_FLAGS);
1134 if (!km) {
1135 r = -EINVAL;
1136 goto exit;
1137 }
1138
1139 r = 0;
1140
1141 exit:
1142 xkb_keymap_unref(km);
1143 xkb_context_unref(ctx);
1144 return r;
1145 }
1146 #else
1147 static int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, const char *options) {
1148 return 0;
1149 }
1150 #endif
1151
1152 static int method_set_x11_keyboard(sd_bus_message *m, void *userdata, sd_bus_error *error) {
1153 Context *c = userdata;
1154 const char *layout, *model, *variant, *options;
1155 int convert, interactive;
1156 int r;
1157
1158 assert(m);
1159 assert(c);
1160
1161 r = sd_bus_message_read(m, "ssssbb", &layout, &model, &variant, &options, &convert, &interactive);
1162 if (r < 0)
1163 return r;
1164
1165 if (isempty(layout))
1166 layout = NULL;
1167
1168 if (isempty(model))
1169 model = NULL;
1170
1171 if (isempty(variant))
1172 variant = NULL;
1173
1174 if (isempty(options))
1175 options = NULL;
1176
1177 if (!streq_ptr(layout, c->x11_layout) ||
1178 !streq_ptr(model, c->x11_model) ||
1179 !streq_ptr(variant, c->x11_variant) ||
1180 !streq_ptr(options, c->x11_options)) {
1181
1182 if ((layout && !string_is_safe(layout)) ||
1183 (model && !string_is_safe(model)) ||
1184 (variant && !string_is_safe(variant)) ||
1185 (options && !string_is_safe(options)))
1186 return sd_bus_error_set_errnof(error, -EINVAL, "Received invalid keyboard data");
1187
1188 r = bus_verify_polkit_async(
1189 m,
1190 CAP_SYS_ADMIN,
1191 "org.freedesktop.locale1.set-keyboard",
1192 NULL,
1193 interactive,
1194 UID_INVALID,
1195 &c->polkit_registry,
1196 error);
1197 if (r < 0)
1198 return r;
1199 if (r == 0)
1200 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
1201
1202 r = verify_xkb_rmlvo(model, layout, variant, options);
1203 if (r < 0) {
1204 log_error_errno(r, "Cannot compile XKB keymap for new x11 keyboard layout ('%s' / '%s' / '%s' / '%s'): %m",
1205 strempty(model), strempty(layout), strempty(variant), strempty(options));
1206 return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Cannot compile XKB keymap, refusing");
1207 }
1208
1209 if (free_and_strdup(&c->x11_layout, layout) < 0 ||
1210 free_and_strdup(&c->x11_model, model) < 0 ||
1211 free_and_strdup(&c->x11_variant, variant) < 0 ||
1212 free_and_strdup(&c->x11_options, options) < 0)
1213 return -ENOMEM;
1214
1215 r = x11_write_data(c);
1216 if (r < 0) {
1217 log_error_errno(r, "Failed to set X11 keyboard layout: %m");
1218 return sd_bus_error_set_errnof(error, r, "Failed to set X11 keyboard layout: %s", strerror(-r));
1219 }
1220
1221 log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
1222 strempty(c->x11_layout),
1223 strempty(c->x11_model),
1224 strempty(c->x11_variant),
1225 strempty(c->x11_options));
1226
1227 (void) sd_bus_emit_properties_changed(
1228 sd_bus_message_get_bus(m),
1229 "/org/freedesktop/locale1",
1230 "org.freedesktop.locale1",
1231 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL);
1232
1233 if (convert) {
1234 r = x11_convert_to_vconsole(c, sd_bus_message_get_bus(m));
1235 if (r < 0)
1236 log_error_errno(r, "Failed to convert keymap data: %m");
1237 }
1238 }
1239
1240 return sd_bus_reply_method_return(m, NULL);
1241 }
1242
1243 static const sd_bus_vtable locale_vtable[] = {
1244 SD_BUS_VTABLE_START(0),
1245 SD_BUS_PROPERTY("Locale", "as", property_get_locale, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1246 SD_BUS_PROPERTY("X11Layout", "s", NULL, offsetof(Context, x11_layout), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1247 SD_BUS_PROPERTY("X11Model", "s", NULL, offsetof(Context, x11_model), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1248 SD_BUS_PROPERTY("X11Variant", "s", NULL, offsetof(Context, x11_variant), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1249 SD_BUS_PROPERTY("X11Options", "s", NULL, offsetof(Context, x11_options), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1250 SD_BUS_PROPERTY("VConsoleKeymap", "s", NULL, offsetof(Context, vc_keymap), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1251 SD_BUS_PROPERTY("VConsoleKeymapToggle", "s", NULL, offsetof(Context, vc_keymap_toggle), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1252 SD_BUS_METHOD("SetLocale", "asb", NULL, method_set_locale, SD_BUS_VTABLE_UNPRIVILEGED),
1253 SD_BUS_METHOD("SetVConsoleKeyboard", "ssbb", NULL, method_set_vc_keyboard, SD_BUS_VTABLE_UNPRIVILEGED),
1254 SD_BUS_METHOD("SetX11Keyboard", "ssssbb", NULL, method_set_x11_keyboard, SD_BUS_VTABLE_UNPRIVILEGED),
1255 SD_BUS_VTABLE_END
1256 };
1257
1258 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
1259 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
1260 int r;
1261
1262 assert(c);
1263 assert(event);
1264 assert(_bus);
1265
1266 r = sd_bus_default_system(&bus);
1267 if (r < 0)
1268 return log_error_errno(r, "Failed to get system bus connection: %m");
1269
1270 r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/locale1", "org.freedesktop.locale1", locale_vtable, c);
1271 if (r < 0)
1272 return log_error_errno(r, "Failed to register object: %m");
1273
1274 r = sd_bus_request_name(bus, "org.freedesktop.locale1", 0);
1275 if (r < 0)
1276 return log_error_errno(r, "Failed to register name: %m");
1277
1278 r = sd_bus_attach_event(bus, event, 0);
1279 if (r < 0)
1280 return log_error_errno(r, "Failed to attach bus to event loop: %m");
1281
1282 *_bus = bus;
1283 bus = NULL;
1284
1285 return 0;
1286 }
1287
1288 int main(int argc, char *argv[]) {
1289 _cleanup_(context_free) Context context = {};
1290 _cleanup_(sd_event_unrefp) sd_event *event = NULL;
1291 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
1292 int r;
1293
1294 log_set_target(LOG_TARGET_AUTO);
1295 log_parse_environment();
1296 log_open();
1297
1298 umask(0022);
1299 mac_selinux_init("/etc");
1300
1301 if (argc != 1) {
1302 log_error("This program takes no arguments.");
1303 r = -EINVAL;
1304 goto finish;
1305 }
1306
1307 r = sd_event_default(&event);
1308 if (r < 0) {
1309 log_error_errno(r, "Failed to allocate event loop: %m");
1310 goto finish;
1311 }
1312
1313 sd_event_set_watchdog(event, true);
1314
1315 r = connect_bus(&context, event, &bus);
1316 if (r < 0)
1317 goto finish;
1318
1319 r = context_read_data(&context);
1320 if (r < 0) {
1321 log_error_errno(r, "Failed to read locale data: %m");
1322 goto finish;
1323 }
1324
1325 r = bus_event_loop_with_idle(event, bus, "org.freedesktop.locale1", DEFAULT_EXIT_USEC, NULL, NULL);
1326 if (r < 0) {
1327 log_error_errno(r, "Failed to run event loop: %m");
1328 goto finish;
1329 }
1330
1331 finish:
1332 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
1333 }