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