]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/locale/localed-util.c
locale,firstboot: add headers to vconsole.conf
[thirdparty/systemd.git] / src / locale / localed-util.c
CommitLineData
db9ecf05 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
4897d1dc
ZJS
2
3#include <errno.h>
ca78ad1d
ZJS
4#include <sys/stat.h>
5#include <sys/types.h>
4897d1dc
ZJS
6#include <unistd.h>
7
269e4d2d 8#include "bus-polkit.h"
8f20232f 9#include "copy.h"
686d13b9 10#include "env-file-label.h"
f05e1d0d 11#include "env-file.h"
4897d1dc
ZJS
12#include "env-util.h"
13#include "fd-util.h"
14#include "fileio-label.h"
15#include "fileio.h"
8f20232f 16#include "fs-util.h"
f05e1d0d 17#include "kbd-util.h"
3e5203b3 18#include "localed-util.h"
4897d1dc 19#include "macro.h"
35cd0ba5 20#include "mkdir-label.h"
d8b4d14d 21#include "nulstr-util.h"
8f20232f 22#include "process-util.h"
7294912f 23#include "stat-util.h"
4897d1dc
ZJS
24#include "string-util.h"
25#include "strv.h"
e4de7287 26#include "tmpfile-util.h"
0327d5b2 27#include "xkbcommon-util.h"
4897d1dc
ZJS
28
29static bool startswith_comma(const char *s, const char *prefix) {
76400a62
YW
30 assert(s);
31 assert(prefix);
32
5ad327dd
ZJS
33 s = startswith(s, prefix);
34 if (!s)
35 return false;
4897d1dc 36
4c701096 37 return IN_SET(*s, ',', '\0');
4897d1dc
ZJS
38}
39
cabffaf8
ZJS
40static const char* systemd_kbd_model_map(void) {
41 const char* s;
42
43 s = getenv("SYSTEMD_KBD_MODEL_MAP");
44 if (s)
45 return s;
46
47 return SYSTEMD_KBD_MODEL_MAP;
48}
49
50static const char* systemd_language_fallback_map(void) {
51 const char* s;
52
53 s = getenv("SYSTEMD_LANGUAGE_FALLBACK_MAP");
54 if (s)
55 return s;
56
57 return SYSTEMD_LANGUAGE_FALLBACK_MAP;
58}
59
90005a4f 60void x11_context_clear(X11Context *xc) {
b41ec10a
YW
61 assert(xc);
62
63 xc->layout = mfree(xc->layout);
64 xc->options = mfree(xc->options);
65 xc->model = mfree(xc->model);
66 xc->variant = mfree(xc->variant);
67}
68
90005a4f
YW
69void x11_context_replace(X11Context *dest, X11Context *src) {
70 assert(dest);
71 assert(src);
72
73 x11_context_clear(dest);
088d71f8 74 *dest = TAKE_STRUCT(*src);
90005a4f
YW
75}
76
77bool x11_context_isempty(const X11Context *xc) {
b41ec10a
YW
78 assert(xc);
79
80 return
81 isempty(xc->layout) &&
82 isempty(xc->model) &&
83 isempty(xc->variant) &&
84 isempty(xc->options);
85}
86
87void x11_context_empty_to_null(X11Context *xc) {
88 assert(xc);
89
90 /* Do not call x11_context_clear() for the passed object. */
91
92 xc->layout = empty_to_null(xc->layout);
93 xc->model = empty_to_null(xc->model);
94 xc->variant = empty_to_null(xc->variant);
95 xc->options = empty_to_null(xc->options);
96}
97
98bool x11_context_is_safe(const X11Context *xc) {
99 assert(xc);
100
101 return
102 (!xc->layout || string_is_safe(xc->layout)) &&
103 (!xc->model || string_is_safe(xc->model)) &&
104 (!xc->variant || string_is_safe(xc->variant)) &&
105 (!xc->options || string_is_safe(xc->options));
106}
107
108bool x11_context_equal(const X11Context *a, const X11Context *b) {
109 assert(a);
110 assert(b);
111
112 return
113 streq_ptr(a->layout, b->layout) &&
114 streq_ptr(a->model, b->model) &&
115 streq_ptr(a->variant, b->variant) &&
116 streq_ptr(a->options, b->options);
117}
118
119int x11_context_copy(X11Context *dest, const X11Context *src) {
120 bool modified;
121 int r;
122
123 assert(dest);
124
125 if (dest == src)
126 return 0;
127
128 if (!src) {
129 modified = !x11_context_isempty(dest);
130 x11_context_clear(dest);
131 return modified;
132 }
133
134 r = free_and_strdup(&dest->layout, src->layout);
135 if (r < 0)
136 return r;
137 modified = r > 0;
138
139 r = free_and_strdup(&dest->model, src->model);
140 if (r < 0)
141 return r;
142 modified = modified || r > 0;
143
144 r = free_and_strdup(&dest->variant, src->variant);
145 if (r < 0)
146 return r;
147 modified = modified || r > 0;
148
149 r = free_and_strdup(&dest->options, src->options);
150 if (r < 0)
151 return r;
152 modified = modified || r > 0;
153
154 return modified;
155}
156
0327d5b2
YW
157int x11_context_verify_and_warn(const X11Context *xc, int log_level, sd_bus_error *error) {
158 int r;
159
160 assert(xc);
161
162 if (!x11_context_is_safe(xc)) {
163 if (error)
164 sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid X11 keyboard layout.");
165 return log_full_errno(log_level, SYNTHETIC_ERRNO(EINVAL), "Invalid X11 keyboard layout.");
166 }
167
168 r = verify_xkb_rmlvo(xc->model, xc->layout, xc->variant, xc->options);
169 if (r == -EOPNOTSUPP) {
170 log_full_errno(MAX(log_level, LOG_NOTICE), r,
171 "Cannot verify if new keymap is correct, libxkbcommon.so unavailable.");
172 return 0;
173 }
174 if (r < 0) {
175 if (error)
176 sd_bus_error_set_errnof(error, r, "Specified keymap cannot be compiled, refusing as invalid.");
177 return log_full_errno(log_level, r,
178 "Cannot compile XKB keymap for x11 keyboard layout "
179 "(model='%s' / layout='%s' / variant='%s' / options='%s'): %m",
180 strempty(xc->model), strempty(xc->layout), strempty(xc->variant), strempty(xc->options));
181 }
182
183 return 0;
184}
185
90005a4f 186void vc_context_clear(VCContext *vc) {
ba4a886f
YW
187 assert(vc);
188
189 vc->keymap = mfree(vc->keymap);
190 vc->toggle = mfree(vc->toggle);
191}
192
90005a4f
YW
193void vc_context_replace(VCContext *dest, VCContext *src) {
194 assert(dest);
195 assert(src);
196
197 vc_context_clear(dest);
088d71f8 198 *dest = TAKE_STRUCT(*src);
90005a4f
YW
199}
200
201bool vc_context_isempty(const VCContext *vc) {
ba4a886f
YW
202 assert(vc);
203
204 return
205 isempty(vc->keymap) &&
206 isempty(vc->toggle);
207}
208
209void vc_context_empty_to_null(VCContext *vc) {
210 assert(vc);
211
212 /* Do not call vc_context_clear() for the passed object. */
213
214 vc->keymap = empty_to_null(vc->keymap);
215 vc->toggle = empty_to_null(vc->toggle);
216}
76400a62 217
ba4a886f
YW
218bool vc_context_equal(const VCContext *a, const VCContext *b) {
219 assert(a);
220 assert(b);
221
222 return
223 streq_ptr(a->keymap, b->keymap) &&
224 streq_ptr(a->toggle, b->toggle);
225}
226
227int vc_context_copy(VCContext *dest, const VCContext *src) {
228 bool modified;
229 int r;
230
231 assert(dest);
232
233 if (dest == src)
234 return 0;
235
236 if (!src) {
237 modified = !vc_context_isempty(dest);
238 vc_context_clear(dest);
239 return modified;
240 }
241
242 r = free_and_strdup(&dest->keymap, src->keymap);
243 if (r < 0)
244 return r;
245 modified = r > 0;
246
247 r = free_and_strdup(&dest->toggle, src->toggle);
248 if (r < 0)
249 return r;
250 modified = modified || r > 0;
251
252 return modified;
4897d1dc
ZJS
253}
254
384f22e3
YW
255static int verify_keymap(const char *keymap, int log_level, sd_bus_error *error) {
256 int r;
257
258 assert(keymap);
259
260 r = keymap_exists(keymap); /* This also verifies that the keymap name is kosher. */
261 if (r < 0) {
262 if (error)
263 sd_bus_error_set_errnof(error, r, "Failed to check keymap %s: %m", keymap);
264 return log_full_errno(log_level, r, "Failed to check keymap %s: %m", keymap);
265 }
266 if (r == 0) {
267 if (error)
268 sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Keymap %s is not installed.", keymap);
269 return log_full_errno(log_level, SYNTHETIC_ERRNO(ENOENT), "Keymap %s is not installed.", keymap);
270 }
271
272 return 0;
273}
274
275int vc_context_verify_and_warn(const VCContext *vc, int log_level, sd_bus_error *error) {
276 int r;
277
278 assert(vc);
279
280 if (vc->keymap) {
281 r = verify_keymap(vc->keymap, log_level, error);
282 if (r < 0)
283 return r;
284 }
285
286 if (vc->toggle) {
287 r = verify_keymap(vc->toggle, log_level, error);
288 if (r < 0)
289 return r;
290 }
291
292 return 0;
293}
294
6804d7a8 295void context_clear(Context *c) {
76400a62
YW
296 assert(c);
297
3d36b5d7 298 locale_context_clear(&c->locale_context);
c4abc558
YW
299 x11_context_clear(&c->x11_from_xorg);
300 x11_context_clear(&c->x11_from_vc);
ba4a886f 301 vc_context_clear(&c->vc);
65d34266 302
c2ddaed4
YW
303 c->locale_cache = sd_bus_message_unref(c->locale_cache);
304 c->x11_cache = sd_bus_message_unref(c->x11_cache);
305 c->vc_cache = sd_bus_message_unref(c->vc_cache);
af7865c1 306
c2ddaed4 307 c->polkit_registry = bus_verify_polkit_async_registry_free(c->polkit_registry);
4897d1dc
ZJS
308};
309
f59d83af 310X11Context *context_get_x11_context(Context *c) {
b41ec10a
YW
311 assert(c);
312
8589823f
YW
313 if (!x11_context_isempty(&c->x11_from_vc))
314 return &c->x11_from_vc;
315
b41ec10a
YW
316 if (!x11_context_isempty(&c->x11_from_xorg))
317 return &c->x11_from_xorg;
318
f59d83af 319 return &c->x11_from_vc;
b41ec10a
YW
320}
321
df4fd2c7 322int locale_read_data(Context *c, sd_bus_message *m) {
3d36b5d7 323 assert(c);
4897d1dc 324
df4fd2c7 325 /* Do not try to re-read the file within single bus operation. */
65d34266
YW
326 if (m) {
327 if (m == c->locale_cache)
328 return 0;
4897d1dc 329
65d34266
YW
330 sd_bus_message_unref(c->locale_cache);
331 c->locale_cache = sd_bus_message_ref(m);
332 }
df4fd2c7 333
3d36b5d7 334 return locale_context_load(&c->locale_context, LOCALE_LOAD_LOCALE_CONF | LOCALE_LOAD_ENVIRONMENT | LOCALE_LOAD_SIMPLIFY);
4897d1dc
ZJS
335}
336
df4fd2c7 337int vconsole_read_data(Context *c, sd_bus_message *m) {
a5937dcf 338 _cleanup_close_ int fd = -EBADF;
df4fd2c7 339 struct stat st;
566aba0b 340 int r;
4897d1dc 341
76400a62
YW
342 assert(c);
343
df4fd2c7 344 /* Do not try to re-read the file within single bus operation. */
65d34266
YW
345 if (m) {
346 if (m == c->vc_cache)
347 return 0;
df4fd2c7 348
65d34266
YW
349 sd_bus_message_unref(c->vc_cache);
350 c->vc_cache = sd_bus_message_ref(m);
351 }
df4fd2c7 352
7294912f
YW
353 fd = RET_NERRNO(open("/etc/vconsole.conf", O_CLOEXEC | O_PATH));
354 if (fd == -ENOENT) {
355 c->vc_stat = (struct stat) {};
ba4a886f 356 vc_context_clear(&c->vc);
181ba13b 357 x11_context_clear(&c->x11_from_vc);
df4fd2c7
YW
358 return 0;
359 }
7294912f
YW
360 if (fd < 0)
361 return fd;
362
363 if (fstat(fd, &st) < 0)
364 return -errno;
df4fd2c7 365
7294912f
YW
366 /* If the file is not changed, then we do not need to re-read */
367 if (stat_inode_unmodified(&c->vc_stat, &st))
df4fd2c7
YW
368 return 0;
369
7294912f 370 c->vc_stat = st;
ba4a886f 371 vc_context_clear(&c->vc);
8589823f 372 x11_context_clear(&c->x11_from_vc);
4897d1dc 373
566aba0b
YW
374 r = parse_env_file_fd(
375 fd, "/etc/vconsole.conf",
376 "KEYMAP", &c->vc.keymap,
377 "KEYMAP_TOGGLE", &c->vc.toggle,
378 "XKBLAYOUT", &c->x11_from_vc.layout,
379 "XKBMODEL", &c->x11_from_vc.model,
380 "XKBVARIANT", &c->x11_from_vc.variant,
381 "XKBOPTIONS", &c->x11_from_vc.options);
382 if (r < 0)
383 return r;
384
385 if (vc_context_verify(&c->vc) < 0)
386 vc_context_clear(&c->vc);
387
388 if (x11_context_verify(&c->x11_from_vc) < 0)
389 x11_context_clear(&c->x11_from_vc);
390
391 return 0;
4897d1dc
ZJS
392}
393
df4fd2c7 394int x11_read_data(Context *c, sd_bus_message *m) {
b839101a 395 _cleanup_close_ int fd = -EBADF;
df4fd2c7 396 _cleanup_fclose_ FILE *f = NULL;
4897d1dc 397 bool in_section = false;
df4fd2c7 398 struct stat st;
4897d1dc
ZJS
399 int r;
400
76400a62
YW
401 assert(c);
402
df4fd2c7 403 /* Do not try to re-read the file within single bus operation. */
65d34266
YW
404 if (m) {
405 if (m == c->x11_cache)
406 return 0;
df4fd2c7 407
65d34266
YW
408 sd_bus_message_unref(c->x11_cache);
409 c->x11_cache = sd_bus_message_ref(m);
410 }
df4fd2c7 411
7294912f
YW
412 fd = RET_NERRNO(open("/etc/X11/xorg.conf.d/00-keyboard.conf", O_CLOEXEC | O_PATH));
413 if (fd == -ENOENT) {
414 c->x11_stat = (struct stat) {};
181ba13b 415 x11_context_clear(&c->x11_from_xorg);
df4fd2c7
YW
416 return 0;
417 }
7294912f
YW
418 if (fd < 0)
419 return fd;
420
421 if (fstat(fd, &st) < 0)
422 return -errno;
df4fd2c7 423
7294912f
YW
424 /* If the file is not changed, then we do not need to re-read */
425 if (stat_inode_unmodified(&c->x11_stat, &st))
df4fd2c7
YW
426 return 0;
427
7294912f 428 c->x11_stat = st;
b41ec10a 429 x11_context_clear(&c->x11_from_xorg);
4897d1dc 430
b839101a
LP
431 r = fdopen_independent(fd, "re", &f);
432 if (r < 0)
433 return r;
7294912f 434
1d47b569
LP
435 for (;;) {
436 _cleanup_free_ char *line = NULL;
4897d1dc 437
0ff6ff2b 438 r = read_stripped_line(f, LONG_LINE_MAX, &line);
1d47b569
LP
439 if (r < 0)
440 return r;
441 if (r == 0)
442 break;
4897d1dc 443
0ff6ff2b 444 if (IN_SET(line[0], 0, '#'))
4897d1dc
ZJS
445 continue;
446
0ff6ff2b 447 if (in_section && first_word(line, "Option")) {
4897d1dc
ZJS
448 _cleanup_strv_free_ char **a = NULL;
449
0ff6ff2b 450 r = strv_split_full(&a, line, WHITESPACE, EXTRACT_UNQUOTE);
4897d1dc
ZJS
451 if (r < 0)
452 return r;
453
454 if (strv_length(a) == 3) {
455 char **p = NULL;
456
457 if (streq(a[1], "XkbLayout"))
b41ec10a 458 p = &c->x11_from_xorg.layout;
4897d1dc 459 else if (streq(a[1], "XkbModel"))
b41ec10a 460 p = &c->x11_from_xorg.model;
4897d1dc 461 else if (streq(a[1], "XkbVariant"))
b41ec10a 462 p = &c->x11_from_xorg.variant;
4897d1dc 463 else if (streq(a[1], "XkbOptions"))
b41ec10a 464 p = &c->x11_from_xorg.options;
4897d1dc 465
38cd55b0 466 if (p)
f9ecfd3b 467 free_and_replace(*p, a[2]);
4897d1dc
ZJS
468 }
469
0ff6ff2b 470 } else if (!in_section && first_word(line, "Section")) {
4897d1dc
ZJS
471 _cleanup_strv_free_ char **a = NULL;
472
0ff6ff2b 473 r = strv_split_full(&a, line, WHITESPACE, EXTRACT_UNQUOTE);
4897d1dc
ZJS
474 if (r < 0)
475 return -ENOMEM;
476
477 if (strv_length(a) == 2 && streq(a[1], "InputClass"))
478 in_section = true;
479
0ff6ff2b 480 } else if (in_section && first_word(line, "EndSection"))
4897d1dc
ZJS
481 in_section = false;
482 }
483
566aba0b
YW
484 if (x11_context_verify(&c->x11_from_xorg) < 0)
485 x11_context_clear(&c->x11_from_xorg);
486
4897d1dc
ZJS
487 return 0;
488}
489
4897d1dc 490int vconsole_write_data(Context *c) {
4897d1dc 491 _cleanup_strv_free_ char **l = NULL;
8589823f 492 const X11Context *xc;
df4fd2c7 493 int r;
4897d1dc 494
76400a62
YW
495 assert(c);
496
8589823f
YW
497 xc = context_get_x11_context(c);
498
aa8fbc74 499 r = load_env_file(NULL, "/etc/vconsole.conf", &l);
4897d1dc
ZJS
500 if (r < 0 && r != -ENOENT)
501 return r;
502
ba4a886f 503 r = strv_env_assign(&l, "KEYMAP", empty_to_null(c->vc.keymap));
f08231fe
ZJS
504 if (r < 0)
505 return r;
4897d1dc 506
ba4a886f 507 r = strv_env_assign(&l, "KEYMAP_TOGGLE", empty_to_null(c->vc.toggle));
f08231fe
ZJS
508 if (r < 0)
509 return r;
4897d1dc 510
f59d83af 511 r = strv_env_assign(&l, "XKBLAYOUT", empty_to_null(xc->layout));
8589823f
YW
512 if (r < 0)
513 return r;
514
f59d83af 515 r = strv_env_assign(&l, "XKBMODEL", empty_to_null(xc->model));
8589823f
YW
516 if (r < 0)
517 return r;
518
f59d83af 519 r = strv_env_assign(&l, "XKBVARIANT", empty_to_null(xc->variant));
8589823f
YW
520 if (r < 0)
521 return r;
522
f59d83af 523 r = strv_env_assign(&l, "XKBOPTIONS", empty_to_null(xc->options));
8589823f
YW
524 if (r < 0)
525 return r;
526
4897d1dc
ZJS
527 if (strv_isempty(l)) {
528 if (unlink("/etc/vconsole.conf") < 0)
529 return errno == ENOENT ? 0 : -errno;
530
7294912f 531 c->vc_stat = (struct stat) {};
4897d1dc
ZJS
532 return 0;
533 }
534
0e7a7cd4 535 r = write_vconsole_conf_label(l);
df4fd2c7
YW
536 if (r < 0)
537 return r;
538
7294912f
YW
539 if (stat("/etc/vconsole.conf", &c->vc_stat) < 0)
540 return -errno;
df4fd2c7
YW
541
542 return 0;
4897d1dc
ZJS
543}
544
545int x11_write_data(Context *c) {
546 _cleanup_fclose_ FILE *f = NULL;
d2b64853 547 _cleanup_(unlink_and_freep) char *temp_path = NULL;
b41ec10a 548 const X11Context *xc;
4897d1dc
ZJS
549 int r;
550
76400a62
YW
551 assert(c);
552
b41ec10a 553 xc = context_get_x11_context(c);
f59d83af 554 if (x11_context_isempty(xc)) {
4897d1dc
ZJS
555 if (unlink("/etc/X11/xorg.conf.d/00-keyboard.conf") < 0)
556 return errno == ENOENT ? 0 : -errno;
557
7294912f 558 c->x11_stat = (struct stat) {};
4897d1dc
ZJS
559 return 0;
560 }
561
6e5dcce4 562 (void) mkdir_p_label("/etc/X11/xorg.conf.d", 0755);
4897d1dc
ZJS
563 r = fopen_temporary("/etc/X11/xorg.conf.d/00-keyboard.conf", &f, &temp_path);
564 if (r < 0)
565 return r;
566
0d536673 567 (void) fchmod(fileno(f), 0644);
4897d1dc 568
0d536673
LP
569 fputs("# Written by systemd-localed(8), read by systemd-localed and Xorg. It's\n"
570 "# probably wise not to edit this file manually. Use localectl(1) to\n"
571 "# instruct systemd-localed to update it.\n"
572 "Section \"InputClass\"\n"
573 " Identifier \"system-keyboard\"\n"
574 " MatchIsKeyboard \"on\"\n", f);
4897d1dc 575
b41ec10a
YW
576 if (!isempty(xc->layout))
577 fprintf(f, " Option \"XkbLayout\" \"%s\"\n", xc->layout);
4897d1dc 578
b41ec10a
YW
579 if (!isempty(xc->model))
580 fprintf(f, " Option \"XkbModel\" \"%s\"\n", xc->model);
4897d1dc 581
b41ec10a
YW
582 if (!isempty(xc->variant))
583 fprintf(f, " Option \"XkbVariant\" \"%s\"\n", xc->variant);
4897d1dc 584
b41ec10a
YW
585 if (!isempty(xc->options))
586 fprintf(f, " Option \"XkbOptions\" \"%s\"\n", xc->options);
4897d1dc 587
0d536673 588 fputs("EndSection\n", f);
4897d1dc 589
0675e94a 590 r = fflush_sync_and_check(f);
4897d1dc 591 if (r < 0)
d2b64853 592 return r;
4897d1dc 593
d2b64853
DT
594 if (rename(temp_path, "/etc/X11/xorg.conf.d/00-keyboard.conf") < 0)
595 return -errno;
4897d1dc 596
7294912f
YW
597 if (stat("/etc/X11/xorg.conf.d/00-keyboard.conf", &c->x11_stat) < 0)
598 return -errno;
df4fd2c7 599
4897d1dc 600 return 0;
4897d1dc
ZJS
601}
602
76400a62
YW
603static int read_next_mapping(
604 const char *filename,
605 unsigned min_fields,
606 unsigned max_fields,
607 FILE *f,
608 unsigned *n,
609 char ***ret) {
610
4897d1dc
ZJS
611 assert(f);
612 assert(n);
76400a62 613 assert(ret);
4897d1dc
ZJS
614
615 for (;;) {
76400a62 616 _cleanup_strv_free_ char **b = NULL;
1d47b569
LP
617 _cleanup_free_ char *line = NULL;
618 size_t length;
4897d1dc 619 int r;
4897d1dc 620
0ff6ff2b 621 r = read_stripped_line(f, LONG_LINE_MAX, &line);
1d47b569
LP
622 if (r < 0)
623 return r;
624 if (r == 0)
625 break;
4897d1dc
ZJS
626
627 (*n)++;
628
0ff6ff2b 629 if (IN_SET(line[0], 0, '#'))
4897d1dc
ZJS
630 continue;
631
0ff6ff2b 632 r = strv_split_full(&b, line, WHITESPACE, EXTRACT_UNQUOTE);
4897d1dc
ZJS
633 if (r < 0)
634 return r;
635
636 length = strv_length(b);
637 if (length < min_fields || length > max_fields) {
7bb322e9 638 log_debug("Invalid line %s:%u, ignoring.", strna(filename), *n);
4897d1dc
ZJS
639 continue;
640
641 }
642
76400a62 643 *ret = TAKE_PTR(b);
4897d1dc
ZJS
644 return 1;
645 }
1d47b569 646
76400a62 647 *ret = NULL;
1d47b569 648 return 0;
4897d1dc
ZJS
649}
650
90005a4f
YW
651int vconsole_convert_to_x11(const VCContext *vc, X11Context *ret) {
652 _cleanup_fclose_ FILE *f = NULL;
653 const char *map;
c821ad7d 654 X11Context xc;
90005a4f 655 int r;
4897d1dc 656
90005a4f
YW
657 assert(vc);
658 assert(ret);
4897d1dc 659
90005a4f
YW
660 if (isempty(vc->keymap)) {
661 *ret = (X11Context) {};
662 return 0;
663 }
4897d1dc 664
90005a4f
YW
665 map = systemd_kbd_model_map();
666 f = fopen(map, "re");
667 if (!f)
668 return -errno;
4897d1dc 669
90005a4f
YW
670 for (unsigned n = 0;;) {
671 _cleanup_strv_free_ char **a = NULL;
4897d1dc 672
90005a4f
YW
673 r = read_next_mapping(map, 5, UINT_MAX, f, &n, &a);
674 if (r < 0)
675 return r;
c821ad7d
FB
676 if (r == 0)
677 break;
4897d1dc 678
90005a4f
YW
679 if (!streq(vc->keymap, a[0]))
680 continue;
681
566aba0b
YW
682 xc = (X11Context) {
683 .layout = empty_or_dash_to_null(a[1]),
684 .model = empty_or_dash_to_null(a[2]),
685 .variant = empty_or_dash_to_null(a[3]),
686 .options = empty_or_dash_to_null(a[4]),
687 };
688
689 if (x11_context_verify(&xc) < 0)
690 continue;
691
692 return x11_context_copy(ret, &xc);
90005a4f 693 }
c821ad7d
FB
694
695 /* No custom mapping has been found, see if the keymap is a converted one. In such case deducing the
696 * corresponding x11 layout is easy. */
697 _cleanup_free_ char *xlayout = NULL, *converted = NULL;
698 char *xvariant;
699
700 xlayout = strdup(vc->keymap);
701 if (!xlayout)
702 return -ENOMEM;
703 xvariant = strchr(xlayout, '-');
704 if (xvariant) {
705 xvariant[0] = '\0';
706 xvariant++;
707 }
708
709 /* Note: by default we use keyboard model "microsoftpro" which should be equivalent to "pc105" but
710 * with the internet/media key mapping added. */
711 xc = (X11Context) {
712 .layout = xlayout,
713 .model = (char*) "microsoftpro",
714 .variant = xvariant,
715 .options = (char*) "terminate:ctrl_alt_bksp",
716 };
717
718 /* This sanity check seems redundant with the verification of the X11 layout done on the next
719 * step. However xkbcommon is an optional dependency hence the verification might be a NOP. */
720 r = find_converted_keymap(&xc, &converted);
b35f71ac
FB
721 if (r == 0 && xc.variant) {
722 /* If we still haven't find a match, try with no variant, it's still better than nothing. */
723 xc.variant = NULL;
724 r = find_converted_keymap(&xc, &converted);
725 }
c821ad7d
FB
726 if (r < 0)
727 return r;
728
729 if (r == 0 || x11_context_verify(&xc) < 0) {
730 *ret = (X11Context) {};
731 return 0;
732 }
733
734 return x11_context_copy(ret, &xc);
4897d1dc
ZJS
735}
736
b41ec10a 737int find_converted_keymap(const X11Context *xc, char **ret) {
c2b2df60 738 _cleanup_free_ char *n = NULL;
4897d1dc 739
b41ec10a
YW
740 assert(xc);
741 assert(!isempty(xc->layout));
76400a62
YW
742 assert(ret);
743
b41ec10a
YW
744 if (xc->variant)
745 n = strjoin(xc->layout, "-", xc->variant);
4897d1dc 746 else
b41ec10a 747 n = strdup(xc->layout);
4897d1dc
ZJS
748 if (!n)
749 return -ENOMEM;
750
751 NULSTR_FOREACH(dir, KBD_KEYMAP_DIRS) {
752 _cleanup_free_ char *p = NULL, *pz = NULL;
753 bool uncompressed;
754
605405c6
ZJS
755 p = strjoin(dir, "xkb/", n, ".map");
756 pz = strjoin(dir, "xkb/", n, ".map.gz");
4897d1dc
ZJS
757 if (!p || !pz)
758 return -ENOMEM;
759
760 uncompressed = access(p, F_OK) == 0;
761 if (uncompressed || access(pz, F_OK) == 0) {
76400a62
YW
762 log_debug("Found converted keymap %s at %s", n, uncompressed ? p : pz);
763 *ret = TAKE_PTR(n);
4897d1dc
ZJS
764 return 1;
765 }
766 }
767
76400a62 768 *ret = NULL;
4897d1dc
ZJS
769 return 0;
770}
771
b41ec10a 772int find_legacy_keymap(const X11Context *xc, char **ret) {
cabffaf8
ZJS
773 const char *map;
774 _cleanup_fclose_ FILE *f = NULL;
6a6e9c03 775 _cleanup_free_ char *new_keymap = NULL;
4897d1dc
ZJS
776 unsigned best_matching = 0;
777 int r;
778
b41ec10a
YW
779 assert(xc);
780 assert(!isempty(xc->layout));
5ad327dd 781
cabffaf8 782 map = systemd_kbd_model_map();
cabffaf8 783 f = fopen(map, "re");
4897d1dc
ZJS
784 if (!f)
785 return -errno;
786
76400a62 787 for (unsigned n = 0;;) {
4897d1dc
ZJS
788 _cleanup_strv_free_ char **a = NULL;
789 unsigned matching = 0;
790
cabffaf8 791 r = read_next_mapping(map, 5, UINT_MAX, f, &n, &a);
4897d1dc
ZJS
792 if (r < 0)
793 return r;
794 if (r == 0)
795 break;
796
797 /* Determine how well matching this entry is */
b41ec10a 798 if (streq(xc->layout, a[1]))
76400a62 799 /* If we got an exact match, this is the best */
4897d1dc
ZJS
800 matching = 10;
801 else {
a22567f5
AW
802 /* see if we get an exact match with the order reversed */
803 _cleanup_strv_free_ char **b = NULL;
804 _cleanup_free_ char *c = NULL;
805 r = strv_split_full(&b, a[1], ",", 0);
806 if (r < 0)
807 return r;
808 strv_reverse(b);
809 c = strv_join(b, ",");
810 if (!c)
811 return log_oom();
812 if (streq(xc->layout, c))
813 matching = 9;
76400a62 814 else {
a22567f5
AW
815 /* We have multiple X layouts, look for an
816 * entry that matches our key with everything
817 * but the first layout stripped off. */
818 if (startswith_comma(xc->layout, a[1]))
819 matching = 5;
820 else {
821 _cleanup_free_ char *x = NULL;
822
823 /* If that didn't work, strip off the
824 * other layouts from the entry, too */
825 x = strdupcspn(a[1], ",");
826 if (!x)
827 return -ENOMEM;
828 if (startswith_comma(xc->layout, x))
829 matching = 1;
830 }
4897d1dc
ZJS
831 }
832 }
833
834 if (matching > 0) {
b41ec10a 835 if (isempty(xc->model) || streq_ptr(xc->model, a[2])) {
4897d1dc
ZJS
836 matching++;
837
537c00c9 838 if (streq_ptr(xc->variant, a[3]) || ((isempty(xc->variant) || streq_skip_trailing_chars(xc->variant, "", ",")) && streq(a[3], "-"))) {
4897d1dc
ZJS
839 matching++;
840
b41ec10a 841 if (streq_ptr(xc->options, a[4]))
4897d1dc
ZJS
842 matching++;
843 }
844 }
845 }
846
847 /* The best matching entry so far, then let's save that */
848 if (matching >= MAX(best_matching, 1u)) {
76400a62 849 log_debug("Found legacy keymap %s with score %u", a[0], matching);
4897d1dc
ZJS
850
851 if (matching > best_matching) {
852 best_matching = matching;
853
6a6e9c03 854 r = free_and_strdup(&new_keymap, a[0]);
4897d1dc
ZJS
855 if (r < 0)
856 return r;
857 }
858 }
859 }
860
a22567f5 861 if (best_matching < 9 && !isempty(xc->layout)) {
6f4514e8
YW
862 _cleanup_free_ char *l = NULL, *v = NULL, *converted = NULL;
863
4897d1dc
ZJS
864 /* The best match is only the first part of the X11
865 * keymap. Check if we have a converted map which
866 * matches just the first layout.
867 */
4897d1dc 868
3c7012cd 869 l = strdupcspn(xc->layout, ",");
6f4514e8
YW
870 if (!l)
871 return -ENOMEM;
872
b41ec10a 873 if (!isempty(xc->variant)) {
3c7012cd 874 v = strdupcspn(xc->variant, ",");
6f4514e8
YW
875 if (!v)
876 return -ENOMEM;
877 }
878
b41ec10a
YW
879 r = find_converted_keymap(
880 &(X11Context) {
881 .layout = l,
882 .variant = v,
883 },
884 &converted);
4897d1dc
ZJS
885 if (r < 0)
886 return r;
6a6e9c03
ZJS
887 if (r > 0)
888 free_and_replace(new_keymap, converted);
4897d1dc
ZJS
889 }
890
6a6e9c03 891 *ret = TAKE_PTR(new_keymap);
76400a62 892 return !!*ret;
4897d1dc
ZJS
893}
894
3017b9b1
YW
895int x11_convert_to_vconsole(const X11Context *xc, VCContext *ret) {
896 _cleanup_free_ char *keymap = NULL;
897 int r;
898
899 assert(xc);
900 assert(ret);
901
902 if (isempty(xc->layout)) {
903 *ret = (VCContext) {};
904 return 0;
905 }
906
907 r = find_converted_keymap(xc, &keymap);
b35f71ac 908 if (r == 0) {
3017b9b1 909 r = find_legacy_keymap(xc, &keymap);
b35f71ac
FB
910 if (r == 0 && xc->variant)
911 /* If we still haven't find a match, try with no variant, it's still better than
912 * nothing. */
913 r = find_converted_keymap(
914 &(X11Context) {
915 .layout = xc->layout,
916 },
917 &keymap);
918 }
3017b9b1
YW
919 if (r < 0)
920 return r;
921
922 *ret = (VCContext) {
923 .keymap = TAKE_PTR(keymap),
924 };
925 return 0;
926}
927
76400a62 928int find_language_fallback(const char *lang, char **ret) {
cabffaf8 929 const char *map;
4897d1dc
ZJS
930 _cleanup_fclose_ FILE *f = NULL;
931 unsigned n = 0;
76400a62 932 int r;
4897d1dc 933
aa63b56f 934 assert(lang);
76400a62 935 assert(ret);
4897d1dc 936
cabffaf8 937 map = systemd_language_fallback_map();
cabffaf8 938 f = fopen(map, "re");
4897d1dc
ZJS
939 if (!f)
940 return -errno;
941
942 for (;;) {
943 _cleanup_strv_free_ char **a = NULL;
4897d1dc 944
cabffaf8 945 r = read_next_mapping(map, 2, 2, f, &n, &a);
4897d1dc
ZJS
946 if (r <= 0)
947 return r;
948
949 if (streq(lang, a[0])) {
950 assert(strv_length(a) == 2);
76400a62 951 *ret = TAKE_PTR(a[1]);
4897d1dc
ZJS
952 return 1;
953 }
954 }
4897d1dc
ZJS
955}
956
8f20232f
MK
957bool locale_gen_check_available(void) {
958#if HAVE_LOCALEGEN
959 if (access(LOCALEGEN_PATH, X_OK) < 0) {
960 if (errno != ENOENT)
961 log_warning_errno(errno, "Unable to determine whether " LOCALEGEN_PATH " exists and is executable, assuming it is not: %m");
962 return false;
963 }
964 if (access("/etc/locale.gen", F_OK) < 0) {
965 if (errno != ENOENT)
966 log_warning_errno(errno, "Unable to determine whether /etc/locale.gen exists, assuming it does not: %m");
967 return false;
968 }
969 return true;
970#else
971 return false;
972#endif
973}
974
975#if HAVE_LOCALEGEN
976static bool locale_encoding_is_utf8_or_unspecified(const char *locale) {
977 const char *c = strchr(locale, '.');
978 return !c || strcaseeq(c, ".UTF-8") || strcasestr(locale, ".UTF-8@");
979}
980
981static int locale_gen_locale_supported(const char *locale_entry) {
982 /* Returns an error valus <= 0 if the locale-gen entry is invalid or unsupported,
983 * 1 in case the locale entry is valid, and -EOPNOTSUPP specifically in case
984 * the distributor has not provided us with a SUPPORTED file to check
985 * locale for validity. */
986
987 _cleanup_fclose_ FILE *f = NULL;
988 int r;
989
990 assert(locale_entry);
991
992 /* Locale templates without country code are never supported */
993 if (!strstr(locale_entry, "_"))
994 return -EINVAL;
995
996 f = fopen("/usr/share/i18n/SUPPORTED", "re");
997 if (!f) {
998 if (errno == ENOENT)
999 return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
1000 "Unable to check validity of locale entry %s: /usr/share/i18n/SUPPORTED does not exist",
1001 locale_entry);
1002 return -errno;
1003 }
1004
1005 for (;;) {
1006 _cleanup_free_ char *line = NULL;
1007
0ff6ff2b 1008 r = read_stripped_line(f, LONG_LINE_MAX, &line);
8f20232f
MK
1009 if (r < 0)
1010 return log_debug_errno(r, "Failed to read /usr/share/i18n/SUPPORTED: %m");
1011 if (r == 0)
1012 return 0;
1013
0ff6ff2b 1014 if (strcaseeq_ptr(line, locale_entry))
8f20232f
MK
1015 return 1;
1016 }
1017}
1018#endif
1019
1020int locale_gen_enable_locale(const char *locale) {
1021#if HAVE_LOCALEGEN
1022 _cleanup_fclose_ FILE *fr = NULL, *fw = NULL;
1023 _cleanup_(unlink_and_freep) char *temp_path = NULL;
1024 _cleanup_free_ char *locale_entry = NULL;
1025 bool locale_enabled = false, first_line = false;
1026 bool write_new = false;
1027 int r;
1028
1029 if (isempty(locale))
1030 return 0;
1031
1032 if (locale_encoding_is_utf8_or_unspecified(locale)) {
1033 locale_entry = strjoin(locale, " UTF-8");
1034 if (!locale_entry)
1035 return -ENOMEM;
1036 } else
1037 return -ENOEXEC; /* We do not process non-UTF-8 locale */
1038
1039 r = locale_gen_locale_supported(locale_entry);
1040 if (r == 0)
1041 return -EINVAL;
1042 if (r < 0 && r != -EOPNOTSUPP)
1043 return r;
1044
1045 fr = fopen("/etc/locale.gen", "re");
1046 if (!fr) {
1047 if (errno != ENOENT)
1048 return -errno;
1049 write_new = true;
1050 }
1051
1052 r = fopen_temporary("/etc/locale.gen", &fw, &temp_path);
1053 if (r < 0)
1054 return r;
1055
1056 if (write_new)
1057 (void) fchmod(fileno(fw), 0644);
1058 else {
1059 /* apply mode & xattrs of the original file to new file */
1060 r = copy_access(fileno(fr), fileno(fw));
1061 if (r < 0)
1062 return r;
c17cfe6e 1063 r = copy_xattr(fileno(fr), NULL, fileno(fw), NULL, COPY_ALL_XATTRS);
8f20232f 1064 if (r < 0)
d3efe294 1065 log_debug_errno(r, "Failed to copy all xattrs from old to new /etc/locale.gen file, ignoring: %m");
8f20232f
MK
1066 }
1067
1068 if (!write_new) {
1069 /* The config file ends with a line break, which we do not want to include before potentially appending a new locale
1070 * instead of uncommenting an existing line. By prepending linebreaks, we can avoid buffering this file but can still write
1071 * a nice config file without empty lines */
1072 first_line = true;
1073 for (;;) {
1074 _cleanup_free_ char *line = NULL;
1075 char *line_locale;
1076
1077 r = read_line(fr, LONG_LINE_MAX, &line);
1078 if (r < 0)
1079 return r;
1080 if (r == 0)
1081 break;
1082
1083 if (locale_enabled) {
1084 /* Just complete writing the file if the new locale was already enabled */
1085 if (!first_line)
1086 fputc('\n', fw);
1087 fputs(line, fw);
1088 first_line = false;
1089 continue;
1090 }
1091
b24b1059
OG
1092 line_locale = strstrip(line);
1093 if (isempty(line_locale)) {
8f20232f
MK
1094 fputc('\n', fw);
1095 first_line = false;
1096 continue;
1097 }
1098
8f20232f
MK
1099 if (line_locale[0] == '#')
1100 line_locale = strstrip(line_locale + 1);
1101 else if (strcaseeq_ptr(line_locale, locale_entry))
1102 return 0; /* the file already had our locale activated, so skip updating it */
1103
1104 if (strcaseeq_ptr(line_locale, locale_entry)) {
1105 /* Uncomment existing line for new locale */
1106 if (!first_line)
1107 fputc('\n', fw);
1108 fputs(locale_entry, fw);
1109 locale_enabled = true;
1110 first_line = false;
1111 continue;
1112 }
1113
1114 /* The line was not for the locale we want to enable, just copy it */
1115 if (!first_line)
1116 fputc('\n', fw);
1117 fputs(line, fw);
1118 first_line = false;
1119 }
1120 }
1121
1122 /* Add locale to enable to the end of the file if it was not found as commented line */
1123 if (!locale_enabled) {
1124 if (!write_new)
1125 fputc('\n', fw);
1126 fputs(locale_entry, fw);
1127 }
1128 fputc('\n', fw);
1129
1130 r = fflush_sync_and_check(fw);
1131 if (r < 0)
1132 return r;
1133
1134 if (rename(temp_path, "/etc/locale.gen") < 0)
1135 return -errno;
1136 temp_path = mfree(temp_path);
1137
1138 return 0;
1139#else
1140 return -EOPNOTSUPP;
1141#endif
1142}
1143
1144int locale_gen_run(void) {
1145#if HAVE_LOCALEGEN
1146 pid_t pid;
1147 int r;
1148
1149 r = safe_fork("(sd-localegen)", FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_CLOSE_ALL_FDS|FORK_LOG|FORK_WAIT, &pid);
1150 if (r < 0)
1151 return r;
1152 if (r == 0) {
1153 execl(LOCALEGEN_PATH, LOCALEGEN_PATH, NULL);
1154 _exit(EXIT_FAILURE);
1155 }
1156
1157 return 0;
1158#else
1159 return -EOPNOTSUPP;
1160#endif
1161}