chiark / gitweb /
bus: let's simplify things by getting rid of unnecessary bus parameters
[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(sd_bus *bus, const char *path, const char *interface,
791                                const char *property, sd_bus_message *reply, sd_bus_error *error, void *userdata) {
792         Context *c = userdata;
793         _cleanup_strv_free_ char **l = NULL;
794         int p, q;
795
796         l = new0(char*, _LOCALE_MAX+1);
797         if (!l)
798                 return -ENOMEM;
799
800         for (p = 0, q = 0; p < _LOCALE_MAX; p++) {
801                 char *t;
802
803                 if (isempty(c->locale[p]))
804                         continue;
805
806                 if (asprintf(&t, "%s=%s", names[p], c->locale[p]) < 0)
807                         return -ENOMEM;
808
809                 l[q++] = t;
810         }
811
812         return sd_bus_message_append_strv(reply, l);
813 }
814
815 static int method_set_locale(sd_bus *bus, sd_bus_message *m, void *userdata) {
816         Context *c = userdata;
817         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
818         _cleanup_strv_free_ char **l = NULL;
819         char **i;
820         int interactive;
821         bool modified = false;
822         bool passed[_LOCALE_MAX] = {};
823         int p;
824         int r;
825
826         r = bus_message_read_strv_extend(m, &l);
827         if (r < 0)
828                 return sd_bus_reply_method_errno(m, r, NULL);
829
830         r = sd_bus_message_read_basic(m, 'b', &interactive);
831         if (r < 0)
832                 return sd_bus_reply_method_errno(m, r, NULL);
833
834         /* Check whether a variable changed and if so valid */
835         STRV_FOREACH(i, l) {
836                 bool valid = false;
837
838                 for (p = 0; p < _LOCALE_MAX; p++) {
839                         size_t k;
840
841                         k = strlen(names[p]);
842                         if (startswith(*i, names[p]) &&
843                             (*i)[k] == '=' &&
844                             string_is_safe((*i) + k + 1)) {
845                                 valid = true;
846                                 passed[p] = true;
847
848                                 if (!streq_ptr(*i + k + 1, c->locale[p]))
849                                         modified = true;
850
851                                 break;
852                         }
853                 }
854
855                 if (!valid)
856                         sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "Invalid Locale data.");
857         }
858
859         /* Check whether a variable is unset */
860         if (!modified)  {
861                 for (p = 0; p < _LOCALE_MAX; p++)
862                         if (!isempty(c->locale[p]) && !passed[p]) {
863                                 modified = true;
864                                 break;
865                         }
866         }
867
868         if (modified) {
869                 r = bus_verify_polkit_async(bus, &c->polkit_registry, m,
870                                             "org.freedesktop.locale1.set-locale", interactive,
871                                             &error, method_set_locale, c);
872                 if (r < 0)
873                         return sd_bus_reply_method_errno(m, r, &error);
874                 if (r == 0)
875                         return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
876
877                 STRV_FOREACH(i, l) {
878                         for (p = 0; p < _LOCALE_MAX; p++) {
879                                 size_t k;
880
881                                 k = strlen(names[p]);
882                                 if (startswith(*i, names[p]) && (*i)[k] == '=') {
883                                         char *t;
884
885                                         t = strdup(*i + k + 1);
886                                         if (!t)
887                                                 return -ENOMEM;
888
889                                         free(c->locale[p]);
890                                         c->locale[p] = t;
891                                         break;
892                                 }
893                         }
894                 }
895
896                 for (p = 0; p < _LOCALE_MAX; p++) {
897                         if (passed[p])
898                                 continue;
899
900                         free_and_replace(&c->locale[p], NULL);
901                 }
902
903                 locale_simplify(c);
904
905                 r = locale_write_data(c);
906                 if (r < 0) {
907                         log_error("Failed to set locale: %s", strerror(-r));
908                         return sd_bus_reply_method_errnof(m, r, "Failed to set locale: %s", strerror(-r));
909                 }
910
911                 locale_update_system_manager(c, bus);
912
913                 log_info("Changed locale information.");
914
915                 sd_bus_emit_properties_changed(bus,
916                                 "/org/freedesktop/locale1",
917                                 "org.freedesktop.locale1",
918                                 "Locale", NULL);
919         }
920
921         return sd_bus_reply_method_return(m, NULL);
922 }
923
924 static int method_set_vc_keyboard(sd_bus *bus, sd_bus_message *m, void *userdata) {
925         Context *c = userdata;
926         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
927         const char *keymap, *keymap_toggle;
928         int convert, interactive;
929         int r;
930
931         r = sd_bus_message_read(m, "ssbb", &keymap, &keymap_toggle, &convert, &interactive);
932         if (r < 0)
933                 return sd_bus_reply_method_errno(m, r, NULL);
934
935         if (isempty(keymap))
936                 keymap = NULL;
937
938         if (isempty(keymap_toggle))
939                 keymap_toggle = NULL;
940
941         if (!streq_ptr(keymap, c->vc_keymap) ||
942             !streq_ptr(keymap_toggle, c->vc_keymap_toggle)) {
943
944                 if ((keymap && (!filename_is_safe(keymap) || !string_is_safe(keymap))) ||
945                     (keymap_toggle && (!filename_is_safe(keymap_toggle) || !string_is_safe(keymap_toggle))))
946                         return sd_bus_reply_method_errnof(m, r, "Received invalid keymap data: %s", -EINVAL);
947
948                 r = bus_verify_polkit_async(bus, &c->polkit_registry, m,
949                                 "org.freedesktop.locale1.set-keyboard",
950                                 interactive, &error, method_set_vc_keyboard, c);
951                 if (r < 0)
952                         return sd_bus_reply_method_errno(m, r, &error);
953                 if (r == 0)
954                         return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
955
956                 if (free_and_copy(&c->vc_keymap, keymap) < 0 ||
957                     free_and_copy(&c->vc_keymap_toggle, keymap_toggle) < 0)
958                         return -ENOMEM;
959
960                 r = vconsole_write_data(c);
961                 if (r < 0) {
962                         log_error("Failed to set virtual console keymap: %s", strerror(-r));
963                         return sd_bus_reply_method_errnof(m, r, "Failed to set virtual console keymap: %s", strerror(-r));
964                 }
965
966                 log_info("Changed virtual console keymap to '%s'", strempty(c->vc_keymap));
967
968                 r = vconsole_reload(bus);
969                 if (r < 0)
970                         log_error("Failed to request keymap reload: %s", strerror(-r));
971
972                 sd_bus_emit_properties_changed(bus,
973                                 "/org/freedesktop/locale1",
974                                 "org.freedesktop.locale1",
975                                 "VConsoleKeymap", "VConsoleKeymapToggle", NULL);
976
977                 if (convert) {
978                         r = vconsole_convert_to_x11(c, bus);
979                         if (r < 0)
980                                 log_error("Failed to convert keymap data: %s", strerror(-r));
981                 }
982         }
983
984         return sd_bus_reply_method_return(m, NULL);
985 }
986
987 static int method_set_x11_keyboard(sd_bus *bus, sd_bus_message *m, void *userdata) {
988         Context *c = userdata;
989         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
990         const char *layout, *model, *variant, *options;
991         int convert, interactive;
992         int r;
993
994         r = sd_bus_message_read(m, "ssssbb", &layout, &model, &variant, &options, &convert, &interactive);
995         if (r < 0)
996                 return sd_bus_reply_method_errno(m, r, NULL);
997
998         if (isempty(layout))
999                 layout = NULL;
1000
1001         if (isempty(model))
1002                 model = NULL;
1003
1004         if (isempty(variant))
1005                 variant = NULL;
1006
1007         if (isempty(options))
1008                 options = NULL;
1009
1010         if (!streq_ptr(layout, c->x11_layout) ||
1011             !streq_ptr(model, c->x11_model) ||
1012             !streq_ptr(variant, c->x11_variant) ||
1013             !streq_ptr(options, c->x11_options)) {
1014
1015                 if ((layout && !string_is_safe(layout)) ||
1016                     (model && !string_is_safe(model)) ||
1017                     (variant && !string_is_safe(variant)) ||
1018                     (options && !string_is_safe(options)))
1019                         return sd_bus_reply_method_errnof(m, r, "Received invalid keyboard data: %s", -EINVAL);
1020
1021                 r = bus_verify_polkit_async(bus, &c->polkit_registry, m,
1022                                 "org.freedesktop.locale1.set-keyboard",
1023                                 interactive, &error, method_set_x11_keyboard, c);
1024                 if (r < 0)
1025                         return sd_bus_reply_method_errno(m, r, &error);
1026                 if (r == 0)
1027                         return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
1028
1029                 if (free_and_copy(&c->x11_layout, layout) < 0 ||
1030                     free_and_copy(&c->x11_model, model) < 0 ||
1031                     free_and_copy(&c->x11_variant, variant) < 0 ||
1032                     free_and_copy(&c->x11_options, options) < 0)
1033                         return -ENOMEM;
1034
1035                 r = write_data_x11(c);
1036                 if (r < 0) {
1037                         log_error("Failed to set X11 keyboard layout: %s", strerror(-r));
1038                         return sd_bus_reply_method_errnof(m, r, "Failed to set X11 keyboard layout: %s", strerror(-r));
1039                 }
1040
1041                 log_info("Changed X11 keyboard layout to '%s'", strempty(c->x11_layout));
1042
1043                 sd_bus_emit_properties_changed(bus,
1044                                 "/org/freedesktop/locale1",
1045                                 "org.freedesktop.locale1",
1046                                 "X11Layout" "X11Model" "X11Variant" "X11Options", NULL);
1047
1048                 if (convert) {
1049                         r = x11_convert_to_vconsole(c, bus);
1050                         if (r < 0)
1051                                 log_error("Failed to convert keymap data: %s", strerror(-r));
1052                 }
1053         }
1054
1055         return sd_bus_reply_method_return(m, NULL);
1056 }
1057
1058 static const sd_bus_vtable locale_vtable[] = {
1059         SD_BUS_VTABLE_START(0),
1060         SD_BUS_PROPERTY("Locale", "as", property_get_locale, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1061         SD_BUS_PROPERTY("X11Layout", "s", NULL, offsetof(Context, x11_layout), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1062         SD_BUS_PROPERTY("X11Model", "s", NULL, offsetof(Context, x11_model), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1063         SD_BUS_PROPERTY("X11Variant", "s", NULL, offsetof(Context, x11_variant), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1064         SD_BUS_PROPERTY("X11Options", "s", NULL, offsetof(Context, x11_options), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1065         SD_BUS_PROPERTY("VConsoleKeymap", "s", NULL, offsetof(Context, vc_keymap), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1066         SD_BUS_PROPERTY("VConsoleKeymapToggle", "s", NULL, offsetof(Context, vc_keymap_toggle), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1067         SD_BUS_METHOD("SetLocale", "asb", NULL, method_set_locale, 0),
1068         SD_BUS_METHOD("SetVConsoleKeyboard", "ssbb", NULL, method_set_vc_keyboard, 0),
1069         SD_BUS_METHOD("SetX11Keyboard", "ssssbb", NULL, method_set_x11_keyboard, 0),
1070         SD_BUS_VTABLE_END
1071 };
1072
1073 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
1074         _cleanup_bus_unref_ sd_bus *bus = NULL;
1075         int r;
1076
1077         assert(c);
1078         assert(event);
1079         assert(_bus);
1080
1081         r = sd_bus_default_system(&bus);
1082         if (r < 0) {
1083                 log_error("Failed to get system bus connection: %s", strerror(-r));
1084                 return r;
1085         }
1086
1087         r = sd_bus_add_object_vtable(bus, "/org/freedesktop/locale1", "org.freedesktop.locale1", locale_vtable, c);
1088         if (r < 0) {
1089                 log_error("Failed to register object: %s", strerror(-r));
1090                 return r;
1091         }
1092
1093         r = sd_bus_request_name(bus, "org.freedesktop.locale1", SD_BUS_NAME_DO_NOT_QUEUE);
1094         if (r < 0) {
1095                 log_error("Failed to register name: %s", strerror(-r));
1096                 return r;
1097         }
1098
1099         if (r != SD_BUS_NAME_PRIMARY_OWNER) {
1100                 log_error("Failed to acquire name.");
1101                 return -EEXIST;
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         r = connect_bus(&context, event, &bus);
1142         if (r < 0)
1143                 goto finish;
1144
1145         r = context_read_data(&context);
1146         if (r < 0) {
1147                 log_error("Failed to read locale data: %s", strerror(-r));
1148                 goto finish;
1149         }
1150
1151         r = bus_event_loop_with_idle(event, bus, "org.freedesktop.locale1", DEFAULT_EXIT_USEC);
1152         if (r < 0) {
1153                 log_error("Failed to run event loop: %s", strerror(-r));
1154                 goto finish;
1155         }
1156
1157         r = 0;
1158
1159 finish:
1160         context_free(&context, bus);
1161
1162         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
1163 }