chiark / gitweb /
locale: make sure that l is freed
[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_parents_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]) && (*i)[k] == '=') {
1043                                         valid = true;
1044                                         passed[p] = true;
1045
1046                                         if (!streq_ptr(*i + k + 1, data[p]))
1047                                                 modified = true;
1048
1049                                         break;
1050                                 }
1051                         }
1052
1053                         if (!valid) {
1054                                 strv_free(l);
1055                                 return bus_send_error_reply(connection, message, NULL, -EINVAL);
1056                         }
1057                 }
1058
1059                 /* Check whether a variable is unset */
1060                 if (!modified)  {
1061                         for (p = 0; p < _PROP_MAX; p++)
1062                                 if (!isempty(data[p]) && !passed[p]) {
1063                                         modified = true;
1064                                         break;
1065                                 }
1066                 }
1067
1068                 if (modified) {
1069
1070                         r = verify_polkit(connection, message, "org.freedesktop.locale1.set-locale", interactive, NULL, &error);
1071                         if (r < 0) {
1072                                 strv_free(l);
1073                                 return bus_send_error_reply(connection, message, &error, r);
1074                         }
1075
1076                         STRV_FOREACH(i, l) {
1077                                 for (p = 0; p < _PROP_MAX; p++) {
1078                                         size_t k;
1079
1080                                         k = strlen(names[p]);
1081                                         if (startswith(*i, names[p]) && (*i)[k] == '=') {
1082                                                 char *t;
1083
1084                                                 t = strdup(*i + k + 1);
1085                                                 if (!t) {
1086                                                         strv_free(l);
1087                                                         goto oom;
1088                                                 }
1089
1090                                                 free(data[p]);
1091                                                 data[p] = t;
1092
1093                                                 break;
1094                                         }
1095                                 }
1096                         }
1097
1098                         strv_free(l);
1099
1100                         for (p = 0; p < _PROP_MAX; p++) {
1101                                 if (passed[p])
1102                                         continue;
1103
1104                                 free(data[p]);
1105                                 data[p] = NULL;
1106                         }
1107
1108                         simplify();
1109
1110                         r = write_data_locale();
1111                         if (r < 0) {
1112                                 log_error("Failed to set locale: %s", strerror(-r));
1113                                 return bus_send_error_reply(connection, message, NULL, r);
1114                         }
1115
1116                         push_data(connection);
1117
1118                         log_info("Changed locale information.");
1119
1120                         changed = bus_properties_changed_new(
1121                                         "/org/freedesktop/locale1",
1122                                         "org.freedesktop.locale1",
1123                                         "Locale\0");
1124                         if (!changed)
1125                                 goto oom;
1126                 } else
1127                         strv_free(l);
1128
1129         } else if (dbus_message_is_method_call(message, "org.freedesktop.locale1", "SetVConsoleKeyboard")) {
1130
1131                 const char *keymap, *keymap_toggle;
1132                 dbus_bool_t convert, interactive;
1133
1134                 if (!dbus_message_get_args(
1135                                     message,
1136                                     &error,
1137                                     DBUS_TYPE_STRING, &keymap,
1138                                     DBUS_TYPE_STRING, &keymap_toggle,
1139                                     DBUS_TYPE_BOOLEAN, &convert,
1140                                     DBUS_TYPE_BOOLEAN, &interactive,
1141                                     DBUS_TYPE_INVALID))
1142                         return bus_send_error_reply(connection, message, &error, -EINVAL);
1143
1144                 if (isempty(keymap))
1145                         keymap = NULL;
1146
1147                 if (isempty(keymap_toggle))
1148                         keymap_toggle = NULL;
1149
1150                 if (!streq_ptr(keymap, state.vc_keymap) ||
1151                     !streq_ptr(keymap_toggle, state.vc_keymap_toggle)) {
1152
1153                         r = verify_polkit(connection, message, "org.freedesktop.locale1.set-keyboard", interactive, NULL, &error);
1154                         if (r < 0)
1155                                 return bus_send_error_reply(connection, message, &error, r);
1156
1157                         if (free_and_set(&state.vc_keymap, keymap) < 0 ||
1158                             free_and_set(&state.vc_keymap_toggle, keymap_toggle) < 0)
1159                                 goto oom;
1160
1161                         r = write_data_vconsole();
1162                         if (r < 0) {
1163                                 log_error("Failed to set virtual console keymap: %s", strerror(-r));
1164                                 return bus_send_error_reply(connection, message, NULL, r);
1165                         }
1166
1167                         log_info("Changed virtual console keymap to '%s'", strempty(state.vc_keymap));
1168
1169                         r = load_vconsole_keymap(connection, NULL);
1170                         if (r < 0)
1171                                 log_error("Failed to request keymap reload: %s", strerror(-r));
1172
1173                         changed = bus_properties_changed_new(
1174                                         "/org/freedesktop/locale1",
1175                                         "org.freedesktop.locale1",
1176                                         "VConsoleKeymap\0"
1177                                         "VConsoleKeymapToggle\0");
1178                         if (!changed)
1179                                 goto oom;
1180
1181                         if (convert) {
1182                                 r = convert_vconsole_to_x11(connection);
1183
1184                                 if (r < 0)
1185                                         log_error("Failed to convert keymap data: %s", strerror(-r));
1186                         }
1187                 }
1188
1189         } else if (dbus_message_is_method_call(message, "org.freedesktop.locale1", "SetX11Keyboard")) {
1190
1191                 const char *layout, *model, *variant, *options;
1192                 dbus_bool_t convert, interactive;
1193
1194                 if (!dbus_message_get_args(
1195                                     message,
1196                                     &error,
1197                                     DBUS_TYPE_STRING, &layout,
1198                                     DBUS_TYPE_STRING, &model,
1199                                     DBUS_TYPE_STRING, &variant,
1200                                     DBUS_TYPE_STRING, &options,
1201                                     DBUS_TYPE_BOOLEAN, &convert,
1202                                     DBUS_TYPE_BOOLEAN, &interactive,
1203                                     DBUS_TYPE_INVALID))
1204                         return bus_send_error_reply(connection, message, &error, -EINVAL);
1205
1206                 if (isempty(layout))
1207                         layout = NULL;
1208
1209                 if (isempty(model))
1210                         model = NULL;
1211
1212                 if (isempty(variant))
1213                         variant = NULL;
1214
1215                 if (isempty(options))
1216                         options = NULL;
1217
1218                 if (!streq_ptr(layout, state.x11_layout) ||
1219                     !streq_ptr(model, state.x11_model) ||
1220                     !streq_ptr(variant, state.x11_variant) ||
1221                     !streq_ptr(options, state.x11_options)) {
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 (!dbus_connection_send(connection, reply, NULL))
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
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 }