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