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