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