• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

systemd / systemd / 14895667988

07 May 2025 08:57PM UTC coverage: 72.225% (-0.007%) from 72.232%
14895667988

push

github

yuwata
network: log_link_message_debug_errno() automatically append %m if necessary

Follow-up for d28746ef5.
Fixes CID#1609753.

0 of 1 new or added line in 1 file covered. (0.0%)

20297 existing lines in 338 files now uncovered.

297407 of 411780 relevant lines covered (72.22%)

695716.85 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.92
/src/shared/vconsole-util.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include "alloc-util.h"
4
#include "env-util.h"
5
#include "extract-word.h"
6
#include "fd-util.h"
7
#include "fileio.h"
8
#include "kbd-util.h"
9
#include "log.h"
10
#include "string-util.h"
11
#include "strv.h"
12
#include "vconsole-util.h"
13

14
static bool startswith_comma(const char *s, const char *prefix) {
1,665✔
15
        assert(s);
1,665✔
16
        assert(prefix);
1,665✔
17

18
        s = startswith(s, prefix);
1,665✔
19
        if (!s)
1,665✔
20
                return false;
21

22
        return IN_SET(*s, ',', '\0');
6✔
23
}
24

25
static const char* systemd_kbd_model_map(void) {
269✔
26
        const char* s;
269✔
27

28
        s = getenv("SYSTEMD_KBD_MODEL_MAP");
269✔
29
        if (s)
269✔
30
                return s;
18✔
31

32
        return SYSTEMD_KBD_MODEL_MAP;
33
}
34

35
static const char* systemd_language_fallback_map(void) {
12✔
36
        const char* s;
12✔
37

38
        s = getenv("SYSTEMD_LANGUAGE_FALLBACK_MAP");
12✔
39
        if (s)
12✔
UNCOV
40
                return s;
×
41

42
        return SYSTEMD_LANGUAGE_FALLBACK_MAP;
43
}
44

45
void x11_context_clear(X11Context *xc) {
764✔
46
        assert(xc);
764✔
47

48
        xc->layout  = mfree(xc->layout);
764✔
49
        xc->options = mfree(xc->options);
764✔
50
        xc->model   = mfree(xc->model);
764✔
51
        xc->variant = mfree(xc->variant);
764✔
52
}
764✔
53

54
void x11_context_replace(X11Context *dest, X11Context *src) {
86✔
55
        assert(dest);
86✔
56
        assert(src);
86✔
57

58
        x11_context_clear(dest);
86✔
59
        *dest = TAKE_STRUCT(*src);
86✔
60
}
86✔
61

62
bool x11_context_isempty(const X11Context *xc) {
3,156✔
63
        assert(xc);
3,156✔
64

65
        return
3,156✔
66
                isempty(xc->layout)  &&
3,156✔
67
                isempty(xc->model)   &&
2,460✔
68
                isempty(xc->variant) &&
2,460✔
69
                isempty(xc->options);
2,460✔
70
}
71

UNCOV
72
void x11_context_empty_to_null(X11Context *xc) {
×
UNCOV
73
        assert(xc);
×
74

75
        /* Do not call x11_context_clear() for the passed object. */
76

77
        xc->layout  = empty_to_null(xc->layout);
×
78
        xc->model   = empty_to_null(xc->model);
×
79
        xc->variant = empty_to_null(xc->variant);
×
UNCOV
80
        xc->options = empty_to_null(xc->options);
×
UNCOV
81
}
×
82

83
bool x11_context_is_safe(const X11Context *xc) {
69✔
84
        assert(xc);
69✔
85

86
        return
69✔
87
                (!xc->layout  || string_is_safe(xc->layout))  &&
67✔
88
                (!xc->model   || string_is_safe(xc->model))   &&
69✔
89
                (!xc->variant || string_is_safe(xc->variant)) &&
138✔
90
                (!xc->options || string_is_safe(xc->options));
69✔
91
}
92

