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