]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/locale/localed-util.c
locale: when no xvariant match select the entry with an empty xvariant
[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
ZJS
437 char *l;
438
1d47b569
LP
439 r = read_line(f, LONG_LINE_MAX, &line);
440 if (r < 0)
441 return r;
442 if (r == 0)
443 break;
4897d1dc 444
1d47b569 445 l = strstrip(line);
4c701096 446 if (IN_SET(l[0], 0, '#'))
4897d1dc
ZJS
447 continue;
448
449 if (in_section && first_word(l, "Option")) {
450 _cleanup_strv_free_ char **a = NULL;
451
90e30d76 452 r = strv_split_full(&a, l, WHITESPACE, EXTRACT_UNQUOTE);
4897d1dc
ZJS
453 if (r < 0)
454 return r;
455
456 if (strv_length(a) == 3) {
457 char **p = NULL;
458
459 if (streq(a[1], "XkbLayout"))
b41ec10a 460 p = &c->x11_from_xorg.layout;
4897d1dc 461 else if (streq(a[1], "XkbModel"))
b41ec10a 462 p = &c->x11_from_xorg.model;
4897d1dc 463 else if (streq(a[1], "XkbVariant"))
b41ec10a 464 p = &c->x11_from_xorg.variant;
4897d1dc 465 else if (streq(a[1], "XkbOptions"))
b41ec10a 466 p = &c->x11_from_xorg.options;
4897d1dc 467
38cd55b0 468 if (p)
f9ecfd3b 469 free_and_replace(*p, a[2]);
4897d1dc
ZJS
470 }
471
472 } else if (!in_section && first_word(l, "Section")) {
473 _cleanup_strv_free_ char **a = NULL;
474
90e30d76 475 r = strv_split_full(&a, l, WHITESPACE, EXTRACT_UNQUOTE);
4897d1dc
ZJS
476 if (r < 0)
477 return -ENOMEM;
478
479 if (strv_length(a) == 2 && streq(a[1], "InputClass"))
480 in_section = true;
481
482 } else if (in_section && first_word(l, "EndSection"))
483 in_section = false;
484 }
485
566aba0b
YW
486 if (x11_context_verify(&c->x11_from_xorg) < 0)
487 x11_context_clear(&c->x11_from_xorg);
488
4897d1dc
ZJS
489 return 0;
490}
491
4897d1dc 492int vconsole_write_data(Context *c) {
4897d1dc 493 _cleanup_strv_free_ char **l = NULL;
8589823f 494 const X11Context *xc;
df4fd2c7 495 int r;
4897d1dc 496
76400a62
YW
497 assert(c);
498
8589823f
YW
499 xc = context_get_x11_context(c);
500
aa8fbc74 501 r = load_env_file(NULL, "/etc/vconsole.conf", &l);
4897d1dc
ZJS
502 if (r < 0 && r != -ENOENT)
503 return r;
504
ba4a886f 505 r = strv_env_assign(&l, "KEYMAP", empty_to_null(c->vc.keymap));
f08231fe
ZJS
506 if (r < 0)
507 return r;
4897d1dc 508
ba4a886f 509 r = strv_env_assign(&l, "KEYMAP_TOGGLE", empty_to_null(c->vc.toggle));
f08231fe
ZJS
510 if (r < 0)
511 return r;
4897d1dc 512
f59d83af 513 r = strv_env_assign(&l, "XKBLAYOUT", empty_to_null(xc->layout));
8589823f
YW
514 if (r < 0)
515 return r;
516
f59d83af 517 r = strv_env_assign(&l, "XKBMODEL", empty_to_null(xc->model));
8589823f
YW
518 if (r < 0)
519 return r;
520
f59d83af 521 r = strv_env_assign(&l, "XKBVARIANT", empty_to_null(xc->variant));
8589823f
YW
522 if (r < 0)
523 return r;
524
f59d83af 525 r = strv_env_assign(&l, "XKBOPTIONS", empty_to_null(xc->options));
8589823f
YW
526 if (r < 0)
527 return r;
528
4897d1dc
ZJS
529 if (strv_isempty(l)) {
530 if (unlink("/etc/vconsole.conf") < 0)
531 return errno == ENOENT ? 0 : -errno;
532
7294912f 533 c->vc_stat = (struct stat) {};
4897d1dc
ZJS
534 return 0;
535 }
536
df4fd2c7
YW
537 r = write_env_file_label("/etc/vconsole.conf", l);
538 if (r < 0)
539 return r;
540
7294912f
YW
541 if (stat("/etc/vconsole.conf", &c->vc_stat) < 0)
542 return -errno;
df4fd2c7
YW
543
544 return 0;
4897d1dc
ZJS
545}
546
547int x11_write_data(Context *c) {
548 _cleanup_fclose_ FILE *f = NULL;
d2b64853 549 _cleanup_(unlink_and_freep) char *temp_path = NULL;
b41ec10a 550 const X11Context *xc;
4897d1dc
ZJS
551 int r;
552
76400a62
YW
553 assert(c);
554
b41ec10a 555 xc = context_get_x11_context(c);
f59d83af 556 if (x11_context_isempty(xc)) {
4897d1dc
ZJS
557 if (unlink("/etc/X11/xorg.conf.d/00-keyboard.conf") < 0)
558 return errno == ENOENT ? 0 : -errno;
559
7294912f 560 c->x11_stat = (struct stat) {};
4897d1dc
ZJS
561 return 0;
562 }
563
6e5dcce4 564 (void) mkdir_p_label("/etc/X11/xorg.conf.d", 0755);
4897d1dc
ZJS
565 r = fopen_temporary("/etc/X11/xorg.conf.d/00-keyboard.conf", &f, &temp_path);
566 if (r < 0)
567 return r;
568
0d536673 569 (void) fchmod(fileno(f), 0644);
4897d1dc 570
0d536673
LP
571 fputs("# Written by systemd-localed(8), read by systemd-localed and Xorg. It's\n"
572 "# probably wise not to edit this file manually. Use localectl(1) to\n"
573 "# instruct systemd-localed to update it.\n"
574 "Section \"InputClass\"\n"
575 " Identifier \"system-keyboard\"\n"
576 " MatchIsKeyboard \"on\"\n", f);
4897d1dc 577
b41ec10a
YW
578 if (!isempty(xc->layout))
579 fprintf(f, " Option \"XkbLayout\" \"%s\"\n", xc->layout);
4897d1dc 580
b41ec10a
YW
581 if (!isempty(xc->model))
582 fprintf(f, " Option \"XkbModel\" \"%s\"\n", xc->model);
4897d1dc 583
b41ec10a
YW
584 if (!isempty(xc->variant))
585 fprintf(f, " Option \"XkbVariant\" \"%s\"\n", xc->variant);
4897d1dc 586
b41ec10a
YW
587 if (!isempty(xc->options))
588 fprintf(f, " Option \"XkbOptions\" \"%s\"\n", xc->options);
4897d1dc 589
0d536673 590 fputs("EndSection\n", f);
4897d1dc 591
0675e94a 592 r = fflush_sync_and_check(f);
4897d1dc 593 if (r < 0)
d2b64853 594 return r;
4897d1dc 595
d2b64853
DT
596 if (rename(temp_path, "/etc/X11/xorg.conf.d/00-keyboard.conf") < 0)
597 return -errno;
4897d1dc 598
7294912f
YW
599 if (stat("/etc/X11/xorg.conf.d/00-keyboard.conf", &c->x11_stat) < 0)
600 return -errno;
df4fd2c7 601
4897d1dc 602 return 0;
4897d1dc
ZJS
603}
604
76400a62
YW
605static int read_next_mapping(
606 const char *filename,
607 unsigned min_fields,
608 unsigned max_fields,
609 FILE *f,
610 unsigned *n,
611 char ***ret) {
612
4897d1dc
ZJS
613 assert(f);
614 assert(n);
76400a62 615 assert(ret);
4897d1dc
ZJS
616
617 for (;;) {
76400a62 618 _cleanup_strv_free_ char **b = NULL;
1d47b569
LP
619 _cleanup_free_ char *line = NULL;
620 size_t length;
76400a62 621 const char *l;
4897d1dc 622 int r;
4897d1dc 623
1d47b569
LP
624 r = read_line(f, LONG_LINE_MAX, &line);
625 if (r < 0)
626 return r;
627 if (r == 0)
628 break;
4897d1dc
ZJS
629
630 (*n)++;
631
632 l = strstrip(line);
4c701096 633 if (IN_SET(l[0], 0, '#'))
4897d1dc
ZJS
634 continue;
635
90e30d76 636 r = strv_split_full(&b, l, WHITESPACE, EXTRACT_UNQUOTE);
4897d1dc
ZJS
637 if (r < 0)
638 return r;
639
640 length = strv_length(b);
641 if (length < min_fields || length > max_fields) {
7bb322e9 642 log_debug("Invalid line %s:%u, ignoring.", strna(filename), *n);
4897d1dc
ZJS
643 continue;
644
645 }
646
76400a62 647 *ret = TAKE_PTR(b);
4897d1dc
ZJS
648 return 1;
649 }
1d47b569 650
76400a62 651 *ret = NULL;
1d47b569 652 return 0;
4897d1dc
ZJS
653}
654
90005a4f
YW
655int vconsole_convert_to_x11(const VCContext *vc, X11Context *ret) {
656 _cleanup_fclose_ FILE *f = NULL;
657 const char *map;
c821ad7d 658 X11Context xc;
90005a4f 659 int r;
4897d1dc 660
90005a4f
YW
661 assert(vc);
662 assert(ret);
4897d1dc 663
90005a4f
YW
664 if (isempty(vc->keymap)) {
665 *ret = (X11Context) {};
666 return 0;
667 }
4897d1dc 668
90005a4f
YW
669 map = systemd_kbd_model_map();
670 f = fopen(map, "re");
671 if (!f)
672 return -errno;
4897d1dc 673
90005a4f
YW
674 for (unsigned n = 0;;) {
675 _cleanup_strv_free_ char **a = NULL;
4897d1dc 676
90005a4f
YW
677 r = read_next_mapping(map, 5, UINT_MAX, f, &n, &a);
678 if (r < 0)
679 return r;
c821ad7d
FB
680 if (r == 0)
681 break;
4897d1dc 682
90005a4f
YW
683 if (!streq(vc->keymap, a[0]))
684 continue;
685
566aba0b
YW
686 xc = (X11Context) {
687 .layout = empty_or_dash_to_null(a[1]),
688 .model = empty_or_dash_to_null(a[2]),
689 .variant = empty_or_dash_to_null(a[3]),
690 .options = empty_or_dash_to_null(a[4]),
691 };
692
693 if (x11_context_verify(&xc) < 0)
694 continue;
695
696 return x11_context_copy(ret, &xc);
90005a4f 697 }
c821ad7d
FB
698
699 /* No custom mapping has been found, see if the keymap is a converted one. In such case deducing the
700 * corresponding x11 layout is easy. */
701 _cleanup_free_ char *xlayout = NULL, *converted = NULL;
702 char *xvariant;
703
704 xlayout = strdup(vc->keymap);
705 if (!xlayout)
706 return -ENOMEM;
707 xvariant = strchr(xlayout, '-');
708 if (xvariant) {
709 xvariant[0] = '\0';
710 xvariant++;
711 }
712
713 /* Note: by default we use keyboard model "microsoftpro" which should be equivalent to "pc105" but
714 * with the internet/media key mapping added. */
715 xc = (X11Context) {
716 .layout = xlayout,
717 .model = (char*) "microsoftpro",
718 .variant = xvariant,
719 .options = (char*) "terminate:ctrl_alt_bksp",
720 };
721
722 /* This sanity check seems redundant with the verification of the X11 layout done on the next
723 * step. However xkbcommon is an optional dependency hence the verification might be a NOP. */
724 r = find_converted_keymap(&xc, &converted);
b35f71ac
FB
725 if (r == 0 && xc.variant) {
726 /* If we still haven't find a match, try with no variant, it's still better than nothing. */
727 xc.variant = NULL;
728 r = find_converted_keymap(&xc, &converted);
729 }
c821ad7d
FB
730 if (r < 0)
731 return r;
732
733 if (r == 0 || x11_context_verify(&xc) < 0) {
734 *ret = (X11Context) {};
735 return 0;
736 }
737
738 return x11_context_copy(ret, &xc);
4897d1dc
ZJS
739}
740
b41ec10a 741int find_converted_keymap(const X11Context *xc, char **ret) {
c2b2df60 742 _cleanup_free_ char *n = NULL;
4897d1dc 743
b41ec10a
YW
744 assert(xc);
745 assert(!isempty(xc->layout));
76400a62
YW
746 assert(ret);
747
b41ec10a
YW
748 if (xc->variant)
749 n = strjoin(xc->layout, "-", xc->variant);
4897d1dc 750 else
b41ec10a 751 n = strdup(xc->layout);
4897d1dc
ZJS
752 if (!n)
753 return -ENOMEM;
754
755 NULSTR_FOREACH(dir, KBD_KEYMAP_DIRS) {
756 _cleanup_free_ char *p = NULL, *pz = NULL;
757 bool uncompressed;
758
605405c6
ZJS
759 p = strjoin(dir, "xkb/", n, ".map");
760 pz = strjoin(dir, "xkb/", n, ".map.gz");
4897d1dc
ZJS
761 if (!p || !pz)
762 return -ENOMEM;
763
764 uncompressed = access(p, F_OK) == 0;
765 if (uncompressed || access(pz, F_OK) == 0) {
76400a62
YW
766 log_debug("Found converted keymap %s at %s", n, uncompressed ? p : pz);
767 *ret = TAKE_PTR(n);
4897d1dc
ZJS
768 return 1;
769 }
770 }
771
76400a62 772 *ret = NULL;
4897d1dc
ZJS
773 return 0;
774}
775
b41ec10a 776int find_legacy_keymap(const X11Context *xc, char **ret) {
cabffaf8
ZJS
777 const char *map;
778 _cleanup_fclose_ FILE *f = NULL;
6a6e9c03 779 _cleanup_free_ char *new_keymap = NULL;
4897d1dc
ZJS
780 unsigned best_matching = 0;
781 int r;
782
b41ec10a
YW
783 assert(xc);
784 assert(!isempty(xc->layout));
5ad327dd 785
cabffaf8 786 map = systemd_kbd_model_map();
cabffaf8 787 f = fopen(map, "re");
4897d1dc
ZJS
788 if (!f)
789 return -errno;
790
76400a62 791 for (unsigned n = 0;;) {
4897d1dc
ZJS
792 _cleanup_strv_free_ char **a = NULL;
793 unsigned matching = 0;
794
cabffaf8 795 r = read_next_mapping(map, 5, UINT_MAX, f, &n, &a);
4897d1dc
ZJS
796 if (r < 0)
797 return r;
798 if (r == 0)
799 break;
800
801 /* Determine how well matching this entry is */
b41ec10a 802 if (streq(xc->layout, a[1]))
76400a62 803 /* If we got an exact match, this is the best */
4897d1dc
ZJS
804 matching = 10;
805 else {
806 /* We have multiple X layouts, look for an
807 * entry that matches our key with everything
808 * but the first layout stripped off. */
b41ec10a 809 if (startswith_comma(xc->layout, a[1]))
4897d1dc 810 matching = 5;
76400a62 811 else {
6d946490 812 _cleanup_free_ char *x = NULL;
4897d1dc
ZJS
813
814 /* If that didn't work, strip off the
815 * other layouts from the entry, too */
e8bec624
LP
816 x = strdupcspn(a[1], ",");
817 if (!x)
818 return -ENOMEM;
b41ec10a 819 if (startswith_comma(xc->layout, x))
4897d1dc
ZJS
820 matching = 1;
821 }
822 }
823
824 if (matching > 0) {
b41ec10a 825 if (isempty(xc->model) || streq_ptr(xc->model, a[2])) {
4897d1dc
ZJS
826 matching++;
827
b41ec10a 828 if (streq_ptr(xc->variant, a[3])) {
4897d1dc
ZJS
829 matching++;
830
b41ec10a 831 if (streq_ptr(xc->options, a[4]))
4897d1dc
ZJS
832 matching++;
833 }
834 }
835 }
836
837 /* The best matching entry so far, then let's save that */
838 if (matching >= MAX(best_matching, 1u)) {
76400a62 839 log_debug("Found legacy keymap %s with score %u", a[0], matching);
4897d1dc
ZJS
840
841 if (matching > best_matching) {
842 best_matching = matching;
843
6a6e9c03 844 r = free_and_strdup(&new_keymap, a[0]);
4897d1dc
ZJS
845 if (r < 0)
846 return r;
847 }
848 }
849 }
850
b41ec10a 851 if (best_matching < 10 && !isempty(xc->layout)) {
6f4514e8
YW
852 _cleanup_free_ char *l = NULL, *v = NULL, *converted = NULL;
853
4897d1dc
ZJS
854 /* The best match is only the first part of the X11
855 * keymap. Check if we have a converted map which
856 * matches just the first layout.
857 */
4897d1dc 858
3c7012cd 859 l = strdupcspn(xc->layout, ",");
6f4514e8
YW
860 if (!l)
861 return -ENOMEM;
862
b41ec10a 863 if (!isempty(xc->variant)) {
3c7012cd 864 v = strdupcspn(xc->variant, ",");
6f4514e8
YW
865 if (!v)
866 return -ENOMEM;
867 }
868
b41ec10a
YW
869 r = find_converted_keymap(
870 &(X11Context) {
871 .layout = l,
872 .variant = v,
873 },
874 &converted);
4897d1dc
ZJS
875 if (r < 0)
876 return r;
6a6e9c03
ZJS
877 if (r > 0)
878 free_and_replace(new_keymap, converted);
4897d1dc
ZJS
879 }
880
6a6e9c03 881 *ret = TAKE_PTR(new_keymap);
76400a62 882 return !!*ret;
4897d1dc
ZJS
883}
884
3017b9b1
YW
885int x11_convert_to_vconsole(const X11Context *xc, VCContext *ret) {
886 _cleanup_free_ char *keymap = NULL;
887 int r;
888
889 assert(xc);
890 assert(ret);
891
892 if (isempty(xc->layout)) {
893 *ret = (VCContext) {};
894 return 0;
895 }
896
897 r = find_converted_keymap(xc, &keymap);
b35f71ac 898 if (r == 0) {
3017b9b1 899 r = find_legacy_keymap(xc, &keymap);
b35f71ac
FB
900 if (r == 0 && xc->variant)
901 /* If we still haven't find a match, try with no variant, it's still better than
902 * nothing. */
903 r = find_converted_keymap(
904 &(X11Context) {
905 .layout = xc->layout,
906 },
907 &keymap);
908 }
3017b9b1
YW
909 if (r < 0)
910 return r;
911
912 *ret = (VCContext) {
913 .keymap = TAKE_PTR(keymap),
914 };
915 return 0;
916}
917
76400a62 918int find_language_fallback(const char *lang, char **ret) {
cabffaf8 919 const char *map;
4897d1dc
ZJS
920 _cleanup_fclose_ FILE *f = NULL;
921 unsigned n = 0;
76400a62 922 int r;
4897d1dc 923
aa63b56f 924 assert(lang);
76400a62 925 assert(ret);
4897d1dc 926
cabffaf8 927 map = systemd_language_fallback_map();
cabffaf8 928 f = fopen(map, "re");
4897d1dc
ZJS
929 if (!f)
930 return -errno;
931
932 for (;;) {
933 _cleanup_strv_free_ char **a = NULL;
4897d1dc 934
cabffaf8 935 r = read_next_mapping(map, 2, 2, f, &n, &a);
4897d1dc
ZJS
936 if (r <= 0)
937 return r;
938
939 if (streq(lang, a[0])) {
940 assert(strv_length(a) == 2);
76400a62 941 *ret = TAKE_PTR(a[1]);
4897d1dc
ZJS
942 return 1;
943 }
944 }
4897d1dc
ZJS
945}
946
8f20232f
MK
947bool locale_gen_check_available(void) {
948#if HAVE_LOCALEGEN
949 if (access(LOCALEGEN_PATH, X_OK) < 0) {
950 if (errno != ENOENT)
951 log_warning_errno(errno, "Unable to determine whether " LOCALEGEN_PATH " exists and is executable, assuming it is not: %m");
952 return false;
953 }
954 if (access("/etc/locale.gen", F_OK) < 0) {
955 if (errno != ENOENT)
956 log_warning_errno(errno, "Unable to determine whether /etc/locale.gen exists, assuming it does not: %m");
957 return false;
958 }
959 return true;
960#else
961 return false;
962#endif
963}
964
965#if HAVE_LOCALEGEN
966static bool locale_encoding_is_utf8_or_unspecified(const char *locale) {
967 const char *c = strchr(locale, '.');
968 return !c || strcaseeq(c, ".UTF-8") || strcasestr(locale, ".UTF-8@");
969}
970
971static int locale_gen_locale_supported(const char *locale_entry) {
972 /* Returns an error valus <= 0 if the locale-gen entry is invalid or unsupported,
973 * 1 in case the locale entry is valid, and -EOPNOTSUPP specifically in case
974 * the distributor has not provided us with a SUPPORTED file to check
975 * locale for validity. */
976
977 _cleanup_fclose_ FILE *f = NULL;
978 int r;
979
980 assert(locale_entry);
981
982 /* Locale templates without country code are never supported */
983 if (!strstr(locale_entry, "_"))
984 return -EINVAL;
985
986 f = fopen("/usr/share/i18n/SUPPORTED", "re");
987 if (!f) {
988 if (errno == ENOENT)
989 return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
990 "Unable to check validity of locale entry %s: /usr/share/i18n/SUPPORTED does not exist",
991 locale_entry);
992 return -errno;
993 }
994
995 for (;;) {
996 _cleanup_free_ char *line = NULL;
b24b1059 997 char *l;
8f20232f
MK
998
999 r = read_line(f, LONG_LINE_MAX, &line);
1000 if (r < 0)
1001 return log_debug_errno(r, "Failed to read /usr/share/i18n/SUPPORTED: %m");
1002 if (r == 0)
1003 return 0;
1004
b24b1059
OG
1005 l = strstrip(line);
1006 if (strcaseeq_ptr(l, locale_entry))
8f20232f
MK
1007 return 1;
1008 }
1009}
1010#endif
1011
1012int locale_gen_enable_locale(const char *locale) {
1013#if HAVE_LOCALEGEN
1014 _cleanup_fclose_ FILE *fr = NULL, *fw = NULL;
1015 _cleanup_(unlink_and_freep) char *temp_path = NULL;
1016 _cleanup_free_ char *locale_entry = NULL;
1017 bool locale_enabled = false, first_line = false;
1018 bool write_new = false;
1019 int r;
1020
1021 if (isempty(locale))
1022 return 0;
1023
1024 if (locale_encoding_is_utf8_or_unspecified(locale)) {
1025 locale_entry = strjoin(locale, " UTF-8");
1026 if (!locale_entry)
1027 return -ENOMEM;
1028 } else
1029 return -ENOEXEC; /* We do not process non-UTF-8 locale */
1030
1031 r = locale_gen_locale_supported(locale_entry);
1032 if (r == 0)
1033 return -EINVAL;
1034 if (r < 0 && r != -EOPNOTSUPP)
1035 return r;
1036
1037 fr = fopen("/etc/locale.gen", "re");
1038 if (!fr) {
1039 if (errno != ENOENT)
1040 return -errno;
1041 write_new = true;
1042 }
1043
1044 r = fopen_temporary("/etc/locale.gen", &fw, &temp_path);
1045 if (r < 0)
1046 return r;
1047
1048 if (write_new)
1049 (void) fchmod(fileno(fw), 0644);
1050 else {
1051 /* apply mode & xattrs of the original file to new file */
1052 r = copy_access(fileno(fr), fileno(fw));
1053 if (r < 0)
1054 return r;
c17cfe6e 1055 r = copy_xattr(fileno(fr), NULL, fileno(fw), NULL, COPY_ALL_XATTRS);
8f20232f 1056 if (r < 0)
d3efe294 1057 log_debug_errno(r, "Failed to copy all xattrs from old to new /etc/locale.gen file, ignoring: %m");
8f20232f
MK
1058 }
1059
1060 if (!write_new) {
1061 /* The config file ends with a line break, which we do not want to include before potentially appending a new locale
1062 * instead of uncommenting an existing line. By prepending linebreaks, we can avoid buffering this file but can still write
1063 * a nice config file without empty lines */
1064 first_line = true;
1065 for (;;) {
1066 _cleanup_free_ char *line = NULL;
1067 char *line_locale;
1068
1069 r = read_line(fr, LONG_LINE_MAX, &line);
1070 if (r < 0)
1071 return r;
1072 if (r == 0)
1073 break;
1074
1075 if (locale_enabled) {
1076 /* Just complete writing the file if the new locale was already enabled */
1077 if (!first_line)
1078 fputc('\n', fw);
1079 fputs(line, fw);
1080 first_line = false;
1081 continue;
1082 }
1083
b24b1059
OG
1084 line_locale = strstrip(line);
1085 if (isempty(line_locale)) {
8f20232f
MK
1086 fputc('\n', fw);
1087 first_line = false;
1088 continue;
1089 }
1090
8f20232f
MK
1091 if (line_locale[0] == '#')
1092 line_locale = strstrip(line_locale + 1);
1093 else if (strcaseeq_ptr(line_locale, locale_entry))
1094 return 0; /* the file already had our locale activated, so skip updating it */
1095
1096 if (strcaseeq_ptr(line_locale, locale_entry)) {
1097 /* Uncomment existing line for new locale */
1098 if (!first_line)
1099 fputc('\n', fw);
1100 fputs(locale_entry, fw);
1101 locale_enabled = true;
1102 first_line = false;
1103 continue;
1104 }
1105
1106 /* The line was not for the locale we want to enable, just copy it */
1107 if (!first_line)
1108 fputc('\n', fw);
1109 fputs(line, fw);
1110 first_line = false;
1111 }
1112 }
1113
1114 /* Add locale to enable to the end of the file if it was not found as commented line */
1115 if (!locale_enabled) {
1116 if (!write_new)
1117 fputc('\n', fw);
1118 fputs(locale_entry, fw);
1119 }
1120 fputc('\n', fw);
1121
1122 r = fflush_sync_and_check(fw);
1123 if (r < 0)
1124 return r;
1125
1126 if (rename(temp_path, "/etc/locale.gen") < 0)
1127 return -errno;
1128 temp_path = mfree(temp_path);
1129
1130 return 0;
1131#else
1132 return -EOPNOTSUPP;
1133#endif
1134}
1135
1136int locale_gen_run(void) {
1137#if HAVE_LOCALEGEN
1138 pid_t pid;
1139 int r;
1140
1141 r = safe_fork("(sd-localegen)", FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_CLOSE_ALL_FDS|FORK_LOG|FORK_WAIT, &pid);
1142 if (r < 0)
1143 return r;
1144 if (r == 0) {
1145 execl(LOCALEGEN_PATH, LOCALEGEN_PATH, NULL);
1146 _exit(EXIT_FAILURE);
1147 }
1148
1149 return 0;
1150#else
1151 return -EOPNOTSUPP;
1152#endif
1153}