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