chiark / gitweb /
shared: make timezone and locale enumeration and validation generic
[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
214         context_free_x11(c);
215
216         f = fopen("/etc/X11/xorg.conf.d/00-keyboard.conf", "re");
217         if (!f)
218                 return errno == ENOENT ? 0 : -errno;
219
220         while (fgets(line, sizeof(line), f)) {
221                 char *l;
222
223                 char_array_0(line);
224                 l = strstrip(line);
225
226                 if (l[0] == 0 || l[0] == '#')
227                         continue;
228
229                 if (in_section && first_word(l, "Option")) {
230                         char **a;
231
232                         a = strv_split_quoted(l);
233                         if (!a) {
234                                 fclose(f);
235                                 return -ENOMEM;
236                         }
237
238                         if (strv_length(a) == 3) {
239                                 if (streq(a[1], "XkbLayout")) {
240                                         free_and_replace(&c->x11_layout, a[2]);
241                                         a[2] = NULL;
242                                 } else if (streq(a[1], "XkbModel")) {
243                                         free_and_replace(&c->x11_model, a[2]);
244                                         a[2] = NULL;
245                                 } else if (streq(a[1], "XkbVariant")) {
246                                         free_and_replace(&c->x11_variant, a[2]);
247                                         a[2] = NULL;
248                                 } else if (streq(a[1], "XkbOptions")) {
249                                         free_and_replace(&c->x11_options, a[2]);
250                                         a[2] = NULL;
251                                 }
252                         }
253
254                         strv_free(a);
255
256                 } else if (!in_section && first_word(l, "Section")) {
257                         char **a;
258
259                         a = strv_split_quoted(l);
260                         if (!a) {
261                                 fclose(f);
262                                 return -ENOMEM;
263                         }
264
265                         if (strv_length(a) == 2 && streq(a[1], "InputClass"))
266                                 in_section = true;
267
268                         strv_free(a);
269                 } else if (in_section && first_word(l, "EndSection"))
270                         in_section = false;
271         }
272
273         fclose(f);
274
275         return 0;
276 }
277
278 static int context_read_data(Context *c) {
279         int r, q, p;
280
281         r = locale_read_data(c);
282         q = vconsole_read_data(c);
283         p = x11_read_data(c);
284
285         return r < 0 ? r : q < 0 ? q : p;
286 }
287
288 static int locale_write_data(Context *c) {
289         int r, p;
290         char **l = NULL;
291
292         r = load_env_file(NULL, "/etc/locale.conf", NULL, &l);
293         if (r < 0 && r != -ENOENT)
294                 return r;
295
296         for (p = 0; p < _LOCALE_MAX; p++) {
297                 char *t, **u;
298
299                 assert(names[p]);
300
301                 if (isempty(c->locale[p])) {
302                         l = strv_env_unset(l, names[p]);
303                         continue;
304                 }
305
306                 if (asprintf(&t, "%s=%s", names[p], c->locale[p]) < 0) {
307                         strv_free(l);
308                         return -ENOMEM;
309                 }
310
311                 u = strv_env_set(l, t);
312                 free(t);
313                 strv_free(l);
314
315                 if (!u)
316                         return -ENOMEM;
317
318                 l = u;
319         }
320
321         if (strv_isempty(l)) {
322                 strv_free(l);
323
324                 if (unlink("/etc/locale.conf") < 0)
325                         return errno == ENOENT ? 0 : -errno;
326
327                 return 0;
328         }
329
330         r = write_env_file_label("/etc/locale.conf", l);
331         strv_free(l);
332
333         return r;
334 }
335
336 static int locale_update_system_manager(Context *c, sd_bus *bus) {
337         _cleanup_free_ char **l_unset = NULL;
338         _cleanup_strv_free_ char **l_set = NULL;
339         _cleanup_bus_message_unref_ sd_bus_message *m = NULL;
340         sd_bus_error error = SD_BUS_ERROR_NULL;
341         unsigned c_set, c_unset, p;
342         int r;
343
344         assert(bus);
345
346         l_unset = new0(char*, _LOCALE_MAX);
347         if (!l_unset)
348                 return -ENOMEM;
349
350         l_set = new0(char*, _LOCALE_MAX);
351         if (!l_set)
352                 return -ENOMEM;
353
354         for (p = 0, c_set = 0, c_unset = 0; p < _LOCALE_MAX; p++) {
355                 assert(names[p]);
356
357                 if (isempty(c->locale[p]))
358                         l_unset[c_set++] = (char*) names[p];
359                 else {
360                         char *s;
361
362                         if (asprintf(&s, "%s=%s", names[p], c->locale[p]) < 0)
363                                 return -ENOMEM;
364
365                         l_set[c_unset++] = s;
366                 }
367         }
368
369         assert(c_set + c_unset == _LOCALE_MAX);
370         r = sd_bus_message_new_method_call(bus, &m,
371                         "org.freedesktop.systemd1",
372                         "/org/freedesktop/systemd1",
373                         "org.freedesktop.systemd1.Manager",
374                         "UnsetAndSetEnvironment");
375         if (r < 0)
376                 return r;
377
378         r = sd_bus_message_append_strv(m, l_unset);
379         if (r < 0)
380                 return r;
381
382         r = sd_bus_message_append_strv(m, l_set);
383         if (r < 0)
384                 return r;
385
386         r = sd_bus_call(bus, m, 0, &error, NULL);
387         if (r < 0)
388                 log_error("Failed to update the manager environment: %s", strerror(-r));
389
390         return 0;
391 }
392
393 static int vconsole_write_data(Context *c) {
394         int r;
395         _cleanup_strv_free_ char **l = NULL;
396
397         r = load_env_file(NULL, "/etc/vconsole.conf", NULL, &l);
398         if (r < 0 && r != -ENOENT)
399                 return r;
400
401         if (isempty(c->vc_keymap))
402                 l = strv_env_unset(l, "KEYMAP");
403         else {
404                 char *s, **u;
405
406                 s = strappend("KEYMAP=", c->vc_keymap);
407                 if (!s)
408                         return -ENOMEM;
409
410                 u = strv_env_set(l, s);
411                 free(s);
412                 strv_free(l);
413
414                 if (!u)
415                         return -ENOMEM;
416
417                 l = u;
418         }
419
420         if (isempty(c->vc_keymap_toggle))
421                 l = strv_env_unset(l, "KEYMAP_TOGGLE");
422         else  {
423                 char *s, **u;
424
425                 s = strappend("KEYMAP_TOGGLE=", c->vc_keymap_toggle);
426                 if (!s)
427                         return -ENOMEM;
428
429                 u = strv_env_set(l, s);
430                 free(s);
431                 strv_free(l);
432
433                 if (!u)
434                         return -ENOMEM;
435
436                 l = u;
437         }
438
439         if (strv_isempty(l)) {
440                 if (unlink("/etc/vconsole.conf") < 0)
441                         return errno == ENOENT ? 0 : -errno;
442
443                 return 0;
444         }
445
446         r = write_env_file_label("/etc/vconsole.conf", l);
447         return r;
448 }
449
450 static int write_data_x11(Context *c) {
451         _cleanup_fclose_ FILE *f = NULL;
452         _cleanup_free_ char *temp_path = NULL;
453         int r;
454
455         if (isempty(c->x11_layout) &&
456             isempty(c->x11_model) &&
457             isempty(c->x11_variant) &&
458             isempty(c->x11_options)) {
459
460                 if (unlink("/etc/X11/xorg.conf.d/00-keyboard.conf") < 0)
461                         return errno == ENOENT ? 0 : -errno;
462
463                 return 0;
464         }
465
466         mkdir_p_label("/etc/X11/xorg.conf.d", 0755);
467
468         r = fopen_temporary("/etc/X11/xorg.conf.d/00-keyboard.conf", &f, &temp_path);
469         if (r < 0)
470                 return r;
471
472         fchmod(fileno(f), 0644);
473
474         fputs("# Read and parsed by systemd-localed. It's probably wise not to edit this file\n"
475               "# manually too freely.\n"
476               "Section \"InputClass\"\n"
477               "        Identifier \"system-keyboard\"\n"
478               "        MatchIsKeyboard \"on\"\n", f);
479
480         if (!isempty(c->x11_layout))
481                 fprintf(f, "        Option \"XkbLayout\" \"%s\"\n", c->x11_layout);
482
483         if (!isempty(c->x11_model))
484                 fprintf(f, "        Option \"XkbModel\" \"%s\"\n", c->x11_model);
485
486         if (!isempty(c->x11_variant))
487                 fprintf(f, "        Option \"XkbVariant\" \"%s\"\n", c->x11_variant);
488
489         if (!isempty(c->x11_options))
490                 fprintf(f, "        Option \"XkbOptions\" \"%s\"\n", c->x11_options);
491
492         fputs("EndSection\n", f);
493         fflush(f);
494
495         if (ferror(f) || rename(temp_path, "/etc/X11/xorg.conf.d/00-keyboard.conf") < 0) {
496                 r = -errno;
497                 unlink("/etc/X11/xorg.conf.d/00-keyboard.conf");
498                 unlink(temp_path);
499                 return r;
500         } else
501                 return 0;
502 }
503
504 static int vconsole_reload(sd_bus *bus) {
505         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
506         int r;
507
508         assert(bus);
509
510         r = sd_bus_call_method(bus,
511                         "org.freedesktop.systemd1",
512                         "/org/freedesktop/systemd1",
513                         "org.freedesktop.systemd1.Manager",
514                         "RestartUnit",
515                         &error,
516                         NULL,
517                         "ss", "systemd-vconsole-setup.service", "replace");
518
519         if (r < 0)
520                 log_error("Failed to issue method call: %s", bus_error_message(&error, -r));
521         return r;
522 }
523
524 static char *strnulldash(const char *s) {
525         return s == NULL || *s == 0 || (s[0] == '-' && s[1] == 0) ? NULL : (char*) s;
526 }
527
528 static int read_next_mapping(FILE *f, unsigned *n, char ***a) {
529         assert(f);
530         assert(n);
531         assert(a);
532
533         for (;;) {
534                 char line[LINE_MAX];
535                 char *l, **b;
536
537                 errno = 0;
538                 if (!fgets(line, sizeof(line), f)) {
539
540                         if (ferror(f))
541                                 return errno ? -errno : -EIO;
542
543                         return 0;
544                 }
545
546                 (*n) ++;
547
548                 l = strstrip(line);
549                 if (l[0] == 0 || l[0] == '#')
550                         continue;
551
552                 b = strv_split_quoted(l);
553                 if (!b)
554                         return -ENOMEM;
555
556                 if (strv_length(b) < 5) {
557                         log_error("Invalid line "SYSTEMD_KBD_MODEL_MAP":%u, ignoring.", *n);
558                         strv_free(b);
559                         continue;
560
561                 }
562
563                 *a = b;
564                 return 1;
565         }
566 }
567
568 static int vconsole_convert_to_x11(Context *c, sd_bus *bus) {
569         bool modified = false;
570
571         assert(bus);
572
573         if (isempty(c->vc_keymap)) {
574
575                 modified =
576                         !isempty(c->x11_layout) ||
577                         !isempty(c->x11_model) ||
578                         !isempty(c->x11_variant) ||
579                         !isempty(c->x11_options);
580
581                 context_free_x11(c);
582         } else {
583                 _cleanup_fclose_ FILE *f = NULL;
584                 unsigned n = 0;
585
586                 f = fopen(SYSTEMD_KBD_MODEL_MAP, "re");
587                 if (!f)
588                         return -errno;
589
590                 for (;;) {
591                         _cleanup_strv_free_ char **a = NULL;
592                         int r;
593
594                         r = read_next_mapping(f, &n, &a);
595                         if (r < 0)
596                                 return r;
597                         if (r == 0)
598                                 break;
599
600                         if (!streq(c->vc_keymap, a[0]))
601                                 continue;
602
603                         if (!streq_ptr(c->x11_layout, strnulldash(a[1])) ||
604                             !streq_ptr(c->x11_model, strnulldash(a[2])) ||
605                             !streq_ptr(c->x11_variant, strnulldash(a[3])) ||
606                             !streq_ptr(c->x11_options, strnulldash(a[4]))) {
607
608                                 if (free_and_copy(&c->x11_layout, strnulldash(a[1])) < 0 ||
609                                     free_and_copy(&c->x11_model, strnulldash(a[2])) < 0 ||
610                                     free_and_copy(&c->x11_variant, strnulldash(a[3])) < 0 ||
611                                     free_and_copy(&c->x11_options, strnulldash(a[4])) < 0)
612                                         return -ENOMEM;
613
614                                 modified = true;
615                         }
616
617                         break;
618                 }
619         }
620
621         if (modified) {
622                 int r;
623
624                 r = write_data_x11(c);
625                 if (r < 0)
626                         log_error("Failed to set X11 keyboard layout: %s", strerror(-r));
627
628                 sd_bus_emit_properties_changed(bus,
629                                 "/org/freedesktop/locale1",
630                                 "org.freedesktop.locale1",
631                                 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL);
632         }
633
634         return 0;
635 }
636
637 static int find_converted_keymap(Context *c, char **new_keymap) {
638         const char *dir;
639         _cleanup_free_ char *n;
640
641         if (c->x11_variant)
642                 n = strjoin(c->x11_layout, "-", c->x11_variant, NULL);
643         else
644                 n = strdup(c->x11_layout);
645         if (!n)
646                 return -ENOMEM;
647
648         NULSTR_FOREACH(dir, KBD_KEYMAP_DIRS) {
649                 _cleanup_free_ char *p = NULL, *pz = NULL;
650
651                 p = strjoin(dir, "xkb/", n, ".map", NULL);
652                 pz = strjoin(dir, "xkb/", n, ".map.gz", NULL);
653                 if (!p || !pz)
654                         return -ENOMEM;
655
656                 if (access(p, F_OK) == 0 || access(pz, F_OK) == 0) {
657                         *new_keymap = n;
658                         n = NULL;
659                         return 1;
660                 }
661         }
662
663         return 0;
664 }
665
666 static int find_legacy_keymap(Context *c, char **new_keymap) {
667         _cleanup_fclose_ FILE *f;
668         unsigned n = 0;
669         unsigned best_matching = 0;
670
671
672         f = fopen(SYSTEMD_KBD_MODEL_MAP, "re");
673         if (!f)
674                 return -errno;
675
676         for (;;) {
677                 _cleanup_strv_free_ char **a = NULL;
678                 unsigned matching = 0;
679                 int r;
680
681                 r = read_next_mapping(f, &n, &a);
682                 if (r < 0)
683                         return r;
684                 if (r == 0)
685                         break;
686
687                 /* Determine how well matching this entry is */
688                 if (streq_ptr(c->x11_layout, a[1]))
689                         /* If we got an exact match, this is best */
690                         matching = 10;
691                 else {
692                         size_t x;
693
694                         x = strcspn(c->x11_layout, ",");
695
696                         /* We have multiple X layouts, look for an
697                          * entry that matches our key with everything
698                          * but the first layout stripped off. */
699                         if (x > 0 &&
700                             strlen(a[1]) == x &&
701                             strneq(c->x11_layout, a[1], x))
702                                 matching = 5;
703                         else  {
704                                 size_t w;
705
706                                 /* If that didn't work, strip off the
707                                  * other layouts from the entry, too */
708                                 w = strcspn(a[1], ",");
709
710                                 if (x > 0 && x == w &&
711                                     memcmp(c->x11_layout, a[1], x) == 0)
712                                         matching = 1;
713                         }
714                 }
715
716                 if (matching > 0) {
717                         if (isempty(c->x11_model) || streq_ptr(c->x11_model, a[2])) {
718                                 matching++;
719
720                                 if (streq_ptr(c->x11_variant, a[3])) {
721                                         matching++;
722
723                                         if (streq_ptr(c->x11_options, a[4]))
724                                                 matching++;
725                                 }
726                         }
727                 }
728
729                 /* The best matching entry so far, then let's save that */
730                 if (matching > best_matching) {
731                         best_matching = matching;
732
733                         free(*new_keymap);
734                         *new_keymap = strdup(a[0]);
735                         if (!*new_keymap)
736                                 return -ENOMEM;
737                 }
738         }
739
740         return 0;
741 }
742
743 static int x11_convert_to_vconsole(Context *c, sd_bus *bus) {
744         bool modified = false;
745         int r;
746
747         assert(bus);
748
749         if (isempty(c->x11_layout)) {
750
751                 modified =
752                         !isempty(c->vc_keymap) ||
753                         !isempty(c->vc_keymap_toggle);
754
755                 context_free_x11(c);
756         } else {
757                 char *new_keymap = NULL;
758
759                 r = find_converted_keymap(c, &new_keymap);
760                 if (r < 0)
761                         return r;
762                 else if (r == 0) {
763                         r = find_legacy_keymap(c, &new_keymap);
764                         if (r < 0)
765                                 return r;
766                 }
767
768                 if (!streq_ptr(c->vc_keymap, new_keymap)) {
769                         free_and_replace(&c->vc_keymap, new_keymap);
770                         free_and_replace(&c->vc_keymap_toggle, NULL);
771                         modified = true;
772                 } else
773                         free(new_keymap);
774         }
775
776         if (modified) {
777                 r = vconsole_write_data(c);
778                 if (r < 0)
779                         log_error("Failed to set virtual console keymap: %s", strerror(-r));
780
781                 sd_bus_emit_properties_changed(bus,
782                                 "/org/freedesktop/locale1",
783                                 "org.freedesktop.locale1",
784                                 "VConsoleKeymap", "VConsoleKeymapToggle", NULL);
785
786                 return vconsole_reload(bus);
787         }
788
789         return 0;
790 }
791
792 static int property_get_locale(
793                 sd_bus *bus,
794                 const char *path,
795                 const char *interface,
796                 const char *property,
797                 sd_bus_message *reply,
798                 void *userdata,
799                 sd_bus_error *error) {
800
801         Context *c = userdata;
802         _cleanup_strv_free_ char **l = NULL;
803         int p, q;
804
805         l = new0(char*, _LOCALE_MAX+1);
806         if (!l)
807                 return -ENOMEM;
808
809         for (p = 0, q = 0; p < _LOCALE_MAX; p++) {
810                 char *t;
811
812                 if (isempty(c->locale[p]))
813                         continue;
814
815                 if (asprintf(&t, "%s=%s", names[p], c->locale[p]) < 0)
816                         return -ENOMEM;
817
818                 l[q++] = t;
819         }
820
821         return sd_bus_message_append_strv(reply, l);
822 }
823
824 static int method_set_locale(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
825         Context *c = userdata;
826         _cleanup_strv_free_ char **l = NULL;
827         char **i;
828         int interactive;
829         bool modified = false;
830         bool passed[_LOCALE_MAX] = {};
831         int p;
832         int r;
833
834         r = bus_message_read_strv_extend(m, &l);
835         if (r < 0)
836                 return r;
837
838         r = sd_bus_message_read_basic(m, 'b', &interactive);
839         if (r < 0)
840                 return r;
841
842         /* Check whether a variable changed and if so valid */
843         STRV_FOREACH(i, l) {
844                 bool valid = false;
845
846                 for (p = 0; p < _LOCALE_MAX; p++) {
847                         size_t k;
848
849                         k = strlen(names[p]);
850                         if (startswith(*i, names[p]) &&
851                             (*i)[k] == '=' &&
852                             locale_is_valid((*i) + k + 1)) {
853                                 valid = true;
854                                 passed[p] = true;
855
856                                 if (!streq_ptr(*i + k + 1, c->locale[p]))
857                                         modified = true;
858
859                                 break;
860                         }
861                 }
862
863                 if (!valid)
864                         return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid Locale data.");
865         }
866
867         /* Check whether a variable is unset */
868         if (!modified)  {
869                 for (p = 0; p < _LOCALE_MAX; p++)
870                         if (!isempty(c->locale[p]) && !passed[p]) {
871                                 modified = true;
872                                 break;
873                         }
874         }
875
876         if (modified) {
877                 r = bus_verify_polkit_async(bus, &c->polkit_registry, m,
878                                             "org.freedesktop.locale1.set-locale", interactive,
879                                             error, method_set_locale, c);
880                 if (r < 0)
881                         return r;
882                 if (r == 0)
883                         return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
884
885                 STRV_FOREACH(i, l) {
886                         for (p = 0; p < _LOCALE_MAX; p++) {
887                                 size_t k;
888
889                                 k = strlen(names[p]);
890                                 if (startswith(*i, names[p]) && (*i)[k] == '=') {
891                                         char *t;
892
893                                         t = strdup(*i + k + 1);
894                                         if (!t)
895                                                 return -ENOMEM;
896
897                                         free(c->locale[p]);
898                                         c->locale[p] = t;
899                                         break;
900                                 }
901                         }
902                 }
903
904                 for (p = 0; p < _LOCALE_MAX; p++) {
905                         if (passed[p])
906                                 continue;
907
908                         free_and_replace(&c->locale[p], NULL);
909                 }
910
911                 locale_simplify(c);
912
913                 r = locale_write_data(c);
914                 if (r < 0) {
915                         log_error("Failed to set locale: %s", strerror(-r));
916                         return sd_bus_error_set_errnof(error, r, "Failed to set locale: %s", strerror(-r));
917                 }
918
919                 locale_update_system_manager(c, bus);
920
921                 log_info("Changed locale information.");
922
923                 sd_bus_emit_properties_changed(bus,
924                                 "/org/freedesktop/locale1",
925                                 "org.freedesktop.locale1",
926                                 "Locale", NULL);
927         }
928
929         return sd_bus_reply_method_return(m, NULL);
930 }
931
932 static int method_set_vc_keyboard(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
933         Context *c = userdata;
934         const char *keymap, *keymap_toggle;
935         int convert, interactive;
936         int r;
937
938         r = sd_bus_message_read(m, "ssbb", &keymap, &keymap_toggle, &convert, &interactive);
939         if (r < 0)
940                 return r;
941
942         if (isempty(keymap))
943                 keymap = NULL;
944
945         if (isempty(keymap_toggle))
946                 keymap_toggle = NULL;
947
948         if (!streq_ptr(keymap, c->vc_keymap) ||
949             !streq_ptr(keymap_toggle, c->vc_keymap_toggle)) {
950
951                 if ((keymap && (!filename_is_safe(keymap) || !string_is_safe(keymap))) ||
952                     (keymap_toggle && (!filename_is_safe(keymap_toggle) || !string_is_safe(keymap_toggle))))
953                         return sd_bus_error_set_errnof(error, -EINVAL, "Received invalid keymap data");
954
955                 r = bus_verify_polkit_async(bus, &c->polkit_registry, m,
956                                 "org.freedesktop.locale1.set-keyboard",
957                                 interactive, error, method_set_vc_keyboard, c);
958                 if (r < 0)
959                         return r;
960                 if (r == 0)
961                         return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
962
963                 if (free_and_copy(&c->vc_keymap, keymap) < 0 ||
964                     free_and_copy(&c->vc_keymap_toggle, keymap_toggle) < 0)
965                         return -ENOMEM;
966
967                 r = vconsole_write_data(c);
968                 if (r < 0) {
969                         log_error("Failed to set virtual console keymap: %s", strerror(-r));
970                         return sd_bus_error_set_errnof(error, r, "Failed to set virtual console keymap: %s", strerror(-r));
971                 }
972
973                 log_info("Changed virtual console keymap to '%s'", strempty(c->vc_keymap));
974
975                 r = vconsole_reload(bus);
976                 if (r < 0)
977                         log_error("Failed to request keymap reload: %s", strerror(-r));
978
979                 sd_bus_emit_properties_changed(bus,
980                                 "/org/freedesktop/locale1",
981                                 "org.freedesktop.locale1",
982                                 "VConsoleKeymap", "VConsoleKeymapToggle", NULL);
983
984                 if (convert) {
985                         r = vconsole_convert_to_x11(c, bus);
986                         if (r < 0)
987                                 log_error("Failed to convert keymap data: %s", strerror(-r));
988                 }
989         }
990
991         return sd_bus_reply_method_return(m, NULL);
992 }
993
994 static int method_set_x11_keyboard(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
995         Context *c = userdata;
996         const char *layout, *model, *variant, *options;
997         int convert, interactive;
998         int r;
999
1000         r = sd_bus_message_read(m, "ssssbb", &layout, &model, &variant, &options, &convert, &interactive);
1001         if (r < 0)
1002                 return r;
1003
1004         if (isempty(layout))
1005                 layout = NULL;
1006
1007         if (isempty(model))
1008                 model = NULL;
1009
1010         if (isempty(variant))
1011                 variant = NULL;
1012
1013         if (isempty(options))
1014                 options = NULL;
1015
1016         if (!streq_ptr(layout, c->x11_layout) ||
1017             !streq_ptr(model, c->x11_model) ||
1018             !streq_ptr(variant, c->x11_variant) ||
1019             !streq_ptr(options, c->x11_options)) {
1020
1021                 if ((layout && !string_is_safe(layout)) ||
1022                     (model && !string_is_safe(model)) ||
1023                     (variant && !string_is_safe(variant)) ||
1024                     (options && !string_is_safe(options)))
1025                         return sd_bus_error_set_errnof(error, -EINVAL, "Received invalid keyboard data");
1026
1027                 r = bus_verify_polkit_async(bus, &c->polkit_registry, m,
1028                                 "org.freedesktop.locale1.set-keyboard",
1029                                 interactive, error, method_set_x11_keyboard, c);
1030                 if (r < 0)
1031                         return r;
1032                 if (r == 0)
1033                         return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
1034
1035                 if (free_and_copy(&c->x11_layout, layout) < 0 ||
1036                     free_and_copy(&c->x11_model, model) < 0 ||
1037                     free_and_copy(&c->x11_variant, variant) < 0 ||
1038                     free_and_copy(&c->x11_options, options) < 0)
1039                         return -ENOMEM;
1040
1041                 r = write_data_x11(c);
1042                 if (r < 0) {
1043                         log_error("Failed to set X11 keyboard layout: %s", strerror(-r));
1044                         return sd_bus_error_set_errnof(error, r, "Failed to set X11 keyboard layout: %s", strerror(-r));
1045                 }
1046
1047                 log_info("Changed X11 keyboard layout to '%s'", strempty(c->x11_layout));
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_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         Context context = {};
1119         _cleanup_event_unref_ sd_event *event = NULL;
1120         _cleanup_bus_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         context_free(&context, bus);
1162
1163         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
1164 }