]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/locale/localed.c
Merge pull request #1072 from piotrdrag/master
[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
8d451309 7 Copyright 2013 Kay Sievers
1822350d
KS
8
9 systemd is free software; you can redistribute it and/or modify it
5430f7f2
LP
10 under the terms of the GNU Lesser General Public License as published by
11 the Free Software Foundation; either version 2.1 of the License, or
1822350d
KS
12 (at your option) any later version.
13
14 systemd is distributed in the hope that it will be useful, but
15 WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
5430f7f2 17 Lesser General Public License for more details.
1822350d 18
5430f7f2 19 You should have received a copy of the GNU Lesser General Public License
1822350d
KS
20 along with systemd; If not, see <http://www.gnu.org/licenses/>.
21***/
22
1822350d
KS
23#include <errno.h>
24#include <string.h>
25#include <unistd.h>
26
8d451309
KS
27#include "sd-bus.h"
28
1822350d 29#include "util.h"
49e942b2 30#include "mkdir.h"
1822350d 31#include "strv.h"
1822350d 32#include "def.h"
4d1a6904 33#include "env-util.h"
a5c32cff
HH
34#include "fileio.h"
35#include "fileio-label.h"
8d451309
KS
36#include "bus-util.h"
37#include "bus-error.h"
38#include "bus-message.h"
39#include "event-util.h"
75683450 40#include "locale-util.h"
d7b8eec7 41#include "selinux-util.h"
1822350d 42
d4f5a1f4
DH
43#ifdef HAVE_XKBCOMMON
44#include <xkbcommon/xkbcommon.h>
45#endif
46
1822350d
KS
47enum {
48 /* We don't list LC_ALL here on purpose. People should be
49 * using LANG instead. */
8d451309
KS
50 LOCALE_LANG,
51 LOCALE_LANGUAGE,
52 LOCALE_LC_CTYPE,
53 LOCALE_LC_NUMERIC,
54 LOCALE_LC_TIME,
55 LOCALE_LC_COLLATE,
56 LOCALE_LC_MONETARY,
57 LOCALE_LC_MESSAGES,
58 LOCALE_LC_PAPER,
59 LOCALE_LC_NAME,
60 LOCALE_LC_ADDRESS,
61 LOCALE_LC_TELEPHONE,
62 LOCALE_LC_MEASUREMENT,
63 LOCALE_LC_IDENTIFICATION,
64 _LOCALE_MAX
1822350d
KS
65};
66
8d451309
KS
67static const char * const names[_LOCALE_MAX] = {
68 [LOCALE_LANG] = "LANG",
69 [LOCALE_LANGUAGE] = "LANGUAGE",
70 [LOCALE_LC_CTYPE] = "LC_CTYPE",
71 [LOCALE_LC_NUMERIC] = "LC_NUMERIC",
72 [LOCALE_LC_TIME] = "LC_TIME",
73 [LOCALE_LC_COLLATE] = "LC_COLLATE",
74 [LOCALE_LC_MONETARY] = "LC_MONETARY",
75 [LOCALE_LC_MESSAGES] = "LC_MESSAGES",
76 [LOCALE_LC_PAPER] = "LC_PAPER",
77 [LOCALE_LC_NAME] = "LC_NAME",
78 [LOCALE_LC_ADDRESS] = "LC_ADDRESS",
79 [LOCALE_LC_TELEPHONE] = "LC_TELEPHONE",
80 [LOCALE_LC_MEASUREMENT] = "LC_MEASUREMENT",
81 [LOCALE_LC_IDENTIFICATION] = "LC_IDENTIFICATION"
1822350d
KS
82};
83
8d451309
KS
84typedef struct Context {
85 char *locale[_LOCALE_MAX];
1822350d 86
8d451309
KS
87 char *x11_layout;
88 char *x11_model;
89 char *x11_variant;
90 char *x11_options;
d200735e 91
8d451309
KS
92 char *vc_keymap;
93 char *vc_keymap_toggle;
1822350d 94
8d451309
KS
95 Hashmap *polkit_registry;
96} Context;
1822350d 97
af76d302
ZJS
98static const char* nonempty(const char *s) {
99 return isempty(s) ? NULL : s;
1822350d
KS
100}
101
81fd105a
ZJS
102static bool startswith_comma(const char *s, const char *prefix) {
103 const char *t;
104
105 return s && (t = startswith(s, prefix)) && (*t == ',');
106}
107
8d451309
KS
108static void context_free_x11(Context *c) {
109 free_and_replace(&c->x11_layout, NULL);
110 free_and_replace(&c->x11_model, NULL);
111 free_and_replace(&c->x11_variant, NULL);
112 free_and_replace(&c->x11_options, NULL);
113}
1822350d 114
8d451309
KS
115static void context_free_vconsole(Context *c) {
116 free_and_replace(&c->vc_keymap, NULL);
117 free_and_replace(&c->vc_keymap_toggle, NULL);
1822350d
KS
118}
119
8d451309
KS
120static void context_free_locale(Context *c) {
121 int p;
1822350d 122
8d451309
KS
123 for (p = 0; p < _LOCALE_MAX; p++)
124 free_and_replace(&c->locale[p], NULL);
1822350d
KS
125}
126
36e34057 127static void context_free(Context *c) {
8d451309
KS
128 context_free_locale(c);
129 context_free_x11(c);
130 context_free_vconsole(c);
131
36e34057 132 bus_verify_polkit_async_registry_free(c->polkit_registry);
8d451309
KS
133};
134
135static void locale_simplify(Context *c) {
1822350d
KS
136 int p;
137
8d451309 138 for (p = LOCALE_LANG+1; p < _LOCALE_MAX; p++)
af76d302
ZJS
139 if (isempty(c->locale[p]) || streq_ptr(c->locale[LOCALE_LANG], c->locale[p]))
140 free_and_replace(&c->locale[p], NULL);
1822350d
KS
141}
142
8d451309 143static int locale_read_data(Context *c) {
1822350d
KS
144 int r;
145
8d451309 146 context_free_locale(c);
1822350d
KS
147
148 r = parse_env_file("/etc/locale.conf", NEWLINE,
8d451309
KS
149 "LANG", &c->locale[LOCALE_LANG],
150 "LANGUAGE", &c->locale[LOCALE_LANGUAGE],
151 "LC_CTYPE", &c->locale[LOCALE_LC_CTYPE],
152 "LC_NUMERIC", &c->locale[LOCALE_LC_NUMERIC],
153 "LC_TIME", &c->locale[LOCALE_LC_TIME],
154 "LC_COLLATE", &c->locale[LOCALE_LC_COLLATE],
155 "LC_MONETARY", &c->locale[LOCALE_LC_MONETARY],
156 "LC_MESSAGES", &c->locale[LOCALE_LC_MESSAGES],
157 "LC_PAPER", &c->locale[LOCALE_LC_PAPER],
158 "LC_NAME", &c->locale[LOCALE_LC_NAME],
159 "LC_ADDRESS", &c->locale[LOCALE_LC_ADDRESS],
160 "LC_TELEPHONE", &c->locale[LOCALE_LC_TELEPHONE],
161 "LC_MEASUREMENT", &c->locale[LOCALE_LC_MEASUREMENT],
162 "LC_IDENTIFICATION", &c->locale[LOCALE_LC_IDENTIFICATION],
1822350d
KS
163 NULL);
164
165 if (r == -ENOENT) {
166 int p;
167
168 /* Fill in what we got passed from systemd. */
8d451309 169 for (p = 0; p < _LOCALE_MAX; p++) {
1822350d
KS
170 assert(names[p]);
171
af76d302
ZJS
172 r = free_and_strdup(&c->locale[p],
173 nonempty(getenv(names[p])));
8d451309
KS
174 if (r < 0)
175 return r;
1822350d
KS
176 }
177
178 r = 0;
179 }
180
8d451309 181 locale_simplify(c);
1822350d
KS
182 return r;
183}
184
8d451309 185static int vconsole_read_data(Context *c) {
1822350d
KS
186 int r;
187
8d451309 188 context_free_vconsole(c);
1822350d
KS
189
190 r = parse_env_file("/etc/vconsole.conf", NEWLINE,
8d451309
KS
191 "KEYMAP", &c->vc_keymap,
192 "KEYMAP_TOGGLE", &c->vc_keymap_toggle,
1822350d
KS
193 NULL);
194
195 if (r < 0 && r != -ENOENT)
196 return r;
197
198 return 0;
199}
200
8d451309 201static int x11_read_data(Context *c) {
28efac0d 202 _cleanup_fclose_ FILE *f;
1822350d
KS
203 char line[LINE_MAX];
204 bool in_section = false;
b2fadec6 205 int r;
1822350d 206
8d451309 207 context_free_x11(c);
1822350d
KS
208
209 f = fopen("/etc/X11/xorg.conf.d/00-keyboard.conf", "re");
f687b273
LP
210 if (!f)
211 return errno == ENOENT ? 0 : -errno;
1822350d
KS
212
213 while (fgets(line, sizeof(line), f)) {
214 char *l;
215
216 char_array_0(line);
217 l = strstrip(line);
218
219 if (l[0] == 0 || l[0] == '#')
220 continue;
221
222 if (in_section && first_word(l, "Option")) {
28efac0d 223 _cleanup_strv_free_ char **a = NULL;
1822350d 224
8adaf7bd 225 r = strv_split_extract(&a, l, WHITESPACE, EXTRACT_QUOTES);
28efac0d 226 if (r < 0)
b2fadec6 227 return r;
1822350d
KS
228
229 if (strv_length(a) == 3) {
1822350d 230 if (streq(a[1], "XkbLayout")) {
8d451309 231 free_and_replace(&c->x11_layout, a[2]);
1822350d
KS
232 a[2] = NULL;
233 } else if (streq(a[1], "XkbModel")) {
8d451309 234 free_and_replace(&c->x11_model, a[2]);
1822350d
KS
235 a[2] = NULL;
236 } else if (streq(a[1], "XkbVariant")) {
8d451309 237 free_and_replace(&c->x11_variant, a[2]);
1822350d
KS
238 a[2] = NULL;
239 } else if (streq(a[1], "XkbOptions")) {
8d451309 240 free_and_replace(&c->x11_options, a[2]);
1822350d
KS
241 a[2] = NULL;
242 }
243 }
244
1822350d 245 } else if (!in_section && first_word(l, "Section")) {
28efac0d 246 _cleanup_strv_free_ char **a = NULL;
1822350d 247
8adaf7bd 248 r = strv_split_extract(&a, l, WHITESPACE, EXTRACT_QUOTES);
28efac0d 249 if (r < 0)
1822350d 250 return -ENOMEM;
1822350d
KS
251
252 if (strv_length(a) == 2 && streq(a[1], "InputClass"))
253 in_section = true;
254
1822350d
KS
255 } else if (in_section && first_word(l, "EndSection"))
256 in_section = false;
257 }
258
1822350d
KS
259 return 0;
260}
261
8d451309 262static int context_read_data(Context *c) {
1822350d
KS
263 int r, q, p;
264
8d451309
KS
265 r = locale_read_data(c);
266 q = vconsole_read_data(c);
267 p = x11_read_data(c);
1822350d
KS
268
269 return r < 0 ? r : q < 0 ? q : p;
270}
271
502f9614 272static int locale_write_data(Context *c, char ***settings) {
1822350d 273 int r, p;
28efac0d 274 _cleanup_strv_free_ char **l = NULL;
1822350d 275
502f9614
ZJS
276 /* Set values will be returned as strv in *settings on success. */
277
717603e3 278 r = load_env_file(NULL, "/etc/locale.conf", NULL, &l);
1822350d
KS
279 if (r < 0 && r != -ENOENT)
280 return r;
281
8d451309 282 for (p = 0; p < _LOCALE_MAX; p++) {
28efac0d
ZJS
283 _cleanup_free_ char *t = NULL;
284 char **u;
1822350d
KS
285
286 assert(names[p]);
287
8d451309 288 if (isempty(c->locale[p])) {
1822350d
KS
289 l = strv_env_unset(l, names[p]);
290 continue;
291 }
292
28efac0d 293 if (asprintf(&t, "%s=%s", names[p], c->locale[p]) < 0)
1822350d 294 return -ENOMEM;
1822350d
KS
295
296 u = strv_env_set(l, t);
1822350d
KS
297 if (!u)
298 return -ENOMEM;
299
28efac0d 300 strv_free(l);
1822350d
KS
301 l = u;
302 }
303
304 if (strv_isempty(l)) {
1822350d
KS
305 if (unlink("/etc/locale.conf") < 0)
306 return errno == ENOENT ? 0 : -errno;
307
308 return 0;
309 }
310
502f9614
ZJS
311 r = write_env_file_label("/etc/locale.conf", l);
312 if (r < 0)
313 return r;
314
315 *settings = l;
316 l = NULL;
317 return 0;
1822350d
KS
318}
319
8d451309
KS
320static int locale_update_system_manager(Context *c, sd_bus *bus) {
321 _cleanup_free_ char **l_unset = NULL;
322 _cleanup_strv_free_ char **l_set = NULL;
323 _cleanup_bus_message_unref_ sd_bus_message *m = NULL;
324 sd_bus_error error = SD_BUS_ERROR_NULL;
325 unsigned c_set, c_unset, p;
326 int r;
1822350d
KS
327
328 assert(bus);
329
8d451309
KS
330 l_unset = new0(char*, _LOCALE_MAX);
331 if (!l_unset)
332 return -ENOMEM;
1822350d 333
8d451309
KS
334 l_set = new0(char*, _LOCALE_MAX);
335 if (!l_set)
336 return -ENOMEM;
337
338 for (p = 0, c_set = 0, c_unset = 0; p < _LOCALE_MAX; p++) {
1822350d
KS
339 assert(names[p]);
340
8d451309 341 if (isempty(c->locale[p]))
1822350d
KS
342 l_unset[c_set++] = (char*) names[p];
343 else {
344 char *s;
345
8d451309
KS
346 if (asprintf(&s, "%s=%s", names[p], c->locale[p]) < 0)
347 return -ENOMEM;
1822350d
KS
348
349 l_set[c_unset++] = s;
350 }
351 }
352
8d451309 353 assert(c_set + c_unset == _LOCALE_MAX);
151b9b96 354 r = sd_bus_message_new_method_call(bus, &m,
8d451309
KS
355 "org.freedesktop.systemd1",
356 "/org/freedesktop/systemd1",
357 "org.freedesktop.systemd1.Manager",
151b9b96 358 "UnsetAndSetEnvironment");
8d451309
KS
359 if (r < 0)
360 return r;
1822350d 361
8d451309
KS
362 r = sd_bus_message_append_strv(m, l_unset);
363 if (r < 0)
364 return r;
1822350d 365
8d451309
KS
366 r = sd_bus_message_append_strv(m, l_set);
367 if (r < 0)
368 return r;
1822350d 369
c49b30a2 370 r = sd_bus_call(bus, m, 0, &error, NULL);
8d451309 371 if (r < 0)
da927ba9 372 log_error_errno(r, "Failed to update the manager environment: %m");
1822350d 373
8d451309 374 return 0;
1822350d
KS
375}
376
8d451309 377static int vconsole_write_data(Context *c) {
1822350d 378 int r;
98fce79d 379 _cleanup_strv_free_ char **l = NULL;
1822350d 380
717603e3 381 r = load_env_file(NULL, "/etc/vconsole.conf", NULL, &l);
1822350d
KS
382 if (r < 0 && r != -ENOENT)
383 return r;
384
8d451309 385 if (isempty(c->vc_keymap))
1822350d
KS
386 l = strv_env_unset(l, "KEYMAP");
387 else {
28efac0d
ZJS
388 _cleanup_free_ char *s = NULL;
389 char **u;
1822350d 390
8d451309 391 s = strappend("KEYMAP=", c->vc_keymap);
98fce79d 392 if (!s)
1822350d 393 return -ENOMEM;
1822350d
KS
394
395 u = strv_env_set(l, s);
1822350d
KS
396 if (!u)
397 return -ENOMEM;
398
28efac0d 399 strv_free(l);
1822350d
KS
400 l = u;
401 }
402
8d451309 403 if (isempty(c->vc_keymap_toggle))
1822350d
KS
404 l = strv_env_unset(l, "KEYMAP_TOGGLE");
405 else {
28efac0d
ZJS
406 _cleanup_free_ char *s = NULL;
407 char **u;
1822350d 408
8d451309 409 s = strappend("KEYMAP_TOGGLE=", c->vc_keymap_toggle);
98fce79d 410 if (!s)
1822350d 411 return -ENOMEM;
1822350d
KS
412
413 u = strv_env_set(l, s);
1822350d
KS
414 if (!u)
415 return -ENOMEM;
416
28efac0d 417 strv_free(l);
1822350d
KS
418 l = u;
419 }
420
421 if (strv_isempty(l)) {
1822350d
KS
422 if (unlink("/etc/vconsole.conf") < 0)
423 return errno == ENOENT ? 0 : -errno;
424
425 return 0;
426 }
427
28efac0d 428 return write_env_file_label("/etc/vconsole.conf", l);
1822350d
KS
429}
430
e78af5ff 431static int x11_write_data(Context *c) {
98fce79d
ZJS
432 _cleanup_fclose_ FILE *f = NULL;
433 _cleanup_free_ char *temp_path = NULL;
1822350d
KS
434 int r;
435
8d451309
KS
436 if (isempty(c->x11_layout) &&
437 isempty(c->x11_model) &&
438 isempty(c->x11_variant) &&
439 isempty(c->x11_options)) {
1822350d 440
1822350d
KS
441 if (unlink("/etc/X11/xorg.conf.d/00-keyboard.conf") < 0)
442 return errno == ENOENT ? 0 : -errno;
443
444 return 0;
445 }
446
9a9bb3ca 447 mkdir_p_label("/etc/X11/xorg.conf.d", 0755);
1822350d
KS
448
449 r = fopen_temporary("/etc/X11/xorg.conf.d/00-keyboard.conf", &f, &temp_path);
450 if (r < 0)
451 return r;
452
453 fchmod(fileno(f), 0644);
454
455 fputs("# Read and parsed by systemd-localed. It's probably wise not to edit this file\n"
456 "# manually too freely.\n"
457 "Section \"InputClass\"\n"
458 " Identifier \"system-keyboard\"\n"
459 " MatchIsKeyboard \"on\"\n", f);
460
8d451309
KS
461 if (!isempty(c->x11_layout))
462 fprintf(f, " Option \"XkbLayout\" \"%s\"\n", c->x11_layout);
1822350d 463
8d451309
KS
464 if (!isempty(c->x11_model))
465 fprintf(f, " Option \"XkbModel\" \"%s\"\n", c->x11_model);
1822350d 466
8d451309
KS
467 if (!isempty(c->x11_variant))
468 fprintf(f, " Option \"XkbVariant\" \"%s\"\n", c->x11_variant);
1822350d 469
8d451309
KS
470 if (!isempty(c->x11_options))
471 fprintf(f, " Option \"XkbOptions\" \"%s\"\n", c->x11_options);
1822350d
KS
472
473 fputs("EndSection\n", f);
1822350d 474
dacd6cee
LP
475 r = fflush_and_check(f);
476 if (r < 0)
477 goto fail;
478
479 if (rename(temp_path, "/etc/X11/xorg.conf.d/00-keyboard.conf") < 0) {
1822350d 480 r = -errno;
dacd6cee
LP
481 goto fail;
482 }
483
484 return 0;
485
486fail:
487 (void) unlink("/etc/X11/xorg.conf.d/00-keyboard.conf");
488
489 if (temp_path)
490 (void) unlink(temp_path);
491
492 return r;
1822350d
KS
493}
494
8d451309
KS
495static int vconsole_reload(sd_bus *bus) {
496 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
1822350d 497 int r;
1822350d
KS
498
499 assert(bus);
500
8d451309 501 r = sd_bus_call_method(bus,
1822350d
KS
502 "org.freedesktop.systemd1",
503 "/org/freedesktop/systemd1",
504 "org.freedesktop.systemd1.Manager",
8d451309
KS
505 "RestartUnit",
506 &error,
507 NULL,
508 "ss", "systemd-vconsole-setup.service", "replace");
1822350d 509
8d451309
KS
510 if (r < 0)
511 log_error("Failed to issue method call: %s", bus_error_message(&error, -r));
1822350d
KS
512 return r;
513}
514
af76d302
ZJS
515static const char* strnulldash(const char *s) {
516 return isempty(s) || streq(s, "-") ? NULL : s;
1822350d
KS
517}
518
4e829d21
ZJS
519static int read_next_mapping(const char* filename,
520 unsigned min_fields, unsigned max_fields,
521 FILE *f, unsigned *n, char ***a) {
1822350d
KS
522 assert(f);
523 assert(n);
524 assert(a);
525
526 for (;;) {
527 char line[LINE_MAX];
528 char *l, **b;
b2fadec6 529 int r;
4e829d21 530 size_t length;
1822350d
KS
531
532 errno = 0;
533 if (!fgets(line, sizeof(line), f)) {
534
535 if (ferror(f))
536 return errno ? -errno : -EIO;
537
538 return 0;
539 }
540
541 (*n) ++;
542
543 l = strstrip(line);
544 if (l[0] == 0 || l[0] == '#')
545 continue;
546
8adaf7bd 547 r = strv_split_extract(&b, l, WHITESPACE, EXTRACT_QUOTES);
b2fadec6
ZJS
548 if (r < 0)
549 return r;
1822350d 550
4e829d21
ZJS
551 length = strv_length(b);
552 if (length < min_fields || length > max_fields) {
553 log_error("Invalid line %s:%u, ignoring.", filename, *n);
1822350d
KS
554 strv_free(b);
555 continue;
556
557 }
558
559 *a = b;
560 return 1;
561 }
562}
563
8d451309 564static int vconsole_convert_to_x11(Context *c, sd_bus *bus) {
1822350d
KS
565 bool modified = false;
566
8d451309 567 assert(bus);
1822350d 568
8d451309 569 if (isempty(c->vc_keymap)) {
1822350d
KS
570
571 modified =
8d451309
KS
572 !isempty(c->x11_layout) ||
573 !isempty(c->x11_model) ||
574 !isempty(c->x11_variant) ||
575 !isempty(c->x11_options);
1822350d 576
8d451309 577 context_free_x11(c);
1822350d 578 } else {
98fce79d 579 _cleanup_fclose_ FILE *f = NULL;
1822350d
KS
580 unsigned n = 0;
581
582 f = fopen(SYSTEMD_KBD_MODEL_MAP, "re");
583 if (!f)
584 return -errno;
585
586 for (;;) {
98fce79d 587 _cleanup_strv_free_ char **a = NULL;
1822350d
KS
588 int r;
589
4e829d21 590 r = read_next_mapping(SYSTEMD_KBD_MODEL_MAP, 5, UINT_MAX, f, &n, &a);
98fce79d 591 if (r < 0)
1822350d 592 return r;
1822350d
KS
593 if (r == 0)
594 break;
595
98fce79d 596 if (!streq(c->vc_keymap, a[0]))
1822350d 597 continue;
1822350d 598
8d451309
KS
599 if (!streq_ptr(c->x11_layout, strnulldash(a[1])) ||
600 !streq_ptr(c->x11_model, strnulldash(a[2])) ||
601 !streq_ptr(c->x11_variant, strnulldash(a[3])) ||
602 !streq_ptr(c->x11_options, strnulldash(a[4]))) {
1822350d 603
af76d302
ZJS
604 if (free_and_strdup(&c->x11_layout, strnulldash(a[1])) < 0 ||
605 free_and_strdup(&c->x11_model, strnulldash(a[2])) < 0 ||
606 free_and_strdup(&c->x11_variant, strnulldash(a[3])) < 0 ||
607 free_and_strdup(&c->x11_options, strnulldash(a[4])) < 0)
1822350d 608 return -ENOMEM;
1822350d
KS
609
610 modified = true;
611 }
612
1822350d
KS
613 break;
614 }
1822350d
KS
615 }
616
617 if (modified) {
1822350d
KS
618 int r;
619
e78af5ff 620 r = x11_write_data(c);
f647962d
MS
621 if (r < 0)
622 return log_error_errno(r, "Failed to set X11 keyboard layout: %m");
1822350d 623
502f9614
ZJS
624 log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
625 strempty(c->x11_layout),
626 strempty(c->x11_model),
627 strempty(c->x11_variant),
628 strempty(c->x11_options));
629
8d451309 630 sd_bus_emit_properties_changed(bus,
1822350d
KS
631 "/org/freedesktop/locale1",
632 "org.freedesktop.locale1",
8d451309 633 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL);
502f9614
ZJS
634 } else
635 log_debug("X11 keyboard layout was not modified.");
1822350d
KS
636
637 return 0;
638}
639
78bd12a0 640static int find_converted_keymap(const char *x11_layout, const char *x11_variant, char **new_keymap) {
0732ef7a
ZJS
641 const char *dir;
642 _cleanup_free_ char *n;
643
78bd12a0
ZJS
644 if (x11_variant)
645 n = strjoin(x11_layout, "-", x11_variant, NULL);
0732ef7a 646 else
78bd12a0 647 n = strdup(x11_layout);
0732ef7a
ZJS
648 if (!n)
649 return -ENOMEM;
650
651 NULSTR_FOREACH(dir, KBD_KEYMAP_DIRS) {
652 _cleanup_free_ char *p = NULL, *pz = NULL;
502f9614 653 bool uncompressed;
0732ef7a
ZJS
654
655 p = strjoin(dir, "xkb/", n, ".map", NULL);
656 pz = strjoin(dir, "xkb/", n, ".map.gz", NULL);
657 if (!p || !pz)
658 return -ENOMEM;
659
502f9614
ZJS
660 uncompressed = access(p, F_OK) == 0;
661 if (uncompressed || access(pz, F_OK) == 0) {
662 log_debug("Found converted keymap %s at %s",
663 n, uncompressed ? p : pz);
664
0732ef7a
ZJS
665 *new_keymap = n;
666 n = NULL;
667 return 1;
668 }
669 }
670
671 return 0;
672}
673
674static int find_legacy_keymap(Context *c, char **new_keymap) {
675 _cleanup_fclose_ FILE *f;
676 unsigned n = 0;
677 unsigned best_matching = 0;
78bd12a0 678 int r;
0732ef7a
ZJS
679
680 f = fopen(SYSTEMD_KBD_MODEL_MAP, "re");
681 if (!f)
682 return -errno;
683
684 for (;;) {
685 _cleanup_strv_free_ char **a = NULL;
686 unsigned matching = 0;
0732ef7a 687
4e829d21 688 r = read_next_mapping(SYSTEMD_KBD_MODEL_MAP, 5, UINT_MAX, f, &n, &a);
0732ef7a
ZJS
689 if (r < 0)
690 return r;
691 if (r == 0)
692 break;
693
694 /* Determine how well matching this entry is */
695 if (streq_ptr(c->x11_layout, a[1]))
696 /* If we got an exact match, this is best */
697 matching = 10;
698 else {
0732ef7a
ZJS
699 /* We have multiple X layouts, look for an
700 * entry that matches our key with everything
701 * but the first layout stripped off. */
81fd105a 702 if (startswith_comma(c->x11_layout, a[1]))
0732ef7a
ZJS
703 matching = 5;
704 else {
81fd105a 705 char *x;
0732ef7a
ZJS
706
707 /* If that didn't work, strip off the
708 * other layouts from the entry, too */
81fd105a
ZJS
709 x = strndupa(a[1], strcspn(a[1], ","));
710 if (startswith_comma(c->x11_layout, x))
0732ef7a
ZJS
711 matching = 1;
712 }
713 }
714
387066c2
MS
715 if (matching > 0) {
716 if (isempty(c->x11_model) || streq_ptr(c->x11_model, a[2])) {
0732ef7a
ZJS
717 matching++;
718
387066c2 719 if (streq_ptr(c->x11_variant, a[3])) {
0732ef7a 720 matching++;
387066c2
MS
721
722 if (streq_ptr(c->x11_options, a[4]))
723 matching++;
724 }
0732ef7a
ZJS
725 }
726 }
727
728 /* The best matching entry so far, then let's save that */
502f9614
ZJS
729 if (matching >= MAX(best_matching, 1u)) {
730 log_debug("Found legacy keymap %s with score %u",
731 a[0], matching);
0732ef7a 732
502f9614
ZJS
733 if (matching > best_matching) {
734 best_matching = matching;
735
736 r = free_and_strdup(new_keymap, a[0]);
737 if (r < 0)
738 return r;
739 }
0732ef7a
ZJS
740 }
741 }
742
78bd12a0
ZJS
743 if (best_matching < 10 && c->x11_layout) {
744 /* The best match is only the first part of the X11
745 * keymap. Check if we have a converted map which
746 * matches just the first layout.
747 */
748 char *l, *v = NULL, *converted;
749
750 l = strndupa(c->x11_layout, strcspn(c->x11_layout, ","));
751 if (c->x11_variant)
752 v = strndupa(c->x11_variant, strcspn(c->x11_variant, ","));
753 r = find_converted_keymap(l, v, &converted);
754 if (r < 0)
755 return r;
756 if (r > 0)
757 free_and_replace(new_keymap, converted);
758 }
759
0732ef7a
ZJS
760 return 0;
761}
762
4e829d21
ZJS
763static int find_language_fallback(const char *lang, char **language) {
764 _cleanup_fclose_ FILE *f = NULL;
765 unsigned n = 0;
766
767 assert(language);
768
769 f = fopen(SYSTEMD_LANGUAGE_FALLBACK_MAP, "re");
770 if (!f)
771 return -errno;
772
773 for (;;) {
774 _cleanup_strv_free_ char **a = NULL;
775 int r;
776
777 r = read_next_mapping(SYSTEMD_LANGUAGE_FALLBACK_MAP, 2, 2, f, &n, &a);
778 if (r <= 0)
779 return r;
780
781 if (streq(lang, a[0])) {
782 assert(strv_length(a) == 2);
783 *language = a[1];
784 a[1] = NULL;
785 return 1;
786 }
787 }
788
789 assert_not_reached("should not be here");
790}
791
8d451309 792static int x11_convert_to_vconsole(Context *c, sd_bus *bus) {
1822350d 793 bool modified = false;
0732ef7a 794 int r;
1822350d 795
8d451309 796 assert(bus);
1822350d 797
8d451309 798 if (isempty(c->x11_layout)) {
1822350d
KS
799
800 modified =
8d451309
KS
801 !isempty(c->vc_keymap) ||
802 !isempty(c->vc_keymap_toggle);
1822350d 803
8d451309 804 context_free_x11(c);
1822350d 805 } else {
1822350d
KS
806 char *new_keymap = NULL;
807
78bd12a0 808 r = find_converted_keymap(c->x11_layout, c->x11_variant, &new_keymap);
0732ef7a
ZJS
809 if (r < 0)
810 return r;
811 else if (r == 0) {
812 r = find_legacy_keymap(c, &new_keymap);
b47d419c 813 if (r < 0)
1822350d 814 return r;
1822350d
KS
815 }
816
8d451309
KS
817 if (!streq_ptr(c->vc_keymap, new_keymap)) {
818 free_and_replace(&c->vc_keymap, new_keymap);
819 free_and_replace(&c->vc_keymap_toggle, NULL);
1822350d
KS
820 modified = true;
821 } else
822 free(new_keymap);
823 }
824
825 if (modified) {
8d451309 826 r = vconsole_write_data(c);
1822350d 827 if (r < 0)
da927ba9 828 log_error_errno(r, "Failed to set virtual console keymap: %m");
1822350d 829
502f9614
ZJS
830 log_info("Changed virtual console keymap to '%s' toggle '%s'",
831 strempty(c->vc_keymap), strempty(c->vc_keymap_toggle));
832
8d451309 833 sd_bus_emit_properties_changed(bus,
1822350d
KS
834 "/org/freedesktop/locale1",
835 "org.freedesktop.locale1",
8d451309 836 "VConsoleKeymap", "VConsoleKeymapToggle", NULL);
1822350d 837
8d451309 838 return vconsole_reload(bus);
502f9614
ZJS
839 } else
840 log_debug("Virtual console keymap was not modified.");
1822350d
KS
841
842 return 0;
843}
844
ebcf1f97
LP
845static int property_get_locale(
846 sd_bus *bus,
847 const char *path,
848 const char *interface,
849 const char *property,
850 sd_bus_message *reply,
851 void *userdata,
852 sd_bus_error *error) {
853
8d451309 854 Context *c = userdata;
b47d419c 855 _cleanup_strv_free_ char **l = NULL;
8d451309 856 int p, q;
1822350d 857
8d451309 858 l = new0(char*, _LOCALE_MAX+1);
1822350d
KS
859 if (!l)
860 return -ENOMEM;
861
8d451309 862 for (p = 0, q = 0; p < _LOCALE_MAX; p++) {
1822350d
KS
863 char *t;
864
8d451309 865 if (isempty(c->locale[p]))
1822350d
KS
866 continue;
867
8d451309 868 if (asprintf(&t, "%s=%s", names[p], c->locale[p]) < 0)
1822350d 869 return -ENOMEM;
1822350d 870
8d451309 871 l[q++] = t;
1822350d
KS
872 }
873
8d451309 874 return sd_bus_message_append_strv(reply, l);
1822350d
KS
875}
876
19070062 877static int method_set_locale(sd_bus_message *m, void *userdata, sd_bus_error *error) {
8d451309 878 Context *c = userdata;
8d451309
KS
879 _cleanup_strv_free_ char **l = NULL;
880 char **i;
4e829d21 881 const char *lang = NULL;
8d451309
KS
882 int interactive;
883 bool modified = false;
4e829d21 884 bool have[_LOCALE_MAX] = {};
8d451309 885 int p;
1822350d
KS
886 int r;
887
19070062
LP
888 assert(m);
889 assert(c);
890
8d451309
KS
891 r = bus_message_read_strv_extend(m, &l);
892 if (r < 0)
ebcf1f97 893 return r;
1822350d 894
8d451309
KS
895 r = sd_bus_message_read_basic(m, 'b', &interactive);
896 if (r < 0)
ebcf1f97 897 return r;
1822350d 898
502f9614 899 /* Check whether a variable changed and if it is valid */
8d451309
KS
900 STRV_FOREACH(i, l) {
901 bool valid = false;
1822350d 902
8d451309
KS
903 for (p = 0; p < _LOCALE_MAX; p++) {
904 size_t k;
1822350d 905
8d451309
KS
906 k = strlen(names[p]);
907 if (startswith(*i, names[p]) &&
908 (*i)[k] == '=' &&
75683450 909 locale_is_valid((*i) + k + 1)) {
8d451309 910 valid = true;
4e829d21
ZJS
911 have[p] = true;
912
913 if (p == LOCALE_LANG)
914 lang = (*i) + k + 1;
1822350d 915
8d451309
KS
916 if (!streq_ptr(*i + k + 1, c->locale[p]))
917 modified = true;
1822350d 918
8d451309
KS
919 break;
920 }
1822350d
KS
921 }
922
8d451309 923 if (!valid)
ebcf1f97 924 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid Locale data.");
8d451309 925 }
1822350d 926
4e829d21
ZJS
927 /* If LANG was specified, but not LANGUAGE, check if we should
928 * set it based on the language fallback table. */
929 if (have[LOCALE_LANG] && !have[LOCALE_LANGUAGE]) {
930 _cleanup_free_ char *language = NULL;
931
932 assert(lang);
933
934 (void) find_language_fallback(lang, &language);
935 if (language) {
936 log_debug("Converted LANG=%s to LANGUAGE=%s", lang, language);
937 if (!streq_ptr(language, c->locale[LOCALE_LANGUAGE])) {
938 r = strv_extendf(&l, "LANGUAGE=%s", language);
939 if (r < 0)
940 return r;
941
942 have[LOCALE_LANGUAGE] = true;
943 modified = true;
944 }
945 }
946 }
947
8d451309 948 /* Check whether a variable is unset */
28efac0d 949 if (!modified)
8d451309 950 for (p = 0; p < _LOCALE_MAX; p++)
4e829d21 951 if (!isempty(c->locale[p]) && !have[p]) {
8d451309
KS
952 modified = true;
953 break;
954 }
8d451309
KS
955
956 if (modified) {
502f9614
ZJS
957 _cleanup_strv_free_ char **settings = NULL;
958
c529695e
LP
959 r = bus_verify_polkit_async(
960 m,
961 CAP_SYS_ADMIN,
962 "org.freedesktop.locale1.set-locale",
963 interactive,
964 UID_INVALID,
965 &c->polkit_registry,
966 error);
8d451309 967 if (r < 0)
ebcf1f97 968 return r;
8d451309
KS
969 if (r == 0)
970 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
1822350d 971
28efac0d 972 STRV_FOREACH(i, l)
8d451309 973 for (p = 0; p < _LOCALE_MAX; p++) {
1822350d
KS
974 size_t k;
975
976 k = strlen(names[p]);
8d451309 977 if (startswith(*i, names[p]) && (*i)[k] == '=') {
af76d302
ZJS
978 r = free_and_strdup(&c->locale[p], *i + k + 1);
979 if (r < 0)
980 return r;
1822350d
KS
981 break;
982 }
983 }
1822350d 984
8d451309 985 for (p = 0; p < _LOCALE_MAX; p++) {
4e829d21 986 if (have[p])
8d451309 987 continue;
1822350d 988
8d451309
KS
989 free_and_replace(&c->locale[p], NULL);
990 }
1822350d 991
8d451309 992 locale_simplify(c);
1822350d 993
502f9614 994 r = locale_write_data(c, &settings);
8d451309 995 if (r < 0) {
da927ba9 996 log_error_errno(r, "Failed to set locale: %m");
ebcf1f97 997 return sd_bus_error_set_errnof(error, r, "Failed to set locale: %s", strerror(-r));
8d451309 998 }
1822350d 999
19070062 1000 locale_update_system_manager(c, sd_bus_message_get_bus(m));
1822350d 1001
502f9614
ZJS
1002 if (settings) {
1003 _cleanup_free_ char *line;
1004
1005 line = strv_join(settings, ", ");
1006 log_info("Changed locale to %s.", strnull(line));
1007 } else
1008 log_info("Changed locale to unset.");
1822350d 1009
19070062
LP
1010 (void) sd_bus_emit_properties_changed(
1011 sd_bus_message_get_bus(m),
8d451309
KS
1012 "/org/freedesktop/locale1",
1013 "org.freedesktop.locale1",
1014 "Locale", NULL);
502f9614
ZJS
1015 } else
1016 log_debug("Locale settings were not modified.");
1017
1822350d 1018
df2d202e 1019 return sd_bus_reply_method_return(m, NULL);
8d451309 1020}
1822350d 1021
19070062 1022static int method_set_vc_keyboard(sd_bus_message *m, void *userdata, sd_bus_error *error) {
8d451309 1023 Context *c = userdata;
8d451309 1024 const char *keymap, *keymap_toggle;
102d8f81 1025 int convert, interactive;
8d451309 1026 int r;
f2cc3753 1027
19070062
LP
1028 assert(m);
1029 assert(c);
1030
8d451309
KS
1031 r = sd_bus_message_read(m, "ssbb", &keymap, &keymap_toggle, &convert, &interactive);
1032 if (r < 0)
ebcf1f97 1033 return r;
1822350d 1034
8d451309
KS
1035 if (isempty(keymap))
1036 keymap = NULL;
1822350d 1037
8d451309
KS
1038 if (isempty(keymap_toggle))
1039 keymap_toggle = NULL;
1822350d 1040
8d451309
KS
1041 if (!streq_ptr(keymap, c->vc_keymap) ||
1042 !streq_ptr(keymap_toggle, c->vc_keymap_toggle)) {
1822350d 1043
ae6c3cc0
LP
1044 if ((keymap && (!filename_is_valid(keymap) || !string_is_safe(keymap))) ||
1045 (keymap_toggle && (!filename_is_valid(keymap_toggle) || !string_is_safe(keymap_toggle))))
d14ab08b 1046 return sd_bus_error_set_errnof(error, -EINVAL, "Received invalid keymap data");
1822350d 1047
c529695e
LP
1048 r = bus_verify_polkit_async(
1049 m,
1050 CAP_SYS_ADMIN,
1051 "org.freedesktop.locale1.set-keyboard",
1052 interactive,
1053 UID_INVALID,
1054 &c->polkit_registry,
1055 error);
8d451309 1056 if (r < 0)
ebcf1f97 1057 return r;
8d451309
KS
1058 if (r == 0)
1059 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
1822350d 1060
af76d302
ZJS
1061 if (free_and_strdup(&c->vc_keymap, keymap) < 0 ||
1062 free_and_strdup(&c->vc_keymap_toggle, keymap_toggle) < 0)
8d451309 1063 return -ENOMEM;
0b507b17 1064
8d451309
KS
1065 r = vconsole_write_data(c);
1066 if (r < 0) {
da927ba9 1067 log_error_errno(r, "Failed to set virtual console keymap: %m");
ebcf1f97 1068 return sd_bus_error_set_errnof(error, r, "Failed to set virtual console keymap: %s", strerror(-r));
8d451309 1069 }
1822350d 1070
502f9614
ZJS
1071 log_info("Changed virtual console keymap to '%s' toggle '%s'",
1072 strempty(c->vc_keymap), strempty(c->vc_keymap_toggle));
1822350d 1073
19070062 1074 r = vconsole_reload(sd_bus_message_get_bus(m));
8d451309 1075 if (r < 0)
da927ba9 1076 log_error_errno(r, "Failed to request keymap reload: %m");
1822350d 1077
19070062
LP
1078 (void) sd_bus_emit_properties_changed(
1079 sd_bus_message_get_bus(m),
8d451309
KS
1080 "/org/freedesktop/locale1",
1081 "org.freedesktop.locale1",
1082 "VConsoleKeymap", "VConsoleKeymapToggle", NULL);
1822350d 1083
8d451309 1084 if (convert) {
19070062 1085 r = vconsole_convert_to_x11(c, sd_bus_message_get_bus(m));
1822350d 1086 if (r < 0)
da927ba9 1087 log_error_errno(r, "Failed to convert keymap data: %m");
1822350d 1088 }
8d451309 1089 }
1822350d 1090
df2d202e 1091 return sd_bus_reply_method_return(m, NULL);
8d451309 1092}
1822350d 1093
d4f5a1f4
DH
1094#ifdef HAVE_XKBCOMMON
1095static void log_xkb(struct xkb_context *ctx, enum xkb_log_level lvl, const char *format, va_list args) {
8433e339
JS
1096 const char *fmt;
1097
63c372cb 1098 fmt = strjoina("libxkbcommon: ", format);
8433e339 1099 log_internalv(LOG_DEBUG, 0, __FILE__, __LINE__, __func__, fmt, args);
d4f5a1f4
DH
1100}
1101
1102static int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, const char *options) {
1103 const struct xkb_rule_names rmlvo = {
1104 .model = model,
1105 .layout = layout,
1106 .variant = variant,
1107 .options = options,
1108 };
1109 struct xkb_context *ctx = NULL;
1110 struct xkb_keymap *km = NULL;
1111 int r;
1112
1113 /* compile keymap from RMLVO information to check out its validity */
1114
1115 ctx = xkb_context_new(XKB_CONTEXT_NO_ENVIRONMENT_NAMES);
1116 if (!ctx) {
1117 r = -ENOMEM;
1118 goto exit;
1119 }
1120
1121 xkb_context_set_log_fn(ctx, log_xkb);
1122
1123 km = xkb_keymap_new_from_names(ctx, &rmlvo, XKB_KEYMAP_COMPILE_NO_FLAGS);
1124 if (!km) {
1125 r = -EINVAL;
1126 goto exit;
1127 }
1128
1129 r = 0;
1130
1131exit:
1132 xkb_keymap_unref(km);
1133 xkb_context_unref(ctx);
1134 return r;
1135}
1136#else
1137static int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, const char *options) {
1138 return 0;
1139}
1140#endif
1141
19070062 1142static int method_set_x11_keyboard(sd_bus_message *m, void *userdata, sd_bus_error *error) {
8d451309 1143 Context *c = userdata;
8d451309 1144 const char *layout, *model, *variant, *options;
102d8f81 1145 int convert, interactive;
8d451309 1146 int r;
1822350d 1147
19070062
LP
1148 assert(m);
1149 assert(c);
1150
8d451309
KS
1151 r = sd_bus_message_read(m, "ssssbb", &layout, &model, &variant, &options, &convert, &interactive);
1152 if (r < 0)
ebcf1f97 1153 return r;
1822350d 1154
8d451309
KS
1155 if (isempty(layout))
1156 layout = NULL;
0b507b17 1157
8d451309
KS
1158 if (isempty(model))
1159 model = NULL;
1822350d 1160
8d451309
KS
1161 if (isempty(variant))
1162 variant = NULL;
1822350d 1163
8d451309
KS
1164 if (isempty(options))
1165 options = NULL;
1822350d 1166
8d451309
KS
1167 if (!streq_ptr(layout, c->x11_layout) ||
1168 !streq_ptr(model, c->x11_model) ||
1169 !streq_ptr(variant, c->x11_variant) ||
1170 !streq_ptr(options, c->x11_options)) {
1822350d 1171
8d451309
KS
1172 if ((layout && !string_is_safe(layout)) ||
1173 (model && !string_is_safe(model)) ||
1174 (variant && !string_is_safe(variant)) ||
1175 (options && !string_is_safe(options)))
d14ab08b 1176 return sd_bus_error_set_errnof(error, -EINVAL, "Received invalid keyboard data");
1822350d 1177
c529695e
LP
1178 r = bus_verify_polkit_async(
1179 m,
1180 CAP_SYS_ADMIN,
1181 "org.freedesktop.locale1.set-keyboard",
1182 interactive,
1183 UID_INVALID,
1184 &c->polkit_registry,
1185 error);
8d451309 1186 if (r < 0)
ebcf1f97 1187 return r;
8d451309
KS
1188 if (r == 0)
1189 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
1190
8623d3a3 1191 r = verify_xkb_rmlvo(model, layout, variant, options);
8433e339
JS
1192 if (r < 0) {
1193 log_error_errno(r, "Cannot compile XKB keymap for new x11 keyboard layout ('%s' / '%s' / '%s' / '%s'): %m",
1194 strempty(model), strempty(layout), strempty(variant), strempty(options));
1195 return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Cannot compile XKB keymap, refusing");
1196 }
8623d3a3 1197
af76d302
ZJS
1198 if (free_and_strdup(&c->x11_layout, layout) < 0 ||
1199 free_and_strdup(&c->x11_model, model) < 0 ||
1200 free_and_strdup(&c->x11_variant, variant) < 0 ||
1201 free_and_strdup(&c->x11_options, options) < 0)
8d451309 1202 return -ENOMEM;
1822350d 1203
e78af5ff 1204 r = x11_write_data(c);
8d451309 1205 if (r < 0) {
da927ba9 1206 log_error_errno(r, "Failed to set X11 keyboard layout: %m");
ebcf1f97 1207 return sd_bus_error_set_errnof(error, r, "Failed to set X11 keyboard layout: %s", strerror(-r));
1822350d 1208 }
1822350d 1209
502f9614
ZJS
1210 log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'",
1211 strempty(c->x11_layout),
1212 strempty(c->x11_model),
1213 strempty(c->x11_variant),
1214 strempty(c->x11_options));
1822350d 1215
19070062
LP
1216 (void) sd_bus_emit_properties_changed(
1217 sd_bus_message_get_bus(m),
8d451309
KS
1218 "/org/freedesktop/locale1",
1219 "org.freedesktop.locale1",
c168eb67 1220 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL);
1822350d 1221
8d451309 1222 if (convert) {
19070062 1223 r = x11_convert_to_vconsole(c, sd_bus_message_get_bus(m));
8d451309 1224 if (r < 0)
da927ba9 1225 log_error_errno(r, "Failed to convert keymap data: %m");
8d451309 1226 }
1822350d
KS
1227 }
1228
df2d202e 1229 return sd_bus_reply_method_return(m, NULL);
1822350d
KS
1230}
1231
8d451309
KS
1232static const sd_bus_vtable locale_vtable[] = {
1233 SD_BUS_VTABLE_START(0),
6d1bd3b2
LP
1234 SD_BUS_PROPERTY("Locale", "as", property_get_locale, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1235 SD_BUS_PROPERTY("X11Layout", "s", NULL, offsetof(Context, x11_layout), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1236 SD_BUS_PROPERTY("X11Model", "s", NULL, offsetof(Context, x11_model), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1237 SD_BUS_PROPERTY("X11Variant", "s", NULL, offsetof(Context, x11_variant), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1238 SD_BUS_PROPERTY("X11Options", "s", NULL, offsetof(Context, x11_options), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1239 SD_BUS_PROPERTY("VConsoleKeymap", "s", NULL, offsetof(Context, vc_keymap), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1240 SD_BUS_PROPERTY("VConsoleKeymapToggle", "s", NULL, offsetof(Context, vc_keymap_toggle), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
adacb957
LP
1241 SD_BUS_METHOD("SetLocale", "asb", NULL, method_set_locale, SD_BUS_VTABLE_UNPRIVILEGED),
1242 SD_BUS_METHOD("SetVConsoleKeyboard", "ssbb", NULL, method_set_vc_keyboard, SD_BUS_VTABLE_UNPRIVILEGED),
1243 SD_BUS_METHOD("SetX11Keyboard", "ssssbb", NULL, method_set_x11_keyboard, SD_BUS_VTABLE_UNPRIVILEGED),
8d451309
KS
1244 SD_BUS_VTABLE_END
1245};
1246
1247static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
03976f7b 1248 _cleanup_bus_flush_close_unref_ sd_bus *bus = NULL;
1822350d
KS
1249 int r;
1250
8d451309
KS
1251 assert(c);
1252 assert(event);
1822350d
KS
1253 assert(_bus);
1254
76b54375 1255 r = sd_bus_default_system(&bus);
f647962d
MS
1256 if (r < 0)
1257 return log_error_errno(r, "Failed to get system bus connection: %m");
1822350d 1258
19befb2d 1259 r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/locale1", "org.freedesktop.locale1", locale_vtable, c);
f647962d
MS
1260 if (r < 0)
1261 return log_error_errno(r, "Failed to register object: %m");
1822350d 1262
5bb658a1 1263 r = sd_bus_request_name(bus, "org.freedesktop.locale1", 0);
f647962d
MS
1264 if (r < 0)
1265 return log_error_errno(r, "Failed to register name: %m");
1822350d 1266
8d451309 1267 r = sd_bus_attach_event(bus, event, 0);
f647962d
MS
1268 if (r < 0)
1269 return log_error_errno(r, "Failed to attach bus to event loop: %m");
1822350d 1270
8d451309
KS
1271 *_bus = bus;
1272 bus = NULL;
1822350d 1273
8d451309 1274 return 0;
1822350d
KS
1275}
1276
1277int main(int argc, char *argv[]) {
28efac0d 1278 _cleanup_(context_free) Context context = {};
8d451309 1279 _cleanup_event_unref_ sd_event *event = NULL;
03976f7b 1280 _cleanup_bus_flush_close_unref_ sd_bus *bus = NULL;
1822350d 1281 int r;
1822350d
KS
1282
1283 log_set_target(LOG_TARGET_AUTO);
1284 log_parse_environment();
1285 log_open();
8d451309 1286
1822350d 1287 umask(0022);
cc56fafe 1288 mac_selinux_init("/etc");
1822350d 1289
1822350d
KS
1290 if (argc != 1) {
1291 log_error("This program takes no arguments.");
1292 r = -EINVAL;
1293 goto finish;
1294 }
1295
afc6adb5 1296 r = sd_event_default(&event);
1822350d 1297 if (r < 0) {
da927ba9 1298 log_error_errno(r, "Failed to allocate event loop: %m");
1822350d
KS
1299 goto finish;
1300 }
1301
cde93897
LP
1302 sd_event_set_watchdog(event, true);
1303
8d451309 1304 r = connect_bus(&context, event, &bus);
1822350d
KS
1305 if (r < 0)
1306 goto finish;
1307
8d451309
KS
1308 r = context_read_data(&context);
1309 if (r < 0) {
da927ba9 1310 log_error_errno(r, "Failed to read locale data: %m");
8d451309
KS
1311 goto finish;
1312 }
1822350d 1313
37224a5f 1314 r = bus_event_loop_with_idle(event, bus, "org.freedesktop.locale1", DEFAULT_EXIT_USEC, NULL, NULL);
8d451309 1315 if (r < 0) {
da927ba9 1316 log_error_errno(r, "Failed to run event loop: %m");
8d451309 1317 goto finish;
1822350d
KS
1318 }
1319
1822350d 1320finish:
1822350d
KS
1321 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
1322}