]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/locale/localed.c
sd-event: reorder header slightly
[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
715 if (matching > 0 &&
716 streq_ptr(c->x11_model, a[2])) {
717 matching++;
718
719 if (streq_ptr(c->x11_variant, a[3])) {
720 matching++;
721
722 if (streq_ptr(c->x11_options, a[4]))
723 matching++;
724 }
725 }
726
727 /* The best matching entry so far, then let's save that */
728 if (matching > best_matching) {
729 best_matching = matching;
730
731 free(*new_keymap);
732 *new_keymap = strdup(a[0]);
733 if (!*new_keymap)
734 return -ENOMEM;
735 }
736 }
737
738 return 0;
739}
740
8d451309 741static int x11_convert_to_vconsole(Context *c, sd_bus *bus) {
1822350d 742 bool modified = false;
0732ef7a 743 int r;
1822350d 744
8d451309 745 assert(bus);
1822350d 746
8d451309 747 if (isempty(c->x11_layout)) {
1822350d
KS
748
749 modified =
8d451309
KS
750 !isempty(c->vc_keymap) ||
751 !isempty(c->vc_keymap_toggle);
1822350d 752
8d451309 753 context_free_x11(c);
1822350d 754 } else {
1822350d
KS
755 char *new_keymap = NULL;
756
0732ef7a
ZJS
757 r = find_converted_keymap(c, &new_keymap);
758 if (r < 0)
759 return r;
760 else if (r == 0) {
761 r = find_legacy_keymap(c, &new_keymap);
b47d419c 762 if (r < 0)
1822350d 763 return r;
1822350d
KS
764 }
765
8d451309
KS
766 if (!streq_ptr(c->vc_keymap, new_keymap)) {
767 free_and_replace(&c->vc_keymap, new_keymap);
768 free_and_replace(&c->vc_keymap_toggle, NULL);
1822350d
KS
769 modified = true;
770 } else
771 free(new_keymap);
772 }
773
774 if (modified) {
8d451309 775 r = vconsole_write_data(c);
1822350d
KS
776 if (r < 0)
777 log_error("Failed to set virtual console keymap: %s", strerror(-r));
778
8d451309 779 sd_bus_emit_properties_changed(bus,
1822350d
KS
780 "/org/freedesktop/locale1",
781 "org.freedesktop.locale1",
8d451309 782 "VConsoleKeymap", "VConsoleKeymapToggle", NULL);
1822350d 783
8d451309 784 return vconsole_reload(bus);
1822350d
KS
785 }
786
787 return 0;
788}
789
ebcf1f97
LP
790static int property_get_locale(
791 sd_bus *bus,
792 const char *path,
793 const char *interface,
794 const char *property,
795 sd_bus_message *reply,
796 void *userdata,
797 sd_bus_error *error) {
798
8d451309 799 Context *c = userdata;
b47d419c 800 _cleanup_strv_free_ char **l = NULL;
8d451309 801 int p, q;
1822350d 802
8d451309 803 l = new0(char*, _LOCALE_MAX+1);
1822350d
KS
804 if (!l)
805 return -ENOMEM;
806
8d451309 807 for (p = 0, q = 0; p < _LOCALE_MAX; p++) {
1822350d
KS
808 char *t;
809
8d451309 810 if (isempty(c->locale[p]))
1822350d
KS
811 continue;
812
8d451309 813 if (asprintf(&t, "%s=%s", names[p], c->locale[p]) < 0)
1822350d 814 return -ENOMEM;
1822350d 815
8d451309 816 l[q++] = t;
1822350d
KS
817 }
818
8d451309 819 return sd_bus_message_append_strv(reply, l);
1822350d
KS
820}
821
ebcf1f97 822static int method_set_locale(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
8d451309 823 Context *c = userdata;
8d451309
KS
824 _cleanup_strv_free_ char **l = NULL;
825 char **i;
826 int interactive;
827 bool modified = false;
828 bool passed[_LOCALE_MAX] = {};
829 int p;
1822350d
KS
830 int r;
831
8d451309
KS
832 r = bus_message_read_strv_extend(m, &l);
833 if (r < 0)
ebcf1f97 834 return r;
1822350d 835
8d451309
KS
836 r = sd_bus_message_read_basic(m, 'b', &interactive);
837 if (r < 0)
ebcf1f97 838 return r;
1822350d 839
8d451309
KS
840 /* Check whether a variable changed and if so valid */
841 STRV_FOREACH(i, l) {
842 bool valid = false;
1822350d 843
8d451309
KS
844 for (p = 0; p < _LOCALE_MAX; p++) {
845 size_t k;
1822350d 846
8d451309
KS
847 k = strlen(names[p]);
848 if (startswith(*i, names[p]) &&
849 (*i)[k] == '=' &&
850 string_is_safe((*i) + k + 1)) {
851 valid = true;
852 passed[p] = true;
1822350d 853
8d451309
KS
854 if (!streq_ptr(*i + k + 1, c->locale[p]))
855 modified = true;
1822350d 856
8d451309
KS
857 break;
858 }
1822350d
KS
859 }
860
8d451309 861 if (!valid)
ebcf1f97 862 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid Locale data.");
8d451309 863 }
1822350d 864
8d451309
KS
865 /* Check whether a variable is unset */
866 if (!modified) {
867 for (p = 0; p < _LOCALE_MAX; p++)
868 if (!isempty(c->locale[p]) && !passed[p]) {
869 modified = true;
870 break;
871 }
872 }
873
874 if (modified) {
875 r = bus_verify_polkit_async(bus, &c->polkit_registry, m,
876 "org.freedesktop.locale1.set-locale", interactive,
ebcf1f97 877 error, method_set_locale, c);
8d451309 878 if (r < 0)
ebcf1f97 879 return r;
8d451309
KS
880 if (r == 0)
881 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
1822350d 882
8d451309
KS
883 STRV_FOREACH(i, l) {
884 for (p = 0; p < _LOCALE_MAX; p++) {
1822350d
KS
885 size_t k;
886
887 k = strlen(names[p]);
8d451309
KS
888 if (startswith(*i, names[p]) && (*i)[k] == '=') {
889 char *t;
1822350d 890
8d451309
KS
891 t = strdup(*i + k + 1);
892 if (!t)
893 return -ENOMEM;
1822350d 894
8d451309
KS
895 free(c->locale[p]);
896 c->locale[p] = t;
1822350d
KS
897 break;
898 }
899 }
1822350d
KS
900 }
901
8d451309
KS
902 for (p = 0; p < _LOCALE_MAX; p++) {
903 if (passed[p])
904 continue;
1822350d 905
8d451309
KS
906 free_and_replace(&c->locale[p], NULL);
907 }
1822350d 908
8d451309 909 locale_simplify(c);
1822350d 910
8d451309
KS
911 r = locale_write_data(c);
912 if (r < 0) {
913 log_error("Failed to set locale: %s", strerror(-r));
ebcf1f97 914 return sd_bus_error_set_errnof(error, r, "Failed to set locale: %s", strerror(-r));
8d451309 915 }
1822350d 916
8d451309 917 locale_update_system_manager(c, bus);
1822350d 918
8d451309 919 log_info("Changed locale information.");
1822350d 920
8d451309
KS
921 sd_bus_emit_properties_changed(bus,
922 "/org/freedesktop/locale1",
923 "org.freedesktop.locale1",
924 "Locale", NULL);
925 }
1822350d 926
df2d202e 927 return sd_bus_reply_method_return(m, NULL);
8d451309 928}
1822350d 929
ebcf1f97 930static int method_set_vc_keyboard(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
8d451309 931 Context *c = userdata;
8d451309 932 const char *keymap, *keymap_toggle;
102d8f81 933 int convert, interactive;
8d451309 934 int r;
f2cc3753 935
8d451309
KS
936 r = sd_bus_message_read(m, "ssbb", &keymap, &keymap_toggle, &convert, &interactive);
937 if (r < 0)
ebcf1f97 938 return r;
1822350d 939
8d451309
KS
940 if (isempty(keymap))
941 keymap = NULL;
1822350d 942
8d451309
KS
943 if (isempty(keymap_toggle))
944 keymap_toggle = NULL;
1822350d 945
8d451309
KS
946 if (!streq_ptr(keymap, c->vc_keymap) ||
947 !streq_ptr(keymap_toggle, c->vc_keymap_toggle)) {
1822350d 948
8d451309
KS
949 if ((keymap && (!filename_is_safe(keymap) || !string_is_safe(keymap))) ||
950 (keymap_toggle && (!filename_is_safe(keymap_toggle) || !string_is_safe(keymap_toggle))))
d14ab08b 951 return sd_bus_error_set_errnof(error, -EINVAL, "Received invalid keymap data");
1822350d 952
8d451309
KS
953 r = bus_verify_polkit_async(bus, &c->polkit_registry, m,
954 "org.freedesktop.locale1.set-keyboard",
ebcf1f97 955 interactive, error, method_set_vc_keyboard, c);
8d451309 956 if (r < 0)
ebcf1f97 957 return r;
8d451309
KS
958 if (r == 0)
959 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
1822350d 960
8d451309
KS
961 if (free_and_copy(&c->vc_keymap, keymap) < 0 ||
962 free_and_copy(&c->vc_keymap_toggle, keymap_toggle) < 0)
963 return -ENOMEM;
0b507b17 964
8d451309
KS
965 r = vconsole_write_data(c);
966 if (r < 0) {
967 log_error("Failed to set virtual console keymap: %s", strerror(-r));
ebcf1f97 968 return sd_bus_error_set_errnof(error, r, "Failed to set virtual console keymap: %s", strerror(-r));
8d451309 969 }
1822350d 970
8d451309 971 log_info("Changed virtual console keymap to '%s'", strempty(c->vc_keymap));
1822350d 972
8d451309
KS
973 r = vconsole_reload(bus);
974 if (r < 0)
975 log_error("Failed to request keymap reload: %s", strerror(-r));
1822350d 976
8d451309
KS
977 sd_bus_emit_properties_changed(bus,
978 "/org/freedesktop/locale1",
979 "org.freedesktop.locale1",
980 "VConsoleKeymap", "VConsoleKeymapToggle", NULL);
1822350d 981
8d451309
KS
982 if (convert) {
983 r = vconsole_convert_to_x11(c, bus);
1822350d 984 if (r < 0)
8d451309 985 log_error("Failed to convert keymap data: %s", strerror(-r));
1822350d 986 }
8d451309 987 }
1822350d 988
df2d202e 989 return sd_bus_reply_method_return(m, NULL);
8d451309 990}
1822350d 991
ebcf1f97 992static int method_set_x11_keyboard(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
8d451309 993 Context *c = userdata;
8d451309 994 const char *layout, *model, *variant, *options;
102d8f81 995 int convert, interactive;
8d451309 996 int r;
1822350d 997
8d451309
KS
998 r = sd_bus_message_read(m, "ssssbb", &layout, &model, &variant, &options, &convert, &interactive);
999 if (r < 0)
ebcf1f97 1000 return r;
1822350d 1001
8d451309
KS
1002 if (isempty(layout))
1003 layout = NULL;
0b507b17 1004
8d451309
KS
1005 if (isempty(model))
1006 model = NULL;
1822350d 1007
8d451309
KS
1008 if (isempty(variant))
1009 variant = NULL;
1822350d 1010
8d451309
KS
1011 if (isempty(options))
1012 options = NULL;
1822350d 1013
8d451309
KS
1014 if (!streq_ptr(layout, c->x11_layout) ||
1015 !streq_ptr(model, c->x11_model) ||
1016 !streq_ptr(variant, c->x11_variant) ||
1017 !streq_ptr(options, c->x11_options)) {
1822350d 1018
8d451309
KS
1019 if ((layout && !string_is_safe(layout)) ||
1020 (model && !string_is_safe(model)) ||
1021 (variant && !string_is_safe(variant)) ||
1022 (options && !string_is_safe(options)))
d14ab08b 1023 return sd_bus_error_set_errnof(error, -EINVAL, "Received invalid keyboard data");
1822350d 1024
8d451309
KS
1025 r = bus_verify_polkit_async(bus, &c->polkit_registry, m,
1026 "org.freedesktop.locale1.set-keyboard",
ebcf1f97 1027 interactive, error, method_set_x11_keyboard, c);
8d451309 1028 if (r < 0)
ebcf1f97 1029 return r;
8d451309
KS
1030 if (r == 0)
1031 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
1032
1033 if (free_and_copy(&c->x11_layout, layout) < 0 ||
1034 free_and_copy(&c->x11_model, model) < 0 ||
1035 free_and_copy(&c->x11_variant, variant) < 0 ||
1036 free_and_copy(&c->x11_options, options) < 0)
1037 return -ENOMEM;
1822350d 1038
8d451309
KS
1039 r = write_data_x11(c);
1040 if (r < 0) {
1041 log_error("Failed to set X11 keyboard layout: %s", strerror(-r));
ebcf1f97 1042 return sd_bus_error_set_errnof(error, r, "Failed to set X11 keyboard layout: %s", strerror(-r));
1822350d 1043 }
1822350d 1044
8d451309 1045 log_info("Changed X11 keyboard layout to '%s'", strempty(c->x11_layout));
1822350d 1046
8d451309
KS
1047 sd_bus_emit_properties_changed(bus,
1048 "/org/freedesktop/locale1",
1049 "org.freedesktop.locale1",
1050 "X11Layout" "X11Model" "X11Variant" "X11Options", NULL);
1822350d 1051
8d451309
KS
1052 if (convert) {
1053 r = x11_convert_to_vconsole(c, bus);
1054 if (r < 0)
1055 log_error("Failed to convert keymap data: %s", strerror(-r));
1056 }
1822350d
KS
1057 }
1058
df2d202e 1059 return sd_bus_reply_method_return(m, NULL);
1822350d
KS
1060}
1061
8d451309
KS
1062static const sd_bus_vtable locale_vtable[] = {
1063 SD_BUS_VTABLE_START(0),
6d1bd3b2
LP
1064 SD_BUS_PROPERTY("Locale", "as", property_get_locale, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1065 SD_BUS_PROPERTY("X11Layout", "s", NULL, offsetof(Context, x11_layout), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1066 SD_BUS_PROPERTY("X11Model", "s", NULL, offsetof(Context, x11_model), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1067 SD_BUS_PROPERTY("X11Variant", "s", NULL, offsetof(Context, x11_variant), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1068 SD_BUS_PROPERTY("X11Options", "s", NULL, offsetof(Context, x11_options), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1069 SD_BUS_PROPERTY("VConsoleKeymap", "s", NULL, offsetof(Context, vc_keymap), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1070 SD_BUS_PROPERTY("VConsoleKeymapToggle", "s", NULL, offsetof(Context, vc_keymap_toggle), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
adacb957
LP
1071 SD_BUS_METHOD("SetLocale", "asb", NULL, method_set_locale, SD_BUS_VTABLE_UNPRIVILEGED),
1072 SD_BUS_METHOD("SetVConsoleKeyboard", "ssbb", NULL, method_set_vc_keyboard, SD_BUS_VTABLE_UNPRIVILEGED),
1073 SD_BUS_METHOD("SetX11Keyboard", "ssssbb", NULL, method_set_x11_keyboard, SD_BUS_VTABLE_UNPRIVILEGED),
8d451309
KS
1074 SD_BUS_VTABLE_END
1075};
1076
1077static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
1078 _cleanup_bus_unref_ sd_bus *bus = NULL;
1822350d
KS
1079 int r;
1080
8d451309
KS
1081 assert(c);
1082 assert(event);
1822350d
KS
1083 assert(_bus);
1084
76b54375 1085 r = sd_bus_default_system(&bus);
8d451309
KS
1086 if (r < 0) {
1087 log_error("Failed to get system bus connection: %s", strerror(-r));
1088 return r;
1822350d
KS
1089 }
1090
8d451309
KS
1091 r = sd_bus_add_object_vtable(bus, "/org/freedesktop/locale1", "org.freedesktop.locale1", locale_vtable, c);
1092 if (r < 0) {
1093 log_error("Failed to register object: %s", strerror(-r));
1094 return r;
1822350d
KS
1095 }
1096
5bb658a1 1097 r = sd_bus_request_name(bus, "org.freedesktop.locale1", 0);
8d451309
KS
1098 if (r < 0) {
1099 log_error("Failed to register name: %s", strerror(-r));
1100 return r;
1822350d
KS
1101 }
1102
8d451309
KS
1103 r = sd_bus_attach_event(bus, event, 0);
1104 if (r < 0) {
1105 log_error("Failed to attach bus to event loop: %s", strerror(-r));
1106 return r;
1107 }
1822350d 1108
8d451309
KS
1109 *_bus = bus;
1110 bus = NULL;
1822350d 1111
8d451309 1112 return 0;
1822350d
KS
1113}
1114
1115int main(int argc, char *argv[]) {
8d451309
KS
1116 Context context = {};
1117 _cleanup_event_unref_ sd_event *event = NULL;
1118 _cleanup_bus_unref_ sd_bus *bus = NULL;
1822350d 1119 int r;
1822350d
KS
1120
1121 log_set_target(LOG_TARGET_AUTO);
1122 log_parse_environment();
1123 log_open();
8d451309 1124
1822350d 1125 umask(0022);
8d451309 1126 label_init("/etc");
1822350d 1127
1822350d
KS
1128 if (argc != 1) {
1129 log_error("This program takes no arguments.");
1130 r = -EINVAL;
1131 goto finish;
1132 }
1133
afc6adb5 1134 r = sd_event_default(&event);
1822350d 1135 if (r < 0) {
8d451309 1136 log_error("Failed to allocate event loop: %s", strerror(-r));
1822350d
KS
1137 goto finish;
1138 }
1139
cde93897
LP
1140 sd_event_set_watchdog(event, true);
1141
8d451309 1142 r = connect_bus(&context, event, &bus);
1822350d
KS
1143 if (r < 0)
1144 goto finish;
1145
8d451309
KS
1146 r = context_read_data(&context);
1147 if (r < 0) {
1148 log_error("Failed to read locale data: %s", strerror(-r));
1149 goto finish;
1150 }
1822350d 1151
37224a5f 1152 r = bus_event_loop_with_idle(event, bus, "org.freedesktop.locale1", DEFAULT_EXIT_USEC, NULL, NULL);
8d451309
KS
1153 if (r < 0) {
1154 log_error("Failed to run event loop: %s", strerror(-r));
1155 goto finish;
1822350d
KS
1156 }
1157
1822350d 1158finish:
8d451309 1159 context_free(&context, bus);
1822350d
KS
1160
1161 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
1162}