93
bool x11_context_equal(const X11Context *a, const X11Context *b) {
408✔
94
        assert(a);
408✔
95
        assert(b);
408✔
96

97
        return
408✔
98
                streq_ptr(a->layout,  b->layout)  &&
408✔
99
                streq_ptr(a->model,   b->model)   &&
330✔
100
                streq_ptr(a->variant, b->variant) &&
737✔
101
                streq_ptr(a->options, b->options);
322✔
102
}
103

104
int x11_context_copy(X11Context *dest, const X11Context *src) {
153✔
105
        bool modified;
153✔
106
        int r;
153✔
107

108
        assert(dest);
153✔
109

110
        if (dest == src)
153✔
111
                return 0;
112

113
        if (!src) {
153✔
114
                modified = !x11_context_isempty(dest);
×
UNCOV
115
                x11_context_clear(dest);
×
UNCOV
116
                return modified;
×
117
        }
118

119
        r = free_and_strdup(&dest->layout, src->layout);
153✔
120
        if (r < 0)
153✔
121
                return r;
122
        modified = r > 0;
153✔
123

124
        r = free_and_strdup(&dest->model, src->model);
153✔
125
        if (r < 0)
153✔
126
                return r;
127
        modified = modified || r > 0;
153✔
128

129
        r = free_and_strdup(&dest->variant, src->variant);
153✔
130
        if (r < 0)
153✔
131
                return r;
132
        modified = modified || r > 0;
153✔
133

134
        r = free_and_strdup(&dest->options, src->options);
153✔
135
        if (r < 0)
153✔
136
                return r;
137
        modified = modified || r > 0;
153✔
138

139
        return modified;
153✔
140
}
141

142
void vc_context_clear(VCContext *vc) {
16✔
143
        assert(vc);
16✔
144

145
        vc->keymap = mfree(vc->keymap);
16✔
146
        vc->toggle = mfree(vc->toggle);
16✔
147
}
16✔
148

149
void vc_context_replace(VCContext *dest, VCContext *src) {
×
UNCOV
150
        assert(dest);
×
151
        assert(src);
×
152

153
        vc_context_clear(dest);
×
UNCOV
154
        *dest = TAKE_STRUCT(*src);
×
UNCOV
155
}
×
156

157
bool vc_context_isempty(const VCContext *vc) {
2✔
158
        assert(vc);
2✔
159

160
        return
2✔
161
                isempty(vc->keymap) &&
2✔
162
                isempty(vc->toggle);
1✔
163
}
164

165
void vc_context_empty_to_null(VCContext *vc) {
247✔
166
        assert(vc);
247✔
167

168
        /* Do not call vc_context_clear() for the passed object. */
169

170
        vc->keymap = empty_to_null(vc->keymap);
247✔
171
        vc->toggle = empty_to_null(vc->toggle);
494✔
172
}
247✔
173

174
bool vc_context_equal(const VCContext *a, const VCContext *b) {
247✔
175
        assert(a);
247✔
176
        assert(b);
247✔
177

178
        return
247✔
179
                streq_ptr(a->keymap, b->keymap) &&
247✔
UNCOV
180
                streq_ptr(a->toggle, b->toggle);
×
181
}
182

183
int vc_context_copy(VCContext *dest, const VCContext *src) {
247✔
184
        bool modified;
247✔
185
        int r;
247✔
186

187
        assert(dest);
247✔
188

189
        if (dest == src)
247✔
190
                return 0;
191

192
        if (!src) {
247✔
193
                modified = !vc_context_isempty(dest);
×
UNCOV
194
                vc_context_clear(dest);
×
UNCOV
195
                return modified;
×
196
        }
197

198
        r = free_and_strdup(&dest->keymap, src->keymap);
247✔
199
        if (r < 0)
247✔
200
                return r;
201
        modified = r > 0;
247✔
202

203
        r = free_and_strdup(&dest->toggle, src->toggle);
247✔
204
        if (r < 0)
247✔
205
                return r;
206
        modified = modified || r > 0;
247✔
207

208
        return modified;
247✔
209
}
210

