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