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