211
static int read_next_mapping(
17,086✔
212
                const char *filename,
213
                unsigned min_fields,
214
                unsigned max_fields,
215
                FILE *f,
216
                unsigned *n,
217
                char ***ret) {
218

219
        assert(f);
17,086✔
220
        assert(n);
17,086✔
221
        assert(ret);
17,086✔
222

223
        for (;;) {
538✔
UNCOV
224
                _cleanup_strv_free_ char **b = NULL;
×
225
                _cleanup_free_ char *line = NULL;
17,624✔
226
                size_t length;
17,624✔
227
                int r;
17,624✔
228

229
                r = read_stripped_line(f, LONG_LINE_MAX, &line);
17,624✔
230
                if (r < 0)
17,624✔
231
                        return r;
232
                if (r == 0)
17,624✔
233
                        break;
234

235
                (*n)++;
17,412✔
236

237
                if (IN_SET(line[0], 0, '#'))
17,412✔
238
                        continue;
538✔
239

240
                r = strv_split_full(&b, line, WHITESPACE, EXTRACT_UNQUOTE);
16,874✔
241
                if (r < 0)
16,874✔
242
                        return r;
243

244
                length = strv_length(b);
16,874✔
245
                if (length < min_fields || length > max_fields) {
16,874✔
UNCOV
246
                        log_debug("Invalid line %s:%u, ignoring.", strna(filename), *n);
×
UNCOV
247
                        continue;
×
248

249
                }
250

251
                *ret = TAKE_PTR(b);
16,874✔
252
                return 1;
16,874✔
253
        }
254

255
        *ret = NULL;
212✔
256
        return 0;
212✔
257
}
258

259
int vconsole_convert_to_x11(const VCContext *vc, X11VerifyCallback verify, X11Context *ret) {
258✔
260
        _cleanup_fclose_ FILE *f = NULL;
258✔
261
        const char *map;
258✔
262
        X11Context xc;
258✔
263
        int r;
258✔
264

265
        assert(vc);
258✔
266
        assert(ret);
258✔
267

268
        if (isempty(vc->keymap)) {
258✔
269
                *ret = (X11Context) {};
1✔
270
                return 0;
1✔
271
        }
272

273
        map = systemd_kbd_model_map();
257✔
274
        f = fopen(map, "re");
257✔
275
        if (!f)
257✔
UNCOV
276
                return -errno;
×
277

278
        for (unsigned n = 0;;) {
16,068✔
279
                _cleanup_strv_free_ char **a = NULL;
16,068✔
280

281
                r = read_next_mapping(map, 5, UINT_MAX, f, &n, &a);
16,068✔
282
                if (r < 0)
16,068✔
283
                        return r;
284
                if (r == 0)
16,068✔
285
                        break;
286

287
                if (!streq(vc->keymap, a[0]))
15,878✔
288
                        continue;
15,811✔
289

290
                xc = (X11Context) {
134✔
291
                        .layout  = empty_or_dash_to_null(a[1]),
67✔
292
                        .model   = empty_or_dash_to_null(a[2]),
67✔
293
                        .variant = empty_or_dash_to_null(a[3]),
117✔
294
                        .options = empty_or_dash_to_null(a[4]),
67✔
295
                };
296

297
                if (verify && verify(&xc) < 0)
67✔
UNCOV
298
                        continue;
×
299

300
                return x11_context_copy(ret, &xc);
67✔
301
        }
302

303
        /* No custom mapping has been found, see if the keymap is a converted one. In such case deducing the
304
         * corresponding x11 layout is easy. */
305
        _cleanup_free_ char *xlayout = NULL, *converted = NULL;
190✔
306
        char *xvariant;
190✔
307

308
        xlayout = strdup(vc->keymap);
190✔
309
        if (!xlayout)
190✔
310
                return -ENOMEM;
311
        xvariant = strchr(xlayout, '-');
190✔
312
        if (xvariant) {
190✔
313
                xvariant[0] = '\0';
135✔
314
                xvariant++;
135✔
315
        }
316

317
        /* Note: by default we use keyboard model "microsoftpro" which should be equivalent to "pc105" but
318
         * with the internet/media key mapping added. */
319
        xc = (X11Context) {
190✔
320
                .layout  = xlayout,
321
                .model   = (char*) "microsoftpro",
322
                .variant = xvariant,
323
                .options = (char*) "terminate:ctrl_alt_bksp",
324
        };
325

326
        /* This sanity check seems redundant with the verification of the X11 layout done on the next
327
         * step. However xkbcommon is an optional dependency hence the verification might be a NOP.  */
328
        r = find_converted_keymap(&xc, &converted);
190✔
329
        if (r == 0 && xc.variant) {
190✔
330
                /* If we still haven't find a match, try with no variant, it's still better than nothing.  */
331
                xc.variant = NULL;
135✔
332
                r = find_converted_keymap(&xc, &converted);
135✔
333
        }
334
        if (r < 0)
190✔
335
                return r;
336

337
        if (r == 0 || (verify && verify(&xc) < 0)) {
190✔
338
                *ret = (X11Context) {};
190✔
339
                return 0;
190✔
340
        }
341

UNCOV
342
        return x11_context_copy(ret, &xc);
×
343
}
344

