]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/locale/localed.c
shared: rework env file reader
[thirdparty/systemd.git] / src / locale / localed.c
CommitLineData
1822350d
KS
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
8 systemd is free software; you can redistribute it and/or modify it
5430f7f2
LP
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
1822350d
KS
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
5430f7f2 16 Lesser General Public License for more details.
1822350d 17
5430f7f2 18 You should have received a copy of the GNU Lesser General Public License
1822350d
KS
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20***/
21
22#include <dbus/dbus.h>
23
24#include <errno.h>
25#include <string.h>
26#include <unistd.h>
27
28#include "util.h"
49e942b2 29#include "mkdir.h"
1822350d
KS
30#include "strv.h"
31#include "dbus-common.h"
32#include "polkit.h"
33#include "def.h"
4d1a6904 34#include "env-util.h"
a5c32cff
HH
35#include "fileio.h"
36#include "fileio-label.h"
37#include "label.h"
1822350d
KS
38
39#define INTERFACE \
40 " <interface name=\"org.freedesktop.locale1\">\n" \
41 " <property name=\"Locale\" type=\"as\" access=\"read\"/>\n" \
42 " <property name=\"VConsoleKeymap\" type=\"s\" access=\"read\"/>\n" \
43 " <property name=\"VConsoleKeymapToggle\" type=\"s\" access=\"read\"/>\n" \
44 " <property name=\"X11Layout\" type=\"s\" access=\"read\"/>\n" \
45 " <property name=\"X11Model\" type=\"s\" access=\"read\"/>\n" \
46 " <property name=\"X11Variant\" type=\"s\" access=\"read\"/>\n" \
47 " <property name=\"X11Options\" type=\"s\" access=\"read\"/>\n" \
48 " <method name=\"SetLocale\">\n" \
49 " <arg name=\"locale\" type=\"as\" direction=\"in\"/>\n" \
50 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
51 " </method>\n" \
52 " <method name=\"SetVConsoleKeyboard\">\n" \
53 " <arg name=\"keymap\" type=\"s\" direction=\"in\"/>\n" \
54 " <arg name=\"keymap_toggle\" type=\"s\" direction=\"in\"/>\n" \
55 " <arg name=\"convert\" type=\"b\" direction=\"in\"/>\n" \
56 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
57 " </method>\n" \
58 " <method name=\"SetX11Keyboard\">\n" \
59 " <arg name=\"layout\" type=\"s\" direction=\"in\"/>\n" \
60 " <arg name=\"model\" type=\"s\" direction=\"in\"/>\n" \
61 " <arg name=\"variant\" type=\"s\" direction=\"in\"/>\n" \
62 " <arg name=\"options\" type=\"s\" direction=\"in\"/>\n" \
63 " <arg name=\"convert\" type=\"b\" direction=\"in\"/>\n" \
64 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
65 " </method>\n" \
66 " </interface>\n"
67
68#define INTROSPECTION \
69 DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
70 "<node>\n" \
71 INTERFACE \
72 BUS_PROPERTIES_INTERFACE \
73 BUS_INTROSPECTABLE_INTERFACE \
74 BUS_PEER_INTERFACE \
75 "</node>\n"
76
77#define INTERFACES_LIST \
78 BUS_GENERIC_INTERFACES_LIST \
79 "org.freedesktop.locale1\0"
80
81const char locale_interface[] _introspect_("locale1") = INTERFACE;
82
83enum {
84 /* We don't list LC_ALL here on purpose. People should be
85 * using LANG instead. */
86
87 PROP_LANG,
88 PROP_LANGUAGE,
89 PROP_LC_CTYPE,
90 PROP_LC_NUMERIC,
91 PROP_LC_TIME,
92 PROP_LC_COLLATE,
93 PROP_LC_MONETARY,
94 PROP_LC_MESSAGES,
95 PROP_LC_PAPER,
96 PROP_LC_NAME,
97 PROP_LC_ADDRESS,
98 PROP_LC_TELEPHONE,
99 PROP_LC_MEASUREMENT,
100 PROP_LC_IDENTIFICATION,
101 _PROP_MAX
102};
103
104static const char * const names[_PROP_MAX] = {
105 [PROP_LANG] = "LANG",
106 [PROP_LANGUAGE] = "LANGUAGE",
107 [PROP_LC_CTYPE] = "LC_CTYPE",
108 [PROP_LC_NUMERIC] = "LC_NUMERIC",
109 [PROP_LC_TIME] = "LC_TIME",
110 [PROP_LC_COLLATE] = "LC_COLLATE",
111 [PROP_LC_MONETARY] = "LC_MONETARY",
112 [PROP_LC_MESSAGES] = "LC_MESSAGES",
113 [PROP_LC_PAPER] = "LC_PAPER",
114 [PROP_LC_NAME] = "LC_NAME",
115 [PROP_LC_ADDRESS] = "LC_ADDRESS",
116 [PROP_LC_TELEPHONE] = "LC_TELEPHONE",
117 [PROP_LC_MEASUREMENT] = "LC_MEASUREMENT",
118 [PROP_LC_IDENTIFICATION] = "LC_IDENTIFICATION"
119};
120
121static char *data[_PROP_MAX] = {
122 NULL,
123 NULL,
124 NULL,
125 NULL,
126 NULL,
127 NULL,
128 NULL,
129 NULL,
130 NULL,
131 NULL,
132 NULL,
133 NULL,
134 NULL
135};
136
d200735e
MS
137typedef struct State {
138 char *x11_layout, *x11_model, *x11_variant, *x11_options;
139 char *vc_keymap, *vc_keymap_toggle;
140} State;
141
142static State state;
1822350d
KS
143
144static usec_t remain_until = 0;
145
146static int free_and_set(char **s, const char *v) {
147 int r;
148 char *t;
149
150 assert(s);
151
152 r = strdup_or_null(isempty(v) ? NULL : v, &t);
153 if (r < 0)
154 return r;
155
156 free(*s);
157 *s = t;
158
159 return 0;
160}
161
162static void free_data_locale(void) {
163 int p;
164
165 for (p = 0; p < _PROP_MAX; p++) {
166 free(data[p]);
167 data[p] = NULL;
168 }
169}
170
171static void free_data_x11(void) {
d200735e
MS
172 free(state.x11_layout);
173 free(state.x11_model);
174 free(state.x11_variant);
175 free(state.x11_options);
1822350d 176
d200735e 177 state.x11_layout = state.x11_model = state.x11_variant = state.x11_options = NULL;
1822350d
KS
178}
179
180static void free_data_vconsole(void) {
d200735e
MS
181 free(state.vc_keymap);
182 free(state.vc_keymap_toggle);
1822350d 183
d200735e 184 state.vc_keymap = state.vc_keymap_toggle = NULL;
1822350d
KS
185}
186
187static void simplify(void) {
188 int p;
189
190 for (p = 1; p < _PROP_MAX; p++)
191 if (isempty(data[p]) || streq_ptr(data[PROP_LANG], data[p])) {
192 free(data[p]);
193 data[p] = NULL;
194 }
195}
196
197static int read_data_locale(void) {
198 int r;
199
200 free_data_locale();
201
202 r = parse_env_file("/etc/locale.conf", NEWLINE,
203 "LANG", &data[PROP_LANG],
204 "LANGUAGE", &data[PROP_LANGUAGE],
205 "LC_CTYPE", &data[PROP_LC_CTYPE],
206 "LC_NUMERIC", &data[PROP_LC_NUMERIC],
207 "LC_TIME", &data[PROP_LC_TIME],
208 "LC_COLLATE", &data[PROP_LC_COLLATE],
209 "LC_MONETARY", &data[PROP_LC_MONETARY],
210 "LC_MESSAGES", &data[PROP_LC_MESSAGES],
211 "LC_PAPER", &data[PROP_LC_PAPER],
212 "LC_NAME", &data[PROP_LC_NAME],
213 "LC_ADDRESS", &data[PROP_LC_ADDRESS],
214 "LC_TELEPHONE", &data[PROP_LC_TELEPHONE],
215 "LC_MEASUREMENT", &data[PROP_LC_MEASUREMENT],
216 "LC_IDENTIFICATION", &data[PROP_LC_IDENTIFICATION],
217 NULL);
218
219 if (r == -ENOENT) {
220 int p;
221
222 /* Fill in what we got passed from systemd. */
223
224 for (p = 0; p < _PROP_MAX; p++) {
225 char *e, *d;
226
227 assert(names[p]);
228
229 e = getenv(names[p]);
230 if (e) {
231 d = strdup(e);
232 if (!d)
233 return -ENOMEM;
234 } else
235 d = NULL;
236
237 free(data[p]);
238 data[p] = d;
239 }
240
241 r = 0;
242 }
243
244 simplify();
245 return r;
246}
247
248static void free_data(void) {
249 free_data_locale();
250 free_data_vconsole();
251 free_data_x11();
252}
253
254static int read_data_vconsole(void) {
255 int r;
256
257 free_data_vconsole();
258
259 r = parse_env_file("/etc/vconsole.conf", NEWLINE,
d200735e
MS
260 "KEYMAP", &state.vc_keymap,
261 "KEYMAP_TOGGLE", &state.vc_keymap_toggle,
1822350d
KS
262 NULL);
263
264 if (r < 0 && r != -ENOENT)
265 return r;
266
267 return 0;
268}
269
270static int read_data_x11(void) {
271 FILE *f;
272 char line[LINE_MAX];
273 bool in_section = false;
274
275 free_data_x11();
276
277 f = fopen("/etc/X11/xorg.conf.d/00-keyboard.conf", "re");
f687b273
LP
278 if (!f)
279 return errno == ENOENT ? 0 : -errno;
1822350d
KS
280
281 while (fgets(line, sizeof(line), f)) {
282 char *l;
283
284 char_array_0(line);
285 l = strstrip(line);
286
287 if (l[0] == 0 || l[0] == '#')
288 continue;
289
290 if (in_section && first_word(l, "Option")) {
291 char **a;
292
293 a = strv_split_quoted(l);
294 if (!a) {
295 fclose(f);
296 return -ENOMEM;
297 }
298
299 if (strv_length(a) == 3) {
300
301 if (streq(a[1], "XkbLayout")) {
d200735e
MS
302 free(state.x11_layout);
303 state.x11_layout = a[2];
1822350d
KS
304 a[2] = NULL;
305 } else if (streq(a[1], "XkbModel")) {
d200735e
MS
306 free(state.x11_model);
307 state.x11_model = a[2];
1822350d
KS
308 a[2] = NULL;
309 } else if (streq(a[1], "XkbVariant")) {
d200735e
MS
310 free(state.x11_variant);
311 state.x11_variant = a[2];
1822350d
KS
312 a[2] = NULL;
313 } else if (streq(a[1], "XkbOptions")) {
d200735e
MS
314 free(state.x11_options);
315 state.x11_options = a[2];
1822350d
KS
316 a[2] = NULL;
317 }
318 }
319
320 strv_free(a);
321
322 } else if (!in_section && first_word(l, "Section")) {
323 char **a;
324
325 a = strv_split_quoted(l);
326 if (!a) {
327 fclose(f);
328 return -ENOMEM;
329 }
330
331 if (strv_length(a) == 2 && streq(a[1], "InputClass"))
332 in_section = true;
333
334 strv_free(a);
335 } else if (in_section && first_word(l, "EndSection"))
336 in_section = false;
337 }
338
339 fclose(f);
340
341 return 0;
342}
343
344static int read_data(void) {
345 int r, q, p;
346
347 r = read_data_locale();
348 q = read_data_vconsole();
349 p = read_data_x11();
350
351 return r < 0 ? r : q < 0 ? q : p;
352}
353
354static int write_data_locale(void) {
355 int r, p;
356 char **l = NULL;
357
f73141d7 358 r = load_env_file("/etc/locale.conf", NULL, &l);
1822350d
KS
359 if (r < 0 && r != -ENOENT)
360 return r;
361
362 for (p = 0; p < _PROP_MAX; p++) {
363 char *t, **u;
364
365 assert(names[p]);
366
367 if (isempty(data[p])) {
368 l = strv_env_unset(l, names[p]);
369 continue;
370 }
371
372 if (asprintf(&t, "%s=%s", names[p], data[p]) < 0) {
373 strv_free(l);
374 return -ENOMEM;
375 }
376
377 u = strv_env_set(l, t);
378 free(t);
379 strv_free(l);
380
381 if (!u)
382 return -ENOMEM;
383
384 l = u;
385 }
386
387 if (strv_isempty(l)) {
388 strv_free(l);
389
390 if (unlink("/etc/locale.conf") < 0)
391 return errno == ENOENT ? 0 : -errno;
392
393 return 0;
394 }
395
a5c32cff 396 r = write_env_file_label("/etc/locale.conf", l);
1822350d
KS
397 strv_free(l);
398
399 return r;
400}
401
402static void push_data(DBusConnection *bus) {
403 char **l_set = NULL, **l_unset = NULL, **t;
404 int c_set = 0, c_unset = 0, p;
405 DBusError error;
406 DBusMessage *m = NULL, *reply = NULL;
407 DBusMessageIter iter, sub;
408
409 dbus_error_init(&error);
410
411 assert(bus);
412
413 l_set = new0(char*, _PROP_MAX);
414 l_unset = new0(char*, _PROP_MAX);
415 if (!l_set || !l_unset) {
0d0f0c50 416 log_oom();
1822350d
KS
417 goto finish;
418 }
419
420 for (p = 0; p < _PROP_MAX; p++) {
421 assert(names[p]);
422
423 if (isempty(data[p]))
424 l_unset[c_set++] = (char*) names[p];
425 else {
426 char *s;
427
428 if (asprintf(&s, "%s=%s", names[p], data[p]) < 0) {
0d0f0c50 429 log_oom();
1822350d
KS
430 goto finish;
431 }
432
433 l_set[c_unset++] = s;
434 }
435 }
436
437 assert(c_set + c_unset == _PROP_MAX);
438 m = dbus_message_new_method_call("org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "UnsetAndSetEnvironment");
439 if (!m) {
440 log_error("Could not allocate message.");
441 goto finish;
442 }
443
444 dbus_message_iter_init_append(m, &iter);
445
446 if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "s", &sub)) {
0d0f0c50 447 log_oom();
1822350d
KS
448 goto finish;
449 }
450
451 STRV_FOREACH(t, l_unset)
452 if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, t)) {
0d0f0c50 453 log_oom();
1822350d
KS
454 goto finish;
455 }
456
457 if (!dbus_message_iter_close_container(&iter, &sub) ||
458 !dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "s", &sub)) {
0d0f0c50 459 log_oom();
1822350d
KS
460 goto finish;
461 }
462
463 STRV_FOREACH(t, l_set)
464 if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, t)) {
0d0f0c50 465 log_oom();
1822350d
KS
466 goto finish;
467 }
468
469 if (!dbus_message_iter_close_container(&iter, &sub)) {
0d0f0c50 470 log_oom();
1822350d
KS
471 goto finish;
472 }
473
474 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
475 if (!reply) {
476 log_error("Failed to set locale information: %s", bus_error_message(&error));
477 goto finish;
478 }
479
480finish:
481 if (m)
482 dbus_message_unref(m);
483
484 if (reply)
485 dbus_message_unref(reply);
486
487 dbus_error_free(&error);
488
489 strv_free(l_set);
490 free(l_unset);
491}
492
493static int write_data_vconsole(void) {
494 int r;
495 char **l = NULL;
496
f73141d7 497 r = load_env_file("/etc/vconsole.conf", NULL, &l);
1822350d
KS
498 if (r < 0 && r != -ENOENT)
499 return r;
500
d200735e 501 if (isempty(state.vc_keymap))
1822350d
KS
502 l = strv_env_unset(l, "KEYMAP");
503 else {
504 char *s, **u;
505
d200735e 506 s = strappend("KEYMAP=", state.vc_keymap);
1822350d
KS
507 if (!s) {
508 strv_free(l);
509 return -ENOMEM;
510 }
511
512 u = strv_env_set(l, s);
513 free(s);
514 strv_free(l);
515
516 if (!u)
517 return -ENOMEM;
518
519 l = u;
520 }
521
d200735e 522 if (isempty(state.vc_keymap_toggle))
1822350d
KS
523 l = strv_env_unset(l, "KEYMAP_TOGGLE");
524 else {
525 char *s, **u;
526
d200735e 527 s = strappend("KEYMAP_TOGGLE=", state.vc_keymap_toggle);
1822350d
KS
528 if (!s) {
529 strv_free(l);
530 return -ENOMEM;
531 }
532
533 u = strv_env_set(l, s);
534 free(s);
535 strv_free(l);
536
537 if (!u)
538 return -ENOMEM;
539
540 l = u;
541 }
542
543 if (strv_isempty(l)) {
544 strv_free(l);
545
546 if (unlink("/etc/vconsole.conf") < 0)
547 return errno == ENOENT ? 0 : -errno;
548
549 return 0;
550 }
551
a5c32cff 552 r = write_env_file_label("/etc/vconsole.conf", l);
1822350d
KS
553 strv_free(l);
554
555 return r;
556}
557
558static int write_data_x11(void) {
559 FILE *f;
560 char *temp_path;
561 int r;
562
d200735e
MS
563 if (isempty(state.x11_layout) &&
564 isempty(state.x11_model) &&
565 isempty(state.x11_variant) &&
566 isempty(state.x11_options)) {
1822350d 567
1822350d
KS
568 if (unlink("/etc/X11/xorg.conf.d/00-keyboard.conf") < 0)
569 return errno == ENOENT ? 0 : -errno;
570
571 return 0;
572 }
573
9a9bb3ca 574 mkdir_p_label("/etc/X11/xorg.conf.d", 0755);
1822350d
KS
575
576 r = fopen_temporary("/etc/X11/xorg.conf.d/00-keyboard.conf", &f, &temp_path);
577 if (r < 0)
578 return r;
579
580 fchmod(fileno(f), 0644);
581
582 fputs("# Read and parsed by systemd-localed. It's probably wise not to edit this file\n"
583 "# manually too freely.\n"
584 "Section \"InputClass\"\n"
585 " Identifier \"system-keyboard\"\n"
586 " MatchIsKeyboard \"on\"\n", f);
587
d200735e
MS
588 if (!isempty(state.x11_layout))
589 fprintf(f, " Option \"XkbLayout\" \"%s\"\n", state.x11_layout);
1822350d 590
d200735e
MS
591 if (!isempty(state.x11_model))
592 fprintf(f, " Option \"XkbModel\" \"%s\"\n", state.x11_model);
1822350d 593
d200735e
MS
594 if (!isempty(state.x11_variant))
595 fprintf(f, " Option \"XkbVariant\" \"%s\"\n", state.x11_variant);
1822350d 596
d200735e
MS
597 if (!isempty(state.x11_options))
598 fprintf(f, " Option \"XkbOptions\" \"%s\"\n", state.x11_options);
1822350d
KS
599
600 fputs("EndSection\n", f);
601 fflush(f);
602
603 if (ferror(f) || rename(temp_path, "/etc/X11/xorg.conf.d/00-keyboard.conf") < 0) {
604 r = -errno;
605 unlink("/etc/X11/xorg.conf.d/00-keyboard.conf");
606 unlink(temp_path);
f687b273 607 } else
1822350d 608 r = 0;
1822350d
KS
609
610 fclose(f);
611 free(temp_path);
612
613 return r;
614}
615
616static int load_vconsole_keymap(DBusConnection *bus, DBusError *error) {
617 DBusMessage *m = NULL, *reply = NULL;
618 const char *name = "systemd-vconsole-setup.service", *mode = "replace";
619 int r;
620 DBusError _error;
621
622 assert(bus);
623
624 if (!error) {
625 dbus_error_init(&_error);
626 error = &_error;
627 }
628
629 m = dbus_message_new_method_call(
630 "org.freedesktop.systemd1",
631 "/org/freedesktop/systemd1",
632 "org.freedesktop.systemd1.Manager",
633 "RestartUnit");
634 if (!m) {
635 log_error("Could not allocate message.");
636 r = -ENOMEM;
637 goto finish;
638 }
639
640 if (!dbus_message_append_args(m,
641 DBUS_TYPE_STRING, &name,
642 DBUS_TYPE_STRING, &mode,
643 DBUS_TYPE_INVALID)) {
644 log_error("Could not append arguments to message.");
645 r = -ENOMEM;
646 goto finish;
647 }
648
649 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
650 if (!reply) {
651 log_error("Failed to issue method call: %s", bus_error_message(error));
652 r = -EIO;
653 goto finish;
654 }
655
656 r = 0;
657
658finish:
659 if (m)
660 dbus_message_unref(m);
661
662 if (reply)
663 dbus_message_unref(reply);
664
665 if (error == &_error)
666 dbus_error_free(error);
667
668 return r;
669}
670
671static char *strnulldash(const char *s) {
672 return s == NULL || *s == 0 || (s[0] == '-' && s[1] == 0) ? NULL : (char*) s;
673}
674
675static int read_next_mapping(FILE *f, unsigned *n, char ***a) {
676 assert(f);
677 assert(n);
678 assert(a);
679
680 for (;;) {
681 char line[LINE_MAX];
682 char *l, **b;
683
684 errno = 0;
685 if (!fgets(line, sizeof(line), f)) {
686
687 if (ferror(f))
688 return errno ? -errno : -EIO;
689
690 return 0;
691 }
692
693 (*n) ++;
694
695 l = strstrip(line);
696 if (l[0] == 0 || l[0] == '#')
697 continue;
698
699 b = strv_split_quoted(l);
700 if (!b)
701 return -ENOMEM;
702
703 if (strv_length(b) < 5) {
704 log_error("Invalid line "SYSTEMD_KBD_MODEL_MAP":%u, ignoring.", *n);
705 strv_free(b);
706 continue;
707
708 }
709
710 *a = b;
711 return 1;
712 }
713}
714
715static int convert_vconsole_to_x11(DBusConnection *connection) {
716 bool modified = false;
717
718 assert(connection);
719
d200735e 720 if (isempty(state.vc_keymap)) {
1822350d
KS
721
722 modified =
d200735e
MS
723 !isempty(state.x11_layout) ||
724 !isempty(state.x11_model) ||
725 !isempty(state.x11_variant) ||
726 !isempty(state.x11_options);
1822350d
KS
727
728 free_data_x11();
729 } else {
730 FILE *f;
731 unsigned n = 0;
732
733 f = fopen(SYSTEMD_KBD_MODEL_MAP, "re");
734 if (!f)
735 return -errno;
736
737 for (;;) {
738 char **a;
739 int r;
740
741 r = read_next_mapping(f, &n, &a);
742 if (r < 0) {
743 fclose(f);
744 return r;
745 }
746
747 if (r == 0)
748 break;
749
d200735e 750 if (!streq(state.vc_keymap, a[0])) {
1822350d
KS
751 strv_free(a);
752 continue;
753 }
754
d200735e
MS
755 if (!streq_ptr(state.x11_layout, strnulldash(a[1])) ||
756 !streq_ptr(state.x11_model, strnulldash(a[2])) ||
757 !streq_ptr(state.x11_variant, strnulldash(a[3])) ||
758 !streq_ptr(state.x11_options, strnulldash(a[4]))) {
1822350d 759
d200735e
MS
760 if (free_and_set(&state.x11_layout, strnulldash(a[1])) < 0 ||
761 free_and_set(&state.x11_model, strnulldash(a[2])) < 0 ||
762 free_and_set(&state.x11_variant, strnulldash(a[3])) < 0 ||
763 free_and_set(&state.x11_options, strnulldash(a[4])) < 0) {
1822350d
KS
764 strv_free(a);
765 fclose(f);
766 return -ENOMEM;
767 }
768
769 modified = true;
770 }
771
772 strv_free(a);
773 break;
774 }
775
776 fclose(f);
777 }
778
779 if (modified) {
780 dbus_bool_t b;
781 DBusMessage *changed;
782 int r;
783
784 r = write_data_x11();
785 if (r < 0)
786 log_error("Failed to set X11 keyboard layout: %s", strerror(-r));
787
788 changed = bus_properties_changed_new(
789 "/org/freedesktop/locale1",
790 "org.freedesktop.locale1",
791 "X11Layout\0"
792 "X11Model\0"
793 "X11Variant\0"
794 "X11Options\0");
795
796 if (!changed)
797 return -ENOMEM;
798
799 b = dbus_connection_send(connection, changed, NULL);
800 dbus_message_unref(changed);
801
802 if (!b)
803 return -ENOMEM;
804 }
805
806 return 0;
807}
808
809static int convert_x11_to_vconsole(DBusConnection *connection) {
810 bool modified = false;
811
812 assert(connection);
813
d200735e 814 if (isempty(state.x11_layout)) {
1822350d
KS
815
816 modified =
d200735e
MS
817 !isempty(state.vc_keymap) ||
818 !isempty(state.vc_keymap_toggle);
1822350d
KS
819
820 free_data_x11();
821 } else {
822 FILE *f;
823 unsigned n = 0;
824 unsigned best_matching = 0;
825 char *new_keymap = NULL;
826
827 f = fopen(SYSTEMD_KBD_MODEL_MAP, "re");
828 if (!f)
829 return -errno;
830
831 for (;;) {
832 char **a;
833 unsigned matching = 0;
834 int r;
835
836 r = read_next_mapping(f, &n, &a);
837 if (r < 0) {
838 fclose(f);
839 return r;
840 }
841
842 if (r == 0)
843 break;
844
845 /* Determine how well matching this entry is */
d200735e 846 if (streq_ptr(state.x11_layout, a[1]))
1822350d
KS
847 /* If we got an exact match, this is best */
848 matching = 10;
849 else {
850 size_t x;
851
d200735e 852 x = strcspn(state.x11_layout, ",");
1822350d
KS
853
854 /* We have multiple X layouts, look
855 * for an entry that matches our key
856 * with the everything but the first
857 * layout stripped off. */
858 if (x > 0 &&
859 strlen(a[1]) == x &&
641906e9 860 strneq(state.x11_layout, a[1], x))
1822350d
KS
861 matching = 5;
862 else {
863 size_t w;
864
865 /* If that didn't work, strip
866 * off the other layouts from
867 * the entry, too */
868
869 w = strcspn(a[1], ",");
870
871 if (x > 0 && x == w &&
d200735e 872 memcmp(state.x11_layout, a[1], x) == 0)
1822350d
KS
873 matching = 1;
874 }
875 }
876
877 if (matching > 0 &&
d200735e 878 streq_ptr(state.x11_model, a[2])) {
1822350d
KS
879 matching++;
880
d200735e 881 if (streq_ptr(state.x11_variant, a[3])) {
1822350d
KS
882 matching++;
883
d200735e 884 if (streq_ptr(state.x11_options, a[4]))
1822350d
KS
885 matching++;
886 }
887 }
888
889 /* The best matching entry so far, then let's
890 * save that */
891 if (matching > best_matching) {
892 best_matching = matching;
893
894 free(new_keymap);
895 new_keymap = strdup(a[0]);
896
897 if (!new_keymap) {
898 strv_free(a);
899 fclose(f);
900 return -ENOMEM;
901 }
902 }
903
904 strv_free(a);
905 }
906
907 fclose(f);
908
d200735e
MS
909 if (!streq_ptr(state.vc_keymap, new_keymap)) {
910 free(state.vc_keymap);
911 state.vc_keymap = new_keymap;
1822350d 912
d200735e
MS
913 free(state.vc_keymap_toggle);
914 state.vc_keymap_toggle = NULL;
1822350d
KS
915
916 modified = true;
917 } else
918 free(new_keymap);
919 }
920
921 if (modified) {
922 dbus_bool_t b;
923 DBusMessage *changed;
924 int r;
925
926 r = write_data_vconsole();
927 if (r < 0)
928 log_error("Failed to set virtual console keymap: %s", strerror(-r));
929
930 changed = bus_properties_changed_new(
931 "/org/freedesktop/locale1",
932 "org.freedesktop.locale1",
933 "VConsoleKeymap\0"
934 "VConsoleKeymapToggle\0");
935
936 if (!changed)
937 return -ENOMEM;
938
939 b = dbus_connection_send(connection, changed, NULL);
940 dbus_message_unref(changed);
941
942 if (!b)
943 return -ENOMEM;
944
945 return load_vconsole_keymap(connection, NULL);
946 }
947
948 return 0;
949}
950
951static int append_locale(DBusMessageIter *i, const char *property, void *userdata) {
952 int r, c = 0, p;
953 char **l;
954
955 l = new0(char*, _PROP_MAX+1);
956 if (!l)
957 return -ENOMEM;
958
959 for (p = 0; p < _PROP_MAX; p++) {
960 char *t;
961
962 if (isempty(data[p]))
963 continue;
964
965 if (asprintf(&t, "%s=%s", names[p], data[p]) < 0) {
966 strv_free(l);
967 return -ENOMEM;
968 }
969
970 l[c++] = t;
971 }
972
973 r = bus_property_append_strv(i, property, (void*) l);
974 strv_free(l);
975
976 return r;
977}
978
d200735e
MS
979static const BusProperty bus_locale_properties[] = {
980 { "Locale", append_locale, "as", 0 },
981 { "X11Layout", bus_property_append_string, "s", offsetof(State, x11_layout), true },
982 { "X11Model", bus_property_append_string, "s", offsetof(State, x11_model), true },
983 { "X11Variant", bus_property_append_string, "s", offsetof(State, x11_variant), true },
984 { "X11Options", bus_property_append_string, "s", offsetof(State, x11_options), true },
985 { "VConsoleKeymap", bus_property_append_string, "s", offsetof(State, vc_keymap), true },
986 { "VConsoleKeymapToggle", bus_property_append_string, "s", offsetof(State, vc_keymap_toggle), true },
987 { NULL, }
988};
989
990static const BusBoundProperties bps[] = {
991 { "org.freedesktop.locale1", bus_locale_properties, &state },
992 { NULL, }
993};
994
1822350d
KS
995static DBusHandlerResult locale_message_handler(
996 DBusConnection *connection,
997 DBusMessage *message,
998 void *userdata) {
999
1822350d
KS
1000 DBusMessage *reply = NULL, *changed = NULL;
1001 DBusError error;
1002 int r;
1003
1004 assert(connection);
1005 assert(message);
1006
1007 dbus_error_init(&error);
1008
1009 if (dbus_message_is_method_call(message, "org.freedesktop.locale1", "SetLocale")) {
1010 char **l = NULL, **i;
1011 dbus_bool_t interactive;
1012 DBusMessageIter iter;
1013 bool modified = false;
1014 bool passed[_PROP_MAX];
1015 int p;
1016
1017 if (!dbus_message_iter_init(message, &iter))
1018 return bus_send_error_reply(connection, message, NULL, -EINVAL);
1019
1020 r = bus_parse_strv_iter(&iter, &l);
1021 if (r < 0) {
1022 if (r == -ENOMEM)
1023 goto oom;
1024
1025 return bus_send_error_reply(connection, message, NULL, r);
1026 }
1027
1028 if (!dbus_message_iter_next(&iter) ||
1029 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_BOOLEAN) {
1030 strv_free(l);
1031 return bus_send_error_reply(connection, message, NULL, -EINVAL);
1032 }
1033
1034 dbus_message_iter_get_basic(&iter, &interactive);
1035
1036 zero(passed);
1037
1038 /* Check whether a variable changed and if so valid */
1039 STRV_FOREACH(i, l) {
1040 bool valid = false;
1041
1042 for (p = 0; p < _PROP_MAX; p++) {
1043 size_t k;
1044
1045 k = strlen(names[p]);
0b507b17
LP
1046 if (startswith(*i, names[p]) &&
1047 (*i)[k] == '=' &&
1048 string_is_safe((*i) + k + 1)) {
1822350d
KS
1049 valid = true;
1050 passed[p] = true;
1051
1052 if (!streq_ptr(*i + k + 1, data[p]))
1053 modified = true;
1054
1055 break;
1056 }
1057 }
1058
1059 if (!valid) {
1060 strv_free(l);
1061 return bus_send_error_reply(connection, message, NULL, -EINVAL);
1062 }
1063 }
1064
1065 /* Check whether a variable is unset */
1066 if (!modified) {
1067 for (p = 0; p < _PROP_MAX; p++)
1068 if (!isempty(data[p]) && !passed[p]) {
1069 modified = true;
1070 break;
1071 }
1072 }
1073
1074 if (modified) {
1075
89f13440 1076 r = verify_polkit(connection, message, "org.freedesktop.locale1.set-locale", interactive, NULL, &error);
1822350d
KS
1077 if (r < 0) {
1078 strv_free(l);
1079 return bus_send_error_reply(connection, message, &error, r);
1080 }
1081
1082 STRV_FOREACH(i, l) {
1083 for (p = 0; p < _PROP_MAX; p++) {
1084 size_t k;
1085
1086 k = strlen(names[p]);
1087 if (startswith(*i, names[p]) && (*i)[k] == '=') {
1088 char *t;
1089
1090 t = strdup(*i + k + 1);
1091 if (!t) {
1092 strv_free(l);
1093 goto oom;
1094 }
1095
1096 free(data[p]);
1097 data[p] = t;
1098
1099 break;
1100 }
1101 }
1102 }
1103
1104 strv_free(l);
1105
1106 for (p = 0; p < _PROP_MAX; p++) {
1107 if (passed[p])
1108 continue;
1109
1110 free(data[p]);
1111 data[p] = NULL;
1112 }
1113
1114 simplify();
1115
1116 r = write_data_locale();
1117 if (r < 0) {
1118 log_error("Failed to set locale: %s", strerror(-r));
1119 return bus_send_error_reply(connection, message, NULL, r);
1120 }
1121
1122 push_data(connection);
1123
1124 log_info("Changed locale information.");
1125
1126 changed = bus_properties_changed_new(
1127 "/org/freedesktop/locale1",
1128 "org.freedesktop.locale1",
1129 "Locale\0");
1130 if (!changed)
1131 goto oom;
f2cc3753
VP
1132 } else
1133 strv_free(l);
1134
1822350d
KS
1135 } else if (dbus_message_is_method_call(message, "org.freedesktop.locale1", "SetVConsoleKeyboard")) {
1136
1137 const char *keymap, *keymap_toggle;
1138 dbus_bool_t convert, interactive;
1139
1140 if (!dbus_message_get_args(
1141 message,
1142 &error,
1143 DBUS_TYPE_STRING, &keymap,
1144 DBUS_TYPE_STRING, &keymap_toggle,
1145 DBUS_TYPE_BOOLEAN, &convert,
1146 DBUS_TYPE_BOOLEAN, &interactive,
1147 DBUS_TYPE_INVALID))
1148 return bus_send_error_reply(connection, message, &error, -EINVAL);
1149
1150 if (isempty(keymap))
1151 keymap = NULL;
1152
1153 if (isempty(keymap_toggle))
1154 keymap_toggle = NULL;
1155
d200735e
MS
1156 if (!streq_ptr(keymap, state.vc_keymap) ||
1157 !streq_ptr(keymap_toggle, state.vc_keymap_toggle)) {
1822350d 1158
0b507b17
LP
1159 if ((keymap && (!filename_is_safe(keymap) || !string_is_safe(keymap))) ||
1160 (keymap_toggle && (!filename_is_safe(keymap_toggle) || !string_is_safe(keymap_toggle))))
1161 return bus_send_error_reply(connection, message, NULL, -EINVAL);
1162
89f13440 1163 r = verify_polkit(connection, message, "org.freedesktop.locale1.set-keyboard", interactive, NULL, &error);
1822350d
KS
1164 if (r < 0)
1165 return bus_send_error_reply(connection, message, &error, r);
1166
d200735e
MS
1167 if (free_and_set(&state.vc_keymap, keymap) < 0 ||
1168 free_and_set(&state.vc_keymap_toggle, keymap_toggle) < 0)
1822350d
KS
1169 goto oom;
1170
1171 r = write_data_vconsole();
1172 if (r < 0) {
1173 log_error("Failed to set virtual console keymap: %s", strerror(-r));
1174 return bus_send_error_reply(connection, message, NULL, r);
1175 }
1176
d200735e 1177 log_info("Changed virtual console keymap to '%s'", strempty(state.vc_keymap));
1822350d
KS
1178
1179 r = load_vconsole_keymap(connection, NULL);
1180 if (r < 0)
1181 log_error("Failed to request keymap reload: %s", strerror(-r));
1182
1183 changed = bus_properties_changed_new(
1184 "/org/freedesktop/locale1",
1185 "org.freedesktop.locale1",
1186 "VConsoleKeymap\0"
1187 "VConsoleKeymapToggle\0");
1188 if (!changed)
1189 goto oom;
1190
1191 if (convert) {
1192 r = convert_vconsole_to_x11(connection);
1193
1194 if (r < 0)
1195 log_error("Failed to convert keymap data: %s", strerror(-r));
1196 }
1197 }
1198
1199 } else if (dbus_message_is_method_call(message, "org.freedesktop.locale1", "SetX11Keyboard")) {
1200
1201 const char *layout, *model, *variant, *options;
1202 dbus_bool_t convert, interactive;
1203
1204 if (!dbus_message_get_args(
1205 message,
1206 &error,
1207 DBUS_TYPE_STRING, &layout,
1208 DBUS_TYPE_STRING, &model,
1209 DBUS_TYPE_STRING, &variant,
1210 DBUS_TYPE_STRING, &options,
1211 DBUS_TYPE_BOOLEAN, &convert,
1212 DBUS_TYPE_BOOLEAN, &interactive,
1213 DBUS_TYPE_INVALID))
1214 return bus_send_error_reply(connection, message, &error, -EINVAL);
1215
1216 if (isempty(layout))
1217 layout = NULL;
1218
1219 if (isempty(model))
1220 model = NULL;
1221
1222 if (isempty(variant))
1223 variant = NULL;
1224
1225 if (isempty(options))
1226 options = NULL;
1227
d200735e
MS
1228 if (!streq_ptr(layout, state.x11_layout) ||
1229 !streq_ptr(model, state.x11_model) ||
1230 !streq_ptr(variant, state.x11_variant) ||
1231 !streq_ptr(options, state.x11_options)) {
1822350d 1232
0b507b17
LP
1233 if ((layout && !string_is_safe(layout)) ||
1234 (model && !string_is_safe(model)) ||
1235 (variant && !string_is_safe(variant)) ||
1236 (options && !string_is_safe(options)))
1237 return bus_send_error_reply(connection, message, NULL, -EINVAL);
1238
89f13440 1239 r = verify_polkit(connection, message, "org.freedesktop.locale1.set-keyboard", interactive, NULL, &error);
1822350d
KS
1240 if (r < 0)
1241 return bus_send_error_reply(connection, message, &error, r);
1242
d200735e
MS
1243 if (free_and_set(&state.x11_layout, layout) < 0 ||
1244 free_and_set(&state.x11_model, model) < 0 ||
1245 free_and_set(&state.x11_variant, variant) < 0 ||
1246 free_and_set(&state.x11_options, options) < 0)
1822350d
KS
1247 goto oom;
1248
1249 r = write_data_x11();
1250 if (r < 0) {
1251 log_error("Failed to set X11 keyboard layout: %s", strerror(-r));
1252 return bus_send_error_reply(connection, message, NULL, r);
1253 }
1254
d200735e 1255 log_info("Changed X11 keyboard layout to '%s'", strempty(state.x11_layout));
1822350d
KS
1256
1257 changed = bus_properties_changed_new(
1258 "/org/freedesktop/locale1",
1259 "org.freedesktop.locale1",
1260 "X11Layout\0"
1261 "X11Model\0"
1262 "X11Variant\0"
1263 "X11Options\0");
1264 if (!changed)
1265 goto oom;
1266
1267 if (convert) {
1268 r = convert_x11_to_vconsole(connection);
1269
1270 if (r < 0)
1271 log_error("Failed to convert keymap data: %s", strerror(-r));
1272 }
1273 }
1274 } else
d200735e 1275 return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps);
1822350d
KS
1276
1277 if (!(reply = dbus_message_new_method_return(message)))
1278 goto oom;
1279
c6a818c8 1280 if (!bus_maybe_send_reply(connection, message, reply))
1822350d
KS
1281 goto oom;
1282
1283 dbus_message_unref(reply);
1284 reply = NULL;
1285
1286 if (changed) {
1287
1288 if (!dbus_connection_send(connection, changed, NULL))
1289 goto oom;
1290
1291 dbus_message_unref(changed);
1292 }
1293
1294 return DBUS_HANDLER_RESULT_HANDLED;
1295
1296oom:
1297 if (reply)
1298 dbus_message_unref(reply);
1299
1300 if (changed)
1301 dbus_message_unref(changed);
1302
1303 dbus_error_free(&error);
1304
1305 return DBUS_HANDLER_RESULT_NEED_MEMORY;
1306}
1307
1308static int connect_bus(DBusConnection **_bus) {
1309 static const DBusObjectPathVTable locale_vtable = {
1310 .message_function = locale_message_handler
1311 };
1312 DBusError error;
1313 DBusConnection *bus = NULL;
1314 int r;
1315
1316 assert(_bus);
1317
1318 dbus_error_init(&error);
1319
1320 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
1321 if (!bus) {
1322 log_error("Failed to get system D-Bus connection: %s", bus_error_message(&error));
1323 r = -ECONNREFUSED;
1324 goto fail;
1325 }
1326
1327 dbus_connection_set_exit_on_disconnect(bus, FALSE);
1328
1329 if (!dbus_connection_register_object_path(bus, "/org/freedesktop/locale1", &locale_vtable, NULL) ||
1330 !dbus_connection_add_filter(bus, bus_exit_idle_filter, &remain_until, NULL)) {
0d0f0c50 1331 r = log_oom();
1822350d
KS
1332 goto fail;
1333 }
1334
1335 r = dbus_bus_request_name(bus, "org.freedesktop.locale1", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error);
1336 if (dbus_error_is_set(&error)) {
1337 log_error("Failed to register name on bus: %s", bus_error_message(&error));
1338 r = -EEXIST;
1339 goto fail;
1340 }
1341
1342 if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
1343 log_error("Failed to acquire name.");
1344 r = -EEXIST;
1345 goto fail;
1346 }
1347
1348 if (_bus)
1349 *_bus = bus;
1350
1351 return 0;
1352
1353fail:
1354 dbus_connection_close(bus);
1355 dbus_connection_unref(bus);
1356
1357 dbus_error_free(&error);
1358
1359 return r;
1360}
1361
1362int main(int argc, char *argv[]) {
1363 int r;
1364 DBusConnection *bus = NULL;
1365 bool exiting = false;
1366
1367 log_set_target(LOG_TARGET_AUTO);
1368 log_parse_environment();
1369 log_open();
a5c32cff 1370 label_init("/etc");
1822350d
KS
1371 umask(0022);
1372
1373 if (argc == 2 && streq(argv[1], "--introspect")) {
1374 fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
1375 "<node>\n", stdout);
1376 fputs(locale_interface, stdout);
1377 fputs("</node>\n", stdout);
1378 return 0;
1379 }
1380
1381 if (argc != 1) {
1382 log_error("This program takes no arguments.");
1383 r = -EINVAL;
1384 goto finish;
1385 }
1386
1387 r = read_data();
1388 if (r < 0) {
1389 log_error("Failed to read locale data: %s", strerror(-r));
1390 goto finish;
1391 }
1392
1393 r = connect_bus(&bus);
1394 if (r < 0)
1395 goto finish;
1396
1397 remain_until = now(CLOCK_MONOTONIC) + DEFAULT_EXIT_USEC;
1398 for (;;) {
1399
1400 if (!dbus_connection_read_write_dispatch(bus, exiting ? -1 : (int) (DEFAULT_EXIT_USEC/USEC_PER_MSEC)))
1401 break;
1402
1403 if (!exiting && remain_until < now(CLOCK_MONOTONIC)) {
1404 exiting = true;
1405 bus_async_unregister_and_exit(bus, "org.freedesktop.locale1");
1406 }
1407 }
1408
1409 r = 0;
1410
1411finish:
1412 free_data();
1413
1414 if (bus) {
1415 dbus_connection_flush(bus);
1416 dbus_connection_close(bus);
1417 dbus_connection_unref(bus);
1418 }
1419
1420 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
1421}