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