345
int find_converted_keymap(const X11Context *xc, char **ret) {
339✔
346
        _cleanup_free_ char *n = NULL, *p = NULL, *pz = NULL;
339✔
UNCOV
347
        _cleanup_strv_free_ char **keymap_dirs = NULL;
×
348
        int r;
339✔
349

350
        assert(xc);
339✔
351
        assert(!isempty(xc->layout));
339✔
352
        assert(ret);
339✔
353

354
        if (xc->variant)
339✔
355
                n = strjoin(xc->layout, "-", xc->variant);
140✔
356
        else
357
                n = strdup(xc->layout);
199✔
358
        if (!n)
339✔
359
                return -ENOMEM;
360

361
        p = strjoin("xkb/", n, ".map");
339✔
362
        pz = strjoin("xkb/", n, ".map.gz");
339✔
363
        if (!p || !pz)
339✔
364
                return -ENOMEM;
365

366
        r = keymap_directories(&keymap_dirs);
339✔
367
        if (r < 0)
339✔
368
                return r;
369

370
        STRV_FOREACH(dir, keymap_dirs) {
1,356✔
371
                _cleanup_close_ int dir_fd = -EBADF;
1,356✔
372
                bool uncompressed;
1,017✔
373

374
                dir_fd = open(*dir, O_CLOEXEC | O_DIRECTORY | O_PATH);
1,017✔
375
                if (dir_fd < 0) {
1,017✔
376
                        if (errno != ENOENT)
678✔
UNCOV
377
                                log_debug_errno(errno, "Failed to open %s, ignoring: %m", *dir);
×
378
                        continue;
678✔
379
                }
380

381
                uncompressed = faccessat(dir_fd, p, F_OK, 0) >= 0;
339✔
382
                if (uncompressed || faccessat(dir_fd, pz, F_OK, 0) >= 0) {
339✔
383
                        log_debug("Found converted keymap %s at %s/%s", n, *dir, uncompressed ? p : pz);
×
UNCOV
384
                        *ret = TAKE_PTR(n);
×
UNCOV
385
                        return 1;
×
386
                }
387
        }
388

389
        *ret = NULL;
339✔
390
        return 0;
339✔
391
}
392

