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