chiark / gitweb /
remove unused includes
[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 "bus-util.h"
37 #include "bus-error.h"
38 #include "bus-message.h"
39 #include "event-util.h"
40 #include "locale-util.h"
41 #include "selinux-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(const char* filename,
515                              unsigned min_fields, unsigned max_fields,
516                              FILE *f, unsigned *n, char ***a) {
517         assert(f);
518         assert(n);
519         assert(a);
520
521         for (;;) {
522                 char line[LINE_MAX];
523                 char *l, **b;
524                 int r;
525                 size_t length;
526
527                 errno = 0;
528                 if (!fgets(line, sizeof(line), f)) {
529
530                         if (ferror(f))
531                                 return errno ? -errno : -EIO;
532
533                         return 0;
534                 }
535
536                 (*n) ++;
537
538                 l = strstrip(line);
539                 if (l[0] == 0 || l[0] == '#')
540                         continue;
541
542                 r = strv_split_quoted(&b, l, false);
543                 if (r < 0)
544                         return r;
545
546                 length = strv_length(b);
547                 if (length < min_fields || length > max_fields) {
548                         log_error("Invalid line %s:%u, ignoring.", filename, *n);
549                         strv_free(b);
550                         continue;
551
552                 }
553
554                 *a = b;
555                 return 1;
556         }
557 }
558
559 static int vconsole_convert_to_x11(Context *c, sd_bus *bus) {
560         bool modified = false;
561
562         assert(bus);
563
564         if (isempty(c->vc_keymap)) {
565
566                 modified =
567                         !isempty(c->x11_layout) ||
568                         !isempty(c->x11_model) ||
569                         !isempty(c->x11_variant) ||
570                         !isempty(c->x11_options);
571
572                 context_free_x11(c);
573         } else {
574                 _cleanup_fclose_ FILE *f = NULL;
575                 unsigned n = 0;
576
577                 f = fopen(SYSTEMD_KBD_MODEL_MAP, "re");
578                 if (!f)
579                         return -errno;
580
581                 for (;;) {
582                         _cleanup_strv_free_ char **a = NULL;
583                         int r;
584
585                         r = read_next_mapping(SYSTEMD_KBD_MODEL_MAP, 5, UINT_MAX, f, &n, &a);
586                         if (r < 0)
587                                 return r;
588                         if (r == 0)
589                                 break;
590
591                         if (!streq(c->vc_keymap, a[0]))
592                                 continue;
593
594                         if (!streq_ptr(c->x11_layout, strnulldash(a[1])) ||
595                             !streq_ptr(c->x11_model, strnulldash(a[2])) ||
596                             !streq_ptr(c->x11_variant, strnulldash(a[3])) ||
597                             !streq_ptr(c->x11_options, strnulldash(a[4]))) {
598
599                                 if (free_and_strdup(&c->x11_layout, strnulldash(a[1])) < 0 ||
600                                     free_and_strdup(&c->x11_model, strnulldash(a[2])) < 0 ||
601                                     free_and_strdup(&c->x11_variant, strnulldash(a[3])) < 0 ||
602                                     free_and_strdup(&c->x11_options, strnulldash(a[4])) < 0)
603                                         return -ENOMEM;
604
605                                 modified = true;
606                         }
607
608                         break;
609                 }
610         }
611
612         if (modified) {
613                 int r;
614
615                 r = x11_write_data(c);
616                 if (r < 0)
617                         return log_error_errno(r, "Failed to set X11 keyboard layout: %m");
618
619                 log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
620                          strempty(c->x11_layout),
621                          strempty(c->x11_model),
622                          strempty(c->x11_variant),
623                          strempty(c->x11_options));
624
625                 sd_bus_emit_properties_changed(bus,
626                                 "/org/freedesktop/locale1",
627                                 "org.freedesktop.locale1",
628                                 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL);
629         } else
630                 log_debug("X11 keyboard layout was not modified.");
631
632         return 0;
633 }
634
635 static int find_converted_keymap(const char *x11_layout, const char *x11_variant, char **new_keymap) {
636         const char *dir;
637         _cleanup_free_ char *n;
638
639         if (x11_variant)
640                 n = strjoin(x11_layout, "-", x11_variant, NULL);
641         else
642                 n = strdup(x11_layout);
643         if (!n)
644                 return -ENOMEM;
645
646         NULSTR_FOREACH(dir, KBD_KEYMAP_DIRS) {
647                 _cleanup_free_ char *p = NULL, *pz = NULL;
648                 bool uncompressed;
649
650                 p = strjoin(dir, "xkb/", n, ".map", NULL);
651                 pz = strjoin(dir, "xkb/", n, ".map.gz", NULL);
652                 if (!p || !pz)
653                         return -ENOMEM;
654
655                 uncompressed = access(p, F_OK) == 0;
656                 if (uncompressed || access(pz, F_OK) == 0) {
657                         log_debug("Found converted keymap %s at %s",
658                                   n, uncompressed ? p : pz);
659
660                         *new_keymap = n;
661                         n = NULL;
662                         return 1;
663                 }
664         }
665
666         return 0;
667 }
668
669 static int find_legacy_keymap(Context *c, char **new_keymap) {
670         _cleanup_fclose_ FILE *f;
671         unsigned n = 0;
672         unsigned best_matching = 0;
673         int r;
674
675         f = fopen(SYSTEMD_KBD_MODEL_MAP, "re");
676         if (!f)
677                 return -errno;
678
679         for (;;) {
680                 _cleanup_strv_free_ char **a = NULL;
681                 unsigned matching = 0;
682
683                 r = read_next_mapping(SYSTEMD_KBD_MODEL_MAP, 5, UINT_MAX, 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                         /* We have multiple X layouts, look for an
695                          * entry that matches our key with everything
696                          * but the first layout stripped off. */
697                         if (startswith_comma(c->x11_layout, a[1]))
698                                 matching = 5;
699                         else  {
700                                 char *x;
701
702                                 /* If that didn't work, strip off the
703                                  * other layouts from the entry, too */
704                                 x = strndupa(a[1], strcspn(a[1], ","));
705                                 if (startswith_comma(c->x11_layout, x))
706                                         matching = 1;
707                         }
708                 }
709
710                 if (matching > 0) {
711                         if (isempty(c->x11_model) || streq_ptr(c->x11_model, a[2])) {
712                                 matching++;
713
714                                 if (streq_ptr(c->x11_variant, a[3])) {
715                                         matching++;
716
717                                         if (streq_ptr(c->x11_options, a[4]))
718                                                 matching++;
719                                 }
720                         }
721                 }
722
723                 /* The best matching entry so far, then let's save that */
724                 if (matching >= MAX(best_matching, 1u)) {
725                         log_debug("Found legacy keymap %s with score %u",
726                                   a[0], matching);
727
728                         if (matching > best_matching) {
729                                 best_matching = matching;
730
731                                 r = free_and_strdup(new_keymap, a[0]);
732                                 if (r < 0)
733                                         return r;
734                         }
735                 }
736         }
737
738         if (best_matching < 10 && c->x11_layout) {
739                 /* The best match is only the first part of the X11
740                  * keymap. Check if we have a converted map which
741                  * matches just the first layout.
742                  */
743                 char *l, *v = NULL, *converted;
744
745                 l = strndupa(c->x11_layout, strcspn(c->x11_layout, ","));
746                 if (c->x11_variant)
747                         v = strndupa(c->x11_variant, strcspn(c->x11_variant, ","));
748                 r = find_converted_keymap(l, v, &converted);
749                 if (r < 0)
750                         return r;
751                 if (r > 0)
752                         free_and_replace(new_keymap, converted);
753         }
754
755         return 0;
756 }
757
758 static int find_language_fallback(const char *lang, char **language) {
759         _cleanup_fclose_ FILE *f = NULL;
760         unsigned n = 0;
761
762         assert(language);
763
764         f = fopen(SYSTEMD_LANGUAGE_FALLBACK_MAP, "re");
765         if (!f)
766                 return -errno;
767
768         for (;;) {
769                 _cleanup_strv_free_ char **a = NULL;
770                 int r;
771
772                 r = read_next_mapping(SYSTEMD_LANGUAGE_FALLBACK_MAP, 2, 2, f, &n, &a);
773                 if (r <= 0)
774                         return r;
775
776                 if (streq(lang, a[0])) {
777                         assert(strv_length(a) == 2);
778                         *language = a[1];
779                         a[1] = NULL;
780                         return 1;
781                 }
782         }
783
784         assert_not_reached("should not be here");
785 }
786
787 static int x11_convert_to_vconsole(Context *c, sd_bus *bus) {
788         bool modified = false;
789         int r;
790
791         assert(bus);
792
793         if (isempty(c->x11_layout)) {
794
795                 modified =
796                         !isempty(c->vc_keymap) ||
797                         !isempty(c->vc_keymap_toggle);
798
799                 context_free_x11(c);
800         } else {
801                 char *new_keymap = NULL;
802
803                 r = find_converted_keymap(c->x11_layout, c->x11_variant, &new_keymap);
804                 if (r < 0)
805                         return r;
806                 else if (r == 0) {
807                         r = find_legacy_keymap(c, &new_keymap);
808                         if (r < 0)
809                                 return r;
810                 }
811
812                 if (!streq_ptr(c->vc_keymap, new_keymap)) {
813                         free_and_replace(&c->vc_keymap, new_keymap);
814                         free_and_replace(&c->vc_keymap_toggle, NULL);
815                         modified = true;
816                 } else
817                         free(new_keymap);
818         }
819
820         if (modified) {
821                 r = vconsole_write_data(c);
822                 if (r < 0)
823                         log_error_errno(r, "Failed to set virtual console keymap: %m");
824
825                 log_info("Changed virtual console keymap to '%s' toggle '%s'",
826                          strempty(c->vc_keymap), strempty(c->vc_keymap_toggle));
827
828                 sd_bus_emit_properties_changed(bus,
829                                 "/org/freedesktop/locale1",
830                                 "org.freedesktop.locale1",
831                                 "VConsoleKeymap", "VConsoleKeymapToggle", NULL);
832
833                 return vconsole_reload(bus);
834         } else
835                 log_debug("Virtual console keymap was not modified.");
836
837         return 0;
838 }
839
840 static int property_get_locale(
841                 sd_bus *bus,
842                 const char *path,
843                 const char *interface,
844                 const char *property,
845                 sd_bus_message *reply,
846                 void *userdata,
847                 sd_bus_error *error) {
848
849         Context *c = userdata;
850         _cleanup_strv_free_ char **l = NULL;
851         int p, q;
852
853         l = new0(char*, _LOCALE_MAX+1);
854         if (!l)
855                 return -ENOMEM;
856
857         for (p = 0, q = 0; p < _LOCALE_MAX; p++) {
858                 char *t;
859
860                 if (isempty(c->locale[p]))
861                         continue;
862
863                 if (asprintf(&t, "%s=%s", names[p], c->locale[p]) < 0)
864                         return -ENOMEM;
865
866                 l[q++] = t;
867         }
868
869         return sd_bus_message_append_strv(reply, l);
870 }
871
872 static int method_set_locale(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
873         Context *c = userdata;
874         _cleanup_strv_free_ char **l = NULL;
875         char **i;
876         const char *lang = NULL;
877         int interactive;
878         bool modified = false;
879         bool have[_LOCALE_MAX] = {};
880         int p;
881         int r;
882
883         r = bus_message_read_strv_extend(m, &l);
884         if (r < 0)
885                 return r;
886
887         r = sd_bus_message_read_basic(m, 'b', &interactive);
888         if (r < 0)
889                 return r;
890
891         /* Check whether a variable changed and if it is valid */
892         STRV_FOREACH(i, l) {
893                 bool valid = false;
894
895                 for (p = 0; p < _LOCALE_MAX; p++) {
896                         size_t k;
897
898                         k = strlen(names[p]);
899                         if (startswith(*i, names[p]) &&
900                             (*i)[k] == '=' &&
901                             locale_is_valid((*i) + k + 1)) {
902                                 valid = true;
903                                 have[p] = true;
904
905                                 if (p == LOCALE_LANG)
906                                         lang = (*i) + k + 1;
907
908                                 if (!streq_ptr(*i + k + 1, c->locale[p]))
909                                         modified = true;
910
911                                 break;
912                         }
913                 }
914
915                 if (!valid)
916                         return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid Locale data.");
917         }
918
919         /* If LANG was specified, but not LANGUAGE, check if we should
920          * set it based on the language fallback table. */
921         if (have[LOCALE_LANG] && !have[LOCALE_LANGUAGE]) {
922                 _cleanup_free_ char *language = NULL;
923
924                 assert(lang);
925
926                 (void) find_language_fallback(lang, &language);
927                 if (language) {
928                         log_debug("Converted LANG=%s to LANGUAGE=%s", lang, language);
929                         if (!streq_ptr(language, c->locale[LOCALE_LANGUAGE])) {
930                                 r = strv_extendf(&l, "LANGUAGE=%s", language);
931                                 if (r < 0)
932                                         return r;
933
934                                 have[LOCALE_LANGUAGE] = true;
935                                 modified = true;
936                         }
937                 }
938         }
939
940         /* Check whether a variable is unset */
941         if (!modified)
942                 for (p = 0; p < _LOCALE_MAX; p++)
943                         if (!isempty(c->locale[p]) && !have[p]) {
944                                 modified = true;
945                                 break;
946                         }
947
948         if (modified) {
949                 _cleanup_strv_free_ char **settings = NULL;
950
951                 r = bus_verify_polkit_async(
952                                 m,
953                                 CAP_SYS_ADMIN,
954                                 "org.freedesktop.locale1.set-locale",
955                                 interactive,
956                                 UID_INVALID,
957                                 &c->polkit_registry,
958                                 error);
959                 if (r < 0)
960                         return r;
961                 if (r == 0)
962                         return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
963
964                 STRV_FOREACH(i, l)
965                         for (p = 0; p < _LOCALE_MAX; p++) {
966                                 size_t k;
967
968                                 k = strlen(names[p]);
969                                 if (startswith(*i, names[p]) && (*i)[k] == '=') {
970                                         r = free_and_strdup(&c->locale[p], *i + k + 1);
971                                         if (r < 0)
972                                                 return r;
973                                         break;
974                                 }
975                         }
976
977                 for (p = 0; p < _LOCALE_MAX; p++) {
978                         if (have[p])
979                                 continue;
980
981                         free_and_replace(&c->locale[p], NULL);
982                 }
983
984                 locale_simplify(c);
985
986                 r = locale_write_data(c, &settings);
987                 if (r < 0) {
988                         log_error_errno(r, "Failed to set locale: %m");
989                         return sd_bus_error_set_errnof(error, r, "Failed to set locale: %s", strerror(-r));
990                 }
991
992                 locale_update_system_manager(c, bus);
993
994                 if (settings) {
995                         _cleanup_free_ char *line;
996
997                         line = strv_join(settings, ", ");
998                         log_info("Changed locale to %s.", strnull(line));
999                 } else
1000                         log_info("Changed locale to unset.");
1001
1002                 sd_bus_emit_properties_changed(bus,
1003                                 "/org/freedesktop/locale1",
1004                                 "org.freedesktop.locale1",
1005                                 "Locale", NULL);
1006         } else
1007                 log_debug("Locale settings were not modified.");
1008
1009
1010         return sd_bus_reply_method_return(m, NULL);
1011 }
1012
1013 static int method_set_vc_keyboard(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
1014         Context *c = userdata;
1015         const char *keymap, *keymap_toggle;
1016         int convert, interactive;
1017         int r;
1018
1019         r = sd_bus_message_read(m, "ssbb", &keymap, &keymap_toggle, &convert, &interactive);
1020         if (r < 0)
1021                 return r;
1022
1023         if (isempty(keymap))
1024                 keymap = NULL;
1025
1026         if (isempty(keymap_toggle))
1027                 keymap_toggle = NULL;
1028
1029         if (!streq_ptr(keymap, c->vc_keymap) ||
1030             !streq_ptr(keymap_toggle, c->vc_keymap_toggle)) {
1031
1032                 if ((keymap && (!filename_is_valid(keymap) || !string_is_safe(keymap))) ||
1033                     (keymap_toggle && (!filename_is_valid(keymap_toggle) || !string_is_safe(keymap_toggle))))
1034                         return sd_bus_error_set_errnof(error, -EINVAL, "Received invalid keymap data");
1035
1036                 r = bus_verify_polkit_async(
1037                                 m,
1038                                 CAP_SYS_ADMIN,
1039                                 "org.freedesktop.locale1.set-keyboard",
1040                                 interactive,
1041                                 UID_INVALID,
1042                                 &c->polkit_registry,
1043                                 error);
1044                 if (r < 0)
1045                         return r;
1046                 if (r == 0)
1047                         return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
1048
1049                 if (free_and_strdup(&c->vc_keymap, keymap) < 0 ||
1050                     free_and_strdup(&c->vc_keymap_toggle, keymap_toggle) < 0)
1051                         return -ENOMEM;
1052
1053                 r = vconsole_write_data(c);
1054                 if (r < 0) {
1055                         log_error_errno(r, "Failed to set virtual console keymap: %m");
1056                         return sd_bus_error_set_errnof(error, r, "Failed to set virtual console keymap: %s", strerror(-r));
1057                 }
1058
1059                 log_info("Changed virtual console keymap to '%s' toggle '%s'",
1060                          strempty(c->vc_keymap), strempty(c->vc_keymap_toggle));
1061
1062                 r = vconsole_reload(bus);
1063                 if (r < 0)
1064                         log_error_errno(r, "Failed to request keymap reload: %m");
1065
1066                 sd_bus_emit_properties_changed(bus,
1067                                 "/org/freedesktop/locale1",
1068                                 "org.freedesktop.locale1",
1069                                 "VConsoleKeymap", "VConsoleKeymapToggle", NULL);
1070
1071                 if (convert) {
1072                         r = vconsole_convert_to_x11(c, bus);
1073                         if (r < 0)
1074                                 log_error_errno(r, "Failed to convert keymap data: %m");
1075                 }
1076         }
1077
1078         return sd_bus_reply_method_return(m, NULL);
1079 }
1080
1081 #ifdef HAVE_XKBCOMMON
1082 static void log_xkb(struct xkb_context *ctx, enum xkb_log_level lvl, const char *format, va_list args) {
1083         const char *fmt;
1084
1085         fmt = strjoina("libxkbcommon: ", format);
1086         log_internalv(LOG_DEBUG, 0, __FILE__, __LINE__, __func__, fmt, args);
1087 }
1088
1089 static int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, const char *options) {
1090         const struct xkb_rule_names rmlvo = {
1091                 .model          = model,
1092                 .layout         = layout,
1093                 .variant        = variant,
1094                 .options        = options,
1095         };
1096         struct xkb_context *ctx = NULL;
1097         struct xkb_keymap *km = NULL;
1098         int r;
1099
1100         /* compile keymap from RMLVO information to check out its validity */
1101
1102         ctx = xkb_context_new(XKB_CONTEXT_NO_ENVIRONMENT_NAMES);
1103         if (!ctx) {
1104                 r = -ENOMEM;
1105                 goto exit;
1106         }
1107
1108         xkb_context_set_log_fn(ctx, log_xkb);
1109
1110         km = xkb_keymap_new_from_names(ctx, &rmlvo, XKB_KEYMAP_COMPILE_NO_FLAGS);
1111         if (!km) {
1112                 r = -EINVAL;
1113                 goto exit;
1114         }
1115
1116         r = 0;
1117
1118 exit:
1119         xkb_keymap_unref(km);
1120         xkb_context_unref(ctx);
1121         return r;
1122 }
1123 #else
1124 static int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, const char *options) {
1125         return 0;
1126 }
1127 #endif
1128
1129 static int method_set_x11_keyboard(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
1130         Context *c = userdata;
1131         const char *layout, *model, *variant, *options;
1132         int convert, interactive;
1133         int r;
1134
1135         r = sd_bus_message_read(m, "ssssbb", &layout, &model, &variant, &options, &convert, &interactive);
1136         if (r < 0)
1137                 return r;
1138
1139         if (isempty(layout))
1140                 layout = NULL;
1141
1142         if (isempty(model))
1143                 model = NULL;
1144
1145         if (isempty(variant))
1146                 variant = NULL;
1147
1148         if (isempty(options))
1149                 options = NULL;
1150
1151         if (!streq_ptr(layout, c->x11_layout) ||
1152             !streq_ptr(model, c->x11_model) ||
1153             !streq_ptr(variant, c->x11_variant) ||
1154             !streq_ptr(options, c->x11_options)) {
1155
1156                 if ((layout && !string_is_safe(layout)) ||
1157                     (model && !string_is_safe(model)) ||
1158                     (variant && !string_is_safe(variant)) ||
1159                     (options && !string_is_safe(options)))
1160                         return sd_bus_error_set_errnof(error, -EINVAL, "Received invalid keyboard data");
1161
1162                 r = bus_verify_polkit_async(
1163                                 m,
1164                                 CAP_SYS_ADMIN,
1165                                 "org.freedesktop.locale1.set-keyboard",
1166                                 interactive,
1167                                 UID_INVALID,
1168                                 &c->polkit_registry,
1169                                 error);
1170                 if (r < 0)
1171                         return r;
1172                 if (r == 0)
1173                         return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
1174
1175                 r = verify_xkb_rmlvo(model, layout, variant, options);
1176                 if (r < 0) {
1177                         log_error_errno(r, "Cannot compile XKB keymap for new x11 keyboard layout ('%s' / '%s' / '%s' / '%s'): %m",
1178                                         strempty(model), strempty(layout), strempty(variant), strempty(options));
1179                         return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Cannot compile XKB keymap, refusing");
1180                 }
1181
1182                 if (free_and_strdup(&c->x11_layout, layout) < 0 ||
1183                     free_and_strdup(&c->x11_model, model) < 0 ||
1184                     free_and_strdup(&c->x11_variant, variant) < 0 ||
1185                     free_and_strdup(&c->x11_options, options) < 0)
1186                         return -ENOMEM;
1187
1188                 r = x11_write_data(c);
1189                 if (r < 0) {
1190                         log_error_errno(r, "Failed to set X11 keyboard layout: %m");
1191                         return sd_bus_error_set_errnof(error, r, "Failed to set X11 keyboard layout: %s", strerror(-r));
1192                 }
1193
1194                 log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
1195                          strempty(c->x11_layout),
1196                          strempty(c->x11_model),
1197                          strempty(c->x11_variant),
1198                          strempty(c->x11_options));
1199
1200                 sd_bus_emit_properties_changed(bus,
1201                                 "/org/freedesktop/locale1",
1202                                 "org.freedesktop.locale1",
1203                                 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL);
1204
1205                 if (convert) {
1206                         r = x11_convert_to_vconsole(c, bus);
1207                         if (r < 0)
1208                                 log_error_errno(r, "Failed to convert keymap data: %m");
1209                 }
1210         }
1211
1212         return sd_bus_reply_method_return(m, NULL);
1213 }
1214
1215 static const sd_bus_vtable locale_vtable[] = {
1216         SD_BUS_VTABLE_START(0),
1217         SD_BUS_PROPERTY("Locale", "as", property_get_locale, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1218         SD_BUS_PROPERTY("X11Layout", "s", NULL, offsetof(Context, x11_layout), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1219         SD_BUS_PROPERTY("X11Model", "s", NULL, offsetof(Context, x11_model), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1220         SD_BUS_PROPERTY("X11Variant", "s", NULL, offsetof(Context, x11_variant), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1221         SD_BUS_PROPERTY("X11Options", "s", NULL, offsetof(Context, x11_options), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1222         SD_BUS_PROPERTY("VConsoleKeymap", "s", NULL, offsetof(Context, vc_keymap), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1223         SD_BUS_PROPERTY("VConsoleKeymapToggle", "s", NULL, offsetof(Context, vc_keymap_toggle), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1224         SD_BUS_METHOD("SetLocale", "asb", NULL, method_set_locale, SD_BUS_VTABLE_UNPRIVILEGED),
1225         SD_BUS_METHOD("SetVConsoleKeyboard", "ssbb", NULL, method_set_vc_keyboard, SD_BUS_VTABLE_UNPRIVILEGED),
1226         SD_BUS_METHOD("SetX11Keyboard", "ssssbb", NULL, method_set_x11_keyboard, SD_BUS_VTABLE_UNPRIVILEGED),
1227         SD_BUS_VTABLE_END
1228 };
1229
1230 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
1231         _cleanup_bus_close_unref_ sd_bus *bus = NULL;
1232         int r;
1233
1234         assert(c);
1235         assert(event);
1236         assert(_bus);
1237
1238         r = sd_bus_default_system(&bus);
1239         if (r < 0)
1240                 return log_error_errno(r, "Failed to get system bus connection: %m");
1241
1242         r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/locale1", "org.freedesktop.locale1", locale_vtable, c);
1243         if (r < 0)
1244                 return log_error_errno(r, "Failed to register object: %m");
1245
1246         r = sd_bus_request_name(bus, "org.freedesktop.locale1", 0);
1247         if (r < 0)
1248                 return log_error_errno(r, "Failed to register name: %m");
1249
1250         r = sd_bus_attach_event(bus, event, 0);
1251         if (r < 0)
1252                 return log_error_errno(r, "Failed to attach bus to event loop: %m");
1253
1254         *_bus = bus;
1255         bus = NULL;
1256
1257         return 0;
1258 }
1259
1260 int main(int argc, char *argv[]) {
1261         _cleanup_(context_free) Context context = {};
1262         _cleanup_event_unref_ sd_event *event = NULL;
1263         _cleanup_bus_close_unref_ sd_bus *bus = NULL;
1264         int r;
1265
1266         log_set_target(LOG_TARGET_AUTO);
1267         log_parse_environment();
1268         log_open();
1269
1270         umask(0022);
1271         mac_selinux_init("/etc");
1272
1273         if (argc != 1) {
1274                 log_error("This program takes no arguments.");
1275                 r = -EINVAL;
1276                 goto finish;
1277         }
1278
1279         r = sd_event_default(&event);
1280         if (r < 0) {
1281                 log_error_errno(r, "Failed to allocate event loop: %m");
1282                 goto finish;
1283         }
1284
1285         sd_event_set_watchdog(event, true);
1286
1287         r = connect_bus(&context, event, &bus);
1288         if (r < 0)
1289                 goto finish;
1290
1291         r = context_read_data(&context);
1292         if (r < 0) {
1293                 log_error_errno(r, "Failed to read locale data: %m");
1294                 goto finish;
1295         }
1296
1297         r = bus_event_loop_with_idle(event, bus, "org.freedesktop.locale1", DEFAULT_EXIT_USEC, NULL, NULL);
1298         if (r < 0) {
1299                 log_error_errno(r, "Failed to run event loop: %m");
1300                 goto finish;
1301         }
1302
1303 finish:
1304         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
1305 }