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