Coverage Report

Created: 2026-07-30 06:11

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}