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