393
int find_legacy_keymap(const X11Context *xc, char **ret) {
12✔
394
        const char *map;
12✔
395
        _cleanup_fclose_ FILE *f = NULL;
12✔
396
        _cleanup_free_ char *new_keymap = NULL;
12✔
397
        unsigned best_matching = 0;
12✔
398
        int r;
12✔
399

400
        assert(xc);
12✔
401
        assert(!isempty(xc->layout));
12✔
402

403
        map = systemd_kbd_model_map();
12✔
404
        f = fopen(map, "re");
12✔
405
        if (!f)
12✔
UNCOV
406
                return -errno;
×
407

408
        for (unsigned n = 0;;) {
864✔
409
                _cleanup_strv_free_ char **a = NULL;
12✔
410
                unsigned matching = 0;
864✔
411

412
                r = read_next_mapping(map, 5, UINT_MAX, f, &n, &a);
864✔
413
                if (r < 0)
864✔
414
                        return r;
415
                if (r == 0)
864✔
416
                        break;
417

418
                /* Determine how well matching this entry is */
419
                if (streq(xc->layout, a[1]))
852✔
420
                        /* If we got an exact match, this is the best */
421
                        matching = 10;
422
                else {
423
                        /* see if we get an exact match with the order reversed */
UNCOV
424
                        _cleanup_strv_free_ char **b = NULL;
×
425
                        _cleanup_free_ char *c = NULL;
836✔
426
                        r = strv_split_full(&b, a[1], ",", 0);
836✔
427
                        if (r < 0)
836✔
428
                                return r;
429
                        strv_reverse(b);
836✔
430
                        c = strv_join(b, ",");
836✔
431
                        if (!c)
836✔
UNCOV
432
                                return log_oom();
×
433
                        if (streq(xc->layout, c))
836✔
434
                                matching = 9;
435
                        else {
436
                                /* We have multiple X layouts, look for an
437
                                 * entry that matches our key with everything
438
                                 * but the first layout stripped off. */
439
                                if (startswith_comma(xc->layout, a[1]))
835✔
440
                                        matching = 5;
441
                                else {
442
                                        _cleanup_free_ char *x = NULL;
830✔
443

444
                                        /* If that didn't work, strip off the
445
                                         * other layouts from the entry, too */
446
                                        x = strdupcspn(a[1], ",");
830✔
447
                                        if (!x)
830✔
UNCOV
448
                                                return -ENOMEM;
×
449
                                        if (startswith_comma(xc->layout, x))
830✔
450
                                                matching = 1;
1✔
451
                                }
452
                        }
453
                }
454

455
                if (matching > 0) {
836✔
456
                        if (isempty(xc->model) || streq_ptr(xc->model, a[2])) {
23✔
457
                                matching++;
23✔
458

459
                                if (streq_ptr(xc->variant, a[3]) || ((isempty(xc->variant) || streq_skip_trailing_chars(xc->variant, "", ",")) && streq(a[3], "-"))) {
31✔
460
                                        matching++;
11✔
461

462
                                        if (streq_ptr(xc->options, a[4]))
11✔
UNCOV
463
                                                matching++;
×
464
                                }
465
                        }
466
                }
467

468
                /* The best matching entry so far, then let's save that */
469
                if (matching >= MAX(best_matching, 1u)) {
871✔
470
                        log_debug("Found legacy keymap %s with score %u", a[0], matching);
19✔
471

472
                        if (matching > best_matching) {
19✔
473
                                best_matching = matching;
16✔
474

475
                                r = free_and_strdup(&new_keymap, a[0]);
16✔
476
                                if (r < 0)
16✔
477
                                        return r;
478
                        }
479
                }
480
        }
481

482
        if (best_matching < 9 && !isempty(xc->layout)) {
12✔
483
                _cleanup_free_ char *l = NULL, *v = NULL, *converted = NULL;
3✔
484

485
                /* The best match is only the first part of the X11
486
                 * keymap. Check if we have a converted map which
487
                 * matches just the first layout.
488
                 */
489

490
                l = strdupcspn(xc->layout, ",");
3✔
491
                if (!l)
3✔
492
                        return -ENOMEM;
493

494
                if (!isempty(xc->variant)) {
3✔
UNCOV
495
                        v = strdupcspn(xc->variant, ",");
×
UNCOV
496
                        if (!v)
×
497
                                return -ENOMEM;
498
                }
499

500
                r = find_converted_keymap(
6✔
501
                                &(X11Context) {
3✔
502
                                        .layout = l,
503
                                        .variant = v,
504
                                },
505
                                &converted);
506
                if (r < 0)
3✔
507
                        return r;
508
                if (r > 0)
3✔
UNCOV
509
                        free_and_replace(new_keymap, converted);
×
510
        }
511

512
        *ret = TAKE_PTR(new_keymap);
12✔
513
        return !!*ret;
12✔
514
}
515

