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