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