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