chiark / gitweb /
bus: don't calculate kmsg message too large
[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", NULL, &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", NULL, &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                 /* Check whether a variable changed and if so valid */
1037                 STRV_FOREACH(i, l) {
1038                         bool valid = false;
1039
1040                         for (p = 0; p < _PROP_MAX; p++) {
1041                                 size_t k;
1042
1043                                 k = strlen(names[p]);
1044                                 if (startswith(*i, names[p]) &&
1045                                     (*i)[k] == '=' &&
1046                                     string_is_safe((*i) + k + 1)) {
1047                                         valid = true;
1048                                         passed[p] = true;
1049
1050                                         if (!streq_ptr(*i + k + 1, data[p]))
1051                                                 modified = true;
1052
1053                                         break;
1054                                 }
1055                         }
1056
1057                         if (!valid) {
1058                                 strv_free(l);
1059                                 return bus_send_error_reply(connection, message, NULL, -EINVAL);
1060                         }
1061                 }
1062
1063                 /* Check whether a variable is unset */
1064                 if (!modified)  {
1065                         for (p = 0; p < _PROP_MAX; p++)
1066                                 if (!isempty(data[p]) && !passed[p]) {
1067                                         modified = true;
1068                                         break;
1069                                 }
1070                 }
1071
1072                 if (modified) {
1073
1074                         r = verify_polkit(connection, message, "org.freedesktop.locale1.set-locale", interactive, NULL, &error);
1075                         if (r < 0) {
1076                                 strv_free(l);
1077                                 return bus_send_error_reply(connection, message, &error, r);
1078                         }
1079
1080                         STRV_FOREACH(i, l) {
1081                                 for (p = 0; p < _PROP_MAX; p++) {
1082                                         size_t k;
1083
1084                                         k = strlen(names[p]);
1085                                         if (startswith(*i, names[p]) && (*i)[k] == '=') {
1086                                                 char *t;
1087
1088                                                 t = strdup(*i + k + 1);
1089                                                 if (!t) {
1090                                                         strv_free(l);
1091                                                         goto oom;
1092                                                 }
1093
1094                                                 free(data[p]);
1095                                                 data[p] = t;
1096
1097                                                 break;
1098                                         }
1099                                 }
1100                         }
1101
1102                         strv_free(l);
1103
1104                         for (p = 0; p < _PROP_MAX; p++) {
1105                                 if (passed[p])
1106                                         continue;
1107
1108                                 free(data[p]);
1109                                 data[p] = NULL;
1110                         }
1111
1112                         simplify();
1113
1114                         r = write_data_locale();
1115                         if (r < 0) {
1116                                 log_error("Failed to set locale: %s", strerror(-r));
1117                                 return bus_send_error_reply(connection, message, NULL, r);
1118                         }
1119
1120                         push_data(connection);
1121
1122                         log_info("Changed locale information.");
1123
1124                         changed = bus_properties_changed_new(
1125                                         "/org/freedesktop/locale1",
1126                                         "org.freedesktop.locale1",
1127                                         "Locale\0");
1128                         if (!changed)
1129                                 goto oom;
1130                 } else
1131                         strv_free(l);
1132
1133         } else if (dbus_message_is_method_call(message, "org.freedesktop.locale1", "SetVConsoleKeyboard")) {
1134
1135                 const char *keymap, *keymap_toggle;
1136                 dbus_bool_t convert, interactive;
1137
1138                 if (!dbus_message_get_args(
1139                                     message,
1140                                     &error,
1141                                     DBUS_TYPE_STRING, &keymap,
1142                                     DBUS_TYPE_STRING, &keymap_toggle,
1143                                     DBUS_TYPE_BOOLEAN, &convert,
1144                                     DBUS_TYPE_BOOLEAN, &interactive,
1145                                     DBUS_TYPE_INVALID))
1146                         return bus_send_error_reply(connection, message, &error, -EINVAL);
1147
1148                 if (isempty(keymap))
1149                         keymap = NULL;
1150
1151                 if (isempty(keymap_toggle))
1152                         keymap_toggle = NULL;
1153
1154                 if (!streq_ptr(keymap, state.vc_keymap) ||
1155                     !streq_ptr(keymap_toggle, state.vc_keymap_toggle)) {
1156
1157                         if ((keymap && (!filename_is_safe(keymap) || !string_is_safe(keymap))) ||
1158                             (keymap_toggle && (!filename_is_safe(keymap_toggle) || !string_is_safe(keymap_toggle))))
1159                                 return bus_send_error_reply(connection, message, NULL, -EINVAL);
1160
1161                         r = verify_polkit(connection, message, "org.freedesktop.locale1.set-keyboard", interactive, NULL, &error);
1162                         if (r < 0)
1163                                 return bus_send_error_reply(connection, message, &error, r);
1164
1165                         if (free_and_set(&state.vc_keymap, keymap) < 0 ||
1166                             free_and_set(&state.vc_keymap_toggle, keymap_toggle) < 0)
1167                                 goto oom;
1168
1169                         r = write_data_vconsole();
1170                         if (r < 0) {
1171                                 log_error("Failed to set virtual console keymap: %s", strerror(-r));
1172                                 return bus_send_error_reply(connection, message, NULL, r);
1173                         }
1174
1175                         log_info("Changed virtual console keymap to '%s'", strempty(state.vc_keymap));
1176
1177                         r = load_vconsole_keymap(connection, NULL);
1178                         if (r < 0)
1179                                 log_error("Failed to request keymap reload: %s", strerror(-r));
1180
1181                         changed = bus_properties_changed_new(
1182                                         "/org/freedesktop/locale1",
1183                                         "org.freedesktop.locale1",
1184                                         "VConsoleKeymap\0"
1185                                         "VConsoleKeymapToggle\0");
1186                         if (!changed)
1187                                 goto oom;
1188
1189                         if (convert) {
1190                                 r = convert_vconsole_to_x11(connection);
1191
1192                                 if (r < 0)
1193                                         log_error("Failed to convert keymap data: %s", strerror(-r));
1194                         }
1195                 }
1196
1197         } else if (dbus_message_is_method_call(message, "org.freedesktop.locale1", "SetX11Keyboard")) {
1198
1199                 const char *layout, *model, *variant, *options;
1200                 dbus_bool_t convert, interactive;
1201
1202                 if (!dbus_message_get_args(
1203                                     message,
1204                                     &error,
1205                                     DBUS_TYPE_STRING, &layout,
1206                                     DBUS_TYPE_STRING, &model,
1207                                     DBUS_TYPE_STRING, &variant,
1208                                     DBUS_TYPE_STRING, &options,
1209                                     DBUS_TYPE_BOOLEAN, &convert,
1210                                     DBUS_TYPE_BOOLEAN, &interactive,
1211                                     DBUS_TYPE_INVALID))
1212                         return bus_send_error_reply(connection, message, &error, -EINVAL);
1213
1214                 if (isempty(layout))
1215                         layout = NULL;
1216
1217                 if (isempty(model))
1218                         model = NULL;
1219
1220                 if (isempty(variant))
1221                         variant = NULL;
1222
1223                 if (isempty(options))
1224                         options = NULL;
1225
1226                 if (!streq_ptr(layout, state.x11_layout) ||
1227                     !streq_ptr(model, state.x11_model) ||
1228                     !streq_ptr(variant, state.x11_variant) ||
1229                     !streq_ptr(options, state.x11_options)) {
1230
1231                         if ((layout && !string_is_safe(layout)) ||
1232                             (model && !string_is_safe(model)) ||
1233                             (variant && !string_is_safe(variant)) ||
1234                             (options && !string_is_safe(options)))
1235                                 return bus_send_error_reply(connection, message, NULL, -EINVAL);
1236
1237                         r = verify_polkit(connection, message, "org.freedesktop.locale1.set-keyboard", interactive, NULL, &error);
1238                         if (r < 0)
1239                                 return bus_send_error_reply(connection, message, &error, r);
1240
1241                         if (free_and_set(&state.x11_layout, layout) < 0 ||
1242                             free_and_set(&state.x11_model, model) < 0 ||
1243                             free_and_set(&state.x11_variant, variant) < 0 ||
1244                             free_and_set(&state.x11_options, options) < 0)
1245                                 goto oom;
1246
1247                         r = write_data_x11();
1248                         if (r < 0) {
1249                                 log_error("Failed to set X11 keyboard layout: %s", strerror(-r));
1250                                 return bus_send_error_reply(connection, message, NULL, r);
1251                         }
1252
1253                         log_info("Changed X11 keyboard layout to '%s'", strempty(state.x11_layout));
1254
1255                         changed = bus_properties_changed_new(
1256                                         "/org/freedesktop/locale1",
1257                                         "org.freedesktop.locale1",
1258                                         "X11Layout\0"
1259                                         "X11Model\0"
1260                                         "X11Variant\0"
1261                                         "X11Options\0");
1262                         if (!changed)
1263                                 goto oom;
1264
1265                         if (convert) {
1266                                 r = convert_x11_to_vconsole(connection);
1267
1268                                 if (r < 0)
1269                                         log_error("Failed to convert keymap data: %s", strerror(-r));
1270                         }
1271                 }
1272         } else
1273                 return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps);
1274
1275         if (!(reply = dbus_message_new_method_return(message)))
1276                 goto oom;
1277
1278         if (!bus_maybe_send_reply(connection, message, reply))
1279                 goto oom;
1280
1281         dbus_message_unref(reply);
1282         reply = NULL;
1283
1284         if (changed) {
1285
1286                 if (!dbus_connection_send(connection, changed, NULL))
1287                         goto oom;
1288
1289                 dbus_message_unref(changed);
1290         }
1291
1292         return DBUS_HANDLER_RESULT_HANDLED;
1293
1294 oom:
1295         if (reply)
1296                 dbus_message_unref(reply);
1297
1298         if (changed)
1299                 dbus_message_unref(changed);
1300
1301         dbus_error_free(&error);
1302
1303         return DBUS_HANDLER_RESULT_NEED_MEMORY;
1304 }
1305
1306 static int connect_bus(DBusConnection **_bus) {
1307         static const DBusObjectPathVTable locale_vtable = {
1308                 .message_function = locale_message_handler
1309         };
1310         DBusError error;
1311         DBusConnection *bus = NULL;
1312         int r;
1313
1314         assert(_bus);
1315
1316         dbus_error_init(&error);
1317
1318         bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
1319         if (!bus) {
1320                 log_error("Failed to get system D-Bus connection: %s", bus_error_message(&error));
1321                 r = -ECONNREFUSED;
1322                 goto fail;
1323         }
1324
1325         dbus_connection_set_exit_on_disconnect(bus, FALSE);
1326
1327         if (!dbus_connection_register_object_path(bus, "/org/freedesktop/locale1", &locale_vtable, NULL) ||
1328             !dbus_connection_add_filter(bus, bus_exit_idle_filter, &remain_until, NULL)) {
1329                 r = log_oom();
1330                 goto fail;
1331         }
1332
1333         r = dbus_bus_request_name(bus, "org.freedesktop.locale1", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error);
1334         if (dbus_error_is_set(&error)) {
1335                 log_error("Failed to register name on bus: %s", bus_error_message(&error));
1336                 r = -EEXIST;
1337                 goto fail;
1338         }
1339
1340         if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
1341                 log_error("Failed to acquire name.");
1342                 r = -EEXIST;
1343                 goto fail;
1344         }
1345
1346         if (_bus)
1347                 *_bus = bus;
1348
1349         return 0;
1350
1351 fail:
1352         dbus_connection_close(bus);
1353         dbus_connection_unref(bus);
1354
1355         dbus_error_free(&error);
1356
1357         return r;
1358 }
1359
1360 int main(int argc, char *argv[]) {
1361         int r;
1362         DBusConnection *bus = NULL;
1363         bool exiting = false;
1364
1365         log_set_target(LOG_TARGET_AUTO);
1366         log_parse_environment();
1367         log_open();
1368         label_init("/etc");
1369         umask(0022);
1370
1371         if (argc == 2 && streq(argv[1], "--introspect")) {
1372                 fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
1373                       "<node>\n", stdout);
1374                 fputs(locale_interface, stdout);
1375                 fputs("</node>\n", stdout);
1376                 return 0;
1377         }
1378
1379         if (argc != 1) {
1380                 log_error("This program takes no arguments.");
1381                 r = -EINVAL;
1382                 goto finish;
1383         }
1384
1385         r = read_data();
1386         if (r < 0) {
1387                 log_error("Failed to read locale data: %s", strerror(-r));
1388                 goto finish;
1389         }
1390
1391         r = connect_bus(&bus);
1392         if (r < 0)
1393                 goto finish;
1394
1395         remain_until = now(CLOCK_MONOTONIC) + DEFAULT_EXIT_USEC;
1396         for (;;) {
1397
1398                 if (!dbus_connection_read_write_dispatch(bus, exiting ? -1 : (int) (DEFAULT_EXIT_USEC/USEC_PER_MSEC)))
1399                         break;
1400
1401                 if (!exiting && remain_until < now(CLOCK_MONOTONIC)) {
1402                         exiting = true;
1403                         bus_async_unregister_and_exit(bus, "org.freedesktop.locale1");
1404                 }
1405         }
1406
1407         r = 0;
1408
1409 finish:
1410         free_data();
1411
1412         if (bus) {
1413                 dbus_connection_flush(bus);
1414                 dbus_connection_close(bus);
1415                 dbus_connection_unref(bus);
1416         }
1417
1418         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
1419 }