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