chiark / gitweb /
localed: mark bus properties as change emitting
[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_send_with_reply_and_block(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         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                         strv_free(l);
408                         return -ENOMEM;
409                 }
410
411                 u = strv_env_set(l, s);
412                 free(s);
413                 strv_free(l);
414
415                 if (!u)
416                         return -ENOMEM;
417
418                 l = u;
419         }
420
421         if (isempty(c->vc_keymap_toggle))
422                 l = strv_env_unset(l, "KEYMAP_TOGGLE");
423         else  {
424                 char *s, **u;
425
426                 s = strappend("KEYMAP_TOGGLE=", c->vc_keymap_toggle);
427                 if (!s) {
428                         strv_free(l);
429                         return -ENOMEM;
430                 }
431
432                 u = strv_env_set(l, s);
433                 free(s);
434                 strv_free(l);
435
436                 if (!u)
437                         return -ENOMEM;
438
439                 l = u;
440         }
441
442         if (strv_isempty(l)) {
443                 strv_free(l);
444
445                 if (unlink("/etc/vconsole.conf") < 0)
446                         return errno == ENOENT ? 0 : -errno;
447
448                 return 0;
449         }
450
451         r = write_env_file_label("/etc/vconsole.conf", l);
452         strv_free(l);
453
454         return r;
455 }
456
457 static int write_data_x11(Context *c) {
458         FILE *f;
459         char *temp_path;
460         int r;
461
462         if (isempty(c->x11_layout) &&
463             isempty(c->x11_model) &&
464             isempty(c->x11_variant) &&
465             isempty(c->x11_options)) {
466
467                 if (unlink("/etc/X11/xorg.conf.d/00-keyboard.conf") < 0)
468                         return errno == ENOENT ? 0 : -errno;
469
470                 return 0;
471         }
472
473         mkdir_p_label("/etc/X11/xorg.conf.d", 0755);
474
475         r = fopen_temporary("/etc/X11/xorg.conf.d/00-keyboard.conf", &f, &temp_path);
476         if (r < 0)
477                 return r;
478
479         fchmod(fileno(f), 0644);
480
481         fputs("# Read and parsed by systemd-localed. It's probably wise not to edit this file\n"
482               "# manually too freely.\n"
483               "Section \"InputClass\"\n"
484               "        Identifier \"system-keyboard\"\n"
485               "        MatchIsKeyboard \"on\"\n", f);
486
487         if (!isempty(c->x11_layout))
488                 fprintf(f, "        Option \"XkbLayout\" \"%s\"\n", c->x11_layout);
489
490         if (!isempty(c->x11_model))
491                 fprintf(f, "        Option \"XkbModel\" \"%s\"\n", c->x11_model);
492
493         if (!isempty(c->x11_variant))
494                 fprintf(f, "        Option \"XkbVariant\" \"%s\"\n", c->x11_variant);
495
496         if (!isempty(c->x11_options))
497                 fprintf(f, "        Option \"XkbOptions\" \"%s\"\n", c->x11_options);
498
499         fputs("EndSection\n", f);
500         fflush(f);
501
502         if (ferror(f) || rename(temp_path, "/etc/X11/xorg.conf.d/00-keyboard.conf") < 0) {
503                 r = -errno;
504                 unlink("/etc/X11/xorg.conf.d/00-keyboard.conf");
505                 unlink(temp_path);
506         } else
507                 r = 0;
508
509         fclose(f);
510         free(temp_path);
511
512         return r;
513 }
514
515 static int vconsole_reload(sd_bus *bus) {
516         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
517         int r;
518
519         assert(bus);
520
521         r = sd_bus_call_method(bus,
522                         "org.freedesktop.systemd1",
523                         "/org/freedesktop/systemd1",
524                         "org.freedesktop.systemd1.Manager",
525                         "RestartUnit",
526                         &error,
527                         NULL,
528                         "ss", "systemd-vconsole-setup.service", "replace");
529
530         if (r < 0)
531                 log_error("Failed to issue method call: %s", bus_error_message(&error, -r));
532         return r;
533 }
534
535 static char *strnulldash(const char *s) {
536         return s == NULL || *s == 0 || (s[0] == '-' && s[1] == 0) ? NULL : (char*) s;
537 }
538
539 static int read_next_mapping(FILE *f, unsigned *n, char ***a) {
540         assert(f);
541         assert(n);
542         assert(a);
543
544         for (;;) {
545                 char line[LINE_MAX];
546                 char *l, **b;
547
548                 errno = 0;
549                 if (!fgets(line, sizeof(line), f)) {
550
551                         if (ferror(f))
552                                 return errno ? -errno : -EIO;
553
554                         return 0;
555                 }
556
557                 (*n) ++;
558
559                 l = strstrip(line);
560                 if (l[0] == 0 || l[0] == '#')
561                         continue;
562
563                 b = strv_split_quoted(l);
564                 if (!b)
565                         return -ENOMEM;
566
567                 if (strv_length(b) < 5) {
568                         log_error("Invalid line "SYSTEMD_KBD_MODEL_MAP":%u, ignoring.", *n);
569                         strv_free(b);
570                         continue;
571
572                 }
573
574                 *a = b;
575                 return 1;
576         }
577 }
578
579 static int vconsole_convert_to_x11(Context *c, sd_bus *bus) {
580         bool modified = false;
581
582         assert(bus);
583
584         if (isempty(c->vc_keymap)) {
585
586                 modified =
587                         !isempty(c->x11_layout) ||
588                         !isempty(c->x11_model) ||
589                         !isempty(c->x11_variant) ||
590                         !isempty(c->x11_options);
591
592                 context_free_x11(c);
593         } else {
594                 FILE *f;
595                 unsigned n = 0;
596
597                 f = fopen(SYSTEMD_KBD_MODEL_MAP, "re");
598                 if (!f)
599                         return -errno;
600
601                 for (;;) {
602                         char **a;
603                         int r;
604
605                         r = read_next_mapping(f, &n, &a);
606                         if (r < 0) {
607                                 fclose(f);
608                                 return r;
609                         }
610
611                         if (r == 0)
612                                 break;
613
614                         if (!streq(c->vc_keymap, a[0])) {
615                                 strv_free(a);
616                                 continue;
617                         }
618
619                         if (!streq_ptr(c->x11_layout, strnulldash(a[1])) ||
620                             !streq_ptr(c->x11_model, strnulldash(a[2])) ||
621                             !streq_ptr(c->x11_variant, strnulldash(a[3])) ||
622                             !streq_ptr(c->x11_options, strnulldash(a[4]))) {
623
624                                 if (free_and_copy(&c->x11_layout, strnulldash(a[1])) < 0 ||
625                                     free_and_copy(&c->x11_model, strnulldash(a[2])) < 0 ||
626                                     free_and_copy(&c->x11_variant, strnulldash(a[3])) < 0 ||
627                                     free_and_copy(&c->x11_options, strnulldash(a[4])) < 0) {
628                                         strv_free(a);
629                                         fclose(f);
630                                         return -ENOMEM;
631                                 }
632
633                                 modified = true;
634                         }
635
636                         strv_free(a);
637                         break;
638                 }
639
640                 fclose(f);
641         }
642
643         if (modified) {
644                 int r;
645
646                 r = write_data_x11(c);
647                 if (r < 0)
648                         log_error("Failed to set X11 keyboard layout: %s", strerror(-r));
649
650                 sd_bus_emit_properties_changed(bus,
651                                 "/org/freedesktop/locale1",
652                                 "org.freedesktop.locale1",
653                                 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL);
654         }
655
656         return 0;
657 }
658
659 static int x11_convert_to_vconsole(Context *c, sd_bus *bus) {
660         bool modified = false;
661
662         assert(bus);
663
664         if (isempty(c->x11_layout)) {
665
666                 modified =
667                         !isempty(c->vc_keymap) ||
668                         !isempty(c->vc_keymap_toggle);
669
670                 context_free_x11(c);
671         } else {
672                 _cleanup_fclose_ FILE *f;
673                 unsigned n = 0;
674                 unsigned best_matching = 0;
675                 char *new_keymap = NULL;
676
677                 f = fopen(SYSTEMD_KBD_MODEL_MAP, "re");
678                 if (!f)
679                         return -errno;
680
681                 for (;;) {
682                         _cleanup_strv_free_ char **a = NULL;
683                         unsigned matching = 0;
684                         int r;
685
686                         r = read_next_mapping(f, &n, &a);
687                         if (r < 0)
688                                 return r;
689                         if (r == 0)
690                                 break;
691
692                         /* Determine how well matching this entry is */
693                         if (streq_ptr(c->x11_layout, a[1]))
694                                 /* If we got an exact match, this is best */
695                                 matching = 10;
696                         else {
697                                 size_t x;
698
699                                 x = strcspn(c->x11_layout, ",");
700
701                                 /* We have multiple X layouts, look
702                                  * for an entry that matches our key
703                                  * with the everything but the first
704                                  * layout stripped off. */
705                                 if (x > 0 &&
706                                     strlen(a[1]) == x &&
707                                     strneq(c->x11_layout, a[1], x))
708                                         matching = 5;
709                                 else  {
710                                         size_t w;
711
712                                         /* If that didn't work, strip
713                                          * off the other layouts from
714                                          * the entry, too */
715                                         w = strcspn(a[1], ",");
716
717                                         if (x > 0 && x == w &&
718                                             memcmp(c->x11_layout, a[1], x) == 0)
719                                                 matching = 1;
720                                 }
721                         }
722
723                         if (matching > 0 &&
724                             streq_ptr(c->x11_model, a[2])) {
725                                 matching++;
726
727                                 if (streq_ptr(c->x11_variant, a[3])) {
728                                         matching++;
729
730                                         if (streq_ptr(c->x11_options, a[4]))
731                                                 matching++;
732                                 }
733                         }
734
735                         /* The best matching entry so far, then let's
736                          * save that */
737                         if (matching > best_matching) {
738                                 best_matching = matching;
739
740                                 free(new_keymap);
741                                 new_keymap = strdup(a[0]);
742                                 if (!new_keymap)
743                                         return -ENOMEM;
744                         }
745                 }
746
747                 if (!streq_ptr(c->vc_keymap, new_keymap)) {
748                         free_and_replace(&c->vc_keymap, new_keymap);
749                         free_and_replace(&c->vc_keymap_toggle, NULL);
750                         modified = true;
751                 } else
752                         free(new_keymap);
753         }
754
755         if (modified) {
756                 int r;
757
758                 r = vconsole_write_data(c);
759                 if (r < 0)
760                         log_error("Failed to set virtual console keymap: %s", strerror(-r));
761
762                 sd_bus_emit_properties_changed(bus,
763                                 "/org/freedesktop/locale1",
764                                 "org.freedesktop.locale1",
765                                 "VConsoleKeymap", "VConsoleKeymapToggle", NULL);
766
767                 return vconsole_reload(bus);
768         }
769
770         return 0;
771 }
772
773 static int property_get_locale(sd_bus *bus, const char *path, const char *interface,
774                                const char *property, sd_bus_message *reply, sd_bus_error *error, void *userdata) {
775         Context *c = userdata;
776         _cleanup_strv_free_ char **l = NULL;
777         int p, q;
778
779         l = new0(char*, _LOCALE_MAX+1);
780         if (!l)
781                 return -ENOMEM;
782
783         for (p = 0, q = 0; p < _LOCALE_MAX; p++) {
784                 char *t;
785
786                 if (isempty(c->locale[p]))
787                         continue;
788
789                 if (asprintf(&t, "%s=%s", names[p], c->locale[p]) < 0)
790                         return -ENOMEM;
791
792                 l[q++] = t;
793         }
794
795         return sd_bus_message_append_strv(reply, l);
796 }
797
798 static int method_set_locale(sd_bus *bus, sd_bus_message *m, void *userdata) {
799         Context *c = userdata;
800         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
801         _cleanup_strv_free_ char **l = NULL;
802         char **i;
803         int interactive;
804         bool modified = false;
805         bool passed[_LOCALE_MAX] = {};
806         int p;
807         int r;
808
809         r = bus_message_read_strv_extend(m, &l);
810         if (r < 0)
811                 return sd_bus_reply_method_errno(bus, m, r, NULL);
812
813         r = sd_bus_message_read_basic(m, 'b', &interactive);
814         if (r < 0)
815                 return sd_bus_reply_method_errno(bus, m, r, NULL);
816
817         /* Check whether a variable changed and if so valid */
818         STRV_FOREACH(i, l) {
819                 bool valid = false;
820
821                 for (p = 0; p < _LOCALE_MAX; p++) {
822                         size_t k;
823
824                         k = strlen(names[p]);
825                         if (startswith(*i, names[p]) &&
826                             (*i)[k] == '=' &&
827                             string_is_safe((*i) + k + 1)) {
828                                 valid = true;
829                                 passed[p] = true;
830
831                                 if (!streq_ptr(*i + k + 1, c->locale[p]))
832                                         modified = true;
833
834                                 break;
835                         }
836                 }
837
838                 if (!valid)
839                         sd_bus_reply_method_errorf(bus, m, SD_BUS_ERROR_INVALID_ARGS, "Invalid Locale data.");
840         }
841
842         /* Check whether a variable is unset */
843         if (!modified)  {
844                 for (p = 0; p < _LOCALE_MAX; p++)
845                         if (!isempty(c->locale[p]) && !passed[p]) {
846                                 modified = true;
847                                 break;
848                         }
849         }
850
851         if (modified) {
852                 r = bus_verify_polkit_async(bus, &c->polkit_registry, m,
853                                             "org.freedesktop.locale1.set-locale", interactive,
854                                             &error, method_set_locale, c);
855                 if (r < 0)
856                         return sd_bus_reply_method_errno(bus, m, r, &error);
857                 if (r == 0)
858                         return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
859
860                 STRV_FOREACH(i, l) {
861                         for (p = 0; p < _LOCALE_MAX; p++) {
862                                 size_t k;
863
864                                 k = strlen(names[p]);
865                                 if (startswith(*i, names[p]) && (*i)[k] == '=') {
866                                         char *t;
867
868                                         t = strdup(*i + k + 1);
869                                         if (!t)
870                                                 return -ENOMEM;
871
872                                         free(c->locale[p]);
873                                         c->locale[p] = t;
874                                         break;
875                                 }
876                         }
877                 }
878
879                 for (p = 0; p < _LOCALE_MAX; p++) {
880                         if (passed[p])
881                                 continue;
882
883                         free_and_replace(&c->locale[p], NULL);
884                 }
885
886                 locale_simplify(c);
887
888                 r = locale_write_data(c);
889                 if (r < 0) {
890                         log_error("Failed to set locale: %s", strerror(-r));
891                         return sd_bus_reply_method_errnof(bus, m, r, "Failed to set locale: %s", strerror(-r));
892                 }
893
894                 locale_update_system_manager(c, bus);
895
896                 log_info("Changed locale information.");
897
898                 sd_bus_emit_properties_changed(bus,
899                                 "/org/freedesktop/locale1",
900                                 "org.freedesktop.locale1",
901                                 "Locale", NULL);
902         }
903
904         return sd_bus_reply_method_return(bus, m, NULL);
905 }
906
907 static int method_set_vc_keyboard(sd_bus *bus, sd_bus_message *m, void *userdata) {
908         Context *c = userdata;
909         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
910         const char *keymap, *keymap_toggle;
911         unsigned convert, interactive;
912         int r;
913
914         r = sd_bus_message_read(m, "ssbb", &keymap, &keymap_toggle, &convert, &interactive);
915         if (r < 0)
916                 return sd_bus_reply_method_errno(bus, m, r, NULL);
917
918         if (isempty(keymap))
919                 keymap = NULL;
920
921         if (isempty(keymap_toggle))
922                 keymap_toggle = NULL;
923
924         if (!streq_ptr(keymap, c->vc_keymap) ||
925             !streq_ptr(keymap_toggle, c->vc_keymap_toggle)) {
926
927                 if ((keymap && (!filename_is_safe(keymap) || !string_is_safe(keymap))) ||
928                     (keymap_toggle && (!filename_is_safe(keymap_toggle) || !string_is_safe(keymap_toggle))))
929                         return sd_bus_reply_method_errnof(bus, m, r, "Received invalid keymap data: %s", -EINVAL);
930
931                 r = bus_verify_polkit_async(bus, &c->polkit_registry, m,
932                                 "org.freedesktop.locale1.set-keyboard",
933                                 interactive, &error, method_set_vc_keyboard, c);
934                 if (r < 0)
935                         return sd_bus_reply_method_errno(bus, m, r, &error);
936                 if (r == 0)
937                         return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
938
939                 if (free_and_copy(&c->vc_keymap, keymap) < 0 ||
940                     free_and_copy(&c->vc_keymap_toggle, keymap_toggle) < 0)
941                         return -ENOMEM;
942
943                 r = vconsole_write_data(c);
944                 if (r < 0) {
945                         log_error("Failed to set virtual console keymap: %s", strerror(-r));
946                         return sd_bus_reply_method_errnof(bus, m, r, "Failed to set virtual console keymap: %s", strerror(-r));
947                 }
948
949                 log_info("Changed virtual console keymap to '%s'", strempty(c->vc_keymap));
950
951                 r = vconsole_reload(bus);
952                 if (r < 0)
953                         log_error("Failed to request keymap reload: %s", strerror(-r));
954
955                 sd_bus_emit_properties_changed(bus,
956                                 "/org/freedesktop/locale1",
957                                 "org.freedesktop.locale1",
958                                 "VConsoleKeymap", "VConsoleKeymapToggle", NULL);
959
960                 if (convert) {
961                         r = vconsole_convert_to_x11(c, bus);
962                         if (r < 0)
963                                 log_error("Failed to convert keymap data: %s", strerror(-r));
964                 }
965         }
966
967         return sd_bus_reply_method_return(bus, m, NULL);
968 }
969
970 static int method_set_x11_keyboard(sd_bus *bus, sd_bus_message *m, void *userdata) {
971         Context *c = userdata;
972         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
973         const char *layout, *model, *variant, *options;
974         unsigned 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 sd_bus_reply_method_errno(bus, m, r, NULL);
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_reply_method_errnof(bus, m, r, "Received invalid keyboard data: %s", -EINVAL);
1003
1004                 r = bus_verify_polkit_async(bus, &c->polkit_registry, m,
1005                                 "org.freedesktop.locale1.set-keyboard",
1006                                 interactive, &error, method_set_x11_keyboard, c);
1007                 if (r < 0)
1008                         return sd_bus_reply_method_errno(bus, m, r, &error);
1009                 if (r == 0)
1010                         return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
1011
1012                 if (free_and_copy(&c->x11_layout, layout) < 0 ||
1013                     free_and_copy(&c->x11_model, model) < 0 ||
1014                     free_and_copy(&c->x11_variant, variant) < 0 ||
1015                     free_and_copy(&c->x11_options, options) < 0)
1016                         return -ENOMEM;
1017
1018                 r = write_data_x11(c);
1019                 if (r < 0) {
1020                         log_error("Failed to set X11 keyboard layout: %s", strerror(-r));
1021                         return sd_bus_reply_method_errnof(bus, m, r, "Failed to set X11 keyboard layout: %s", strerror(-r));
1022                 }
1023
1024                 log_info("Changed X11 keyboard layout to '%s'", strempty(c->x11_layout));
1025
1026                 sd_bus_emit_properties_changed(bus,
1027                                 "/org/freedesktop/locale1",
1028                                 "org.freedesktop.locale1",
1029                                 "X11Layout" "X11Model" "X11Variant" "X11Options", NULL);
1030
1031                 if (convert) {
1032                         r = x11_convert_to_vconsole(c, bus);
1033                         if (r < 0)
1034                                 log_error("Failed to convert keymap data: %s", strerror(-r));
1035                 }
1036         }
1037
1038         return sd_bus_reply_method_return(bus, m, NULL);
1039 }
1040
1041 static const sd_bus_vtable locale_vtable[] = {
1042         SD_BUS_VTABLE_START(0),
1043         SD_BUS_PROPERTY("Locale", "as", property_get_locale, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1044         SD_BUS_PROPERTY("X11Layout", "s", NULL, offsetof(Context, x11_layout), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1045         SD_BUS_PROPERTY("X11Model", "s", NULL, offsetof(Context, x11_model), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1046         SD_BUS_PROPERTY("X11Variant", "s", NULL, offsetof(Context, x11_variant), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1047         SD_BUS_PROPERTY("X11Options", "s", NULL, offsetof(Context, x11_options), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1048         SD_BUS_PROPERTY("VConsoleKeymap", "s", NULL, offsetof(Context, vc_keymap), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1049         SD_BUS_PROPERTY("VConsoleKeymapToggle", "s", NULL, offsetof(Context, vc_keymap_toggle), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1050         SD_BUS_METHOD("SetLocale", "asb", NULL, method_set_locale, 0),
1051         SD_BUS_METHOD("SetVConsoleKeyboard", "ssbb", NULL, method_set_vc_keyboard, 0),
1052         SD_BUS_METHOD("SetX11Keyboard", "ssssbb", NULL, method_set_x11_keyboard, 0),
1053         SD_BUS_VTABLE_END
1054 };
1055
1056 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
1057         _cleanup_bus_unref_ sd_bus *bus = NULL;
1058         int r;
1059
1060         assert(c);
1061         assert(event);
1062         assert(_bus);
1063
1064         r = sd_bus_open_system(&bus);
1065         if (r < 0) {
1066                 log_error("Failed to get system bus connection: %s", strerror(-r));
1067                 return r;
1068         }
1069
1070         r = sd_bus_add_object_vtable(bus, "/org/freedesktop/locale1", "org.freedesktop.locale1", locale_vtable, c);
1071         if (r < 0) {
1072                 log_error("Failed to register object: %s", strerror(-r));
1073                 return r;
1074         }
1075
1076         r = sd_bus_request_name(bus, "org.freedesktop.locale1", SD_BUS_NAME_DO_NOT_QUEUE);
1077         if (r < 0) {
1078                 log_error("Failed to register name: %s", strerror(-r));
1079                 return r;
1080         }
1081
1082         if (r != SD_BUS_NAME_PRIMARY_OWNER) {
1083                 log_error("Failed to acquire name.");
1084                 return -EEXIST;
1085         }
1086
1087         r = sd_bus_attach_event(bus, event, 0);
1088         if (r < 0) {
1089                 log_error("Failed to attach bus to event loop: %s", strerror(-r));
1090                 return r;
1091         }
1092
1093         *_bus = bus;
1094         bus = NULL;
1095
1096         return 0;
1097 }
1098
1099 int main(int argc, char *argv[]) {
1100         Context context = {};
1101         _cleanup_event_unref_ sd_event *event = NULL;
1102         _cleanup_bus_unref_ sd_bus *bus = NULL;
1103         int r;
1104
1105         log_set_target(LOG_TARGET_AUTO);
1106         log_parse_environment();
1107         log_open();
1108
1109         umask(0022);
1110         label_init("/etc");
1111
1112         if (argc != 1) {
1113                 log_error("This program takes no arguments.");
1114                 r = -EINVAL;
1115                 goto finish;
1116         }
1117
1118         r = sd_event_new(&event);
1119         if (r < 0) {
1120                 log_error("Failed to allocate event loop: %s", strerror(-r));
1121                 goto finish;
1122         }
1123
1124         r = connect_bus(&context, event, &bus);
1125         if (r < 0)
1126                 goto finish;
1127
1128         r = context_read_data(&context);
1129         if (r < 0) {
1130                 log_error("Failed to read locale data: %s", strerror(-r));
1131                 goto finish;
1132         }
1133
1134         r = bus_event_loop_with_idle(event, bus, "org.freedesktop.locale1", DEFAULT_EXIT_USEC);
1135         if (r < 0) {
1136                 log_error("Failed to run event loop: %s", strerror(-r));
1137                 goto finish;
1138         }
1139
1140         r = 0;
1141
1142 finish:
1143         context_free(&context, bus);
1144
1145         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
1146 }