516
int x11_convert_to_vconsole(const X11Context *xc, VCContext *ret) {
10✔
517
        _cleanup_free_ char *keymap = NULL;
10✔
518
        int r;
10✔
519

520
        assert(xc);
10✔
521
        assert(ret);
10✔
522

523
        if (isempty(xc->layout)) {
10✔
524
                *ret = (VCContext) {};
1✔
525
                return 0;
1✔
526
        }
527

528
        r = find_converted_keymap(xc, &keymap);
9✔
529
        if (r == 0) {
9✔
530
                r = find_legacy_keymap(xc, &keymap);
9✔
531
                if (r == 0 && xc->variant)
9✔
532
                        /* If we still haven't find a match, try with no variant, it's still better than
533
                         * nothing.  */
534
                        r = find_converted_keymap(
×
UNCOV
535
                                        &(X11Context) {
×
UNCOV
536
                                                .layout = xc->layout,
×
537
                                        },
538
                                        &keymap);
539
        }
540
        if (r < 0)
9✔
541
                return r;
542

543
        *ret = (VCContext) {
9✔
544
                .keymap = TAKE_PTR(keymap),
9✔
545
        };
546
        return 0;
9✔
547
}
548

549
int find_language_fallback(const char *lang, char **ret) {
12✔
550
        const char *map;
12✔
551
        _cleanup_fclose_ FILE *f = NULL;
12✔
552
        unsigned n = 0;
12✔
553
        int r;
12✔
554

555
        assert(lang);
12✔
556
        assert(ret);
12✔
557

558
        map = systemd_language_fallback_map();
12✔
559
        f = fopen(map, "re");
12✔
560
        if (!f)
12✔
UNCOV
561
                return -errno;
×
562

563
        for (;;) {
142✔
564
                _cleanup_strv_free_ char **a = NULL;
154✔
565

566
                r = read_next_mapping(map, 2, 2, f, &n, &a);
154✔
567
                if (r <= 0)
154✔
568
                        return r;
569

570
                if (streq(lang, a[0])) {
144✔
571
                        assert(strv_length(a) == 2);
2✔
572
                        *ret = TAKE_PTR(a[1]);
2✔
573
                        return 1;
2✔
574
                }
575
        }
576
}
577

578
int vconsole_serialize(const VCContext *vc, const X11Context *xc, char ***env) {
251✔
579
        int r;
251✔
580

581
        /* This function modifies the passed strv in place. */
582

583
        assert(vc);
251✔
584
        assert(xc);
251✔
585
        assert(env);
251✔
586

587
        r = strv_env_assign(env, "KEYMAP", empty_to_null(vc->keymap));
502✔
588
        if (r < 0)
251✔
589
                return r;
590

591
        r = strv_env_assign(env, "KEYMAP_TOGGLE", empty_to_null(vc->toggle));
251✔
592
        if (r < 0)
251✔
593
                return r;
594

595
        r = strv_env_assign(env, "XKBLAYOUT", empty_to_null(xc->layout));
313✔
596
        if (r < 0)
251✔
597
                return r;
598

599
        r = strv_env_assign(env, "XKBMODEL", empty_to_null(xc->model));
313✔
600
        if (r < 0)
251✔
601
                return r;
602

603
        r = strv_env_assign(env, "XKBVARIANT", empty_to_null(xc->variant));
266✔
604
        if (r < 0)
251✔
605
                return r;
606

607
        r = strv_env_assign(env, "XKBOPTIONS", empty_to_null(xc->options));
313✔
608
        if (r < 0)
251✔
UNCOV
609
                return r;
×
610

611
        return 0;
612
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc