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