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