chiark / gitweb /
dbus: expose cgroup properties in introspection everywhere
[elogind.git] / src / locale / localed.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2011 Lennart Poettering
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <dbus/dbus.h>
23
24 #include <errno.h>
25 #include <string.h>
26 #include <unistd.h>
27
28 #include "util.h"
29 #include "mkdir.h"
30 #include "strv.h"
31 #include "dbus-common.h"
32 #include "polkit.h"
33 #include "def.h"
34 #include "env-util.h"
35 #include "fileio.h"
36 #include "fileio-label.h"
37 #include "label.h"
38
39 #define INTERFACE                                                       \
40         " <interface name=\"org.freedesktop.locale1\">\n"               \
41         "  <property name=\"Locale\" type=\"as\" access=\"read\"/>\n"   \
42         "  <property name=\"VConsoleKeymap\" type=\"s\" access=\"read\"/>\n" \
43         "  <property name=\"VConsoleKeymapToggle\" type=\"s\" access=\"read\"/>\n" \
44         "  <property name=\"X11Layout\" type=\"s\" access=\"read\"/>\n" \
45         "  <property name=\"X11Model\" type=\"s\" access=\"read\"/>\n"  \
46         "  <property name=\"X11Variant\" type=\"s\" access=\"read\"/>\n" \
47         "  <property name=\"X11Options\" type=\"s\" access=\"read\"/>\n" \
48         "  <method name=\"SetLocale\">\n"                               \
49         "   <arg name=\"locale\" type=\"as\" direction=\"in\"/>\n"      \
50         "   <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
51         "  </method>\n"                                                 \
52         "  <method name=\"SetVConsoleKeyboard\">\n"                      \
53         "   <arg name=\"keymap\" type=\"s\" direction=\"in\"/>\n"       \
54         "   <arg name=\"keymap_toggle\" type=\"s\" direction=\"in\"/>\n" \
55         "   <arg name=\"convert\" type=\"b\" direction=\"in\"/>\n"      \
56         "   <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
57         "  </method>\n"                                                 \
58         "  <method name=\"SetX11Keyboard\">\n"                          \
59         "   <arg name=\"layout\" type=\"s\" direction=\"in\"/>\n"       \
60         "   <arg name=\"model\" type=\"s\" direction=\"in\"/>\n"        \
61         "   <arg name=\"variant\" type=\"s\" direction=\"in\"/>\n"      \
62         "   <arg name=\"options\" type=\"s\" direction=\"in\"/>\n"      \
63         "   <arg name=\"convert\" type=\"b\" direction=\"in\"/>\n"      \
64         "   <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
65         "  </method>\n"                                                 \
66         " </interface>\n"
67
68 #define INTROSPECTION                                                   \
69         DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                       \
70         "<node>\n"                                                      \
71         INTERFACE                                                       \
72         BUS_PROPERTIES_INTERFACE                                        \
73         BUS_INTROSPECTABLE_INTERFACE                                    \
74         BUS_PEER_INTERFACE                                              \
75         "</node>\n"
76
77 #define INTERFACES_LIST                         \
78         BUS_GENERIC_INTERFACES_LIST             \
79         "org.freedesktop.locale1\0"
80
81 const char locale_interface[] _introspect_("locale1") = INTERFACE;
82
83 enum {
84         /* We don't list LC_ALL here on purpose. People should be
85          * using LANG instead. */
86
87         PROP_LANG,
88         PROP_LANGUAGE,
89         PROP_LC_CTYPE,
90         PROP_LC_NUMERIC,
91         PROP_LC_TIME,
92         PROP_LC_COLLATE,
93         PROP_LC_MONETARY,
94         PROP_LC_MESSAGES,
95         PROP_LC_PAPER,
96         PROP_LC_NAME,
97         PROP_LC_ADDRESS,
98         PROP_LC_TELEPHONE,
99         PROP_LC_MEASUREMENT,
100         PROP_LC_IDENTIFICATION,
101         _PROP_MAX
102 };
103
104 static const char * const names[_PROP_MAX] = {
105         [PROP_LANG] = "LANG",
106         [PROP_LANGUAGE] = "LANGUAGE",
107         [PROP_LC_CTYPE] = "LC_CTYPE",
108         [PROP_LC_NUMERIC] = "LC_NUMERIC",
109         [PROP_LC_TIME] = "LC_TIME",
110         [PROP_LC_COLLATE] = "LC_COLLATE",
111         [PROP_LC_MONETARY] = "LC_MONETARY",
112         [PROP_LC_MESSAGES] = "LC_MESSAGES",
113         [PROP_LC_PAPER] = "LC_PAPER",
114         [PROP_LC_NAME] = "LC_NAME",
115         [PROP_LC_ADDRESS] = "LC_ADDRESS",
116         [PROP_LC_TELEPHONE] = "LC_TELEPHONE",
117         [PROP_LC_MEASUREMENT] = "LC_MEASUREMENT",
118         [PROP_LC_IDENTIFICATION] = "LC_IDENTIFICATION"
119 };
120
121 static char *data[_PROP_MAX] = {};
122
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                 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                         char **a;
819                         unsigned matching = 0;
820                         int r;
821
822                         r = read_next_mapping(f, &n, &a);
823                         if (r < 0) {
824                                 fclose(f);
825                                 return r;
826                         }
827
828                         if (r == 0)
829                                 break;
830
831                         /* Determine how well matching this entry is */
832                         if (streq_ptr(state.x11_layout, a[1]))
833                                 /* If we got an exact match, this is best */
834                                 matching = 10;
835                         else {
836                                 size_t x;
837
838                                 x = strcspn(state.x11_layout, ",");
839
840                                 /* We have multiple X layouts, look
841                                  * for an entry that matches our key
842                                  * with the everything but the first
843                                  * layout stripped off. */
844                                 if (x > 0 &&
845                                     strlen(a[1]) == x &&
846                                     strneq(state.x11_layout, a[1], x))
847                                         matching = 5;
848                                 else  {
849                                         size_t w;
850
851                                         /* If that didn't work, strip
852                                          * off the other layouts from
853                                          * the entry, too */
854
855                                         w = strcspn(a[1], ",");
856
857                                         if (x > 0 && x == w &&
858                                             memcmp(state.x11_layout, a[1], x) == 0)
859                                                 matching = 1;
860                                 }
861                         }
862
863                         if (matching > 0 &&
864                             streq_ptr(state.x11_model, a[2])) {
865                                 matching++;
866
867                                 if (streq_ptr(state.x11_variant, a[3])) {
868                                         matching++;
869
870                                         if (streq_ptr(state.x11_options, a[4]))
871                                                 matching++;
872                                 }
873                         }
874
875                         /* The best matching entry so far, then let's
876                          * save that */
877                         if (matching > best_matching) {
878                                 best_matching = matching;
879
880                                 free(new_keymap);
881                                 new_keymap = strdup(a[0]);
882
883                                 if (!new_keymap) {
884                                         strv_free(a);
885                                         fclose(f);
886                                         return -ENOMEM;
887                                 }
888                         }
889
890                         strv_free(a);
891                 }
892
893                 fclose(f);
894
895                 if (!streq_ptr(state.vc_keymap, new_keymap)) {
896                         free(state.vc_keymap);
897                         state.vc_keymap = new_keymap;
898
899                         free(state.vc_keymap_toggle);
900                         state.vc_keymap_toggle = NULL;
901
902                         modified = true;
903                 } else
904                         free(new_keymap);
905         }
906
907         if (modified) {
908                 dbus_bool_t b;
909                 DBusMessage *changed;
910                 int r;
911
912                 r = write_data_vconsole();
913                 if (r < 0)
914                         log_error("Failed to set virtual console keymap: %s", strerror(-r));
915
916                 changed = bus_properties_changed_new(
917                                 "/org/freedesktop/locale1",
918                                 "org.freedesktop.locale1",
919                                 "VConsoleKeymap\0"
920                                 "VConsoleKeymapToggle\0");
921
922                 if (!changed)
923                         return -ENOMEM;
924
925                 b = dbus_connection_send(connection, changed, NULL);
926                 dbus_message_unref(changed);
927
928                 if (!b)
929                         return -ENOMEM;
930
931                 return load_vconsole_keymap(connection, NULL);
932         }
933
934         return 0;
935 }
936
937 static int append_locale(DBusMessageIter *i, const char *property, void *userdata) {
938         int r, c = 0, p;
939         char **l;
940
941         l = new0(char*, _PROP_MAX+1);
942         if (!l)
943                 return -ENOMEM;
944
945         for (p = 0; p < _PROP_MAX; p++) {
946                 char *t;
947
948                 if (isempty(data[p]))
949                         continue;
950
951                 if (asprintf(&t, "%s=%s", names[p], data[p]) < 0) {
952                         strv_free(l);
953                         return -ENOMEM;
954                 }
955
956                 l[c++] = t;
957         }
958
959         r = bus_property_append_strv(i, property, (void*) l);
960         strv_free(l);
961
962         return r;
963 }
964
965 static const BusProperty bus_locale_properties[] = {
966         { "Locale",               append_locale,             "as", 0 },
967         { "X11Layout",            bus_property_append_string, "s", offsetof(State, x11_layout),       true },
968         { "X11Model",             bus_property_append_string, "s", offsetof(State, x11_model),        true },
969         { "X11Variant",           bus_property_append_string, "s", offsetof(State, x11_variant),      true },
970         { "X11Options",           bus_property_append_string, "s", offsetof(State, x11_options),      true },
971         { "VConsoleKeymap",       bus_property_append_string, "s", offsetof(State, vc_keymap),        true },
972         { "VConsoleKeymapToggle", bus_property_append_string, "s", offsetof(State, vc_keymap_toggle), true },
973         { NULL, }
974 };
975
976 static const BusBoundProperties bps[] = {
977         { "org.freedesktop.locale1", bus_locale_properties, &state },
978         { NULL, }
979 };
980
981 static DBusHandlerResult locale_message_handler(
982                 DBusConnection *connection,
983                 DBusMessage *message,
984                 void *userdata) {
985
986         DBusMessage *reply = NULL, *changed = NULL;
987         DBusError error;
988         int r;
989
990         assert(connection);
991         assert(message);
992
993         dbus_error_init(&error);
994
995         if (dbus_message_is_method_call(message, "org.freedesktop.locale1", "SetLocale")) {
996                 char **l = NULL, **i;
997                 dbus_bool_t interactive;
998                 DBusMessageIter iter;
999                 bool modified = false;
1000                 bool passed[_PROP_MAX] = {};
1001                 int p;
1002
1003                 if (!dbus_message_iter_init(message, &iter))
1004                         return bus_send_error_reply(connection, message, NULL, -EINVAL);
1005
1006                 r = bus_parse_strv_iter(&iter, &l);
1007                 if (r < 0) {
1008                         if (r == -ENOMEM)
1009                                 goto oom;
1010
1011                         return bus_send_error_reply(connection, message, NULL, r);
1012                 }
1013
1014                 if (!dbus_message_iter_next(&iter) ||
1015                     dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_BOOLEAN)  {
1016                         strv_free(l);
1017                         return bus_send_error_reply(connection, message, NULL, -EINVAL);
1018                 }
1019
1020                 dbus_message_iter_get_basic(&iter, &interactive);
1021
1022                 /* Check whether a variable changed and if so valid */
1023                 STRV_FOREACH(i, l) {
1024                         bool valid = false;
1025
1026                         for (p = 0; p < _PROP_MAX; p++) {
1027                                 size_t k;
1028
1029                                 k = strlen(names[p]);
1030                                 if (startswith(*i, names[p]) &&
1031                                     (*i)[k] == '=' &&
1032                                     string_is_safe((*i) + k + 1)) {
1033                                         valid = true;
1034                                         passed[p] = true;
1035
1036                                         if (!streq_ptr(*i + k + 1, data[p]))
1037                                                 modified = true;
1038
1039                                         break;
1040                                 }
1041                         }
1042
1043                         if (!valid) {
1044                                 strv_free(l);
1045                                 return bus_send_error_reply(connection, message, NULL, -EINVAL);
1046                         }
1047                 }
1048
1049                 /* Check whether a variable is unset */
1050                 if (!modified)  {
1051                         for (p = 0; p < _PROP_MAX; p++)
1052                                 if (!isempty(data[p]) && !passed[p]) {
1053                                         modified = true;
1054                                         break;
1055                                 }
1056                 }
1057
1058                 if (modified) {
1059
1060                         r = verify_polkit(connection, message, "org.freedesktop.locale1.set-locale", interactive, NULL, &error);
1061                         if (r < 0) {
1062                                 strv_free(l);
1063                                 return bus_send_error_reply(connection, message, &error, r);
1064                         }
1065
1066                         STRV_FOREACH(i, l) {
1067                                 for (p = 0; p < _PROP_MAX; p++) {
1068                                         size_t k;
1069
1070                                         k = strlen(names[p]);
1071                                         if (startswith(*i, names[p]) && (*i)[k] == '=') {
1072                                                 char *t;
1073
1074                                                 t = strdup(*i + k + 1);
1075                                                 if (!t) {
1076                                                         strv_free(l);
1077                                                         goto oom;
1078                                                 }
1079
1080                                                 free(data[p]);
1081                                                 data[p] = t;
1082
1083                                                 break;
1084                                         }
1085                                 }
1086                         }
1087
1088                         strv_free(l);
1089
1090                         for (p = 0; p < _PROP_MAX; p++) {
1091                                 if (passed[p])
1092                                         continue;
1093
1094                                 free(data[p]);
1095                                 data[p] = NULL;
1096                         }
1097
1098                         simplify();
1099
1100                         r = write_data_locale();
1101                         if (r < 0) {
1102                                 log_error("Failed to set locale: %s", strerror(-r));
1103                                 return bus_send_error_reply(connection, message, NULL, r);
1104                         }
1105
1106                         push_data(connection);
1107
1108                         log_info("Changed locale information.");
1109
1110                         changed = bus_properties_changed_new(
1111                                         "/org/freedesktop/locale1",
1112                                         "org.freedesktop.locale1",
1113                                         "Locale\0");
1114                         if (!changed)
1115                                 goto oom;
1116                 } else
1117                         strv_free(l);
1118
1119         } else if (dbus_message_is_method_call(message, "org.freedesktop.locale1", "SetVConsoleKeyboard")) {
1120
1121                 const char *keymap, *keymap_toggle;
1122                 dbus_bool_t convert, interactive;
1123
1124                 if (!dbus_message_get_args(
1125                                     message,
1126                                     &error,
1127                                     DBUS_TYPE_STRING, &keymap,
1128                                     DBUS_TYPE_STRING, &keymap_toggle,
1129                                     DBUS_TYPE_BOOLEAN, &convert,
1130                                     DBUS_TYPE_BOOLEAN, &interactive,
1131                                     DBUS_TYPE_INVALID))
1132                         return bus_send_error_reply(connection, message, &error, -EINVAL);
1133
1134                 if (isempty(keymap))
1135                         keymap = NULL;
1136
1137                 if (isempty(keymap_toggle))
1138                         keymap_toggle = NULL;
1139
1140                 if (!streq_ptr(keymap, state.vc_keymap) ||
1141                     !streq_ptr(keymap_toggle, state.vc_keymap_toggle)) {
1142
1143                         if ((keymap && (!filename_is_safe(keymap) || !string_is_safe(keymap))) ||
1144                             (keymap_toggle && (!filename_is_safe(keymap_toggle) || !string_is_safe(keymap_toggle))))
1145                                 return bus_send_error_reply(connection, message, NULL, -EINVAL);
1146
1147                         r = verify_polkit(connection, message, "org.freedesktop.locale1.set-keyboard", interactive, NULL, &error);
1148                         if (r < 0)
1149                                 return bus_send_error_reply(connection, message, &error, r);
1150
1151                         if (free_and_set(&state.vc_keymap, keymap) < 0 ||
1152                             free_and_set(&state.vc_keymap_toggle, keymap_toggle) < 0)
1153                                 goto oom;
1154
1155                         r = write_data_vconsole();
1156                         if (r < 0) {
1157                                 log_error("Failed to set virtual console keymap: %s", strerror(-r));
1158                                 return bus_send_error_reply(connection, message, NULL, r);
1159                         }
1160
1161                         log_info("Changed virtual console keymap to '%s'", strempty(state.vc_keymap));
1162
1163                         r = load_vconsole_keymap(connection, NULL);
1164                         if (r < 0)
1165                                 log_error("Failed to request keymap reload: %s", strerror(-r));
1166
1167                         changed = bus_properties_changed_new(
1168                                         "/org/freedesktop/locale1",
1169                                         "org.freedesktop.locale1",
1170                                         "VConsoleKeymap\0"
1171                                         "VConsoleKeymapToggle\0");
1172                         if (!changed)
1173                                 goto oom;
1174
1175                         if (convert) {
1176                                 r = convert_vconsole_to_x11(connection);
1177
1178                                 if (r < 0)
1179                                         log_error("Failed to convert keymap data: %s", strerror(-r));
1180                         }
1181                 }
1182
1183         } else if (dbus_message_is_method_call(message, "org.freedesktop.locale1", "SetX11Keyboard")) {
1184
1185                 const char *layout, *model, *variant, *options;
1186                 dbus_bool_t convert, interactive;
1187
1188                 if (!dbus_message_get_args(
1189                                     message,
1190                                     &error,
1191                                     DBUS_TYPE_STRING, &layout,
1192                                     DBUS_TYPE_STRING, &model,
1193                                     DBUS_TYPE_STRING, &variant,
1194                                     DBUS_TYPE_STRING, &options,
1195                                     DBUS_TYPE_BOOLEAN, &convert,
1196                                     DBUS_TYPE_BOOLEAN, &interactive,
1197                                     DBUS_TYPE_INVALID))
1198                         return bus_send_error_reply(connection, message, &error, -EINVAL);
1199
1200                 if (isempty(layout))
1201                         layout = NULL;
1202
1203                 if (isempty(model))
1204                         model = NULL;
1205
1206                 if (isempty(variant))
1207                         variant = NULL;
1208
1209                 if (isempty(options))
1210                         options = NULL;
1211
1212                 if (!streq_ptr(layout, state.x11_layout) ||
1213                     !streq_ptr(model, state.x11_model) ||
1214                     !streq_ptr(variant, state.x11_variant) ||
1215                     !streq_ptr(options, state.x11_options)) {
1216
1217                         if ((layout && !string_is_safe(layout)) ||
1218                             (model && !string_is_safe(model)) ||
1219                             (variant && !string_is_safe(variant)) ||
1220                             (options && !string_is_safe(options)))
1221                                 return bus_send_error_reply(connection, message, NULL, -EINVAL);
1222
1223                         r = verify_polkit(connection, message, "org.freedesktop.locale1.set-keyboard", interactive, NULL, &error);
1224                         if (r < 0)
1225                                 return bus_send_error_reply(connection, message, &error, r);
1226
1227                         if (free_and_set(&state.x11_layout, layout) < 0 ||
1228                             free_and_set(&state.x11_model, model) < 0 ||
1229                             free_and_set(&state.x11_variant, variant) < 0 ||
1230                             free_and_set(&state.x11_options, options) < 0)
1231                                 goto oom;
1232
1233                         r = write_data_x11();
1234                         if (r < 0) {
1235                                 log_error("Failed to set X11 keyboard layout: %s", strerror(-r));
1236                                 return bus_send_error_reply(connection, message, NULL, r);
1237                         }
1238
1239                         log_info("Changed X11 keyboard layout to '%s'", strempty(state.x11_layout));
1240
1241                         changed = bus_properties_changed_new(
1242                                         "/org/freedesktop/locale1",
1243                                         "org.freedesktop.locale1",
1244                                         "X11Layout\0"
1245                                         "X11Model\0"
1246                                         "X11Variant\0"
1247                                         "X11Options\0");
1248                         if (!changed)
1249                                 goto oom;
1250
1251                         if (convert) {
1252                                 r = convert_x11_to_vconsole(connection);
1253
1254                                 if (r < 0)
1255                                         log_error("Failed to convert keymap data: %s", strerror(-r));
1256                         }
1257                 }
1258         } else
1259                 return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps);
1260
1261         if (!(reply = dbus_message_new_method_return(message)))
1262                 goto oom;
1263
1264         if (!bus_maybe_send_reply(connection, message, reply))
1265                 goto oom;
1266
1267         dbus_message_unref(reply);
1268         reply = NULL;
1269
1270         if (changed) {
1271
1272                 if (!dbus_connection_send(connection, changed, NULL))
1273                         goto oom;
1274
1275                 dbus_message_unref(changed);
1276         }
1277
1278         return DBUS_HANDLER_RESULT_HANDLED;
1279
1280 oom:
1281         if (reply)
1282                 dbus_message_unref(reply);
1283
1284         if (changed)
1285                 dbus_message_unref(changed);
1286
1287         dbus_error_free(&error);
1288
1289         return DBUS_HANDLER_RESULT_NEED_MEMORY;
1290 }
1291
1292 static int connect_bus(DBusConnection **_bus) {
1293         static const DBusObjectPathVTable locale_vtable = {
1294                 .message_function = locale_message_handler
1295         };
1296         DBusError error;
1297         DBusConnection *bus = NULL;
1298         int r;
1299
1300         assert(_bus);
1301
1302         dbus_error_init(&error);
1303
1304         bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
1305         if (!bus) {
1306                 log_error("Failed to get system D-Bus connection: %s", bus_error_message(&error));
1307                 r = -ECONNREFUSED;
1308                 goto fail;
1309         }
1310
1311         dbus_connection_set_exit_on_disconnect(bus, FALSE);
1312
1313         if (!dbus_connection_register_object_path(bus, "/org/freedesktop/locale1", &locale_vtable, NULL) ||
1314             !dbus_connection_add_filter(bus, bus_exit_idle_filter, &remain_until, NULL)) {
1315                 r = log_oom();
1316                 goto fail;
1317         }
1318
1319         r = dbus_bus_request_name(bus, "org.freedesktop.locale1", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error);
1320         if (dbus_error_is_set(&error)) {
1321                 log_error("Failed to register name on bus: %s", bus_error_message(&error));
1322                 r = -EEXIST;
1323                 goto fail;
1324         }
1325
1326         if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
1327                 log_error("Failed to acquire name.");
1328                 r = -EEXIST;
1329                 goto fail;
1330         }
1331
1332         if (_bus)
1333                 *_bus = bus;
1334
1335         return 0;
1336
1337 fail:
1338         dbus_connection_close(bus);
1339         dbus_connection_unref(bus);
1340
1341         dbus_error_free(&error);
1342
1343         return r;
1344 }
1345
1346 int main(int argc, char *argv[]) {
1347         int r;
1348         DBusConnection *bus = NULL;
1349         bool exiting = false;
1350
1351         log_set_target(LOG_TARGET_AUTO);
1352         log_parse_environment();
1353         log_open();
1354         label_init("/etc");
1355         umask(0022);
1356
1357         if (argc == 2 && streq(argv[1], "--introspect")) {
1358                 fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
1359                       "<node>\n", stdout);
1360                 fputs(locale_interface, stdout);
1361                 fputs("</node>\n", stdout);
1362                 return 0;
1363         }
1364
1365         if (argc != 1) {
1366                 log_error("This program takes no arguments.");
1367                 r = -EINVAL;
1368                 goto finish;
1369         }
1370
1371         r = read_data();
1372         if (r < 0) {
1373                 log_error("Failed to read locale data: %s", strerror(-r));
1374                 goto finish;
1375         }
1376
1377         r = connect_bus(&bus);
1378         if (r < 0)
1379                 goto finish;
1380
1381         remain_until = now(CLOCK_MONOTONIC) + DEFAULT_EXIT_USEC;
1382         for (;;) {
1383
1384                 if (!dbus_connection_read_write_dispatch(bus, exiting ? -1 : (int) (DEFAULT_EXIT_USEC/USEC_PER_MSEC)))
1385                         break;
1386
1387                 if (!exiting && remain_until < now(CLOCK_MONOTONIC)) {
1388                         exiting = true;
1389                         bus_async_unregister_and_exit(bus, "org.freedesktop.locale1");
1390                 }
1391         }
1392
1393         r = 0;
1394
1395 finish:
1396         free_data();
1397
1398         if (bus) {
1399                 dbus_connection_flush(bus);
1400                 dbus_connection_close(bus);
1401                 dbus_connection_unref(bus);
1402         }
1403
1404         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
1405 }