]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/locale/localed.c
bus: add forgotten _public_
[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
KS
368 assert(c_set + c_unset == _LOCALE_MAX);
369 r = sd_bus_message_new_method_call(bus,
370 "org.freedesktop.systemd1",
371 "/org/freedesktop/systemd1",
372 "org.freedesktop.systemd1.Manager",
373 "UnsetAndSetEnvironment", &m);
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
8d451309
KS
385 r = sd_bus_send_with_reply_and_block(bus, m, 0, &error, NULL);
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
KS
393 int r;
394 char **l = NULL;
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);
1822350d
KS
406 if (!s) {
407 strv_free(l);
408 return -ENOMEM;
409 }
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);
1822350d
KS
427 if (!s) {
428 strv_free(l);
429 return -ENOMEM;
430 }
431
432 u = strv_env_set(l, s);
433 free(s);
434 strv_free(l);
435
436 if (!u)
437 return -ENOMEM;
438
439 l = u;
440 }
441
442 if (strv_isempty(l)) {
443 strv_free(l);
444
445 if (unlink("/etc/vconsole.conf") < 0)
446 return errno == ENOENT ? 0 : -errno;
447
448 return 0;
449 }
450
a5c32cff 451 r = write_env_file_label("/etc/vconsole.conf", l);
1822350d
KS
452 strv_free(l);
453
454 return r;
455}
456
8d451309 457static int write_data_x11(Context *c) {
1822350d
KS
458 FILE *f;
459 char *temp_path;
460 int r;
461
8d451309
KS
462 if (isempty(c->x11_layout) &&
463 isempty(c->x11_model) &&
464 isempty(c->x11_variant) &&
465 isempty(c->x11_options)) {
1822350d 466
1822350d
KS
467 if (unlink("/etc/X11/xorg.conf.d/00-keyboard.conf") < 0)
468 return errno == ENOENT ? 0 : -errno;
469
470 return 0;
471 }
472
9a9bb3ca 473 mkdir_p_label("/etc/X11/xorg.conf.d", 0755);
1822350d
KS
474
475 r = fopen_temporary("/etc/X11/xorg.conf.d/00-keyboard.conf", &f, &temp_path);
476 if (r < 0)
477 return r;
478
479 fchmod(fileno(f), 0644);
480
481 fputs("# Read and parsed by systemd-localed. It's probably wise not to edit this file\n"
482 "# manually too freely.\n"
483 "Section \"InputClass\"\n"
484 " Identifier \"system-keyboard\"\n"
485 " MatchIsKeyboard \"on\"\n", f);
486
8d451309
KS
487 if (!isempty(c->x11_layout))
488 fprintf(f, " Option \"XkbLayout\" \"%s\"\n", c->x11_layout);
1822350d 489
8d451309
KS
490 if (!isempty(c->x11_model))
491 fprintf(f, " Option \"XkbModel\" \"%s\"\n", c->x11_model);
1822350d 492
8d451309
KS
493 if (!isempty(c->x11_variant))
494 fprintf(f, " Option \"XkbVariant\" \"%s\"\n", c->x11_variant);
1822350d 495
8d451309
KS
496 if (!isempty(c->x11_options))
497 fprintf(f, " Option \"XkbOptions\" \"%s\"\n", c->x11_options);
1822350d
KS
498
499 fputs("EndSection\n", f);
500 fflush(f);
501
502 if (ferror(f) || rename(temp_path, "/etc/X11/xorg.conf.d/00-keyboard.conf") < 0) {
503 r = -errno;
504 unlink("/etc/X11/xorg.conf.d/00-keyboard.conf");
505 unlink(temp_path);
f687b273 506 } else
1822350d 507 r = 0;
1822350d
KS
508
509 fclose(f);
510 free(temp_path);
511
512 return r;
513}
514
8d451309
KS
515static int vconsole_reload(sd_bus *bus) {
516 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
1822350d 517 int r;
1822350d
KS
518
519 assert(bus);
520
8d451309 521 r = sd_bus_call_method(bus,
1822350d
KS
522 "org.freedesktop.systemd1",
523 "/org/freedesktop/systemd1",
524 "org.freedesktop.systemd1.Manager",
8d451309
KS
525 "RestartUnit",
526 &error,
527 NULL,
528 "ss", "systemd-vconsole-setup.service", "replace");
1822350d 529
8d451309
KS
530 if (r < 0)
531 log_error("Failed to issue method call: %s", bus_error_message(&error, -r));
1822350d
KS
532 return r;
533}
534
535static char *strnulldash(const char *s) {
536 return s == NULL || *s == 0 || (s[0] == '-' && s[1] == 0) ? NULL : (char*) s;
537}
538
539static int read_next_mapping(FILE *f, unsigned *n, char ***a) {
540 assert(f);
541 assert(n);
542 assert(a);
543
544 for (;;) {
545 char line[LINE_MAX];
546 char *l, **b;
547
548 errno = 0;
549 if (!fgets(line, sizeof(line), f)) {
550
551 if (ferror(f))
552 return errno ? -errno : -EIO;
553
554 return 0;
555 }
556
557 (*n) ++;
558
559 l = strstrip(line);
560 if (l[0] == 0 || l[0] == '#')
561 continue;
562
563 b = strv_split_quoted(l);
564 if (!b)
565 return -ENOMEM;
566
567 if (strv_length(b) < 5) {
568 log_error("Invalid line "SYSTEMD_KBD_MODEL_MAP":%u, ignoring.", *n);
569 strv_free(b);
570 continue;
571
572 }
573
574 *a = b;
575 return 1;
576 }
577}
578
8d451309 579static int vconsole_convert_to_x11(Context *c, sd_bus *bus) {
1822350d
KS
580 bool modified = false;
581
8d451309 582 assert(bus);
1822350d 583
8d451309 584 if (isempty(c->vc_keymap)) {
1822350d
KS
585
586 modified =
8d451309
KS
587 !isempty(c->x11_layout) ||
588 !isempty(c->x11_model) ||
589 !isempty(c->x11_variant) ||
590 !isempty(c->x11_options);
1822350d 591
8d451309 592 context_free_x11(c);
1822350d
KS
593 } else {
594 FILE *f;
595 unsigned n = 0;
596
597 f = fopen(SYSTEMD_KBD_MODEL_MAP, "re");
598 if (!f)
599 return -errno;
600
601 for (;;) {
602 char **a;
603 int r;
604
605 r = read_next_mapping(f, &n, &a);
606 if (r < 0) {
607 fclose(f);
608 return r;
609 }
610
611 if (r == 0)
612 break;
613
8d451309 614 if (!streq(c->vc_keymap, a[0])) {
1822350d
KS
615 strv_free(a);
616 continue;
617 }
618
8d451309
KS
619 if (!streq_ptr(c->x11_layout, strnulldash(a[1])) ||
620 !streq_ptr(c->x11_model, strnulldash(a[2])) ||
621 !streq_ptr(c->x11_variant, strnulldash(a[3])) ||
622 !streq_ptr(c->x11_options, strnulldash(a[4]))) {
1822350d 623
8d451309
KS
624 if (free_and_copy(&c->x11_layout, strnulldash(a[1])) < 0 ||
625 free_and_copy(&c->x11_model, strnulldash(a[2])) < 0 ||
626 free_and_copy(&c->x11_variant, strnulldash(a[3])) < 0 ||
627 free_and_copy(&c->x11_options, strnulldash(a[4])) < 0) {
1822350d
KS
628 strv_free(a);
629 fclose(f);
630 return -ENOMEM;
631 }
632
633 modified = true;
634 }
635
636 strv_free(a);
637 break;
638 }
639
640 fclose(f);
641 }
642
643 if (modified) {
1822350d
KS
644 int r;
645
8d451309 646 r = write_data_x11(c);
1822350d
KS
647 if (r < 0)
648 log_error("Failed to set X11 keyboard layout: %s", strerror(-r));
649
8d451309 650 sd_bus_emit_properties_changed(bus,
1822350d
KS
651 "/org/freedesktop/locale1",
652 "org.freedesktop.locale1",
8d451309 653 "X11Layout", "X11Model", "X11Variant", "X11Options", NULL);
1822350d
KS
654 }
655
656 return 0;
657}
658
8d451309 659static int x11_convert_to_vconsole(Context *c, sd_bus *bus) {
1822350d
KS
660 bool modified = false;
661
8d451309 662 assert(bus);
1822350d 663
8d451309 664 if (isempty(c->x11_layout)) {
1822350d
KS
665
666 modified =
8d451309
KS
667 !isempty(c->vc_keymap) ||
668 !isempty(c->vc_keymap_toggle);
1822350d 669
8d451309 670 context_free_x11(c);
1822350d 671 } else {
b47d419c 672 _cleanup_fclose_ FILE *f;
1822350d
KS
673 unsigned n = 0;
674 unsigned best_matching = 0;
675 char *new_keymap = NULL;
676
677 f = fopen(SYSTEMD_KBD_MODEL_MAP, "re");
678 if (!f)
679 return -errno;
680
681 for (;;) {
b47d419c 682 _cleanup_strv_free_ char **a = NULL;
1822350d
KS
683 unsigned matching = 0;
684 int r;
685
686 r = read_next_mapping(f, &n, &a);
b47d419c 687 if (r < 0)
1822350d 688 return r;
1822350d
KS
689 if (r == 0)
690 break;
691
692 /* Determine how well matching this entry is */
8d451309 693 if (streq_ptr(c->x11_layout, a[1]))
1822350d
KS
694 /* If we got an exact match, this is best */
695 matching = 10;
696 else {
697 size_t x;
698
8d451309 699 x = strcspn(c->x11_layout, ",");
1822350d
KS
700
701 /* We have multiple X layouts, look
702 * for an entry that matches our key
703 * with the everything but the first
704 * layout stripped off. */
705 if (x > 0 &&
706 strlen(a[1]) == x &&
8d451309 707 strneq(c->x11_layout, a[1], x))
1822350d
KS
708 matching = 5;
709 else {
710 size_t w;
711
712 /* If that didn't work, strip
713 * off the other layouts from
714 * the entry, too */
1822350d
KS
715 w = strcspn(a[1], ",");
716
717 if (x > 0 && x == w &&
8d451309 718 memcmp(c->x11_layout, a[1], x) == 0)
1822350d
KS
719 matching = 1;
720 }
721 }
722
723 if (matching > 0 &&
8d451309 724 streq_ptr(c->x11_model, a[2])) {
1822350d
KS
725 matching++;
726
8d451309 727 if (streq_ptr(c->x11_variant, a[3])) {
1822350d
KS
728 matching++;
729
8d451309 730 if (streq_ptr(c->x11_options, a[4]))
1822350d
KS
731 matching++;
732 }
733 }
734
735 /* The best matching entry so far, then let's
736 * save that */
737 if (matching > best_matching) {
738 best_matching = matching;
739
740 free(new_keymap);
741 new_keymap = strdup(a[0]);
b47d419c 742 if (!new_keymap)
1822350d 743 return -ENOMEM;
1822350d 744 }
1822350d
KS
745 }
746
8d451309
KS
747 if (!streq_ptr(c->vc_keymap, new_keymap)) {
748 free_and_replace(&c->vc_keymap, new_keymap);
749 free_and_replace(&c->vc_keymap_toggle, NULL);
1822350d
KS
750 modified = true;
751 } else
752 free(new_keymap);
753 }
754
755 if (modified) {
1822350d
KS
756 int r;
757
8d451309 758 r = vconsole_write_data(c);
1822350d
KS
759 if (r < 0)
760 log_error("Failed to set virtual console keymap: %s", strerror(-r));
761
8d451309 762 sd_bus_emit_properties_changed(bus,
1822350d
KS
763 "/org/freedesktop/locale1",
764 "org.freedesktop.locale1",
8d451309 765 "VConsoleKeymap", "VConsoleKeymapToggle", NULL);
1822350d 766
8d451309 767 return vconsole_reload(bus);
1822350d
KS
768 }
769
770 return 0;
771}
772
8d451309
KS
773static int property_get_locale(sd_bus *bus, const char *path, const char *interface,
774 const char *property, sd_bus_message *reply, sd_bus_error *error, void *userdata) {
775 Context *c = userdata;
b47d419c 776 _cleanup_strv_free_ char **l = NULL;
8d451309 777 int p, q;
1822350d 778
8d451309 779 l = new0(char*, _LOCALE_MAX+1);
1822350d
KS
780 if (!l)
781 return -ENOMEM;
782
8d451309 783 for (p = 0, q = 0; p < _LOCALE_MAX; p++) {
1822350d
KS
784 char *t;
785
8d451309 786 if (isempty(c->locale[p]))
1822350d
KS
787 continue;
788
8d451309 789 if (asprintf(&t, "%s=%s", names[p], c->locale[p]) < 0)
1822350d 790 return -ENOMEM;
1822350d 791
8d451309 792 l[q++] = t;
1822350d
KS
793 }
794
8d451309 795 return sd_bus_message_append_strv(reply, l);
1822350d
KS
796}
797
8d451309
KS
798static int method_set_locale(sd_bus *bus, sd_bus_message *m, void *userdata) {
799 Context *c = userdata;
800 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
801 _cleanup_strv_free_ char **l = NULL;
802 char **i;
803 int interactive;
804 bool modified = false;
805 bool passed[_LOCALE_MAX] = {};
806 int p;
1822350d
KS
807 int r;
808
8d451309
KS
809 r = bus_message_read_strv_extend(m, &l);
810 if (r < 0)
811 return sd_bus_reply_method_errno(bus, m, r, NULL);
1822350d 812
8d451309
KS
813 r = sd_bus_message_read_basic(m, 'b', &interactive);
814 if (r < 0)
815 return sd_bus_reply_method_errno(bus, m, r, NULL);
1822350d 816
8d451309
KS
817 /* Check whether a variable changed and if so valid */
818 STRV_FOREACH(i, l) {
819 bool valid = false;
1822350d 820
8d451309
KS
821 for (p = 0; p < _LOCALE_MAX; p++) {
822 size_t k;
1822350d 823
8d451309
KS
824 k = strlen(names[p]);
825 if (startswith(*i, names[p]) &&
826 (*i)[k] == '=' &&
827 string_is_safe((*i) + k + 1)) {
828 valid = true;
829 passed[p] = true;
1822350d 830
8d451309
KS
831 if (!streq_ptr(*i + k + 1, c->locale[p]))
832 modified = true;
1822350d 833
8d451309
KS
834 break;
835 }
1822350d
KS
836 }
837
8d451309
KS
838 if (!valid)
839 sd_bus_reply_method_errorf(bus, m, SD_BUS_ERROR_INVALID_ARGS, "Invalid Locale data.");
840 }
1822350d 841
8d451309
KS
842 /* Check whether a variable is unset */
843 if (!modified) {
844 for (p = 0; p < _LOCALE_MAX; p++)
845 if (!isempty(c->locale[p]) && !passed[p]) {
846 modified = true;
847 break;
848 }
849 }
850
851 if (modified) {
852 r = bus_verify_polkit_async(bus, &c->polkit_registry, m,
853 "org.freedesktop.locale1.set-locale", interactive,
854 &error, method_set_locale, c);
855 if (r < 0)
856 return sd_bus_reply_method_errno(bus, m, r, &error);
857 if (r == 0)
858 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
1822350d 859
8d451309
KS
860 STRV_FOREACH(i, l) {
861 for (p = 0; p < _LOCALE_MAX; p++) {
1822350d
KS
862 size_t k;
863
864 k = strlen(names[p]);
8d451309
KS
865 if (startswith(*i, names[p]) && (*i)[k] == '=') {
866 char *t;
1822350d 867
8d451309
KS
868 t = strdup(*i + k + 1);
869 if (!t)
870 return -ENOMEM;
1822350d 871
8d451309
KS
872 free(c->locale[p]);
873 c->locale[p] = t;
1822350d
KS
874 break;
875 }
876 }
1822350d
KS
877 }
878
8d451309
KS
879 for (p = 0; p < _LOCALE_MAX; p++) {
880 if (passed[p])
881 continue;
1822350d 882
8d451309
KS
883 free_and_replace(&c->locale[p], NULL);
884 }
1822350d 885
8d451309 886 locale_simplify(c);
1822350d 887
8d451309
KS
888 r = locale_write_data(c);
889 if (r < 0) {
890 log_error("Failed to set locale: %s", strerror(-r));
891 return sd_bus_reply_method_errnof(bus, m, r, "Failed to set locale: %s", strerror(-r));
892 }
1822350d 893
8d451309 894 locale_update_system_manager(c, bus);
1822350d 895
8d451309 896 log_info("Changed locale information.");
1822350d 897
8d451309
KS
898 sd_bus_emit_properties_changed(bus,
899 "/org/freedesktop/locale1",
900 "org.freedesktop.locale1",
901 "Locale", NULL);
902 }
1822350d 903
8d451309
KS
904 return sd_bus_reply_method_return(bus, m, NULL);
905}
1822350d 906
8d451309
KS
907static int method_set_vc_keyboard(sd_bus *bus, sd_bus_message *m, void *userdata) {
908 Context *c = userdata;
909 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
910 const char *keymap, *keymap_toggle;
911 unsigned convert, interactive;
912 int r;
f2cc3753 913
8d451309
KS
914 r = sd_bus_message_read(m, "ssbb", &keymap, &keymap_toggle, &convert, &interactive);
915 if (r < 0)
916 return sd_bus_reply_method_errno(bus, m, r, NULL);
1822350d 917
8d451309
KS
918 if (isempty(keymap))
919 keymap = NULL;
1822350d 920
8d451309
KS
921 if (isempty(keymap_toggle))
922 keymap_toggle = NULL;
1822350d 923
8d451309
KS
924 if (!streq_ptr(keymap, c->vc_keymap) ||
925 !streq_ptr(keymap_toggle, c->vc_keymap_toggle)) {
1822350d 926
8d451309
KS
927 if ((keymap && (!filename_is_safe(keymap) || !string_is_safe(keymap))) ||
928 (keymap_toggle && (!filename_is_safe(keymap_toggle) || !string_is_safe(keymap_toggle))))
929 return sd_bus_reply_method_errnof(bus, m, r, "Received invalid keymap data: %s", -EINVAL);
1822350d 930
8d451309
KS
931 r = bus_verify_polkit_async(bus, &c->polkit_registry, m,
932 "org.freedesktop.locale1.set-keyboard",
933 interactive, &error, method_set_vc_keyboard, c);
934 if (r < 0)
935 return sd_bus_reply_method_errno(bus, m, r, &error);
936 if (r == 0)
937 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
1822350d 938
8d451309
KS
939 if (free_and_copy(&c->vc_keymap, keymap) < 0 ||
940 free_and_copy(&c->vc_keymap_toggle, keymap_toggle) < 0)
941 return -ENOMEM;
0b507b17 942
8d451309
KS
943 r = vconsole_write_data(c);
944 if (r < 0) {
945 log_error("Failed to set virtual console keymap: %s", strerror(-r));
946 return sd_bus_reply_method_errnof(bus, m, r, "Failed to set virtual console keymap: %s", strerror(-r));
947 }
1822350d 948
8d451309 949 log_info("Changed virtual console keymap to '%s'", strempty(c->vc_keymap));
1822350d 950
8d451309
KS
951 r = vconsole_reload(bus);
952 if (r < 0)
953 log_error("Failed to request keymap reload: %s", strerror(-r));
1822350d 954
8d451309
KS
955 sd_bus_emit_properties_changed(bus,
956 "/org/freedesktop/locale1",
957 "org.freedesktop.locale1",
958 "VConsoleKeymap", "VConsoleKeymapToggle", NULL);
1822350d 959
8d451309
KS
960 if (convert) {
961 r = vconsole_convert_to_x11(c, bus);
1822350d 962 if (r < 0)
8d451309 963 log_error("Failed to convert keymap data: %s", strerror(-r));
1822350d 964 }
8d451309 965 }
1822350d 966
8d451309
KS
967 return sd_bus_reply_method_return(bus, m, NULL);
968}
1822350d 969
8d451309
KS
970static int method_set_x11_keyboard(sd_bus *bus, sd_bus_message *m, void *userdata) {
971 Context *c = userdata;
972 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
973 const char *layout, *model, *variant, *options;
974 unsigned convert, interactive;
975 int r;
1822350d 976
8d451309
KS
977 r = sd_bus_message_read(m, "ssssbb", &layout, &model, &variant, &options, &convert, &interactive);
978 if (r < 0)
979 return sd_bus_reply_method_errno(bus, m, r, NULL);
1822350d 980
8d451309
KS
981 if (isempty(layout))
982 layout = NULL;
0b507b17 983
8d451309
KS
984 if (isempty(model))
985 model = NULL;
1822350d 986
8d451309
KS
987 if (isempty(variant))
988 variant = NULL;
1822350d 989
8d451309
KS
990 if (isempty(options))
991 options = NULL;
1822350d 992
8d451309
KS
993 if (!streq_ptr(layout, c->x11_layout) ||
994 !streq_ptr(model, c->x11_model) ||
995 !streq_ptr(variant, c->x11_variant) ||
996 !streq_ptr(options, c->x11_options)) {
1822350d 997
8d451309
KS
998 if ((layout && !string_is_safe(layout)) ||
999 (model && !string_is_safe(model)) ||
1000 (variant && !string_is_safe(variant)) ||
1001 (options && !string_is_safe(options)))
1002 return sd_bus_reply_method_errnof(bus, m, r, "Received invalid keyboard data: %s", -EINVAL);
1822350d 1003
8d451309
KS
1004 r = bus_verify_polkit_async(bus, &c->polkit_registry, m,
1005 "org.freedesktop.locale1.set-keyboard",
1006 interactive, &error, method_set_x11_keyboard, c);
1007 if (r < 0)
1008 return sd_bus_reply_method_errno(bus, m, r, &error);
1009 if (r == 0)
1010 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
1011
1012 if (free_and_copy(&c->x11_layout, layout) < 0 ||
1013 free_and_copy(&c->x11_model, model) < 0 ||
1014 free_and_copy(&c->x11_variant, variant) < 0 ||
1015 free_and_copy(&c->x11_options, options) < 0)
1016 return -ENOMEM;
1822350d 1017
8d451309
KS
1018 r = write_data_x11(c);
1019 if (r < 0) {
1020 log_error("Failed to set X11 keyboard layout: %s", strerror(-r));
1021 return sd_bus_reply_method_errnof(bus, m, r, "Failed to set X11 keyboard layout: %s", strerror(-r));
1822350d 1022 }
1822350d 1023
8d451309 1024 log_info("Changed X11 keyboard layout to '%s'", strempty(c->x11_layout));
1822350d 1025
8d451309
KS
1026 sd_bus_emit_properties_changed(bus,
1027 "/org/freedesktop/locale1",
1028 "org.freedesktop.locale1",
1029 "X11Layout" "X11Model" "X11Variant" "X11Options", NULL);
1822350d 1030
8d451309
KS
1031 if (convert) {
1032 r = x11_convert_to_vconsole(c, bus);
1033 if (r < 0)
1034 log_error("Failed to convert keymap data: %s", strerror(-r));
1035 }
1822350d
KS
1036 }
1037
8d451309 1038 return sd_bus_reply_method_return(bus, m, NULL);
1822350d
KS
1039}
1040
8d451309
KS
1041static const sd_bus_vtable locale_vtable[] = {
1042 SD_BUS_VTABLE_START(0),
6d1bd3b2
LP
1043 SD_BUS_PROPERTY("Locale", "as", property_get_locale, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1044 SD_BUS_PROPERTY("X11Layout", "s", NULL, offsetof(Context, x11_layout), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1045 SD_BUS_PROPERTY("X11Model", "s", NULL, offsetof(Context, x11_model), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1046 SD_BUS_PROPERTY("X11Variant", "s", NULL, offsetof(Context, x11_variant), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1047 SD_BUS_PROPERTY("X11Options", "s", NULL, offsetof(Context, x11_options), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1048 SD_BUS_PROPERTY("VConsoleKeymap", "s", NULL, offsetof(Context, vc_keymap), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
1049 SD_BUS_PROPERTY("VConsoleKeymapToggle", "s", NULL, offsetof(Context, vc_keymap_toggle), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
8d451309
KS
1050 SD_BUS_METHOD("SetLocale", "asb", NULL, method_set_locale, 0),
1051 SD_BUS_METHOD("SetVConsoleKeyboard", "ssbb", NULL, method_set_vc_keyboard, 0),
1052 SD_BUS_METHOD("SetX11Keyboard", "ssssbb", NULL, method_set_x11_keyboard, 0),
1053 SD_BUS_VTABLE_END
1054};
1055
1056static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
1057 _cleanup_bus_unref_ sd_bus *bus = NULL;
1822350d
KS
1058 int r;
1059
8d451309
KS
1060 assert(c);
1061 assert(event);
1822350d
KS
1062 assert(_bus);
1063
8d451309
KS
1064 r = sd_bus_open_system(&bus);
1065 if (r < 0) {
1066 log_error("Failed to get system bus connection: %s", strerror(-r));
1067 return r;
1822350d
KS
1068 }
1069
8d451309
KS
1070 r = sd_bus_add_object_vtable(bus, "/org/freedesktop/locale1", "org.freedesktop.locale1", locale_vtable, c);
1071 if (r < 0) {
1072 log_error("Failed to register object: %s", strerror(-r));
1073 return r;
1822350d
KS
1074 }
1075
8d451309
KS
1076 r = sd_bus_request_name(bus, "org.freedesktop.locale1", SD_BUS_NAME_DO_NOT_QUEUE);
1077 if (r < 0) {
1078 log_error("Failed to register name: %s", strerror(-r));
1079 return r;
1822350d
KS
1080 }
1081
8d451309 1082 if (r != SD_BUS_NAME_PRIMARY_OWNER) {
1822350d 1083 log_error("Failed to acquire name.");
8d451309 1084 return -EEXIST;
1822350d
KS
1085 }
1086
8d451309
KS
1087 r = sd_bus_attach_event(bus, event, 0);
1088 if (r < 0) {
1089 log_error("Failed to attach bus to event loop: %s", strerror(-r));
1090 return r;
1091 }
1822350d 1092
8d451309
KS
1093 *_bus = bus;
1094 bus = NULL;
1822350d 1095
8d451309 1096 return 0;
1822350d
KS
1097}
1098
1099int main(int argc, char *argv[]) {
8d451309
KS
1100 Context context = {};
1101 _cleanup_event_unref_ sd_event *event = NULL;
1102 _cleanup_bus_unref_ sd_bus *bus = NULL;
1822350d 1103 int r;
1822350d
KS
1104
1105 log_set_target(LOG_TARGET_AUTO);
1106 log_parse_environment();
1107 log_open();
8d451309 1108
1822350d 1109 umask(0022);
8d451309 1110 label_init("/etc");
1822350d 1111
1822350d
KS
1112 if (argc != 1) {
1113 log_error("This program takes no arguments.");
1114 r = -EINVAL;
1115 goto finish;
1116 }
1117
8d451309 1118 r = sd_event_new(&event);
1822350d 1119 if (r < 0) {
8d451309 1120 log_error("Failed to allocate event loop: %s", strerror(-r));
1822350d
KS
1121 goto finish;
1122 }
1123
8d451309 1124 r = connect_bus(&context, event, &bus);
1822350d
KS
1125 if (r < 0)
1126 goto finish;
1127
8d451309
KS
1128 r = context_read_data(&context);
1129 if (r < 0) {
1130 log_error("Failed to read locale data: %s", strerror(-r));
1131 goto finish;
1132 }
1822350d 1133
8d451309
KS
1134 r = bus_event_loop_with_idle(event, bus, "org.freedesktop.locale1", DEFAULT_EXIT_USEC);
1135 if (r < 0) {
1136 log_error("Failed to run event loop: %s", strerror(-r));
1137 goto finish;
1822350d
KS
1138 }
1139
1140 r = 0;
1141
1142finish:
8d451309 1143 context_free(&context, bus);
1822350d
KS
1144
1145 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
1146}