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