]> git.proxmox.com Git - systemd.git/blob - src/locale/localed.c
Imported Upstream version 204
[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
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <dbus/dbus.h>
23
24 #include <errno.h>
25 #include <string.h>
26 #include <unistd.h>
27
28 #include "util.h"
29 #include "mkdir.h"
30 #include "strv.h"
31 #include "dbus-common.h"
32 #include "polkit.h"
33 #include "def.h"
34 #include "env-util.h"
35 #include "fileio.h"
36 #include "fileio-label.h"
37 #include "label.h"
38
39 #define INTERFACE \
40 " <interface name=\"org.freedesktop.locale1\">\n" \
41 " <property name=\"Locale\" type=\"as\" access=\"read\"/>\n" \
42 " <property name=\"VConsoleKeymap\" type=\"s\" access=\"read\"/>\n" \
43 " <property name=\"VConsoleKeymapToggle\" type=\"s\" access=\"read\"/>\n" \
44 " <property name=\"X11Layout\" type=\"s\" access=\"read\"/>\n" \
45 " <property name=\"X11Model\" type=\"s\" access=\"read\"/>\n" \
46 " <property name=\"X11Variant\" type=\"s\" access=\"read\"/>\n" \
47 " <property name=\"X11Options\" type=\"s\" access=\"read\"/>\n" \
48 " <method name=\"SetLocale\">\n" \
49 " <arg name=\"locale\" type=\"as\" direction=\"in\"/>\n" \
50 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
51 " </method>\n" \
52 " <method name=\"SetVConsoleKeyboard\">\n" \
53 " <arg name=\"keymap\" type=\"s\" direction=\"in\"/>\n" \
54 " <arg name=\"keymap_toggle\" type=\"s\" direction=\"in\"/>\n" \
55 " <arg name=\"convert\" type=\"b\" direction=\"in\"/>\n" \
56 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
57 " </method>\n" \
58 " <method name=\"SetX11Keyboard\">\n" \
59 " <arg name=\"layout\" type=\"s\" direction=\"in\"/>\n" \
60 " <arg name=\"model\" type=\"s\" direction=\"in\"/>\n" \
61 " <arg name=\"variant\" type=\"s\" direction=\"in\"/>\n" \
62 " <arg name=\"options\" type=\"s\" direction=\"in\"/>\n" \
63 " <arg name=\"convert\" type=\"b\" direction=\"in\"/>\n" \
64 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
65 " </method>\n" \
66 " </interface>\n"
67
68 #define INTROSPECTION \
69 DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
70 "<node>\n" \
71 INTERFACE \
72 BUS_PROPERTIES_INTERFACE \
73 BUS_INTROSPECTABLE_INTERFACE \
74 BUS_PEER_INTERFACE \
75 "</node>\n"
76
77 #define INTERFACES_LIST \
78 BUS_GENERIC_INTERFACES_LIST \
79 "org.freedesktop.locale1\0"
80
81 const char locale_interface[] _introspect_("locale1") = INTERFACE;
82
83 enum {
84 /* We don't list LC_ALL here on purpose. People should be
85 * using LANG instead. */
86
87 PROP_LANG,
88 PROP_LANGUAGE,
89 PROP_LC_CTYPE,
90 PROP_LC_NUMERIC,
91 PROP_LC_TIME,
92 PROP_LC_COLLATE,
93 PROP_LC_MONETARY,
94 PROP_LC_MESSAGES,
95 PROP_LC_PAPER,
96 PROP_LC_NAME,
97 PROP_LC_ADDRESS,
98 PROP_LC_TELEPHONE,
99 PROP_LC_MEASUREMENT,
100 PROP_LC_IDENTIFICATION,
101 _PROP_MAX
102 };
103
104 static const char * const names[_PROP_MAX] = {
105 [PROP_LANG] = "LANG",
106 [PROP_LANGUAGE] = "LANGUAGE",
107 [PROP_LC_CTYPE] = "LC_CTYPE",
108 [PROP_LC_NUMERIC] = "LC_NUMERIC",
109 [PROP_LC_TIME] = "LC_TIME",
110 [PROP_LC_COLLATE] = "LC_COLLATE",
111 [PROP_LC_MONETARY] = "LC_MONETARY",
112 [PROP_LC_MESSAGES] = "LC_MESSAGES",
113 [PROP_LC_PAPER] = "LC_PAPER",
114 [PROP_LC_NAME] = "LC_NAME",
115 [PROP_LC_ADDRESS] = "LC_ADDRESS",
116 [PROP_LC_TELEPHONE] = "LC_TELEPHONE",
117 [PROP_LC_MEASUREMENT] = "LC_MEASUREMENT",
118 [PROP_LC_IDENTIFICATION] = "LC_IDENTIFICATION"
119 };
120
121 static char *data[_PROP_MAX] = {};
122
123 typedef struct State {
124 char *x11_layout, *x11_model, *x11_variant, *x11_options;
125 char *vc_keymap, *vc_keymap_toggle;
126 } State;
127
128 static State state;
129
130 static usec_t remain_until = 0;
131
132 static int free_and_set(char **s, const char *v) {
133 int r;
134 char *t;
135
136 assert(s);
137
138 r = strdup_or_null(isempty(v) ? NULL : v, &t);
139 if (r < 0)
140 return r;
141
142 free(*s);
143 *s = t;
144
145 return 0;
146 }
147
148 static void free_data_locale(void) {
149 int p;
150
151 for (p = 0; p < _PROP_MAX; p++) {
152 free(data[p]);
153 data[p] = NULL;
154 }
155 }
156
157 static void free_data_x11(void) {
158 free(state.x11_layout);
159 free(state.x11_model);
160 free(state.x11_variant);
161 free(state.x11_options);
162
163 state.x11_layout = state.x11_model = state.x11_variant = state.x11_options = NULL;
164 }
165
166 static void free_data_vconsole(void) {
167 free(state.vc_keymap);
168 free(state.vc_keymap_toggle);
169
170 state.vc_keymap = state.vc_keymap_toggle = NULL;
171 }
172
173 static void simplify(void) {
174 int p;
175
176 for (p = 1; p < _PROP_MAX; p++)
177 if (isempty(data[p]) || streq_ptr(data[PROP_LANG], data[p])) {
178 free(data[p]);
179 data[p] = NULL;
180 }
181 }
182
183 static int read_data_locale(void) {
184 int r;
185
186 free_data_locale();
187
188 r = parse_env_file("/etc/locale.conf", NEWLINE,
189 "LANG", &data[PROP_LANG],
190 "LANGUAGE", &data[PROP_LANGUAGE],
191 "LC_CTYPE", &data[PROP_LC_CTYPE],
192 "LC_NUMERIC", &data[PROP_LC_NUMERIC],
193 "LC_TIME", &data[PROP_LC_TIME],
194 "LC_COLLATE", &data[PROP_LC_COLLATE],
195 "LC_MONETARY", &data[PROP_LC_MONETARY],
196 "LC_MESSAGES", &data[PROP_LC_MESSAGES],
197 "LC_PAPER", &data[PROP_LC_PAPER],
198 "LC_NAME", &data[PROP_LC_NAME],
199 "LC_ADDRESS", &data[PROP_LC_ADDRESS],
200 "LC_TELEPHONE", &data[PROP_LC_TELEPHONE],
201 "LC_MEASUREMENT", &data[PROP_LC_MEASUREMENT],
202 "LC_IDENTIFICATION", &data[PROP_LC_IDENTIFICATION],
203 NULL);
204
205 if (r == -ENOENT) {
206 int p;
207
208 /* Fill in what we got passed from systemd. */
209
210 for (p = 0; p < _PROP_MAX; p++) {
211 char *e, *d;
212
213 assert(names[p]);
214
215 e = getenv(names[p]);
216 if (e) {
217 d = strdup(e);
218 if (!d)
219 return -ENOMEM;
220 } else
221 d = NULL;
222
223 free(data[p]);
224 data[p] = d;
225 }
226
227 r = 0;
228 }
229
230 simplify();
231 return r;
232 }
233
234 static void free_data(void) {
235 free_data_locale();
236 free_data_vconsole();
237 free_data_x11();
238 }
239
240 static int read_data_vconsole(void) {
241 int r;
242
243 free_data_vconsole();
244
245 r = parse_env_file("/etc/vconsole.conf", NEWLINE,
246 "KEYMAP", &state.vc_keymap,
247 "KEYMAP_TOGGLE", &state.vc_keymap_toggle,
248 NULL);
249
250 if (r < 0 && r != -ENOENT)
251 return r;
252
253 return 0;
254 }
255
256 static int read_data_x11(void) {
257 FILE *f;
258 char line[LINE_MAX];
259 bool in_section = false;
260
261 free_data_x11();
262
263 f = fopen("/etc/X11/xorg.conf.d/00-keyboard.conf", "re");
264 if (!f)
265 return errno == ENOENT ? 0 : -errno;
266
267 while (fgets(line, sizeof(line), f)) {
268 char *l;
269
270 char_array_0(line);
271 l = strstrip(line);
272
273 if (l[0] == 0 || l[0] == '#')
274 continue;
275
276 if (in_section && first_word(l, "Option")) {
277 char **a;
278
279 a = strv_split_quoted(l);
280 if (!a) {
281 fclose(f);
282 return -ENOMEM;
283 }
284
285 if (strv_length(a) == 3) {
286
287 if (streq(a[1], "XkbLayout")) {
288 free(state.x11_layout);
289 state.x11_layout = a[2];
290 a[2] = NULL;
291 } else if (streq(a[1], "XkbModel")) {
292 free(state.x11_model);
293 state.x11_model = a[2];
294 a[2] = NULL;
295 } else if (streq(a[1], "XkbVariant")) {
296 free(state.x11_variant);
297 state.x11_variant = a[2];
298 a[2] = NULL;
299 } else if (streq(a[1], "XkbOptions")) {
300 free(state.x11_options);
301 state.x11_options = a[2];
302 a[2] = NULL;
303 }
304 }
305
306 strv_free(a);
307
308 } else if (!in_section && first_word(l, "Section")) {
309 char **a;
310
311 a = strv_split_quoted(l);
312 if (!a) {
313 fclose(f);
314 return -ENOMEM;
315 }
316
317 if (strv_length(a) == 2 && streq(a[1], "InputClass"))
318 in_section = true;
319
320 strv_free(a);
321 } else if (in_section && first_word(l, "EndSection"))
322 in_section = false;
323 }
324
325 fclose(f);
326
327 return 0;
328 }
329
330 static int read_data(void) {
331 int r, q, p;
332
333 r = read_data_locale();
334 q = read_data_vconsole();
335 p = read_data_x11();
336
337 return r < 0 ? r : q < 0 ? q : p;
338 }
339
340 static int write_data_locale(void) {
341 int r, p;
342 char **l = NULL;
343
344 r = load_env_file("/etc/locale.conf", NULL, &l);
345 if (r < 0 && r != -ENOENT)
346 return r;
347
348 for (p = 0; p < _PROP_MAX; p++) {
349 char *t, **u;
350
351 assert(names[p]);
352
353 if (isempty(data[p])) {
354 l = strv_env_unset(l, names[p]);
355 continue;
356 }
357
358 if (asprintf(&t, "%s=%s", names[p], data[p]) < 0) {
359 strv_free(l);
360 return -ENOMEM;
361 }
362
363 u = strv_env_set(l, t);
364 free(t);
365 strv_free(l);
366
367 if (!u)
368 return -ENOMEM;
369
370 l = u;
371 }
372
373 if (strv_isempty(l)) {
374 strv_free(l);
375
376 if (unlink("/etc/locale.conf") < 0)
377 return errno == ENOENT ? 0 : -errno;
378
379 return 0;
380 }
381
382 r = write_env_file_label("/etc/locale.conf", l);
383 strv_free(l);
384
385 return r;
386 }
387
388 static void push_data(DBusConnection *bus) {
389 char **l_set = NULL, **l_unset = NULL, **t;
390 int c_set = 0, c_unset = 0, p;
391 DBusError error;
392 DBusMessage *m = NULL, *reply = NULL;
393 DBusMessageIter iter, sub;
394
395 dbus_error_init(&error);
396
397 assert(bus);
398
399 l_set = new0(char*, _PROP_MAX);
400 l_unset = new0(char*, _PROP_MAX);
401 if (!l_set || !l_unset) {
402 log_oom();
403 goto finish;
404 }
405
406 for (p = 0; p < _PROP_MAX; p++) {
407 assert(names[p]);
408
409 if (isempty(data[p]))
410 l_unset[c_set++] = (char*) names[p];
411 else {
412 char *s;
413
414 if (asprintf(&s, "%s=%s", names[p], data[p]) < 0) {
415 log_oom();
416 goto finish;
417 }
418
419 l_set[c_unset++] = s;
420 }
421 }
422
423 assert(c_set + c_unset == _PROP_MAX);
424 m = dbus_message_new_method_call("org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "UnsetAndSetEnvironment");
425 if (!m) {
426 log_error("Could not allocate message.");
427 goto finish;
428 }
429
430 dbus_message_iter_init_append(m, &iter);
431
432 if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "s", &sub)) {
433 log_oom();
434 goto finish;
435 }
436
437 STRV_FOREACH(t, l_unset)
438 if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, t)) {
439 log_oom();
440 goto finish;
441 }
442
443 if (!dbus_message_iter_close_container(&iter, &sub) ||
444 !dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "s", &sub)) {
445 log_oom();
446 goto finish;
447 }
448
449 STRV_FOREACH(t, l_set)
450 if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, t)) {
451 log_oom();
452 goto finish;
453 }
454
455 if (!dbus_message_iter_close_container(&iter, &sub)) {
456 log_oom();
457 goto finish;
458 }
459
460 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
461 if (!reply) {
462 log_error("Failed to set locale information: %s", bus_error_message(&error));
463 goto finish;
464 }
465
466 finish:
467 if (m)
468 dbus_message_unref(m);
469
470 if (reply)
471 dbus_message_unref(reply);
472
473 dbus_error_free(&error);
474
475 strv_free(l_set);
476 free(l_unset);
477 }
478
479 static int write_data_vconsole(void) {
480 int r;
481 char **l = NULL;
482
483 r = load_env_file("/etc/vconsole.conf", NULL, &l);
484 if (r < 0 && r != -ENOENT)
485 return r;
486
487 if (isempty(state.vc_keymap))
488 l = strv_env_unset(l, "KEYMAP");
489 else {
490 char *s, **u;
491
492 s = strappend("KEYMAP=", state.vc_keymap);
493 if (!s) {
494 strv_free(l);
495 return -ENOMEM;
496 }
497
498 u = strv_env_set(l, s);
499 free(s);
500 strv_free(l);
501
502 if (!u)
503 return -ENOMEM;
504
505 l = u;
506 }
507
508 if (isempty(state.vc_keymap_toggle))
509 l = strv_env_unset(l, "KEYMAP_TOGGLE");
510 else {
511 char *s, **u;
512
513 s = strappend("KEYMAP_TOGGLE=", state.vc_keymap_toggle);
514 if (!s) {
515 strv_free(l);
516 return -ENOMEM;
517 }
518
519 u = strv_env_set(l, s);
520 free(s);
521 strv_free(l);
522
523 if (!u)
524 return -ENOMEM;
525
526 l = u;
527 }
528
529 if (strv_isempty(l)) {
530 strv_free(l);
531
532 if (unlink("/etc/vconsole.conf") < 0)
533 return errno == ENOENT ? 0 : -errno;
534
535 return 0;
536 }
537
538 r = write_env_file_label("/etc/vconsole.conf", l);
539 strv_free(l);
540
541 return r;
542 }
543
544 static int write_data_x11(void) {
545 FILE *f;
546 char *temp_path;
547 int r;
548
549 if (isempty(state.x11_layout) &&
550 isempty(state.x11_model) &&
551 isempty(state.x11_variant) &&
552 isempty(state.x11_options)) {
553
554 if (unlink("/etc/X11/xorg.conf.d/00-keyboard.conf") < 0)
555 return errno == ENOENT ? 0 : -errno;
556
557 return 0;
558 }
559
560 mkdir_p_label("/etc/X11/xorg.conf.d", 0755);
561
562 r = fopen_temporary("/etc/X11/xorg.conf.d/00-keyboard.conf", &f, &temp_path);
563 if (r < 0)
564 return r;
565
566 fchmod(fileno(f), 0644);
567
568 fputs("# Read and parsed by systemd-localed. It's probably wise not to edit this file\n"
569 "# manually too freely.\n"
570 "Section \"InputClass\"\n"
571 " Identifier \"system-keyboard\"\n"
572 " MatchIsKeyboard \"on\"\n", f);
573
574 if (!isempty(state.x11_layout))
575 fprintf(f, " Option \"XkbLayout\" \"%s\"\n", state.x11_layout);
576
577 if (!isempty(state.x11_model))
578 fprintf(f, " Option \"XkbModel\" \"%s\"\n", state.x11_model);
579
580 if (!isempty(state.x11_variant))
581 fprintf(f, " Option \"XkbVariant\" \"%s\"\n", state.x11_variant);
582
583 if (!isempty(state.x11_options))
584 fprintf(f, " Option \"XkbOptions\" \"%s\"\n", state.x11_options);
585
586 fputs("EndSection\n", f);
587 fflush(f);
588
589 if (ferror(f) || rename(temp_path, "/etc/X11/xorg.conf.d/00-keyboard.conf") < 0) {
590 r = -errno;
591 unlink("/etc/X11/xorg.conf.d/00-keyboard.conf");
592 unlink(temp_path);
593 } else
594 r = 0;
595
596 fclose(f);
597 free(temp_path);
598
599 return r;
600 }
601
602 static int load_vconsole_keymap(DBusConnection *bus, DBusError *error) {
603 DBusMessage *m = NULL, *reply = NULL;
604 const char *name = "systemd-vconsole-setup.service", *mode = "replace";
605 int r;
606 DBusError _error;
607
608 assert(bus);
609
610 if (!error) {
611 dbus_error_init(&_error);
612 error = &_error;
613 }
614
615 m = dbus_message_new_method_call(
616 "org.freedesktop.systemd1",
617 "/org/freedesktop/systemd1",
618 "org.freedesktop.systemd1.Manager",
619 "RestartUnit");
620 if (!m) {
621 log_error("Could not allocate message.");
622 r = -ENOMEM;
623 goto finish;
624 }
625
626 if (!dbus_message_append_args(m,
627 DBUS_TYPE_STRING, &name,
628 DBUS_TYPE_STRING, &mode,
629 DBUS_TYPE_INVALID)) {
630 log_error("Could not append arguments to message.");
631 r = -ENOMEM;
632 goto finish;
633 }
634
635 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
636 if (!reply) {
637 log_error("Failed to issue method call: %s", bus_error_message(error));
638 r = -EIO;
639 goto finish;
640 }
641
642 r = 0;
643
644 finish:
645 if (m)
646 dbus_message_unref(m);
647
648 if (reply)
649 dbus_message_unref(reply);
650
651 if (error == &_error)
652 dbus_error_free(error);
653
654 return r;
655 }
656
657 static char *strnulldash(const char *s) {
658 return s == NULL || *s == 0 || (s[0] == '-' && s[1] == 0) ? NULL : (char*) s;
659 }
660
661 static int read_next_mapping(FILE *f, unsigned *n, char ***a) {
662 assert(f);
663 assert(n);
664 assert(a);
665
666 for (;;) {
667 char line[LINE_MAX];
668 char *l, **b;
669
670 errno = 0;
671 if (!fgets(line, sizeof(line), f)) {
672
673 if (ferror(f))
674 return errno ? -errno : -EIO;
675
676 return 0;
677 }
678
679 (*n) ++;
680
681 l = strstrip(line);
682 if (l[0] == 0 || l[0] == '#')
683 continue;
684
685 b = strv_split_quoted(l);
686 if (!b)
687 return -ENOMEM;
688
689 if (strv_length(b) < 5) {
690 log_error("Invalid line "SYSTEMD_KBD_MODEL_MAP":%u, ignoring.", *n);
691 strv_free(b);
692 continue;
693
694 }
695
696 *a = b;
697 return 1;
698 }
699 }
700
701 static int convert_vconsole_to_x11(DBusConnection *connection) {
702 bool modified = false;
703
704 assert(connection);
705
706 if (isempty(state.vc_keymap)) {
707
708 modified =
709 !isempty(state.x11_layout) ||
710 !isempty(state.x11_model) ||
711 !isempty(state.x11_variant) ||
712 !isempty(state.x11_options);
713
714 free_data_x11();
715 } else {
716 FILE *f;
717 unsigned n = 0;
718
719 f = fopen(SYSTEMD_KBD_MODEL_MAP, "re");
720 if (!f)
721 return -errno;
722
723 for (;;) {
724 char **a;
725 int r;
726
727 r = read_next_mapping(f, &n, &a);
728 if (r < 0) {
729 fclose(f);
730 return r;
731 }
732
733 if (r == 0)
734 break;
735
736 if (!streq(state.vc_keymap, a[0])) {
737 strv_free(a);
738 continue;
739 }
740
741 if (!streq_ptr(state.x11_layout, strnulldash(a[1])) ||
742 !streq_ptr(state.x11_model, strnulldash(a[2])) ||
743 !streq_ptr(state.x11_variant, strnulldash(a[3])) ||
744 !streq_ptr(state.x11_options, strnulldash(a[4]))) {
745
746 if (free_and_set(&state.x11_layout, strnulldash(a[1])) < 0 ||
747 free_and_set(&state.x11_model, strnulldash(a[2])) < 0 ||
748 free_and_set(&state.x11_variant, strnulldash(a[3])) < 0 ||
749 free_and_set(&state.x11_options, strnulldash(a[4])) < 0) {
750 strv_free(a);
751 fclose(f);
752 return -ENOMEM;
753 }
754
755 modified = true;
756 }
757
758 strv_free(a);
759 break;
760 }
761
762 fclose(f);
763 }
764
765 if (modified) {
766 dbus_bool_t b;
767 DBusMessage *changed;
768 int r;
769
770 r = write_data_x11();
771 if (r < 0)
772 log_error("Failed to set X11 keyboard layout: %s", strerror(-r));
773
774 changed = bus_properties_changed_new(
775 "/org/freedesktop/locale1",
776 "org.freedesktop.locale1",
777 "X11Layout\0"
778 "X11Model\0"
779 "X11Variant\0"
780 "X11Options\0");
781
782 if (!changed)
783 return -ENOMEM;
784
785 b = dbus_connection_send(connection, changed, NULL);
786 dbus_message_unref(changed);
787
788 if (!b)
789 return -ENOMEM;
790 }
791
792 return 0;
793 }
794
795 static int convert_x11_to_vconsole(DBusConnection *connection) {
796 bool modified = false;
797
798 assert(connection);
799
800 if (isempty(state.x11_layout)) {
801
802 modified =
803 !isempty(state.vc_keymap) ||
804 !isempty(state.vc_keymap_toggle);
805
806 free_data_x11();
807 } else {
808 FILE *f;
809 unsigned n = 0;
810 unsigned best_matching = 0;
811 char *new_keymap = NULL;
812
813 f = fopen(SYSTEMD_KBD_MODEL_MAP, "re");
814 if (!f)
815 return -errno;
816
817 for (;;) {
818 char **a;
819 unsigned matching = 0;
820 int r;
821
822 r = read_next_mapping(f, &n, &a);
823 if (r < 0) {
824 fclose(f);
825 return r;
826 }
827
828 if (r == 0)
829 break;
830
831 /* Determine how well matching this entry is */
832 if (streq_ptr(state.x11_layout, a[1]))
833 /* If we got an exact match, this is best */
834 matching = 10;
835 else {
836 size_t x;
837
838 x = strcspn(state.x11_layout, ",");
839
840 /* We have multiple X layouts, look
841 * for an entry that matches our key
842 * with the everything but the first
843 * layout stripped off. */
844 if (x > 0 &&
845 strlen(a[1]) == x &&
846 strneq(state.x11_layout, a[1], x))
847 matching = 5;
848 else {
849 size_t w;
850
851 /* If that didn't work, strip
852 * off the other layouts from
853 * the entry, too */
854
855 w = strcspn(a[1], ",");
856
857 if (x > 0 && x == w &&
858 memcmp(state.x11_layout, a[1], x) == 0)
859 matching = 1;
860 }
861 }
862
863 if (matching > 0 &&
864 streq_ptr(state.x11_model, a[2])) {
865 matching++;
866
867 if (streq_ptr(state.x11_variant, a[3])) {
868 matching++;
869
870 if (streq_ptr(state.x11_options, a[4]))
871 matching++;
872 }
873 }
874
875 /* The best matching entry so far, then let's
876 * save that */
877 if (matching > best_matching) {
878 best_matching = matching;
879
880 free(new_keymap);
881 new_keymap = strdup(a[0]);
882
883 if (!new_keymap) {
884 strv_free(a);
885 fclose(f);
886 return -ENOMEM;
887 }
888 }
889
890 strv_free(a);
891 }
892
893 fclose(f);
894
895 if (!streq_ptr(state.vc_keymap, new_keymap)) {
896 free(state.vc_keymap);
897 state.vc_keymap = new_keymap;
898
899 free(state.vc_keymap_toggle);
900 state.vc_keymap_toggle = NULL;
901
902 modified = true;
903 } else
904 free(new_keymap);
905 }
906
907 if (modified) {
908 dbus_bool_t b;
909 DBusMessage *changed;
910 int r;
911
912 r = write_data_vconsole();
913 if (r < 0)
914 log_error("Failed to set virtual console keymap: %s", strerror(-r));
915
916 changed = bus_properties_changed_new(
917 "/org/freedesktop/locale1",
918 "org.freedesktop.locale1",
919 "VConsoleKeymap\0"
920 "VConsoleKeymapToggle\0");
921
922 if (!changed)
923 return -ENOMEM;
924
925 b = dbus_connection_send(connection, changed, NULL);
926 dbus_message_unref(changed);
927
928 if (!b)
929 return -ENOMEM;
930
931 return load_vconsole_keymap(connection, NULL);
932 }
933
934 return 0;
935 }
936
937 static int append_locale(DBusMessageIter *i, const char *property, void *userdata) {
938 int r, c = 0, p;
939 char **l;
940
941 l = new0(char*, _PROP_MAX+1);
942 if (!l)
943 return -ENOMEM;
944
945 for (p = 0; p < _PROP_MAX; p++) {
946 char *t;
947
948 if (isempty(data[p]))
949 continue;
950
951 if (asprintf(&t, "%s=%s", names[p], data[p]) < 0) {
952 strv_free(l);
953 return -ENOMEM;
954 }
955
956 l[c++] = t;
957 }
958
959 r = bus_property_append_strv(i, property, (void*) l);
960 strv_free(l);
961
962 return r;
963 }
964
965 static const BusProperty bus_locale_properties[] = {
966 { "Locale", append_locale, "as", 0 },
967 { "X11Layout", bus_property_append_string, "s", offsetof(State, x11_layout), true },
968 { "X11Model", bus_property_append_string, "s", offsetof(State, x11_model), true },
969 { "X11Variant", bus_property_append_string, "s", offsetof(State, x11_variant), true },
970 { "X11Options", bus_property_append_string, "s", offsetof(State, x11_options), true },
971 { "VConsoleKeymap", bus_property_append_string, "s", offsetof(State, vc_keymap), true },
972 { "VConsoleKeymapToggle", bus_property_append_string, "s", offsetof(State, vc_keymap_toggle), true },
973 { NULL, }
974 };
975
976 static const BusBoundProperties bps[] = {
977 { "org.freedesktop.locale1", bus_locale_properties, &state },
978 { NULL, }
979 };
980
981 static DBusHandlerResult locale_message_handler(
982 DBusConnection *connection,
983 DBusMessage *message,
984 void *userdata) {
985
986 DBusMessage *reply = NULL, *changed = NULL;
987 DBusError error;
988 int r;
989
990 assert(connection);
991 assert(message);
992
993 dbus_error_init(&error);
994
995 if (dbus_message_is_method_call(message, "org.freedesktop.locale1", "SetLocale")) {
996 char **l = NULL, **i;
997 dbus_bool_t interactive;
998 DBusMessageIter iter;
999 bool modified = false;
1000 bool passed[_PROP_MAX] = {};
1001 int p;
1002
1003 if (!dbus_message_iter_init(message, &iter))
1004 return bus_send_error_reply(connection, message, NULL, -EINVAL);
1005
1006 r = bus_parse_strv_iter(&iter, &l);
1007 if (r < 0) {
1008 if (r == -ENOMEM)
1009 goto oom;
1010
1011 return bus_send_error_reply(connection, message, NULL, r);
1012 }
1013
1014 if (!dbus_message_iter_next(&iter) ||
1015 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_BOOLEAN) {
1016 strv_free(l);
1017 return bus_send_error_reply(connection, message, NULL, -EINVAL);
1018 }
1019
1020 dbus_message_iter_get_basic(&iter, &interactive);
1021
1022 /* Check whether a variable changed and if so valid */
1023 STRV_FOREACH(i, l) {
1024 bool valid = false;
1025
1026 for (p = 0; p < _PROP_MAX; p++) {
1027 size_t k;
1028
1029 k = strlen(names[p]);
1030 if (startswith(*i, names[p]) &&
1031 (*i)[k] == '=' &&
1032 string_is_safe((*i) + k + 1)) {
1033 valid = true;
1034 passed[p] = true;
1035
1036 if (!streq_ptr(*i + k + 1, data[p]))
1037 modified = true;
1038
1039 break;
1040 }
1041 }
1042
1043 if (!valid) {
1044 strv_free(l);
1045 return bus_send_error_reply(connection, message, NULL, -EINVAL);
1046 }
1047 }
1048
1049 /* Check whether a variable is unset */
1050 if (!modified) {
1051 for (p = 0; p < _PROP_MAX; p++)
1052 if (!isempty(data[p]) && !passed[p]) {
1053 modified = true;
1054 break;
1055 }
1056 }
1057
1058 if (modified) {
1059
1060 r = verify_polkit(connection, message, "org.freedesktop.locale1.set-locale", interactive, NULL, &error);
1061 if (r < 0) {
1062 strv_free(l);
1063 return bus_send_error_reply(connection, message, &error, r);
1064 }
1065
1066 STRV_FOREACH(i, l) {
1067 for (p = 0; p < _PROP_MAX; p++) {
1068 size_t k;
1069
1070 k = strlen(names[p]);
1071 if (startswith(*i, names[p]) && (*i)[k] == '=') {
1072 char *t;
1073
1074 t = strdup(*i + k + 1);
1075 if (!t) {
1076 strv_free(l);
1077 goto oom;
1078 }
1079
1080 free(data[p]);
1081 data[p] = t;
1082
1083 break;
1084 }
1085 }
1086 }
1087
1088 strv_free(l);
1089
1090 for (p = 0; p < _PROP_MAX; p++) {
1091 if (passed[p])
1092 continue;
1093
1094 free(data[p]);
1095 data[p] = NULL;
1096 }
1097
1098 simplify();
1099
1100 r = write_data_locale();
1101 if (r < 0) {
1102 log_error("Failed to set locale: %s", strerror(-r));
1103 return bus_send_error_reply(connection, message, NULL, r);
1104 }
1105
1106 push_data(connection);
1107
1108 log_info("Changed locale information.");
1109
1110 changed = bus_properties_changed_new(
1111 "/org/freedesktop/locale1",
1112 "org.freedesktop.locale1",
1113 "Locale\0");
1114 if (!changed)
1115 goto oom;
1116 } else
1117 strv_free(l);
1118
1119 } else if (dbus_message_is_method_call(message, "org.freedesktop.locale1", "SetVConsoleKeyboard")) {
1120
1121 const char *keymap, *keymap_toggle;
1122 dbus_bool_t convert, interactive;
1123
1124 if (!dbus_message_get_args(
1125 message,
1126 &error,
1127 DBUS_TYPE_STRING, &keymap,
1128 DBUS_TYPE_STRING, &keymap_toggle,
1129 DBUS_TYPE_BOOLEAN, &convert,
1130 DBUS_TYPE_BOOLEAN, &interactive,
1131 DBUS_TYPE_INVALID))
1132 return bus_send_error_reply(connection, message, &error, -EINVAL);
1133
1134 if (isempty(keymap))
1135 keymap = NULL;
1136
1137 if (isempty(keymap_toggle))
1138 keymap_toggle = NULL;
1139
1140 if (!streq_ptr(keymap, state.vc_keymap) ||
1141 !streq_ptr(keymap_toggle, state.vc_keymap_toggle)) {
1142
1143 if ((keymap && (!filename_is_safe(keymap) || !string_is_safe(keymap))) ||
1144 (keymap_toggle && (!filename_is_safe(keymap_toggle) || !string_is_safe(keymap_toggle))))
1145 return bus_send_error_reply(connection, message, NULL, -EINVAL);
1146
1147 r = verify_polkit(connection, message, "org.freedesktop.locale1.set-keyboard", interactive, NULL, &error);
1148 if (r < 0)
1149 return bus_send_error_reply(connection, message, &error, r);
1150
1151 if (free_and_set(&state.vc_keymap, keymap) < 0 ||
1152 free_and_set(&state.vc_keymap_toggle, keymap_toggle) < 0)
1153 goto oom;
1154
1155 r = write_data_vconsole();
1156 if (r < 0) {
1157 log_error("Failed to set virtual console keymap: %s", strerror(-r));
1158 return bus_send_error_reply(connection, message, NULL, r);
1159 }
1160
1161 log_info("Changed virtual console keymap to '%s'", strempty(state.vc_keymap));
1162
1163 r = load_vconsole_keymap(connection, NULL);
1164 if (r < 0)
1165 log_error("Failed to request keymap reload: %s", strerror(-r));
1166
1167 changed = bus_properties_changed_new(
1168 "/org/freedesktop/locale1",
1169 "org.freedesktop.locale1",
1170 "VConsoleKeymap\0"
1171 "VConsoleKeymapToggle\0");
1172 if (!changed)
1173 goto oom;
1174
1175 if (convert) {
1176 r = convert_vconsole_to_x11(connection);
1177
1178 if (r < 0)
1179 log_error("Failed to convert keymap data: %s", strerror(-r));
1180 }
1181 }
1182
1183 } else if (dbus_message_is_method_call(message, "org.freedesktop.locale1", "SetX11Keyboard")) {
1184
1185 const char *layout, *model, *variant, *options;
1186 dbus_bool_t convert, interactive;
1187
1188 if (!dbus_message_get_args(
1189 message,
1190 &error,
1191 DBUS_TYPE_STRING, &layout,
1192 DBUS_TYPE_STRING, &model,
1193 DBUS_TYPE_STRING, &variant,
1194 DBUS_TYPE_STRING, &options,
1195 DBUS_TYPE_BOOLEAN, &convert,
1196 DBUS_TYPE_BOOLEAN, &interactive,
1197 DBUS_TYPE_INVALID))
1198 return bus_send_error_reply(connection, message, &error, -EINVAL);
1199
1200 if (isempty(layout))
1201 layout = NULL;
1202
1203 if (isempty(model))
1204 model = NULL;
1205
1206 if (isempty(variant))
1207 variant = NULL;
1208
1209 if (isempty(options))
1210 options = NULL;
1211
1212 if (!streq_ptr(layout, state.x11_layout) ||
1213 !streq_ptr(model, state.x11_model) ||
1214 !streq_ptr(variant, state.x11_variant) ||
1215 !streq_ptr(options, state.x11_options)) {
1216
1217 if ((layout && !string_is_safe(layout)) ||
1218 (model && !string_is_safe(model)) ||
1219 (variant && !string_is_safe(variant)) ||
1220 (options && !string_is_safe(options)))
1221 return bus_send_error_reply(connection, message, NULL, -EINVAL);
1222
1223 r = verify_polkit(connection, message, "org.freedesktop.locale1.set-keyboard", interactive, NULL, &error);
1224 if (r < 0)
1225 return bus_send_error_reply(connection, message, &error, r);
1226
1227 if (free_and_set(&state.x11_layout, layout) < 0 ||
1228 free_and_set(&state.x11_model, model) < 0 ||
1229 free_and_set(&state.x11_variant, variant) < 0 ||
1230 free_and_set(&state.x11_options, options) < 0)
1231 goto oom;
1232
1233 r = write_data_x11();
1234 if (r < 0) {
1235 log_error("Failed to set X11 keyboard layout: %s", strerror(-r));
1236 return bus_send_error_reply(connection, message, NULL, r);
1237 }
1238
1239 log_info("Changed X11 keyboard layout to '%s'", strempty(state.x11_layout));
1240
1241 changed = bus_properties_changed_new(
1242 "/org/freedesktop/locale1",
1243 "org.freedesktop.locale1",
1244 "X11Layout\0"
1245 "X11Model\0"
1246 "X11Variant\0"
1247 "X11Options\0");
1248 if (!changed)
1249 goto oom;
1250
1251 if (convert) {
1252 r = convert_x11_to_vconsole(connection);
1253
1254 if (r < 0)
1255 log_error("Failed to convert keymap data: %s", strerror(-r));
1256 }
1257 }
1258 } else
1259 return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps);
1260
1261 if (!(reply = dbus_message_new_method_return(message)))
1262 goto oom;
1263
1264 if (!bus_maybe_send_reply(connection, message, reply))
1265 goto oom;
1266
1267 dbus_message_unref(reply);
1268 reply = NULL;
1269
1270 if (changed) {
1271
1272 if (!dbus_connection_send(connection, changed, NULL))
1273 goto oom;
1274
1275 dbus_message_unref(changed);
1276 }
1277
1278 return DBUS_HANDLER_RESULT_HANDLED;
1279
1280 oom:
1281 if (reply)
1282 dbus_message_unref(reply);
1283
1284 if (changed)
1285 dbus_message_unref(changed);
1286
1287 dbus_error_free(&error);
1288
1289 return DBUS_HANDLER_RESULT_NEED_MEMORY;
1290 }
1291
1292 static int connect_bus(DBusConnection **_bus) {
1293 static const DBusObjectPathVTable locale_vtable = {
1294 .message_function = locale_message_handler
1295 };
1296 DBusError error;
1297 DBusConnection *bus = NULL;
1298 int r;
1299
1300 assert(_bus);
1301
1302 dbus_error_init(&error);
1303
1304 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
1305 if (!bus) {
1306 log_error("Failed to get system D-Bus connection: %s", bus_error_message(&error));
1307 r = -ECONNREFUSED;
1308 goto fail;
1309 }
1310
1311 dbus_connection_set_exit_on_disconnect(bus, FALSE);
1312
1313 if (!dbus_connection_register_object_path(bus, "/org/freedesktop/locale1", &locale_vtable, NULL) ||
1314 !dbus_connection_add_filter(bus, bus_exit_idle_filter, &remain_until, NULL)) {
1315 r = log_oom();
1316 goto fail;
1317 }
1318
1319 r = dbus_bus_request_name(bus, "org.freedesktop.locale1", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error);
1320 if (dbus_error_is_set(&error)) {
1321 log_error("Failed to register name on bus: %s", bus_error_message(&error));
1322 r = -EEXIST;
1323 goto fail;
1324 }
1325
1326 if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
1327 log_error("Failed to acquire name.");
1328 r = -EEXIST;
1329 goto fail;
1330 }
1331
1332 if (_bus)
1333 *_bus = bus;
1334
1335 return 0;
1336
1337 fail:
1338 dbus_connection_close(bus);
1339 dbus_connection_unref(bus);
1340
1341 dbus_error_free(&error);
1342
1343 return r;
1344 }
1345
1346 int main(int argc, char *argv[]) {
1347 int r;
1348 DBusConnection *bus = NULL;
1349 bool exiting = false;
1350
1351 log_set_target(LOG_TARGET_AUTO);
1352 log_parse_environment();
1353 log_open();
1354 label_init("/etc");
1355 umask(0022);
1356
1357 if (argc == 2 && streq(argv[1], "--introspect")) {
1358 fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
1359 "<node>\n", stdout);
1360 fputs(locale_interface, stdout);
1361 fputs("</node>\n", stdout);
1362 return 0;
1363 }
1364
1365 if (argc != 1) {
1366 log_error("This program takes no arguments.");
1367 r = -EINVAL;
1368 goto finish;
1369 }
1370
1371 r = read_data();
1372 if (r < 0) {
1373 log_error("Failed to read locale data: %s", strerror(-r));
1374 goto finish;
1375 }
1376
1377 r = connect_bus(&bus);
1378 if (r < 0)
1379 goto finish;
1380
1381 remain_until = now(CLOCK_MONOTONIC) + DEFAULT_EXIT_USEC;
1382 for (;;) {
1383
1384 if (!dbus_connection_read_write_dispatch(bus, exiting ? -1 : (int) (DEFAULT_EXIT_USEC/USEC_PER_MSEC)))
1385 break;
1386
1387 if (!exiting && remain_until < now(CLOCK_MONOTONIC)) {
1388 exiting = true;
1389 bus_async_unregister_and_exit(bus, "org.freedesktop.locale1");
1390 }
1391 }
1392
1393 r = 0;
1394
1395 finish:
1396 free_data();
1397
1398 if (bus) {
1399 dbus_connection_flush(bus);
1400 dbus_connection_close(bus);
1401 dbus_connection_unref(bus);
1402 }
1403
1404 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
1405 }