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