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