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