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