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