chiark / gitweb /
sysusers: fix uninitialized warning
[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(NULL, "/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, &m,
370                         "org.freedesktop.systemd1",
371                         "/org/freedesktop/systemd1",
372                         "org.freedesktop.systemd1.Manager",
373                         "UnsetAndSetEnvironment");
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(NULL, "/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                         if (isempty(c->x11_model) || 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
728                 /* The best matching entry so far, then let's save that */
729                 if (matching > best_matching) {
730                         best_matching = matching;
731
732                         free(*new_keymap);
733                         *new_keymap = strdup(a[0]);
734                         if (!*new_keymap)
735                                 return -ENOMEM;
736                 }
737         }
738
739         return 0;
740 }
741
742 static int x11_convert_to_vconsole(Context *c, sd_bus *bus) {
743         bool modified = false;
744         int r;
745
746         assert(bus);
747
748         if (isempty(c->x11_layout)) {
749
750                 modified =
751                         !isempty(c->vc_keymap) ||
752                         !isempty(c->vc_keymap_toggle);
753
754                 context_free_x11(c);
755         } else {
756                 char *new_keymap = NULL;
757
758                 r = find_converted_keymap(c, &new_keymap);
759                 if (r < 0)
760                         return r;
761                 else if (r == 0) {
762                         r = find_legacy_keymap(c, &new_keymap);
763                         if (r < 0)
764                                 return r;
765                 }
766
767                 if (!streq_ptr(c->vc_keymap, new_keymap)) {
768                         free_and_replace(&c->vc_keymap, new_keymap);
769                         free_and_replace(&c->vc_keymap_toggle, NULL);
770                         modified = true;
771                 } else
772                         free(new_keymap);
773         }
774
775         if (modified) {
776                 r = vconsole_write_data(c);
777                 if (r < 0)
778                         log_error("Failed to set virtual console keymap: %s", strerror(-r));
779
780                 sd_bus_emit_properties_changed(bus,
781                                 "/org/freedesktop/locale1",
782                                 "org.freedesktop.locale1",
783                                 "VConsoleKeymap", "VConsoleKeymapToggle", NULL);
784
785                 return vconsole_reload(bus);
786         }
787
788         return 0;
789 }
790
791 static int property_get_locale(
792                 sd_bus *bus,
793                 const char *path,
794                 const char *interface,
795                 const char *property,
796                 sd_bus_message *reply,
797                 void *userdata,
798                 sd_bus_error *error) {
799
800         Context *c = userdata;
801         _cleanup_strv_free_ char **l = NULL;
802         int p, q;
803
804         l = new0(char*, _LOCALE_MAX+1);
805         if (!l)
806                 return -ENOMEM;
807
808         for (p = 0, q = 0; p < _LOCALE_MAX; p++) {
809                 char *t;
810
811                 if (isempty(c->locale[p]))
812                         continue;
813
814                 if (asprintf(&t, "%s=%s", names[p], c->locale[p]) < 0)
815                         return -ENOMEM;
816
817                 l[q++] = t;
818         }
819
820         return sd_bus_message_append_strv(reply, l);
821 }
822
823 static int method_set_locale(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
824         Context *c = userdata;
825         _cleanup_strv_free_ char **l = NULL;
826         char **i;
827         int interactive;
828         bool modified = false;
829         bool passed[_LOCALE_MAX] = {};
830         int p;
831         int r;
832
833         r = bus_message_read_strv_extend(m, &l);
834         if (r < 0)
835                 return r;
836
837         r = sd_bus_message_read_basic(m, 'b', &interactive);
838         if (r < 0)
839                 return r;
840
841         /* Check whether a variable changed and if so valid */
842         STRV_FOREACH(i, l) {
843                 bool valid = false;
844
845                 for (p = 0; p < _LOCALE_MAX; p++) {
846                         size_t k;
847
848                         k = strlen(names[p]);
849                         if (startswith(*i, names[p]) &&
850                             (*i)[k] == '=' &&
851                             string_is_safe((*i) + k + 1)) {
852                                 valid = true;
853                                 passed[p] = true;
854
855                                 if (!streq_ptr(*i + k + 1, c->locale[p]))
856                                         modified = true;
857
858                                 break;
859                         }
860                 }
861
862                 if (!valid)
863                         return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid Locale data.");
864         }
865
866         /* Check whether a variable is unset */
867         if (!modified)  {
868                 for (p = 0; p < _LOCALE_MAX; p++)
869                         if (!isempty(c->locale[p]) && !passed[p]) {
870                                 modified = true;
871                                 break;
872                         }
873         }
874
875         if (modified) {
876                 r = bus_verify_polkit_async(bus, &c->polkit_registry, m,
877                                             "org.freedesktop.locale1.set-locale", interactive,
878                                             error, method_set_locale, c);
879                 if (r < 0)
880                         return r;
881                 if (r == 0)
882                         return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
883
884                 STRV_FOREACH(i, l) {
885                         for (p = 0; p < _LOCALE_MAX; p++) {
886                                 size_t k;
887
888                                 k = strlen(names[p]);
889                                 if (startswith(*i, names[p]) && (*i)[k] == '=') {
890                                         char *t;
891
892                                         t = strdup(*i + k + 1);
893                                         if (!t)
894                                                 return -ENOMEM;
895
896                                         free(c->locale[p]);
897                                         c->locale[p] = t;
898                                         break;
899                                 }
900                         }
901                 }
902
903                 for (p = 0; p < _LOCALE_MAX; p++) {
904                         if (passed[p])
905                                 continue;
906
907                         free_and_replace(&c->locale[p], NULL);
908                 }
909
910                 locale_simplify(c);
911
912                 r = locale_write_data(c);
913                 if (r < 0) {
914                         log_error("Failed to set locale: %s", strerror(-r));
915                         return sd_bus_error_set_errnof(error, r, "Failed to set locale: %s", strerror(-r));
916                 }
917
918                 locale_update_system_manager(c, bus);
919
920                 log_info("Changed locale information.");
921
922                 sd_bus_emit_properties_changed(bus,
923                                 "/org/freedesktop/locale1",
924                                 "org.freedesktop.locale1",
925                                 "Locale", NULL);
926         }
927
928         return sd_bus_reply_method_return(m, NULL);
929 }
930
931 static int method_set_vc_keyboard(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
932         Context *c = userdata;
933         const char *keymap, *keymap_toggle;
934         int convert, interactive;
935         int r;
936
937         r = sd_bus_message_read(m, "ssbb", &keymap, &keymap_toggle, &convert, &interactive);
938         if (r < 0)
939                 return r;
940
941         if (isempty(keymap))
942                 keymap = NULL;
943
944         if (isempty(keymap_toggle))
945                 keymap_toggle = NULL;
946
947         if (!streq_ptr(keymap, c->vc_keymap) ||
948             !streq_ptr(keymap_toggle, c->vc_keymap_toggle)) {
949
950                 if ((keymap && (!filename_is_safe(keymap) || !string_is_safe(keymap))) ||
951                     (keymap_toggle && (!filename_is_safe(keymap_toggle) || !string_is_safe(keymap_toggle))))
952                         return sd_bus_error_set_errnof(error, -EINVAL, "Received invalid keymap data");
953
954                 r = bus_verify_polkit_async(bus, &c->polkit_registry, m,
955                                 "org.freedesktop.locale1.set-keyboard",
956                                 interactive, error, method_set_vc_keyboard, c);
957                 if (r < 0)
958                         return r;
959                 if (r == 0)
960                         return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
961
962                 if (free_and_copy(&c->vc_keymap, keymap) < 0 ||
963                     free_and_copy(&c->vc_keymap_toggle, keymap_toggle) < 0)
964                         return -ENOMEM;
965
966                 r = vconsole_write_data(c);
967                 if (r < 0) {
968                         log_error("Failed to set virtual console keymap: %s", strerror(-r));
969                         return sd_bus_error_set_errnof(error, r, "Failed to set virtual console keymap: %s", strerror(-r));
970                 }
971
972                 log_info("Changed virtual console keymap to '%s'", strempty(c->vc_keymap));
973
974                 r = vconsole_reload(bus);
975                 if (r < 0)
976                         log_error("Failed to request keymap reload: %s", strerror(-r));
977
978                 sd_bus_emit_properties_changed(bus,
979                                 "/org/freedesktop/locale1",
980                                 "org.freedesktop.locale1",
981                                 "VConsoleKeymap", "VConsoleKeymapToggle", NULL);
982
983                 if (convert) {
984                         r = vconsole_convert_to_x11(c, bus);
985                         if (r < 0)
986                                 log_error("Failed to convert keymap data: %s", strerror(-r));
987                 }
988         }
989
990         return sd_bus_reply_method_return(m, NULL);
991 }
992
993 static int method_set_x11_keyboard(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
994         Context *c = userdata;
995         const char *layout, *model, *variant, *options;
996         int convert, interactive;
997         int r;
998
999         r = sd_bus_message_read(m, "ssssbb", &layout, &model, &variant, &options, &convert, &interactive);
1000         if (r < 0)
1001                 return r;
1002
1003         if (isempty(layout))
1004                 layout = NULL;
1005
1006         if (isempty(model))
1007                 model = NULL;
1008
1009         if (isempty(variant))
1010                 variant = NULL;
1011
1012         if (isempty(options))
1013                 options = NULL;
1014
1015         if (!streq_ptr(layout, c->x11_layout) ||
1016             !streq_ptr(model, c->x11_model) ||
1017             !streq_ptr(variant, c->x11_variant) ||
1018             !streq_ptr(options, c->x11_options)) {
1019
1020                 if ((layout && !string_is_safe(layout)) ||
1021                     (model && !string_is_safe(model)) ||
1022                     (variant && !string_is_safe(variant)) ||
1023                     (options && !string_is_safe(options)))
1024                         return sd_bus_error_set_errnof(error, -EINVAL, "Received invalid keyboard data");
1025
1026                 r = bus_verify_polkit_async(bus, &c->polkit_registry, m,
1027                                 "org.freedesktop.locale1.set-keyboard",
1028                                 interactive, error, method_set_x11_keyboard, c);
1029                 if (r < 0)
1030                         return r;
1031                 if (r == 0)
1032                         return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
1033
1034                 if (free_and_copy(&c->x11_layout, layout) < 0 ||
1035                     free_and_copy(&c->x11_model, model) < 0 ||
1036                     free_and_copy(&c->x11_variant, variant) < 0 ||
1037                     free_and_copy(&c->x11_options, options) < 0)
1038                         return -ENOMEM;
1039
1040                 r = write_data_x11(c);
1041                 if (r < 0) {
1042                         log_error("Failed to set X11 keyboard layout: %s", strerror(-r));
1043                         return sd_bus_error_set_errnof(error, r, "Failed to set X11 keyboard layout: %s", strerror(-r));
1044                 }
1045
1046                 log_info("Changed X11 keyboard layout to '%s'", strempty(c->x11_layout));
1047
1048                 sd_bus_emit_properties_changed(bus,
1049                                 "/org/freedesktop/locale1",
1050                                 "org.freedesktop.locale1",
1051                                 "X11Layout" "X11Model" "X11Variant" "X11Options", NULL);
1052
1053                 if (convert) {
1054                         r = x11_convert_to_vconsole(c, bus);
1055                         if (r < 0)
1056                                 log_error("Failed to convert keymap data: %s", strerror(-r));
1057                 }
1058         }
1059
1060         return sd_bus_reply_method_return(m, NULL);
1061 }
1062
1063 static const sd_bus_vtable locale_vtable[] = {
1064         SD_BUS_VTABLE_START(0),
1065         SD_BUS_PROPERTY("Locale", "as", property_get_locale, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1066         SD_BUS_PROPERTY("X11Layout", "s", NULL, offsetof(Context, x11_layout), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1067         SD_BUS_PROPERTY("X11Model", "s", NULL, offsetof(Context, x11_model), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1068         SD_BUS_PROPERTY("X11Variant", "s", NULL, offsetof(Context, x11_variant), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1069         SD_BUS_PROPERTY("X11Options", "s", NULL, offsetof(Context, x11_options), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1070         SD_BUS_PROPERTY("VConsoleKeymap", "s", NULL, offsetof(Context, vc_keymap), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1071         SD_BUS_PROPERTY("VConsoleKeymapToggle", "s", NULL, offsetof(Context, vc_keymap_toggle), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1072         SD_BUS_METHOD("SetLocale", "asb", NULL, method_set_locale, SD_BUS_VTABLE_UNPRIVILEGED),
1073         SD_BUS_METHOD("SetVConsoleKeyboard", "ssbb", NULL, method_set_vc_keyboard, SD_BUS_VTABLE_UNPRIVILEGED),
1074         SD_BUS_METHOD("SetX11Keyboard", "ssssbb", NULL, method_set_x11_keyboard, SD_BUS_VTABLE_UNPRIVILEGED),
1075         SD_BUS_VTABLE_END
1076 };
1077
1078 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
1079         _cleanup_bus_unref_ sd_bus *bus = NULL;
1080         int r;
1081
1082         assert(c);
1083         assert(event);
1084         assert(_bus);
1085
1086         r = sd_bus_default_system(&bus);
1087         if (r < 0) {
1088                 log_error("Failed to get system bus connection: %s", strerror(-r));
1089                 return r;
1090         }
1091
1092         r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/locale1", "org.freedesktop.locale1", locale_vtable, c);
1093         if (r < 0) {
1094                 log_error("Failed to register object: %s", strerror(-r));
1095                 return r;
1096         }
1097
1098         r = sd_bus_request_name(bus, "org.freedesktop.locale1", 0);
1099         if (r < 0) {
1100                 log_error("Failed to register name: %s", strerror(-r));
1101                 return r;
1102         }
1103
1104         r = sd_bus_attach_event(bus, event, 0);
1105         if (r < 0) {
1106                 log_error("Failed to attach bus to event loop: %s", strerror(-r));
1107                 return r;
1108         }
1109
1110         *_bus = bus;
1111         bus = NULL;
1112
1113         return 0;
1114 }
1115
1116 int main(int argc, char *argv[]) {
1117         Context context = {};
1118         _cleanup_event_unref_ sd_event *event = NULL;
1119         _cleanup_bus_unref_ sd_bus *bus = NULL;
1120         int r;
1121
1122         log_set_target(LOG_TARGET_AUTO);
1123         log_parse_environment();
1124         log_open();
1125
1126         umask(0022);
1127         label_init("/etc");
1128
1129         if (argc != 1) {
1130                 log_error("This program takes no arguments.");
1131                 r = -EINVAL;
1132                 goto finish;
1133         }
1134
1135         r = sd_event_default(&event);
1136         if (r < 0) {
1137                 log_error("Failed to allocate event loop: %s", strerror(-r));
1138                 goto finish;
1139         }
1140
1141         sd_event_set_watchdog(event, true);
1142
1143         r = connect_bus(&context, event, &bus);
1144         if (r < 0)
1145                 goto finish;
1146
1147         r = context_read_data(&context);
1148         if (r < 0) {
1149                 log_error("Failed to read locale data: %s", strerror(-r));
1150                 goto finish;
1151         }
1152
1153         r = bus_event_loop_with_idle(event, bus, "org.freedesktop.locale1", DEFAULT_EXIT_USEC, NULL, NULL);
1154         if (r < 0) {
1155                 log_error("Failed to run event loop: %s", strerror(-r));
1156                 goto finish;
1157         }
1158
1159 finish:
1160         context_free(&context, bus);
1161
1162         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
1163 }