/src/systemd/src/shared/vconsole-util.c
Line | Count | Source |
1 | | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
2 | | |
3 | | #include <stdlib.h> |
4 | | #include <unistd.h> |
5 | | |
6 | | #include "alloc-util.h" |
7 | | #include "efivars.h" |
8 | | #include "env-util.h" |
9 | | #include "extract-word.h" |
10 | | #include "fd-util.h" |
11 | | #include "fileio.h" |
12 | | #include "kbd-util.h" |
13 | | #include "log.h" |
14 | | #include "string-util.h" |
15 | | #include "strv.h" |
16 | | #include "vconsole-util.h" |
17 | | |
18 | 0 | static bool startswith_comma(const char *s, const char *prefix) { |
19 | 0 | assert(s); |
20 | 0 | assert(prefix); |
21 | |
|
22 | 0 | s = startswith(s, prefix); |
23 | 0 | if (!s) |
24 | 0 | return false; |
25 | | |
26 | 0 | return IN_SET(*s, ',', '\0'); |
27 | 0 | } |
28 | | |
29 | 0 | static const char* systemd_kbd_model_map(void) { |
30 | 0 | const char* s; |
31 | |
|
32 | 0 | s = getenv("SYSTEMD_KBD_MODEL_MAP"); |
33 | 0 | if (s) |
34 | 0 | return s; |
35 | | |
36 | 0 | return SYSTEMD_KBD_MODEL_MAP; |
37 | 0 | } |
38 | | |
39 | 0 | static const char* systemd_language_fallback_map(void) { |
40 | 0 | const char* s; |
41 | |
|
42 | 0 | s = getenv("SYSTEMD_LANGUAGE_FALLBACK_MAP"); |
43 | 0 | if (s) |
44 | 0 | return s; |
45 | | |
46 | 0 | return SYSTEMD_LANGUAGE_FALLBACK_MAP; |
47 | 0 | } |
48 | | |
49 | 0 | void x11_context_clear(X11Context *xc) { |
50 | 0 | assert(xc); |
51 | |
|
52 | 0 | xc->layout = mfree(xc->layout); |
53 | 0 | xc->options = mfree(xc->options); |
54 | 0 | xc->model = mfree(xc->model); |
55 | 0 | xc->variant = mfree(xc->variant); |
56 | 0 | } |
57 | | |
58 | 0 | void x11_context_replace(X11Context *dest, X11Context *src) { |
59 | 0 | assert(dest); |
60 | 0 | assert(src); |
61 | |
|
62 | 0 | x11_context_clear(dest); |
63 | 0 | *dest = TAKE_STRUCT(*src); |
64 | 0 | } |
65 | | |
66 | 0 | bool x11_context_isempty(const X11Context *xc) { |
67 | 0 | assert(xc); |
68 | |
|
69 | 0 | return |
70 | 0 | isempty(xc->layout) && |
71 | 0 | isempty(xc->model) && |
72 | 0 | isempty(xc->variant) && |
73 | 0 | isempty(xc->options); |
74 | 0 | } |
75 | | |
76 | 0 | void x11_context_empty_to_null(X11Context *xc) { |
77 | 0 | assert(xc); |
78 | | |
79 | | /* Do not call x11_context_clear() for the passed object. */ |
80 | |
|
81 | 0 | xc->layout = empty_to_null(xc->layout); |
82 | 0 | xc->model = empty_to_null(xc->model); |
83 | 0 | xc->variant = empty_to_null(xc->variant); |
84 | 0 | xc->options = empty_to_null(xc->options); |
85 | 0 | } |
86 | | |
87 | 0 | static char* empty_to_null_and_free(char *p) { |
88 | 0 | return isempty(p) ? mfree(p) : p; |
89 | 0 | } |
90 | | |
91 | 0 | void x11_context_normalize(X11Context *xc) { |
92 | 0 | assert(xc); |
93 | | |
94 | | /* Like x11_context_empty_to_null(), but also frees the heap memory |
95 | | * owned by the fields. Use this for contexts whose strings are |
96 | | * heap-allocated (e.g. x11_read_data()), not for borrowed contexts |
97 | | * such as the one built by method_set_x11_keyboard() from a D-Bus |
98 | | * message. */ |
99 | |
|
100 | 0 | xc->layout = empty_to_null_and_free(xc->layout); |
101 | 0 | xc->model = empty_to_null_and_free(xc->model); |
102 | 0 | xc->variant = empty_to_null_and_free(xc->variant); |
103 | 0 | xc->options = empty_to_null_and_free(xc->options); |
104 | 0 | } |
105 | | |
106 | 0 | bool x11_context_is_safe(const X11Context *xc) { |
107 | 0 | assert(xc); |
108 | |
|
109 | 0 | return |
110 | 0 | (!xc->layout || string_is_safe(xc->layout, /* flags= */ 0)) && |
111 | 0 | (!xc->model || string_is_safe(xc->model, /* flags= */ 0)) && |
112 | 0 | (!xc->variant || string_is_safe(xc->variant, /* flags= */ 0)) && |
113 | 0 | (!xc->options || string_is_safe(xc->options, /* flags= */ 0)); |
114 | 0 | } |
115 | | |
116 | 0 | bool x11_context_equal(const X11Context *a, const X11Context *b) { |
117 | 0 | assert(a); |
118 | 0 | assert(b); |
119 | |
|
120 | 0 | return |
121 | 0 | streq_ptr(a->layout, b->layout) && |
122 | 0 | streq_ptr(a->model, b->model) && |
123 | 0 | streq_ptr(a->variant, b->variant) && |
124 | 0 | streq_ptr(a->options, b->options); |
125 | 0 | } |
126 | | |
127 | 0 | int x11_context_copy(X11Context *dest, const X11Context *src) { |
128 | 0 | bool modified; |
129 | 0 | int r; |
130 | |
|
131 | 0 | assert(dest); |
132 | |
|
133 | 0 | if (dest == src) |
134 | 0 | return 0; |
135 | | |
136 | 0 | if (!src) { |
137 | 0 | modified = !x11_context_isempty(dest); |
138 | 0 | x11_context_clear(dest); |
139 | 0 | return modified; |
140 | 0 | } |
141 | | |
142 | 0 | r = free_and_strdup(&dest->layout, src->layout); |
143 | 0 | if (r < 0) |
144 | 0 | return r; |
145 | 0 | modified = r > 0; |
146 | |
|
147 | 0 | r = free_and_strdup(&dest->model, src->model); |
148 | 0 | if (r < 0) |
149 | 0 | return r; |
150 | 0 | modified = modified || r > 0; |
151 | |
|
152 | 0 | r = free_and_strdup(&dest->variant, src->variant); |
153 | 0 | if (r < 0) |
154 | 0 | return r; |
155 | 0 | modified = modified || r > 0; |
156 | |
|
157 | 0 | r = free_and_strdup(&dest->options, src->options); |
158 | 0 | if (r < 0) |
159 | 0 | return r; |
160 | 0 | modified = modified || r > 0; |
161 | |
|
162 | 0 | return modified; |
163 | 0 | } |
164 | | |
165 | 0 | void vc_context_clear(VCContext *vc) { |
166 | 0 | assert(vc); |
167 | |
|
168 | 0 | vc->keymap = mfree(vc->keymap); |
169 | 0 | vc->toggle = mfree(vc->toggle); |
170 | 0 | } |
171 | | |
172 | 0 | void vc_context_replace(VCContext *dest, VCContext *src) { |
173 | 0 | assert(dest); |
174 | 0 | assert(src); |
175 | |
|
176 | 0 | vc_context_clear(dest); |
177 | 0 | *dest = TAKE_STRUCT(*src); |
178 | 0 | } |
179 | | |
180 | 0 | bool vc_context_isempty(const VCContext *vc) { |
181 | 0 | assert(vc); |
182 | |
|
183 | 0 | return |
184 | 0 | isempty(vc->keymap) && |
185 | 0 | isempty(vc->toggle); |
186 | 0 | } |
187 | | |
188 | 0 | void vc_context_empty_to_null(VCContext *vc) { |
189 | 0 | assert(vc); |
190 | | |
191 | | /* Do not call vc_context_clear() for the passed object. */ |
192 | |
|
193 | 0 | vc->keymap = empty_to_null(vc->keymap); |
194 | 0 | vc->toggle = empty_to_null(vc->toggle); |
195 | 0 | } |
196 | | |
197 | 0 | bool vc_context_equal(const VCContext *a, const VCContext *b) { |
198 | 0 | assert(a); |
199 | 0 | assert(b); |
200 | |
|
201 | 0 | return |
202 | 0 | streq_ptr(a->keymap, b->keymap) && |
203 | 0 | streq_ptr(a->toggle, b->toggle); |
204 | 0 | } |
205 | | |
206 | 0 | int vc_context_copy(VCContext *dest, const VCContext *src) { |
207 | 0 | bool modified; |
208 | 0 | int r; |
209 | |
|
210 | 0 | assert(dest); |
211 | |
|
212 | 0 | if (dest == src) |
213 | 0 | return 0; |
214 | | |
215 | 0 | if (!src) { |
216 | 0 | modified = !vc_context_isempty(dest); |
217 | 0 | vc_context_clear(dest); |
218 | 0 | return modified; |
219 | 0 | } |
220 | | |
221 | 0 | r = free_and_strdup(&dest->keymap, src->keymap); |
222 | 0 | if (r < 0) |
223 | 0 | return r; |
224 | 0 | modified = r > 0; |
225 | |
|
226 | 0 | r = free_and_strdup(&dest->toggle, src->toggle); |
227 | 0 | if (r < 0) |
228 | 0 | return r; |
229 | 0 | modified = modified || r > 0; |
230 | |
|
231 | 0 | return modified; |
232 | 0 | } |
233 | | |
234 | | static int read_next_mapping( |
235 | | const char *filename, |
236 | | unsigned min_fields, |
237 | | unsigned max_fields, |
238 | | FILE *f, |
239 | | unsigned *n, |
240 | 0 | char ***ret) { |
241 | |
|
242 | 0 | assert(f); |
243 | 0 | assert(n); |
244 | 0 | assert(ret); |
245 | |
|
246 | 0 | for (;;) { |
247 | 0 | _cleanup_strv_free_ char **b = NULL; |
248 | 0 | _cleanup_free_ char *line = NULL; |
249 | 0 | size_t length; |
250 | 0 | int r; |
251 | |
|
252 | 0 | r = read_stripped_line(f, LONG_LINE_MAX, &line); |
253 | 0 | if (r < 0) |
254 | 0 | return r; |
255 | 0 | if (r == 0) |
256 | 0 | break; |
257 | | |
258 | 0 | (*n)++; |
259 | |
|
260 | 0 | if (IN_SET(line[0], 0, '#')) |
261 | 0 | continue; |
262 | | |
263 | 0 | r = strv_split_full(&b, line, WHITESPACE, EXTRACT_UNQUOTE); |
264 | 0 | if (r < 0) |
265 | 0 | return r; |
266 | | |
267 | 0 | length = strv_length(b); |
268 | 0 | if (length < min_fields || length > max_fields) { |
269 | 0 | log_debug("Invalid line %s:%u, ignoring.", strna(filename), *n); |
270 | 0 | continue; |
271 | |
|
272 | 0 | } |
273 | | |
274 | 0 | *ret = TAKE_PTR(b); |
275 | 0 | return 1; |
276 | 0 | } |
277 | | |
278 | 0 | *ret = NULL; |
279 | 0 | return 0; |
280 | 0 | } |
281 | | |
282 | 0 | int vconsole_convert_to_x11(const VCContext *vc, X11VerifyCallback verify, X11Context *ret) { |
283 | 0 | _cleanup_fclose_ FILE *f = NULL; |
284 | 0 | const char *map; |
285 | 0 | X11Context xc; |
286 | 0 | int r; |
287 | |
|
288 | 0 | assert(vc); |
289 | 0 | assert(ret); |
290 | |
|
291 | 0 | if (isempty(vc->keymap)) { |
292 | 0 | *ret = (X11Context) {}; |
293 | 0 | return 0; |
294 | 0 | } |
295 | | |
296 | 0 | map = systemd_kbd_model_map(); |
297 | 0 | f = fopen(map, "re"); |
298 | 0 | if (!f) |
299 | 0 | return -errno; |
300 | | |
301 | 0 | for (unsigned n = 0;;) { |
302 | 0 | _cleanup_strv_free_ char **a = NULL; |
303 | |
|
304 | 0 | r = read_next_mapping(map, 5, UINT_MAX, f, &n, &a); |
305 | 0 | if (r < 0) |
306 | 0 | return r; |
307 | 0 | if (r == 0) |
308 | 0 | break; |
309 | | |
310 | 0 | if (!streq(vc->keymap, a[0])) |
311 | 0 | continue; |
312 | | |
313 | 0 | xc = (X11Context) { |
314 | 0 | .layout = empty_or_dash_to_null(a[1]), |
315 | 0 | .model = empty_or_dash_to_null(a[2]), |
316 | 0 | .variant = empty_or_dash_to_null(a[3]), |
317 | 0 | .options = empty_or_dash_to_null(a[4]), |
318 | 0 | }; |
319 | |
|
320 | 0 | if (verify && verify(&xc) < 0) |
321 | 0 | continue; |
322 | | |
323 | 0 | return x11_context_copy(ret, &xc); |
324 | 0 | } |
325 | | |
326 | | /* No custom mapping has been found, see if the keymap is a converted one. In such case deducing the |
327 | | * corresponding x11 layout is easy. */ |
328 | 0 | _cleanup_free_ char *xlayout = NULL, *converted = NULL; |
329 | 0 | char *xvariant; |
330 | |
|
331 | 0 | xlayout = strdup(vc->keymap); |
332 | 0 | if (!xlayout) |
333 | 0 | return -ENOMEM; |
334 | 0 | xvariant = strchr(xlayout, '-'); |
335 | 0 | if (xvariant) { |
336 | 0 | xvariant[0] = '\0'; |
337 | 0 | xvariant++; |
338 | 0 | } |
339 | | |
340 | | /* Note: by default we use keyboard model "microsoftpro" which should be equivalent to "pc105" but |
341 | | * with the internet/media key mapping added. */ |
342 | 0 | xc = (X11Context) { |
343 | 0 | .layout = xlayout, |
344 | 0 | .model = (char*) "microsoftpro", |
345 | 0 | .variant = xvariant, |
346 | 0 | .options = (char*) "terminate:ctrl_alt_bksp", |
347 | 0 | }; |
348 | | |
349 | | /* This sanity check seems redundant with the verification of the X11 layout done on the next |
350 | | * step. However xkbcommon is an optional dependency hence the verification might be a NOP. */ |
351 | 0 | r = find_converted_keymap(&xc, &converted); |
352 | 0 | if (r == 0 && xc.variant) { |
353 | | /* If we still haven't find a match, try with no variant, it's still better than nothing. */ |
354 | 0 | xc.variant = NULL; |
355 | 0 | r = find_converted_keymap(&xc, &converted); |
356 | 0 | } |
357 | 0 | if (r < 0) |
358 | 0 | return r; |
359 | | |
360 | 0 | if (r == 0 || (verify && verify(&xc) < 0)) { |
361 | 0 | *ret = (X11Context) {}; |
362 | 0 | return 0; |
363 | 0 | } |
364 | | |
365 | 0 | return x11_context_copy(ret, &xc); |
366 | 0 | } |
367 | | |
368 | 0 | int find_converted_keymap(const X11Context *xc, char **ret) { |
369 | 0 | _cleanup_free_ char *n = NULL, *p = NULL, *pz = NULL; |
370 | 0 | _cleanup_strv_free_ char **keymap_dirs = NULL; |
371 | 0 | int r; |
372 | |
|
373 | 0 | assert(xc); |
374 | 0 | assert(ret); |
375 | |
|
376 | 0 | if (isempty(xc->layout)) |
377 | 0 | return -EINVAL; |
378 | | |
379 | 0 | if (xc->variant) |
380 | 0 | n = strjoin(xc->layout, "-", xc->variant); |
381 | 0 | else |
382 | 0 | n = strdup(xc->layout); |
383 | 0 | if (!n) |
384 | 0 | return -ENOMEM; |
385 | | |
386 | 0 | p = strjoin("xkb/", n, ".map"); |
387 | 0 | pz = strjoin("xkb/", n, ".map.gz"); |
388 | 0 | if (!p || !pz) |
389 | 0 | return -ENOMEM; |
390 | | |
391 | 0 | r = keymap_directories(&keymap_dirs); |
392 | 0 | if (r < 0) |
393 | 0 | return r; |
394 | | |
395 | 0 | STRV_FOREACH(dir, keymap_dirs) { |
396 | 0 | _cleanup_close_ int dir_fd = -EBADF; |
397 | 0 | bool uncompressed; |
398 | |
|
399 | 0 | dir_fd = open(*dir, O_CLOEXEC | O_DIRECTORY | O_PATH); |
400 | 0 | if (dir_fd < 0) { |
401 | 0 | if (errno != ENOENT) |
402 | 0 | log_debug_errno(errno, "Failed to open %s, ignoring: %m", *dir); |
403 | 0 | continue; |
404 | 0 | } |
405 | | |
406 | 0 | uncompressed = faccessat(dir_fd, p, F_OK, 0) >= 0; |
407 | 0 | if (uncompressed || faccessat(dir_fd, pz, F_OK, 0) >= 0) { |
408 | 0 | log_debug("Found converted keymap %s at %s/%s", n, *dir, uncompressed ? p : pz); |
409 | 0 | *ret = TAKE_PTR(n); |
410 | 0 | return 1; |
411 | 0 | } |
412 | 0 | } |
413 | | |
414 | 0 | *ret = NULL; |
415 | 0 | return 0; |
416 | 0 | } |
417 | | |
418 | 0 | int find_legacy_keymap(const X11Context *xc, char **ret) { |
419 | 0 | const char *map; |
420 | 0 | _cleanup_fclose_ FILE *f = NULL; |
421 | 0 | _cleanup_free_ char *new_keymap = NULL; |
422 | 0 | unsigned best_matching = 0; |
423 | 0 | int r; |
424 | |
|
425 | 0 | assert(xc); |
426 | 0 | assert(!isempty(xc->layout)); |
427 | |
|
428 | 0 | map = systemd_kbd_model_map(); |
429 | 0 | f = fopen(map, "re"); |
430 | 0 | if (!f) |
431 | 0 | return -errno; |
432 | | |
433 | 0 | for (unsigned n = 0;;) { |
434 | 0 | _cleanup_strv_free_ char **a = NULL; |
435 | 0 | unsigned matching = 0; |
436 | |
|
437 | 0 | r = read_next_mapping(map, 5, UINT_MAX, f, &n, &a); |
438 | 0 | if (r < 0) |
439 | 0 | return r; |
440 | 0 | if (r == 0) |
441 | 0 | break; |
442 | | |
443 | | /* Determine how well matching this entry is */ |
444 | 0 | if (streq(xc->layout, a[1])) |
445 | | /* If we got an exact match, this is the best */ |
446 | 0 | matching = 10; |
447 | 0 | else { |
448 | | /* see if we get an exact match with the order reversed */ |
449 | 0 | _cleanup_strv_free_ char **b = NULL; |
450 | 0 | _cleanup_free_ char *c = NULL; |
451 | 0 | r = strv_split_full(&b, a[1], ",", 0); |
452 | 0 | if (r < 0) |
453 | 0 | return r; |
454 | 0 | strv_reverse(b); |
455 | 0 | c = strv_join(b, ","); |
456 | 0 | if (!c) |
457 | 0 | return log_oom(); |
458 | 0 | if (streq(xc->layout, c)) |
459 | 0 | matching = 9; |
460 | 0 | else { |
461 | | /* We have multiple X layouts, look for an |
462 | | * entry that matches our key with everything |
463 | | * but the first layout stripped off. */ |
464 | 0 | if (startswith_comma(xc->layout, a[1])) |
465 | 0 | matching = 5; |
466 | 0 | else { |
467 | 0 | _cleanup_free_ char *x = NULL; |
468 | | |
469 | | /* If that didn't work, strip off the |
470 | | * other layouts from the entry, too */ |
471 | 0 | x = strdupcspn(a[1], ","); |
472 | 0 | if (!x) |
473 | 0 | return -ENOMEM; |
474 | 0 | if (startswith_comma(xc->layout, x)) |
475 | 0 | matching = 1; |
476 | 0 | } |
477 | 0 | } |
478 | 0 | } |
479 | | |
480 | 0 | if (matching > 0) { |
481 | 0 | if (isempty(xc->model) || streq_ptr(xc->model, a[2])) { |
482 | 0 | matching++; |
483 | |
|
484 | 0 | if (streq_ptr(xc->variant, a[3]) || ((isempty(xc->variant) || streq_skip_trailing_chars(xc->variant, "", ",")) && streq(a[3], "-"))) { |
485 | 0 | matching++; |
486 | |
|
487 | 0 | if (streq_ptr(xc->options, a[4])) |
488 | 0 | matching++; |
489 | 0 | } |
490 | 0 | } |
491 | 0 | } |
492 | | |
493 | | /* The best matching entry so far, then let's save that */ |
494 | 0 | if (matching >= MAX(best_matching, 1u)) { |
495 | 0 | log_debug("Found legacy keymap %s with score %u", a[0], matching); |
496 | |
|
497 | 0 | if (matching > best_matching) { |
498 | 0 | best_matching = matching; |
499 | |
|
500 | 0 | r = free_and_strdup(&new_keymap, a[0]); |
501 | 0 | if (r < 0) |
502 | 0 | return r; |
503 | 0 | } |
504 | 0 | } |
505 | 0 | } |
506 | | |
507 | 0 | if (best_matching < 9 && !isempty(xc->layout)) { |
508 | 0 | _cleanup_free_ char *l = NULL, *v = NULL, *converted = NULL; |
509 | | |
510 | | /* The best match is only the first part of the X11 |
511 | | * keymap. Check if we have a converted map which |
512 | | * matches just the first layout. |
513 | | */ |
514 | |
|
515 | 0 | l = strdupcspn(xc->layout, ","); |
516 | 0 | if (!l) |
517 | 0 | return -ENOMEM; |
518 | | |
519 | 0 | if (!isempty(xc->variant)) { |
520 | 0 | v = strdupcspn(xc->variant, ","); |
521 | 0 | if (!v) |
522 | 0 | return -ENOMEM; |
523 | 0 | } |
524 | | |
525 | 0 | r = find_converted_keymap( |
526 | 0 | &(X11Context) { |
527 | 0 | .layout = l, |
528 | 0 | .variant = v, |
529 | 0 | }, |
530 | 0 | &converted); |
531 | 0 | if (r < 0) |
532 | 0 | return r; |
533 | 0 | if (r > 0) |
534 | 0 | free_and_replace(new_keymap, converted); |
535 | 0 | } |
536 | | |
537 | 0 | *ret = TAKE_PTR(new_keymap); |
538 | 0 | return !!*ret; |
539 | 0 | } |
540 | | |
541 | 0 | int x11_convert_to_vconsole(const X11Context *xc, VCContext *ret) { |
542 | 0 | _cleanup_free_ char *keymap = NULL; |
543 | 0 | int r; |
544 | |
|
545 | 0 | assert(xc); |
546 | 0 | assert(ret); |
547 | |
|
548 | 0 | if (isempty(xc->layout)) { |
549 | 0 | *ret = (VCContext) {}; |
550 | 0 | return 0; |
551 | 0 | } |
552 | | |
553 | 0 | r = find_converted_keymap(xc, &keymap); |
554 | 0 | if (r == 0) { |
555 | 0 | r = find_legacy_keymap(xc, &keymap); |
556 | 0 | if (r == 0 && xc->variant) |
557 | | /* If we still haven't find a match, try with no variant, it's still better than |
558 | | * nothing. */ |
559 | 0 | r = find_converted_keymap( |
560 | 0 | &(X11Context) { |
561 | 0 | .layout = xc->layout, |
562 | 0 | }, |
563 | 0 | &keymap); |
564 | 0 | } |
565 | 0 | if (r < 0) |
566 | 0 | return r; |
567 | | |
568 | 0 | *ret = (VCContext) { |
569 | 0 | .keymap = TAKE_PTR(keymap), |
570 | 0 | }; |
571 | 0 | return 0; |
572 | 0 | } |
573 | | |
574 | 0 | int find_language_fallback(const char *lang, char **ret) { |
575 | 0 | const char *map; |
576 | 0 | _cleanup_fclose_ FILE *f = NULL; |
577 | 0 | unsigned n = 0; |
578 | 0 | int r; |
579 | |
|
580 | 0 | assert(lang); |
581 | 0 | assert(ret); |
582 | |
|
583 | 0 | map = systemd_language_fallback_map(); |
584 | 0 | f = fopen(map, "re"); |
585 | 0 | if (!f) |
586 | 0 | return -errno; |
587 | | |
588 | 0 | for (;;) { |
589 | 0 | _cleanup_strv_free_ char **a = NULL; |
590 | |
|
591 | 0 | r = read_next_mapping(map, 2, 2, f, &n, &a); |
592 | 0 | if (r <= 0) |
593 | 0 | return r; |
594 | | |
595 | 0 | if (streq(lang, a[0])) { |
596 | 0 | assert(strv_length(a) == 2); |
597 | 0 | *ret = TAKE_PTR(a[1]); |
598 | 0 | return 1; |
599 | 0 | } |
600 | 0 | } |
601 | 0 | } |
602 | | |
603 | 0 | int find_vconsole_keymap_for_bcp47(const char *tag, char **ret) { |
604 | 0 | _cleanup_fclose_ FILE *f = NULL; |
605 | 0 | _cleanup_free_ char *fallback = NULL; |
606 | 0 | const char *map; |
607 | 0 | int r; |
608 | | |
609 | | /* Look up a vconsole keymap by RFC 4646 / BCP 47 language tag (e.g. "de-DE") using the optional |
610 | | * sixth column of /usr/share/systemd/kbd-model-map. That column lists comma-separated tags the |
611 | | * row matches. An exact (case-insensitive) tag match returns immediately; if no exact match |
612 | | * exists, the first row whose tag matches the input's primary subtag wins. Returns 1 on match, |
613 | | * 0 otherwise. */ |
614 | |
|
615 | 0 | assert(tag); |
616 | 0 | assert(ret); |
617 | |
|
618 | 0 | if (isempty(tag)) { |
619 | 0 | *ret = NULL; |
620 | 0 | return 0; |
621 | 0 | } |
622 | | |
623 | 0 | size_t primary_len = strcspn(tag, "-"); |
624 | 0 | if (primary_len == 0) { |
625 | 0 | *ret = NULL; |
626 | 0 | return 0; |
627 | 0 | } |
628 | | |
629 | 0 | map = systemd_kbd_model_map(); |
630 | 0 | f = fopen(map, "re"); |
631 | 0 | if (!f) |
632 | 0 | return -errno; |
633 | | |
634 | 0 | for (unsigned n = 0;;) { |
635 | 0 | _cleanup_strv_free_ char **a = NULL, **tags = NULL; |
636 | |
|
637 | 0 | r = read_next_mapping(map, 5, UINT_MAX, f, &n, &a); |
638 | 0 | if (r < 0) |
639 | 0 | return r; |
640 | 0 | if (r == 0) |
641 | 0 | break; |
642 | | |
643 | | /* The BCP 47 tag list is the optional 6th column. "-" / empty means "no tags". */ |
644 | 0 | if (strv_length(a) < 6 || isempty(a[5]) || streq(a[5], "-")) |
645 | 0 | continue; |
646 | | |
647 | 0 | r = strv_split_full(&tags, a[5], ",", /* flags= */ 0); |
648 | 0 | if (r < 0) |
649 | 0 | return r; |
650 | | |
651 | 0 | STRV_FOREACH(t, tags) { |
652 | 0 | if (strcaseeq(*t, tag)) { |
653 | 0 | log_debug("Found vconsole keymap '%s' for BCP 47 tag '%s' (exact match).", |
654 | 0 | a[0], tag); |
655 | |
|
656 | 0 | r = strdup_to(ret, a[0]); |
657 | 0 | if (r < 0) |
658 | 0 | return r; |
659 | | |
660 | 0 | return 1; |
661 | 0 | } |
662 | 0 | if (!fallback && strlen(*t) == primary_len && !strchr(*t, '-') && strncaseeq(*t, tag, primary_len)) { |
663 | 0 | fallback = strdup(a[0]); |
664 | 0 | if (!fallback) |
665 | 0 | return -ENOMEM; |
666 | 0 | } |
667 | 0 | } |
668 | 0 | } |
669 | | |
670 | 0 | if (!fallback) { |
671 | 0 | *ret = NULL; |
672 | 0 | return 0; |
673 | 0 | } |
674 | | |
675 | 0 | log_debug("Found vconsole keymap '%s' for BCP 47 tag '%s' (primary subtag match).", fallback, tag); |
676 | 0 | *ret = TAKE_PTR(fallback); |
677 | 0 | return 1; |
678 | 0 | } |
679 | | |
680 | 0 | int vconsole_keymap_from_efi(char **ret) { |
681 | 0 | int r; |
682 | |
|
683 | 0 | assert(ret); |
684 | |
|
685 | 0 | if (!is_efi_boot()) { |
686 | 0 | *ret = NULL; |
687 | 0 | return 0; |
688 | 0 | } |
689 | | |
690 | 0 | _cleanup_free_ char *tag = NULL; |
691 | 0 | r = efi_get_variable_string(EFI_LOADER_VARIABLE_STR("LoaderKeyboardLayout"), &tag); |
692 | 0 | if (r == -ENOENT) { |
693 | 0 | *ret = NULL; |
694 | 0 | return 0; |
695 | 0 | } |
696 | 0 | if (r < 0) |
697 | 0 | return log_debug_errno(r, "Failed to read LoaderKeyboardLayout EFI variable: %m"); |
698 | | |
699 | 0 | r = find_vconsole_keymap_for_bcp47(tag, ret); |
700 | 0 | if (r < 0) |
701 | 0 | return log_debug_errno(r, "Failed to look up vconsole keymap for firmware tag '%s': %m", tag); |
702 | | |
703 | 0 | return r; |
704 | 0 | } |
705 | | |
706 | 0 | int vconsole_serialize(const VCContext *vc, const X11Context *xc, char ***env) { |
707 | 0 | int r; |
708 | | |
709 | | /* This function modifies the passed strv in place. */ |
710 | |
|
711 | 0 | assert(vc); |
712 | 0 | assert(xc); |
713 | 0 | assert(env); |
714 | |
|
715 | 0 | r = strv_env_assign(env, "KEYMAP", empty_to_null(vc->keymap)); |
716 | 0 | if (r < 0) |
717 | 0 | return r; |
718 | | |
719 | 0 | r = strv_env_assign(env, "KEYMAP_TOGGLE", empty_to_null(vc->toggle)); |
720 | 0 | if (r < 0) |
721 | 0 | return r; |
722 | | |
723 | 0 | r = strv_env_assign(env, "XKBLAYOUT", empty_to_null(xc->layout)); |
724 | 0 | if (r < 0) |
725 | 0 | return r; |
726 | | |
727 | 0 | r = strv_env_assign(env, "XKBMODEL", empty_to_null(xc->model)); |
728 | 0 | if (r < 0) |
729 | 0 | return r; |
730 | | |
731 | 0 | r = strv_env_assign(env, "XKBVARIANT", empty_to_null(xc->variant)); |
732 | 0 | if (r < 0) |
733 | 0 | return r; |
734 | | |
735 | 0 | r = strv_env_assign(env, "XKBOPTIONS", empty_to_null(xc->options)); |
736 | 0 | if (r < 0) |
737 | 0 | return r; |
738 | | |
739 | 0 | return 0; |
740 | 0 | } |