chiark / gitweb /
localed: log locale/keymap changes in detail
[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   Copyright 2013 Kay Sievers
8
9   systemd is free software; you can redistribute it and/or modify it
10   under the terms of the GNU Lesser General Public License as published by
11   the Free Software Foundation; either version 2.1 of the License, or
12   (at your option) any later version.
13
14   systemd is distributed in the hope that it will be useful, but
15   WITHOUT ANY WARRANTY; without even the implied warranty of
16   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17   Lesser General Public License for more details.
18
19   You should have received a copy of the GNU Lesser General Public License
20   along with systemd; If not, see <http://www.gnu.org/licenses/>.
21 ***/
22
23 #include <errno.h>
24 #include <string.h>
25 #include <unistd.h>
26 #include <sys/capability.h>
27
28 #include "sd-bus.h"
29
30 #include "util.h"
31 #include "mkdir.h"
32 #include "strv.h"
33 #include "def.h"
34 #include "env-util.h"
35 #include "fileio.h"
36 #include "fileio-label.h"
37 #include "label.h"
38 #include "bus-util.h"
39 #include "bus-error.h"
40 #include "bus-message.h"
41 #include "event-util.h"
42 #include "locale-util.h"
43
44 enum {
45         /* We don't list LC_ALL here on purpose. People should be
46          * using LANG instead. */
47         LOCALE_LANG,
48         LOCALE_LANGUAGE,
49         LOCALE_LC_CTYPE,
50         LOCALE_LC_NUMERIC,
51         LOCALE_LC_TIME,
52         LOCALE_LC_COLLATE,
53         LOCALE_LC_MONETARY,
54         LOCALE_LC_MESSAGES,
55         LOCALE_LC_PAPER,
56         LOCALE_LC_NAME,
57         LOCALE_LC_ADDRESS,
58         LOCALE_LC_TELEPHONE,
59         LOCALE_LC_MEASUREMENT,
60         LOCALE_LC_IDENTIFICATION,
61         _LOCALE_MAX
62 };
63
64 static const char * const names[_LOCALE_MAX] = {
65         [LOCALE_LANG] = "LANG",
66         [LOCALE_LANGUAGE] = "LANGUAGE",
67         [LOCALE_LC_CTYPE] = "LC_CTYPE",
68         [LOCALE_LC_NUMERIC] = "LC_NUMERIC",
69         [LOCALE_LC_TIME] = "LC_TIME",
70         [LOCALE_LC_COLLATE] = "LC_COLLATE",
71         [LOCALE_LC_MONETARY] = "LC_MONETARY",
72         [LOCALE_LC_MESSAGES] = "LC_MESSAGES",
73         [LOCALE_LC_PAPER] = "LC_PAPER",
74         [LOCALE_LC_NAME] = "LC_NAME",
75         [LOCALE_LC_ADDRESS] = "LC_ADDRESS",
76         [LOCALE_LC_TELEPHONE] = "LC_TELEPHONE",
77         [LOCALE_LC_MEASUREMENT] = "LC_MEASUREMENT",
78         [LOCALE_LC_IDENTIFICATION] = "LC_IDENTIFICATION"
79 };
80
81 typedef struct Context {
82         char *locale[_LOCALE_MAX];
83
84         char *x11_layout;
85         char *x11_model;
86         char *x11_variant;
87         char *x11_options;
88
89         char *vc_keymap;
90         char *vc_keymap_toggle;
91
92         Hashmap *polkit_registry;
93 } Context;
94
95 static const char* nonempty(const char *s) {
96         return isempty(s) ? NULL : s;
97 }
98
99 static void free_and_replace(char **s, char *v) {
100         free(*s);
101         *s = v;
102 }
103
104 static void context_free_x11(Context *c) {
105         free_and_replace(&c->x11_layout, NULL);
106         free_and_replace(&c->x11_model, NULL);
107         free_and_replace(&c->x11_variant, NULL);
108         free_and_replace(&c->x11_options, NULL);
109 }
110
111 static void context_free_vconsole(Context *c) {
112         free_and_replace(&c->vc_keymap, NULL);
113         free_and_replace(&c->vc_keymap_toggle, NULL);
114 }
115
116 static void context_free_locale(Context *c) {
117         int p;
118
119         for (p = 0; p < _LOCALE_MAX; p++)
120                 free_and_replace(&c->locale[p], NULL);
121 }
122
123 static void context_free(Context *c) {
124         context_free_locale(c);
125         context_free_x11(c);
126         context_free_vconsole(c);
127
128         bus_verify_polkit_async_registry_free(c->polkit_registry);
129 };
130
131 static void locale_simplify(Context *c) {
132         int p;
133
134         for (p = LOCALE_LANG+1; p < _LOCALE_MAX; p++)
135                 if (isempty(c->locale[p]) || streq_ptr(c->locale[LOCALE_LANG], c->locale[p]))
136                         free_and_replace(&c->locale[p], NULL);
137 }
138
139 static int locale_read_data(Context *c) {
140         int r;
141
142         context_free_locale(c);
143
144         r = parse_env_file("/etc/locale.conf", NEWLINE,
145                            "LANG",              &c->locale[LOCALE_LANG],
146                            "LANGUAGE",          &c->locale[LOCALE_LANGUAGE],
147                            "LC_CTYPE",          &c->locale[LOCALE_LC_CTYPE],
148                            "LC_NUMERIC",        &c->locale[LOCALE_LC_NUMERIC],
149                            "LC_TIME",           &c->locale[LOCALE_LC_TIME],
150                            "LC_COLLATE",        &c->locale[LOCALE_LC_COLLATE],
151                            "LC_MONETARY",       &c->locale[LOCALE_LC_MONETARY],
152                            "LC_MESSAGES",       &c->locale[LOCALE_LC_MESSAGES],
153                            "LC_PAPER",          &c->locale[LOCALE_LC_PAPER],
154                            "LC_NAME",           &c->locale[LOCALE_LC_NAME],
155                            "LC_ADDRESS",        &c->locale[LOCALE_LC_ADDRESS],
156                            "LC_TELEPHONE",      &c->locale[LOCALE_LC_TELEPHONE],
157                            "LC_MEASUREMENT",    &c->locale[LOCALE_LC_MEASUREMENT],
158                            "LC_IDENTIFICATION", &c->locale[LOCALE_LC_IDENTIFICATION],
159                            NULL);
160
161         if (r == -ENOENT) {
162                 int p;
163
164                 /* Fill in what we got passed from systemd. */
165                 for (p = 0; p < _LOCALE_MAX; p++) {
166                         assert(names[p]);
167
168                         r = free_and_strdup(&c->locale[p],
169                                             nonempty(getenv(names[p])));
170                         if (r < 0)
171                                 return r;
172                 }
173
174                 r = 0;
175         }
176
177         locale_simplify(c);
178         return r;
179 }
180
181 static int vconsole_read_data(Context *c) {
182         int r;
183
184         context_free_vconsole(c);
185
186         r = parse_env_file("/etc/vconsole.conf", NEWLINE,
187                            "KEYMAP",        &c->vc_keymap,
188                            "KEYMAP_TOGGLE", &c->vc_keymap_toggle,
189                            NULL);
190
191         if (r < 0 && r != -ENOENT)
192                 return r;
193
194         return 0;
195 }
196
197 static int x11_read_data(Context *c) {
198         _cleanup_fclose_ FILE *f;
199         char line[LINE_MAX];
200         bool in_section = false;
201         int r;
202
203         context_free_x11(c);
204
205         f = fopen("/etc/X11/xorg.conf.d/00-keyboard.conf", "re");
206         if (!f)
207                 return errno == ENOENT ? 0 : -errno;
208
209         while (fgets(line, sizeof(line), f)) {
210                 char *l;
211
212                 char_array_0(line);
213                 l = strstrip(line);
214
215                 if (l[0] == 0 || l[0] == '#')
216                         continue;
217
218                 if (in_section && first_word(l, "Option")) {
219                         _cleanup_strv_free_ char **a = NULL;
220
221                         r = strv_split_quoted(&a, l);
222                         if (r < 0)
223                                 return r;
224
225                         if (strv_length(a) == 3) {
226                                 if (streq(a[1], "XkbLayout")) {
227                                         free_and_replace(&c->x11_layout, a[2]);
228                                         a[2] = NULL;
229                                 } else if (streq(a[1], "XkbModel")) {
230                                         free_and_replace(&c->x11_model, a[2]);
231                                         a[2] = NULL;
232                                 } else if (streq(a[1], "XkbVariant")) {
233                                         free_and_replace(&c->x11_variant, a[2]);
234                                         a[2] = NULL;
235                                 } else if (streq(a[1], "XkbOptions")) {
236                                         free_and_replace(&c->x11_options, a[2]);
237                                         a[2] = NULL;
238                                 }
239                         }
240
241                 } else if (!in_section && first_word(l, "Section")) {
242                         _cleanup_strv_free_ char **a = NULL;
243
244                         r = strv_split_quoted(&a, l);
245                         if (r < 0)
246                                 return -ENOMEM;
247
248                         if (strv_length(a) == 2 && streq(a[1], "InputClass"))
249                                 in_section = true;
250
251                 } else if (in_section && first_word(l, "EndSection"))
252                         in_section = false;
253         }
254
255         return 0;
256 }
257
258 static int context_read_data(Context *c) {
259         int r, q, p;
260
261         r = locale_read_data(c);
262         q = vconsole_read_data(c);
263         p = x11_read_data(c);
264
265         return r < 0 ? r : q < 0 ? q : p;
266 }
267
268 static int locale_write_data(Context *c, char ***settings) {
269         int r, p;
270         _cleanup_strv_free_ char **l = NULL;
271
272         /* Set values will be returned as strv in *settings on success. */
273
274         r = load_env_file(NULL, "/etc/locale.conf", NULL, &l);
275         if (r < 0 && r != -ENOENT)
276                 return r;
277
278         for (p = 0; p < _LOCALE_MAX; p++) {
279                 _cleanup_free_ char *t = NULL;
280                 char **u;
281
282                 assert(names[p]);
283
284                 if (isempty(c->locale[p])) {
285                         l = strv_env_unset(l, names[p]);
286                         continue;
287                 }
288
289                 if (asprintf(&t, "%s=%s", names[p], c->locale[p]) < 0)
290                         return -ENOMEM;
291
292                 u = strv_env_set(l, t);
293                 if (!u)
294                         return -ENOMEM;
295
296                 strv_free(l);
297                 l = u;
298         }
299
300         if (strv_isempty(l)) {
301                 if (unlink("/etc/locale.conf") < 0)
302                         return errno == ENOENT ? 0 : -errno;
303
304                 return 0;
305         }
306
307         r = write_env_file_label("/etc/locale.conf", l);
308         if (r < 0)
309                 return r;
310
311         *settings = l;
312         l = NULL;
313         return 0;
314 }
315
316 static int locale_update_system_manager(Context *c, sd_bus *bus) {
317         _cleanup_free_ char **l_unset = NULL;
318         _cleanup_strv_free_ char **l_set = NULL;
319         _cleanup_bus_message_unref_ sd_bus_message *m = NULL;
320         sd_bus_error error = SD_BUS_ERROR_NULL;
321         unsigned c_set, c_unset, p;
322         int r;
323
324         assert(bus);
325
326         l_unset = new0(char*, _LOCALE_MAX);
327         if (!l_unset)
328                 return -ENOMEM;
329
330         l_set = new0(char*, _LOCALE_MAX);
331         if (!l_set)
332                 return -ENOMEM;
333
334         for (p = 0, c_set = 0, c_unset = 0; p < _LOCALE_MAX; p++) {
335                 assert(names[p]);
336
337                 if (isempty(c->locale[p]))
338                         l_unset[c_set++] = (char*) names[p];
339                 else {
340                         char *s;
341
342                         if (asprintf(&s, "%s=%s", names[p], c->locale[p]) < 0)
343                                 return -ENOMEM;
344
345                         l_set[c_unset++] = s;
346                 }
347         }
348
349         assert(c_set + c_unset == _LOCALE_MAX);
350         r = sd_bus_message_new_method_call(bus, &m,
351                         "org.freedesktop.systemd1",
352                         "/org/freedesktop/systemd1",
353                         "org.freedesktop.systemd1.Manager",
354                         "UnsetAndSetEnvironment");
355         if (r < 0)
356                 return r;
357
358         r = sd_bus_message_append_strv(m, l_unset);
359         if (r < 0)
360                 return r;
361
362         r = sd_bus_message_append_strv(m, l_set);
363         if (r < 0)
364                 return r;
365
366         r = sd_bus_call(bus, m, 0, &error, NULL);
367         if (r < 0)
368                 log_error("Failed to update the manager environment: %s", strerror(-r));
369
370         return 0;
371 }
372
373 static int vconsole_write_data(Context *c) {
374         int r;
375         _cleanup_strv_free_ char **l = NULL;
376
377         r = load_env_file(NULL, "/etc/vconsole.conf", NULL, &l);
378         if (r < 0 && r != -ENOENT)
379                 return r;
380
381         if (isempty(c->vc_keymap))
382                 l = strv_env_unset(l, "KEYMAP");
383         else {
384                 _cleanup_free_ char *s = NULL;
385                 char **u;
386
387                 s = strappend("KEYMAP=", c->vc_keymap);
388                 if (!s)
389                         return -ENOMEM;
390
391                 u = strv_env_set(l, s);
392                 if (!u)
393                         return -ENOMEM;
394
395                 strv_free(l);
396                 l = u;
397         }
398
399         if (isempty(c->vc_keymap_toggle))
400                 l = strv_env_unset(l, "KEYMAP_TOGGLE");
401         else  {
402                 _cleanup_free_ char *s = NULL;
403                 char **u;
404
405                 s = strappend("KEYMAP_TOGGLE=", c->vc_keymap_toggle);
406                 if (!s)
407                         return -ENOMEM;
408
409                 u = strv_env_set(l, s);
410                 if (!u)
411                         return -ENOMEM;
412
413                 strv_free(l);
414                 l = u;
415         }
416
417         if (strv_isempty(l)) {
418                 if (unlink("/etc/vconsole.conf") < 0)
419                         return errno == ENOENT ? 0 : -errno;
420
421                 return 0;
422         }
423
424         return write_env_file_label("/etc/vconsole.conf", l);
425 }
426
427 static int write_data_x11(Context *c) {
428         _cleanup_fclose_ FILE *f = NULL;
429         _cleanup_free_ char *temp_path = NULL;
430         int r;
431
432         if (isempty(c->x11_layout) &&
433             isempty(c->x11_model) &&
434             isempty(c->x11_variant) &&
435             isempty(c->x11_options)) {
436
437                 if (unlink("/etc/X11/xorg.conf.d/00-keyboard.conf") < 0)
438                         return errno == ENOENT ? 0 : -errno;
439
440                 return 0;
441         }
442
443         mkdir_p_label("/etc/X11/xorg.conf.d", 0755);
444
445         r = fopen_temporary("/etc/X11/xorg.conf.d/00-keyboard.conf", &f, &temp_path);
446         if (r < 0)
447                 return r;
448
449         fchmod(fileno(f), 0644);
450
451         fputs("# Read and parsed by systemd-localed. It's probably wise not to edit this file\n"
452               "# manually too freely.\n"
453               "Section \"InputClass\"\n"
454               "        Identifier \"system-keyboard\"\n"
455               "        MatchIsKeyboard \"on\"\n", f);
456
457         if (!isempty(c->x11_layout))
458                 fprintf(f, "        Option \"XkbLayout\" \"%s\"\n", c->x11_layout);
459
460         if (!isempty(c->x11_model))
461                 fprintf(f, "        Option \"XkbModel\" \"%s\"\n", c->x11_model);
462
463         if (!isempty(c->x11_variant))
464                 fprintf(f, "        Option \"XkbVariant\" \"%s\"\n", c->x11_variant);
465
466         if (!isempty(c->x11_options))
467                 fprintf(f, "        Option \"XkbOptions\" \"%s\"\n", c->x11_options);
468
469         fputs("EndSection\n", f);
470         fflush(f);
471
472         if (ferror(f) || rename(temp_path, "/etc/X11/xorg.conf.d/00-keyboard.conf") < 0) {
473                 r = -errno;
474                 unlink("/etc/X11/xorg.conf.d/00-keyboard.conf");
475                 unlink(temp_path);
476                 return r;
477         } else
478                 return 0;
479 }
480
481 static int vconsole_reload(sd_bus *bus) {
482         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
483         int r;
484
485         assert(bus);
486
487         r = sd_bus_call_method(bus,
488                         "org.freedesktop.systemd1",
489                         "/org/freedesktop/systemd1",
490                         "org.freedesktop.systemd1.Manager",
491                         "RestartUnit",
492                         &error,
493                         NULL,
494                         "ss", "systemd-vconsole-setup.service", "replace");
495
496         if (r < 0)
497                 log_error("Failed to issue method call: %s", bus_error_message(&error, -r));
498         return r;
499 }
500
501 static const char* strnulldash(const char *s) {
502         return isempty(s) || streq(s, "-") ? NULL : s;
503 }
504
505 static int read_next_mapping(FILE *f, unsigned *n, char ***a) {
506         assert(f);
507         assert(n);
508         assert(a);
509
510         for (;;) {
511                 char line[LINE_MAX];
512                 char *l, **b;
513                 int r;
514
515                 errno = 0;
516                 if (!fgets(line, sizeof(line), f)) {
517
518                         if (ferror(f))
519                                 return errno ? -errno : -EIO;
520
521                         return 0;
522                 }
523
524                 (*n) ++;
525
526                 l = strstrip(line);
527                 if (l[0] == 0 || l[0] == '#')
528                         continue;
529
530                 r = strv_split_quoted(&b, l);
531                 if (r < 0)
532                         return r;
533
534                 if (strv_length(b) < 5) {
535                         log_error("Invalid line "SYSTEMD_KBD_MODEL_MAP":%u, ignoring.", *n);
536                         strv_free(b);
537                         continue;
538
539                 }
540
541                 *a = b;
542                 return 1;
543         }
544 }
545
546 static int vconsole_convert_to_x11(Context *c, sd_bus *bus) {
547         bool modified = false;
548
549         assert(bus);
550
551         if (isempty(c->vc_keymap)) {
552
553                 modified =
554                         !isempty(c->x11_layout) ||
555                         !isempty(c->x11_model) ||
556                         !isempty(c->x11_variant) ||
557                         !isempty(c->x11_options);
558
559                 context_free_x11(c);
560         } else {
561                 _cleanup_fclose_ FILE *f = NULL;
562                 unsigned n = 0;
563
564                 f = fopen(SYSTEMD_KBD_MODEL_MAP, "re");
565                 if (!f)
566                         return -errno;
567
568                 for (;;) {
569                         _cleanup_strv_free_ char **a = NULL;
570                         int r;
571
572                         r = read_next_mapping(f, &n, &a);
573                         if (r < 0)
574                                 return r;
575                         if (r == 0)
576                                 break;
577
578                         if (!streq(c->vc_keymap, a[0]))
579                                 continue;
580
581                         if (!streq_ptr(c->x11_layout, strnulldash(a[1])) ||
582                             !streq_ptr(c->x11_model, strnulldash(a[2])) ||
583                             !streq_ptr(c->x11_variant, strnulldash(a[3])) ||
584                             !streq_ptr(c->x11_options, strnulldash(a[4]))) {
585
586                                 if (free_and_strdup(&c->x11_layout, strnulldash(a[1])) < 0 ||
587                                     free_and_strdup(&c->x11_model, strnulldash(a[2])) < 0 ||
588                                     free_and_strdup(&c->x11_variant, strnulldash(a[3])) < 0 ||
589                                     free_and_strdup(&c->x11_options, strnulldash(a[4])) < 0)
590                                         return -ENOMEM;
591
592                                 modified = true;
593                         }
594
595                         break;
596                 }
597         }
598
599         if (modified) {
600                 int r;
601
602                 r = write_data_x11(c);
603                 if (r < 0)
604                         log_error("Failed to set X11 keyboard layout: %s", strerror(-r));
605
606                 log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
607                          strempty(c->x11_layout),
608                          strempty(c->x11_model),
609                          strempty(c->x11_variant),
610                          strempty(c->x11_options));
611
612                 sd_bus_emit_properties_changed(bus,
613                                 "/org/freedesktop/locale1",
614                                 "org.freedesktop.locale1",
615                                 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL);
616         } else
617                 log_debug("X11 keyboard layout was not modified.");
618
619         return 0;
620 }
621
622 static int find_converted_keymap(Context *c, char **new_keymap) {
623         const char *dir;
624         _cleanup_free_ char *n;
625
626         if (c->x11_variant)
627                 n = strjoin(c->x11_layout, "-", c->x11_variant, NULL);
628         else
629                 n = strdup(c->x11_layout);
630         if (!n)
631                 return -ENOMEM;
632
633         NULSTR_FOREACH(dir, KBD_KEYMAP_DIRS) {
634                 _cleanup_free_ char *p = NULL, *pz = NULL;
635                 bool uncompressed;
636
637                 p = strjoin(dir, "xkb/", n, ".map", NULL);
638                 pz = strjoin(dir, "xkb/", n, ".map.gz", NULL);
639                 if (!p || !pz)
640                         return -ENOMEM;
641
642                 uncompressed = access(p, F_OK) == 0;
643                 if (uncompressed || access(pz, F_OK) == 0) {
644                         log_debug("Found converted keymap %s at %s",
645                                   n, uncompressed ? p : pz);
646
647                         *new_keymap = n;
648                         n = NULL;
649                         return 1;
650                 }
651         }
652
653         return 0;
654 }
655
656 static int find_legacy_keymap(Context *c, char **new_keymap) {
657         _cleanup_fclose_ FILE *f;
658         unsigned n = 0;
659         unsigned best_matching = 0;
660
661
662         f = fopen(SYSTEMD_KBD_MODEL_MAP, "re");
663         if (!f)
664                 return -errno;
665
666         for (;;) {
667                 _cleanup_strv_free_ char **a = NULL;
668                 unsigned matching = 0;
669                 int r;
670
671                 r = read_next_mapping(f, &n, &a);
672                 if (r < 0)
673                         return r;
674                 if (r == 0)
675                         break;
676
677                 /* Determine how well matching this entry is */
678                 if (streq_ptr(c->x11_layout, a[1]))
679                         /* If we got an exact match, this is best */
680                         matching = 10;
681                 else {
682                         size_t x;
683
684                         x = strcspn(c->x11_layout, ",");
685
686                         /* We have multiple X layouts, look for an
687                          * entry that matches our key with everything
688                          * but the first layout stripped off. */
689                         if (x > 0 &&
690                             strlen(a[1]) == x &&
691                             strneq(c->x11_layout, a[1], x))
692                                 matching = 5;
693                         else  {
694                                 size_t w;
695
696                                 /* If that didn't work, strip off the
697                                  * other layouts from the entry, too */
698                                 w = strcspn(a[1], ",");
699
700                                 if (x > 0 && x == w &&
701                                     memcmp(c->x11_layout, a[1], x) == 0)
702                                         matching = 1;
703                         }
704                 }
705
706                 if (matching > 0) {
707                         if (isempty(c->x11_model) || streq_ptr(c->x11_model, a[2])) {
708                                 matching++;
709
710                                 if (streq_ptr(c->x11_variant, a[3])) {
711                                         matching++;
712
713                                         if (streq_ptr(c->x11_options, a[4]))
714                                                 matching++;
715                                 }
716                         }
717                 }
718
719                 /* The best matching entry so far, then let's save that */
720                 if (matching >= MAX(best_matching, 1u)) {
721                         log_debug("Found legacy keymap %s with score %u",
722                                   a[0], matching);
723
724                         if (matching > best_matching) {
725                                 best_matching = matching;
726
727                                 r = free_and_strdup(new_keymap, a[0]);
728                                 if (r < 0)
729                                         return r;
730                         }
731                 }
732         }
733
734         return 0;
735 }
736
737 static int x11_convert_to_vconsole(Context *c, sd_bus *bus) {
738         bool modified = false;
739         int r;
740
741         assert(bus);
742
743         if (isempty(c->x11_layout)) {
744
745                 modified =
746                         !isempty(c->vc_keymap) ||
747                         !isempty(c->vc_keymap_toggle);
748
749                 context_free_x11(c);
750         } else {
751                 char *new_keymap = NULL;
752
753                 r = find_converted_keymap(c, &new_keymap);
754                 if (r < 0)
755                         return r;
756                 else if (r == 0) {
757                         r = find_legacy_keymap(c, &new_keymap);
758                         if (r < 0)
759                                 return r;
760                 }
761
762                 if (!streq_ptr(c->vc_keymap, new_keymap)) {
763                         free_and_replace(&c->vc_keymap, new_keymap);
764                         free_and_replace(&c->vc_keymap_toggle, NULL);
765                         modified = true;
766                 } else
767                         free(new_keymap);
768         }
769
770         if (modified) {
771                 r = vconsole_write_data(c);
772                 if (r < 0)
773                         log_error("Failed to set virtual console keymap: %s", strerror(-r));
774
775                 log_info("Changed virtual console keymap to '%s' toggle '%s'",
776                          strempty(c->vc_keymap), strempty(c->vc_keymap_toggle));
777
778                 sd_bus_emit_properties_changed(bus,
779                                 "/org/freedesktop/locale1",
780                                 "org.freedesktop.locale1",
781                                 "VConsoleKeymap", "VConsoleKeymapToggle", NULL);
782
783                 return vconsole_reload(bus);
784         } else
785                 log_debug("Virtual console keymap was not modified.");
786
787         return 0;
788 }
789
790 static int property_get_locale(
791                 sd_bus *bus,
792                 const char *path,
793                 const char *interface,
794                 const char *property,
795                 sd_bus_message *reply,
796                 void *userdata,
797                 sd_bus_error *error) {
798
799         Context *c = userdata;
800         _cleanup_strv_free_ char **l = NULL;
801         int p, q;
802
803         l = new0(char*, _LOCALE_MAX+1);
804         if (!l)
805                 return -ENOMEM;
806
807         for (p = 0, q = 0; p < _LOCALE_MAX; p++) {
808                 char *t;
809
810                 if (isempty(c->locale[p]))
811                         continue;
812
813                 if (asprintf(&t, "%s=%s", names[p], c->locale[p]) < 0)
814                         return -ENOMEM;
815
816                 l[q++] = t;
817         }
818
819         return sd_bus_message_append_strv(reply, l);
820 }
821
822 static int method_set_locale(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
823         Context *c = userdata;
824         _cleanup_strv_free_ char **l = NULL;
825         char **i;
826         int interactive;
827         bool modified = false;
828         bool passed[_LOCALE_MAX] = {};
829         int p;
830         int r;
831
832         r = bus_message_read_strv_extend(m, &l);
833         if (r < 0)
834                 return r;
835
836         r = sd_bus_message_read_basic(m, 'b', &interactive);
837         if (r < 0)
838                 return r;
839
840         /* Check whether a variable changed and if it is valid */
841         STRV_FOREACH(i, l) {
842                 bool valid = false;
843
844                 for (p = 0; p < _LOCALE_MAX; p++) {
845                         size_t k;
846
847                         k = strlen(names[p]);
848                         if (startswith(*i, names[p]) &&
849                             (*i)[k] == '=' &&
850                             locale_is_valid((*i) + k + 1)) {
851                                 valid = true;
852                                 passed[p] = true;
853
854                                 if (!streq_ptr(*i + k + 1, c->locale[p]))
855                                         modified = true;
856
857                                 break;
858                         }
859                 }
860
861                 if (!valid)
862                         return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid Locale data.");
863         }
864
865         /* Check whether a variable is unset */
866         if (!modified)
867                 for (p = 0; p < _LOCALE_MAX; p++)
868                         if (!isempty(c->locale[p]) && !passed[p]) {
869                                 modified = true;
870                                 break;
871                         }
872
873         if (modified) {
874                 _cleanup_strv_free_ char **settings = NULL;
875
876                 r = bus_verify_polkit_async(m, CAP_SYS_ADMIN, "org.freedesktop.locale1.set-locale", interactive, &c->polkit_registry, error);
877                 if (r < 0)
878                         return r;
879                 if (r == 0)
880                         return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
881
882                 STRV_FOREACH(i, l)
883                         for (p = 0; p < _LOCALE_MAX; p++) {
884                                 size_t k;
885
886                                 k = strlen(names[p]);
887                                 if (startswith(*i, names[p]) && (*i)[k] == '=') {
888                                         r = free_and_strdup(&c->locale[p], *i + k + 1);
889                                         if (r < 0)
890                                                 return r;
891                                         break;
892                                 }
893                         }
894
895                 for (p = 0; p < _LOCALE_MAX; p++) {
896                         if (passed[p])
897                                 continue;
898
899                         free_and_replace(&c->locale[p], NULL);
900                 }
901
902                 locale_simplify(c);
903
904                 r = locale_write_data(c, &settings);
905                 if (r < 0) {
906                         log_error("Failed to set locale: %s", strerror(-r));
907                         return sd_bus_error_set_errnof(error, r, "Failed to set locale: %s", strerror(-r));
908                 }
909
910                 locale_update_system_manager(c, bus);
911
912                 if (settings) {
913                         _cleanup_free_ char *line;
914
915                         line = strv_join(settings, ", ");
916                         log_info("Changed locale to %s.", strnull(line));
917                 } else
918                         log_info("Changed locale to unset.");
919
920                 sd_bus_emit_properties_changed(bus,
921                                 "/org/freedesktop/locale1",
922                                 "org.freedesktop.locale1",
923                                 "Locale", NULL);
924         } else
925                 log_debug("Locale settings were not modified.");
926
927
928         return sd_bus_reply_method_return(m, NULL);
929 }
930
931 static int method_set_vc_keyboard(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
932         Context *c = userdata;
933         const char *keymap, *keymap_toggle;
934         int convert, interactive;
935         int r;
936
937         r = sd_bus_message_read(m, "ssbb", &keymap, &keymap_toggle, &convert, &interactive);
938         if (r < 0)
939                 return r;
940
941         if (isempty(keymap))
942                 keymap = NULL;
943
944         if (isempty(keymap_toggle))
945                 keymap_toggle = NULL;
946
947         if (!streq_ptr(keymap, c->vc_keymap) ||
948             !streq_ptr(keymap_toggle, c->vc_keymap_toggle)) {
949
950                 if ((keymap && (!filename_is_safe(keymap) || !string_is_safe(keymap))) ||
951                     (keymap_toggle && (!filename_is_safe(keymap_toggle) || !string_is_safe(keymap_toggle))))
952                         return sd_bus_error_set_errnof(error, -EINVAL, "Received invalid keymap data");
953
954                 r = bus_verify_polkit_async(m, CAP_SYS_ADMIN, "org.freedesktop.locale1.set-keyboard", interactive, &c->polkit_registry, error);
955                 if (r < 0)
956                         return r;
957                 if (r == 0)
958                         return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
959
960                 if (free_and_strdup(&c->vc_keymap, keymap) < 0 ||
961                     free_and_strdup(&c->vc_keymap_toggle, keymap_toggle) < 0)
962                         return -ENOMEM;
963
964                 r = vconsole_write_data(c);
965                 if (r < 0) {
966                         log_error("Failed to set virtual console keymap: %s", strerror(-r));
967                         return sd_bus_error_set_errnof(error, r, "Failed to set virtual console keymap: %s", strerror(-r));
968                 }
969
970                 log_info("Changed virtual console keymap to '%s' toggle '%s'",
971                          strempty(c->vc_keymap), strempty(c->vc_keymap_toggle));
972
973                 r = vconsole_reload(bus);
974                 if (r < 0)
975                         log_error("Failed to request keymap reload: %s", strerror(-r));
976
977                 sd_bus_emit_properties_changed(bus,
978                                 "/org/freedesktop/locale1",
979                                 "org.freedesktop.locale1",
980                                 "VConsoleKeymap", "VConsoleKeymapToggle", NULL);
981
982                 if (convert) {
983                         r = vconsole_convert_to_x11(c, bus);
984                         if (r < 0)
985                                 log_error("Failed to convert keymap data: %s", strerror(-r));
986                 }
987         }
988
989         return sd_bus_reply_method_return(m, NULL);
990 }
991
992 static int method_set_x11_keyboard(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
993         Context *c = userdata;
994         const char *layout, *model, *variant, *options;
995         int convert, interactive;
996         int r;
997
998         r = sd_bus_message_read(m, "ssssbb", &layout, &model, &variant, &options, &convert, &interactive);
999         if (r < 0)
1000                 return r;
1001
1002         if (isempty(layout))
1003                 layout = NULL;
1004
1005         if (isempty(model))
1006                 model = NULL;
1007
1008         if (isempty(variant))
1009                 variant = NULL;
1010
1011         if (isempty(options))
1012                 options = NULL;
1013
1014         if (!streq_ptr(layout, c->x11_layout) ||
1015             !streq_ptr(model, c->x11_model) ||
1016             !streq_ptr(variant, c->x11_variant) ||
1017             !streq_ptr(options, c->x11_options)) {
1018
1019                 if ((layout && !string_is_safe(layout)) ||
1020                     (model && !string_is_safe(model)) ||
1021                     (variant && !string_is_safe(variant)) ||
1022                     (options && !string_is_safe(options)))
1023                         return sd_bus_error_set_errnof(error, -EINVAL, "Received invalid keyboard data");
1024
1025                 r = bus_verify_polkit_async(m, CAP_SYS_ADMIN, "org.freedesktop.locale1.set-keyboard", interactive, &c->polkit_registry, error);
1026                 if (r < 0)
1027                         return r;
1028                 if (r == 0)
1029                         return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
1030
1031                 if (free_and_strdup(&c->x11_layout, layout) < 0 ||
1032                     free_and_strdup(&c->x11_model, model) < 0 ||
1033                     free_and_strdup(&c->x11_variant, variant) < 0 ||
1034                     free_and_strdup(&c->x11_options, options) < 0)
1035                         return -ENOMEM;
1036
1037                 r = write_data_x11(c);
1038                 if (r < 0) {
1039                         log_error("Failed to set X11 keyboard layout: %s", strerror(-r));
1040                         return sd_bus_error_set_errnof(error, r, "Failed to set X11 keyboard layout: %s", strerror(-r));
1041                 }
1042
1043                 log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
1044                          strempty(c->x11_layout),
1045                          strempty(c->x11_model),
1046                          strempty(c->x11_variant),
1047                          strempty(c->x11_options));
1048
1049                 sd_bus_emit_properties_changed(bus,
1050                                 "/org/freedesktop/locale1",
1051                                 "org.freedesktop.locale1",
1052                                 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL);
1053
1054                 if (convert) {
1055                         r = x11_convert_to_vconsole(c, bus);
1056                         if (r < 0)
1057                                 log_error("Failed to convert keymap data: %s", strerror(-r));
1058                 }
1059         }
1060
1061         return sd_bus_reply_method_return(m, NULL);
1062 }
1063
1064 static const sd_bus_vtable locale_vtable[] = {
1065         SD_BUS_VTABLE_START(0),
1066         SD_BUS_PROPERTY("Locale", "as", property_get_locale, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1067         SD_BUS_PROPERTY("X11Layout", "s", NULL, offsetof(Context, x11_layout), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1068         SD_BUS_PROPERTY("X11Model", "s", NULL, offsetof(Context, x11_model), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1069         SD_BUS_PROPERTY("X11Variant", "s", NULL, offsetof(Context, x11_variant), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1070         SD_BUS_PROPERTY("X11Options", "s", NULL, offsetof(Context, x11_options), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1071         SD_BUS_PROPERTY("VConsoleKeymap", "s", NULL, offsetof(Context, vc_keymap), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1072         SD_BUS_PROPERTY("VConsoleKeymapToggle", "s", NULL, offsetof(Context, vc_keymap_toggle), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1073         SD_BUS_METHOD("SetLocale", "asb", NULL, method_set_locale, SD_BUS_VTABLE_UNPRIVILEGED),
1074         SD_BUS_METHOD("SetVConsoleKeyboard", "ssbb", NULL, method_set_vc_keyboard, SD_BUS_VTABLE_UNPRIVILEGED),
1075         SD_BUS_METHOD("SetX11Keyboard", "ssssbb", NULL, method_set_x11_keyboard, SD_BUS_VTABLE_UNPRIVILEGED),
1076         SD_BUS_VTABLE_END
1077 };
1078
1079 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
1080         _cleanup_bus_close_unref_ sd_bus *bus = NULL;
1081         int r;
1082
1083         assert(c);
1084         assert(event);
1085         assert(_bus);
1086
1087         r = sd_bus_default_system(&bus);
1088         if (r < 0) {
1089                 log_error("Failed to get system bus connection: %s", strerror(-r));
1090                 return r;
1091         }
1092
1093         r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/locale1", "org.freedesktop.locale1", locale_vtable, c);
1094         if (r < 0) {
1095                 log_error("Failed to register object: %s", strerror(-r));
1096                 return r;
1097         }
1098
1099         r = sd_bus_request_name(bus, "org.freedesktop.locale1", 0);
1100         if (r < 0) {
1101                 log_error("Failed to register name: %s", strerror(-r));
1102                 return r;
1103         }
1104
1105         r = sd_bus_attach_event(bus, event, 0);
1106         if (r < 0) {
1107                 log_error("Failed to attach bus to event loop: %s", strerror(-r));
1108                 return r;
1109         }
1110
1111         *_bus = bus;
1112         bus = NULL;
1113
1114         return 0;
1115 }
1116
1117 int main(int argc, char *argv[]) {
1118         _cleanup_(context_free) Context context = {};
1119         _cleanup_event_unref_ sd_event *event = NULL;
1120         _cleanup_bus_close_unref_ sd_bus *bus = NULL;
1121         int r;
1122
1123         log_set_target(LOG_TARGET_AUTO);
1124         log_parse_environment();
1125         log_open();
1126
1127         umask(0022);
1128         label_init("/etc");
1129
1130         if (argc != 1) {
1131                 log_error("This program takes no arguments.");
1132                 r = -EINVAL;
1133                 goto finish;
1134         }
1135
1136         r = sd_event_default(&event);
1137         if (r < 0) {
1138                 log_error("Failed to allocate event loop: %s", strerror(-r));
1139                 goto finish;
1140         }
1141
1142         sd_event_set_watchdog(event, true);
1143
1144         r = connect_bus(&context, event, &bus);
1145         if (r < 0)
1146                 goto finish;
1147
1148         r = context_read_data(&context);
1149         if (r < 0) {
1150                 log_error("Failed to read locale data: %s", strerror(-r));
1151                 goto finish;
1152         }
1153
1154         r = bus_event_loop_with_idle(event, bus, "org.freedesktop.locale1", DEFAULT_EXIT_USEC, NULL, NULL);
1155         if (r < 0) {
1156                 log_error("Failed to run event loop: %s", strerror(-r));
1157                 goto finish;
1158         }
1159
1160 finish:
1161         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
1162 }