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