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