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

systemd / systemd / 26546993077

27 May 2026 08:34PM UTC coverage: 72.995% (+0.3%) from 72.667%
26546993077

push

github

bluca
test-pressure: set timeout to make not wait forever

If this runs on a slow or busy machine, then we may not get enough
pressure to trigger the event sources. In such case, the test does not
finish. It is problematic when the test is _not_ run with 'meson test',
e.g. debian/ubuntu CIs.

Let's introduce a timeout for each event loop, and skip test cases
gracefully.

8 of 12 new or added lines in 1 file covered. (66.67%)

19671 existing lines in 226 files now uncovered.

337119 of 461841 relevant lines covered (72.99%)

1326365.62 hits per line

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

78.12
/src/shared/bootspec.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include <fnmatch.h>
4
#include <unistd.h>
5

6
#include "sd-json.h"
7

8
#include "alloc-util.h"
9
#include "bootspec.h"
10
#include "chase.h"
11
#include "devnum-util.h"
12
#include "dirent-util.h"
13
#include "efi-loader.h"
14
#include "efivars.h"
15
#include "env-file.h"
16
#include "errno-util.h"
17
#include "extract-word.h"
18
#include "fd-util.h"
19
#include "fileio.h"
20
#include "find-esp.h"
21
#include "json-util.h"
22
#include "log.h"
23
#include "parse-util.h"
24
#include "path-util.h"
25
#include "pe-binary.h"
26
#include "pretty-print.h"
27
#include "recurse-dir.h"
28
#include "set.h"
29
#include "sort-util.h"
30
#include "stat-util.h"
31
#include "string-table.h"
32
#include "string-util.h"
33
#include "strv.h"
34
#include "uki.h"
35
#include "utf8.h"
36

37
static const char* const boot_entry_type_description_table[_BOOT_ENTRY_TYPE_MAX] = {
38
        [BOOT_ENTRY_TYPE1]  = "Boot Loader Specification Type #1 (.conf)",
39
        [BOOT_ENTRY_TYPE2]  = "Boot Loader Specification Type #2 (UKI, .efi)",
40
        [BOOT_ENTRY_LOADER] = "Reported by Boot Loader",
41
        [BOOT_ENTRY_AUTO]   = "Automatic",
42
};
43

44
DEFINE_STRING_TABLE_LOOKUP_TO_STRING(boot_entry_type_description, BootEntryType);
22✔
45

46
static const char* const boot_entry_type_table[_BOOT_ENTRY_TYPE_MAX] = {
47
        [BOOT_ENTRY_TYPE1]  = "type1",
48
        [BOOT_ENTRY_TYPE2]  = "type2",
49
        [BOOT_ENTRY_LOADER] = "loader",
50
        [BOOT_ENTRY_AUTO]   = "auto",
51
};
52

53
DEFINE_STRING_TABLE_LOOKUP(boot_entry_type, BootEntryType);
27✔
54

55
static const char* const boot_entry_source_description_table[_BOOT_ENTRY_SOURCE_MAX] = {
56
        [BOOT_ENTRY_ESP]      = "EFI System Partition",
57
        [BOOT_ENTRY_XBOOTLDR] = "Extended Boot Loader Partition",
58
};
59

60
DEFINE_STRING_TABLE_LOOKUP_TO_STRING(boot_entry_source_description, BootEntrySource);
22✔
61

62
static const char* const boot_entry_source_table[_BOOT_ENTRY_SOURCE_MAX] = {
63
        [BOOT_ENTRY_ESP]      = "esp",
64
        [BOOT_ENTRY_XBOOTLDR] = "xbootldr",
65
};
66

67
DEFINE_STRING_TABLE_LOOKUP_TO_STRING(boot_entry_source, BootEntrySource);
21✔
68

69
static BootEntryExtraType boot_entry_extra_type_from_filename(const char *path) {
15✔
70
        if (!path)
15✔
71
                return _BOOT_ENTRY_EXTRA_TYPE_INVALID;
72

73
        if (endswith_no_case(path, ".addon.efi"))
15✔
74
                return BOOT_ENTRY_ADDON;
75
        if (endswith_no_case(path, ".confext.raw"))
3✔
76
                return BOOT_ENTRY_CONFEXT;
77
        if (endswith_no_case(path, ".sysext.raw"))
2✔
78
                return BOOT_ENTRY_SYSEXT;
79
        if (endswith_no_case(path, ".cred"))
1✔
80
                return BOOT_ENTRY_CREDENTIAL;
1✔
81

82
        return _BOOT_ENTRY_EXTRA_TYPE_INVALID;
83
}
84

85
static void boot_entry_extras_done(BootEntryExtras *extras) {
302✔
86
        assert(extras);
302✔
87

88
        FOREACH_ARRAY(extra, extras->items, extras->n_items) {
317✔
89
                free(extra->location);
15✔
90
                free(extra->cmdline);
15✔
91
        }
92
        extras->items = mfree(extras->items);
302✔
93
        extras->n_items = 0;
302✔
94
}
302✔
95

96
static int boot_entry_extras_add(
15✔
97
                BootEntryExtras *extras,
98
                BootEntryExtraType type,
99
                const char *path,
100
                const char *cmdline) {
101

102
        assert(extras);
15✔
103
        assert(type >= 0);
15✔
104
        assert(type < _BOOT_ENTRY_EXTRA_TYPE_MAX);
15✔
105
        assert(path);
15✔
106

107
        _cleanup_free_ char *p = strdup(path);
15✔
108
        if (!p)
15✔
109
                return -ENOMEM;
110

111
        _cleanup_free_ char *c = NULL;
15✔
112
        if (cmdline) {
15✔
113
                c = strdup(cmdline);
12✔
114
                if (!c)
12✔
115
                        return -ENOMEM;
116
        }
117

118
        if (!GREEDY_REALLOC(extras->items, extras->n_items + 1))
15✔
119
                return -ENOMEM;
120

121
        extras->items[extras->n_items++] = (BootEntryExtra) {
15✔
122
                .type = type,
123
                .location = TAKE_PTR(p),
15✔
124
                .cmdline = TAKE_PTR(c),
15✔
125
        };
126

127
        return 0;
15✔
128
}
129

130
static void boot_entry_free(BootEntry *entry) {
208✔
131
        assert(entry);
208✔
132

133
        free(entry->id);
208✔
134
        free(entry->id_old);
208✔
135
        free(entry->id_without_profile);
208✔
136
        free(entry->path);
208✔
137
        free(entry->root);
208✔
138
        free(entry->title);
208✔
139
        free(entry->show_title);
208✔
140
        free(entry->sort_key);
208✔
141
        free(entry->version);
208✔
142
        free(entry->machine_id);
208✔
143
        free(entry->architecture);
208✔
144
        strv_free(entry->options);
208✔
145
        boot_entry_extras_done(&entry->local_extras);
208✔
146
        free(entry->kernel);
208✔
147
        free(entry->efi);
208✔
148
        free(entry->uki);
208✔
149
        free(entry->uki_url);
208✔
150
        strv_free(entry->initrd);
208✔
151
        free(entry->device_tree);
208✔
152
        strv_free(entry->device_tree_overlay);
208✔
153
}
208✔
154

155
static int mangle_path(
15✔
156
                const char *fname,
157
                unsigned line,
158
                const char *field,
159
                const char *p,
160
                char **ret) {
161

162
        _cleanup_free_ char *c = NULL;
15✔
163

164
        assert(field);
15✔
165
        assert(p);
15✔
166
        assert(ret);
15✔
167

168
        /* Spec leaves open if prefixed with "/" or not, let's normalize that */
169
        c = path_make_absolute(p, "/");
15✔
170
        if (!c)
15✔
171
                return -ENOMEM;
172

173
        /* We only reference files, never directories */
174
        if (endswith(c, "/")) {
15✔
UNCOV
175
                log_syntax(NULL, LOG_WARNING, fname, line, 0, "Path in field '%s' has trailing slash, ignoring: %s", field, c);
×
176
                *ret = NULL;
×
177
                return 0;
×
178
        }
179

180
        /* Remove duplicate "/" */
181
        path_simplify(c);
15✔
182

183
        /* No ".." or "." or so */
184
        if (!path_is_normalized(c)) {
15✔
UNCOV
185
                log_syntax(NULL, LOG_WARNING, fname, line, 0, "Path in field '%s' is not normalized, ignoring: %s", field, c);
×
186
                *ret = NULL;
×
187
                return 0;
×
188
        }
189

190
        *ret = TAKE_PTR(c);
15✔
191
        return 1;
15✔
192
}
193

194
static int parse_path_one(
12✔
195
                const char *fname,
196
                unsigned line,
197
                const char *field,
198
                char **s,
199
                const char *p) {
200

201
        _cleanup_free_ char *c = NULL;
12✔
202
        int r;
12✔
203

204
        assert(field);
12✔
205
        assert(s);
12✔
206
        assert(p);
12✔
207

208
        r = mangle_path(fname, line, field, p, &c);
12✔
209
        if (r <= 0)
12✔
210
                return r;
211

212
        return free_and_replace(*s, c);
12✔
213
}
214

UNCOV
215
static int parse_path_strv(
×
216
                const char *fname,
217
                unsigned line,
218
                const char *field,
219
                char ***s,
220
                const char *p) {
221

UNCOV
222
        char *c;
×
223
        int r;
×
224

UNCOV
225
        assert(field);
×
226
        assert(s);
×
227
        assert(p);
×
228

UNCOV
229
        r = mangle_path(fname, line, field, p, &c);
×
230
        if (r <= 0)
×
231
                return r;
×
232

UNCOV
233
        return strv_consume(s, c);
×
234
}
235

UNCOV
236
static int parse_path_many(
×
237
                const char *fname,
238
                unsigned line,
239
                const char *field,
240
                char ***s,
241
                const char *p) {
242

UNCOV
243
        _cleanup_strv_free_ char **l = NULL, **f = NULL;
×
244
        int r;
×
245

UNCOV
246
        l = strv_split(p, NULL);
×
247
        if (!l)
×
248
                return -ENOMEM;
249

UNCOV
250
        STRV_FOREACH(i, l) {
×
251
                char *c;
×
252

UNCOV
253
                r = mangle_path(fname, line, field, *i, &c);
×
254
                if (r < 0)
×
255
                        return r;
×
256
                if (r == 0)
×
257
                        continue;
×
258

UNCOV
259
                r = strv_consume(&f, c);
×
260
                if (r < 0)
×
261
                        return r;
262
        }
263

UNCOV
264
        return strv_extend_strv_consume(s, TAKE_PTR(f), /* filter_duplicates= */ false);
×
265
}
266

267
static int parse_extra(
3✔
268
                const char *fname,
269
                unsigned line,
270
                const char *field,
271
                BootEntryExtras *extras,
272
                const char *p) {
273

274
        int r;
3✔
275

276
        assert(extras);
3✔
277

278
        _cleanup_strv_free_ char **l = strv_split(p, NULL);
6✔
279
        if (!l)
3✔
280
                return -ENOMEM;
281

282
        STRV_FOREACH(i, l) {
6✔
283
                _cleanup_free_ char *c = NULL;
3✔
284
                r = mangle_path(fname, line, field, *i, &c);
3✔
285
                if (r < 0)
3✔
286
                        return r;
287
                if (r == 0)
3✔
UNCOV
288
                        continue;
×
289

290
                BootEntryExtraType type = boot_entry_extra_type_from_filename(c);
3✔
291
                if (type < 0) {
3✔
UNCOV
292
                        log_debug_errno(type, "Failed to determine boot entry extra type of '%s', skipping: %m", c);
×
293
                        continue;
×
294
                }
295

296
                /* Let's filter out EFI addons for now. We have no protocol for passing them from sd-boot to
297
                 * sd-stub, hence supporting them would require major plumbing first. */
298
                if (type == BOOT_ENTRY_ADDON) {
3✔
UNCOV
299
                        log_debug("EFI addons are currently not supported for Type #1 entries, skipping '%s'.", c);
×
300
                        continue;
×
301
                }
302

303
                r = boot_entry_extras_add(extras, type, c, /* cmdline= */ NULL);
3✔
304
                if (r < 0)
3✔
305
                        return r;
306
        }
307

308
        return 0;
309
}
310

311
static int parse_tries(const char *fname, const char **p, unsigned *ret) {
41✔
312
        _cleanup_free_ char *d = NULL;
41✔
313
        unsigned tries;
41✔
314
        size_t n;
41✔
315
        int r;
41✔
316

317
        assert(fname);
41✔
318
        assert(p);
41✔
319
        assert(*p);
41✔
320
        assert(ret);
41✔
321

322
        n = strspn(*p, DIGITS);
41✔
323
        if (n == 0) {
41✔
324
                *ret = UINT_MAX;
2✔
325
                return 0;
2✔
326
        }
327

328
        d = strndup(*p, n);
39✔
329
        if (!d)
39✔
330
                return -ENOMEM;
331

332
        r = safe_atou_full(d, 10, &tries);
39✔
333
        if (r < 0)
39✔
334
                return r;
335
        if (tries > INT_MAX) /* sd-boot allows INT_MAX, let's use the same limit */
39✔
336
                return -ERANGE;
337

338
        *p = *p + n;
37✔
339
        *ret = tries;
37✔
340
        return 1;
37✔
341
}
342

343
int boot_filename_extract_tries(
114✔
344
                const char *fname,
345
                char **ret_stripped,
346
                unsigned *ret_tries_left,
347
                unsigned *ret_tries_done) {
348

349
        unsigned tries_left = UINT_MAX, tries_done = UINT_MAX;
114✔
350
        _cleanup_free_ char *stripped = NULL;
114✔
351
        const char *p, *suffix, *m;
114✔
352
        int r;
114✔
353

354
        assert(fname);
114✔
355
        assert(ret_stripped);
114✔
356

357
        /* Be liberal with suffix, only insist on a dot. After all we want to cover any capitalization here
358
         * (vfat is case insensitive after all), and at least .efi and .conf as suffix. */
359
        suffix = strrchr(fname, '.');
114✔
360
        if (!suffix)
114✔
361
                goto nothing;
1✔
362

363
        p = m = memrchr(fname, '+', suffix - fname);
113✔
364
        if (!p)
113✔
365
                goto nothing;
85✔
366
        p++;
28✔
367

368
        r = parse_tries(fname, &p, &tries_left);
28✔
369
        if (r < 0)
28✔
370
                return r;
371
        if (r == 0)
27✔
372
                goto nothing;
2✔
373

374
        if (*p == '-') {
25✔
375
                p++;
13✔
376

377
                r = parse_tries(fname, &p, &tries_done);
13✔
378
                if (r < 0)
13✔
379
                        return r;
380
                if (r == 0)
12✔
UNCOV
381
                        goto nothing;
×
382
        }
383

384
        if (p != suffix)
24✔
385
                goto nothing;
3✔
386

387
        stripped = strndup(fname, m - fname);
21✔
388
        if (!stripped)
21✔
389
                return -ENOMEM;
390

391
        if (!strextend(&stripped, suffix))
21✔
392
                return -ENOMEM;
393

394
        *ret_stripped = TAKE_PTR(stripped);
21✔
395
        if (ret_tries_left)
21✔
396
                *ret_tries_left = tries_left;
17✔
397
        if (ret_tries_done)
21✔
398
                *ret_tries_done = tries_done;
17✔
399

400
        return 0;
401

402
nothing:
91✔
403
        stripped = strdup(fname);
91✔
404
        if (!stripped)
91✔
405
                return -ENOMEM;
406

407
        *ret_stripped = TAKE_PTR(stripped);
91✔
408
        if (ret_tries_left)
91✔
409
                *ret_tries_left = UINT_MAX;
66✔
410
        if (ret_tries_done)
91✔
411
                *ret_tries_done = UINT_MAX;
66✔
412
        return 0;
413
}
414

415
static int boot_entry_load_type1(
20✔
416
                FILE *f,
417
                const char *root,
418
                const BootEntrySource source,
419
                const char *dir,
420
                const char *fname,
421
                BootEntry *ret) {
422

423
        _cleanup_(boot_entry_free) BootEntry tmp = BOOT_ENTRY_INIT(BOOT_ENTRY_TYPE1, source);
20✔
424
        char *c;
20✔
425
        int r;
20✔
426

427
        assert(f);
20✔
428
        assert(root);
20✔
429
        assert(dir);
20✔
430
        assert(fname);
20✔
431
        assert(ret);
20✔
432

433
        /* Loads a Type #1 boot menu entry from the specified FILE* object */
434

435
        r = boot_filename_extract_tries(fname, &tmp.id, &tmp.tries_left, &tmp.tries_done);
20✔
436
        if (r < 0)
20✔
UNCOV
437
                return log_error_errno(r, "Failed to extract tries counters from '%s': %m", fname);
×
438

439
        if (!efi_loader_entry_name_valid(tmp.id))
20✔
UNCOV
440
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry name: %s", fname);
×
441

442
        c = endswith_no_case(tmp.id, ".conf");
20✔
443
        if (!c)
20✔
UNCOV
444
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry file suffix: %s", fname);
×
445

446
        tmp.id_old = strndup(tmp.id, c - tmp.id); /* Without .conf suffix */
20✔
447
        if (!tmp.id_old)
20✔
UNCOV
448
                return log_oom();
×
449

450
        tmp.path = path_join(dir, fname);
20✔
451
        if (!tmp.path)
20✔
UNCOV
452
                return log_oom();
×
453

454
        tmp.root = strdup(root);
20✔
455
        if (!tmp.root)
20✔
UNCOV
456
                return log_oom();
×
457

458
        for (unsigned line = 1;; line++) {
90✔
459
                _cleanup_free_ char *buf = NULL, *field = NULL;
90✔
460

461
                r = read_stripped_line(f, LONG_LINE_MAX, &buf);
110✔
462
                if (r == -ENOBUFS)
110✔
UNCOV
463
                        return log_syntax(NULL, LOG_ERR, tmp.path, line, r, "Line too long.");
×
464
                if (r < 0)
110✔
UNCOV
465
                        return log_syntax(NULL, LOG_ERR, tmp.path, line, r, "Error while reading: %m");
×
466
                if (r == 0)
110✔
467
                        break;
468

469
                if (IN_SET(buf[0], '#', '\0'))
90✔
UNCOV
470
                        continue;
×
471

472
                const char *p = buf;
90✔
473
                r = extract_first_word(&p, &field, NULL, 0);
90✔
474
                if (r < 0) {
90✔
UNCOV
475
                        log_syntax(NULL, LOG_WARNING, tmp.path, line, r, "Failed to parse, ignoring line: %m");
×
476
                        continue;
×
477
                }
478
                if (r == 0) {
90✔
UNCOV
479
                        log_syntax(NULL, LOG_WARNING, tmp.path, line, 0, "Bad syntax, ignoring line.");
×
480
                        continue;
×
481
                }
482

483
                if (isempty(p)) {
90✔
484
                        /* Some fields can reasonably have an empty value. In other cases warn. */
UNCOV
485
                        if (!STR_IN_SET(field, "options", "devicetree-overlay"))
×
486
                                log_syntax(NULL, LOG_WARNING, tmp.path, line, 0, "Field '%s' without value, ignoring line.", field);
×
487

UNCOV
488
                        continue;
×
489
                }
490

491
                if (streq(field, "title"))
90✔
492
                        r = free_and_strdup(&tmp.title, p);
20✔
493
                else if (streq(field, "sort-key"))
70✔
494
                        r = free_and_strdup(&tmp.sort_key, p);
15✔
495
                else if (streq(field, "version"))
55✔
496
                        r = free_and_strdup(&tmp.version, p);
20✔
497
                else if (streq(field, "machine-id"))
35✔
498
                        r = free_and_strdup(&tmp.machine_id, p);
20✔
499
                else if (streq(field, "architecture"))
15✔
UNCOV
500
                        r = free_and_strdup(&tmp.architecture, p);
×
501
                else if (streq(field, "options"))
15✔
UNCOV
502
                        r = strv_extend(&tmp.options, p);
×
503
                else if (streq(field, "linux"))
15✔
UNCOV
504
                        r = parse_path_one(tmp.path, line, field, &tmp.kernel, p);
×
505
                else if (streq(field, "efi"))
15✔
UNCOV
506
                        r = parse_path_one(tmp.path, line, field, &tmp.efi, p);
×
507
                else if (streq(field, "uki"))
15✔
508
                        r = parse_path_one(tmp.path, line, field, &tmp.uki, p);
12✔
509
                else if (streq(field, "uki-url"))
3✔
UNCOV
510
                        r = free_and_strdup(&tmp.uki_url, p);
×
511
                else if (streq(field, "profile"))
3✔
UNCOV
512
                        r = safe_atou_full(p, 10, &tmp.profile);
×
513
                else if (streq(field, "initrd"))
3✔
UNCOV
514
                        r = parse_path_strv(tmp.path, line, field, &tmp.initrd, p);
×
515
                else if (streq(field, "devicetree"))
3✔
UNCOV
516
                        r = parse_path_one(tmp.path, line, field, &tmp.device_tree, p);
×
517
                else if (streq(field, "devicetree-overlay"))
3✔
UNCOV
518
                        r = parse_path_many(tmp.path, line, field, &tmp.device_tree_overlay, p);
×
519
                else if (streq(field, "extra"))
3✔
520
                        r = parse_extra(tmp.path, line, field, &tmp.local_extras, p);
3✔
521
                else {
UNCOV
522
                        log_syntax(NULL, LOG_WARNING, tmp.path, line, 0, "Unknown line '%s', ignoring.", field);
×
523
                        continue;
×
524
                }
525
                if (r < 0)
90✔
UNCOV
526
                        return log_syntax(NULL, LOG_ERR, tmp.path, line, r, "Error while parsing: %m");
×
527
        }
528

529
        *ret = TAKE_STRUCT(tmp);
20✔
530
        return 0;
20✔
531
}
532

533
int boot_config_load_type1(
20✔
534
                BootConfig *config,
535
                FILE *f,
536
                const char *root,
537
                const BootEntrySource source,
538
                const char *dir,
539
                const char *filename) {
540
        int r;
20✔
541

542
        assert(config);
20✔
543
        assert(f);
20✔
544
        assert(root);
20✔
545
        assert(dir);
20✔
546
        assert(filename);
20✔
547

548
        if (!GREEDY_REALLOC(config->entries, config->n_entries + 1))
20✔
UNCOV
549
                return log_oom();
×
550

551
        BootEntry *entry = config->entries + config->n_entries;
20✔
552

553
        r = boot_entry_load_type1(f, root, source, dir, filename, entry);
20✔
554
        if (r < 0)
20✔
555
                return r;
556
        config->n_entries++;
20✔
557

558
        entry->global_extras = &config->global_extras[source];
20✔
559

560
        return 0;
20✔
561
}
562

563
void boot_config_free(BootConfig *config) {
47✔
564
        assert(config);
47✔
565

566
        free(config->preferred_pattern);
47✔
567
        free(config->default_pattern);
47✔
568

569
        free(config->entry_oneshot);
47✔
570
        free(config->entry_preferred);
47✔
571
        free(config->entry_default);
47✔
572
        free(config->entry_selected);
47✔
573
        free(config->entry_sysfail);
47✔
574

575
        FOREACH_ARRAY(i, config->entries, config->n_entries)
199✔
576
                boot_entry_free(i);
152✔
577
        free(config->entries);
47✔
578

579
        FOREACH_ELEMENT(i, config->global_extras)
141✔
580
                boot_entry_extras_done(i);
94✔
581

582
        set_free(config->inodes_seen);
47✔
583
}
47✔
584

585
int boot_loader_read_conf(BootConfig *config, FILE *file, const char *path) {
45✔
586
        int r;
45✔
587

588
        assert(config);
45✔
589
        assert(file);
45✔
590
        assert(path);
45✔
591

592
        for (unsigned line = 1;; line++) {
100✔
593
                _cleanup_free_ char *buf = NULL, *field = NULL;
100✔
594

595
                r = read_stripped_line(file, LONG_LINE_MAX, &buf);
145✔
596
                if (r == -ENOBUFS)
145✔
UNCOV
597
                        return log_syntax(NULL, LOG_ERR, path, line, r, "Line too long.");
×
598
                if (r < 0)
145✔
UNCOV
599
                        return log_syntax(NULL, LOG_ERR, path, line, r, "Error while reading: %m");
×
600
                if (r == 0)
145✔
601
                        break;
602

603
                if (IN_SET(buf[0], '#', '\0'))
100✔
604
                        continue;
90✔
605

606
                const char *p = buf;
10✔
607
                r = extract_first_word(&p, &field, NULL, 0);
10✔
608
                if (r < 0) {
10✔
UNCOV
609
                        log_syntax(NULL, LOG_WARNING, path, line, r, "Failed to parse, ignoring line: %m");
×
610
                        continue;
×
611
                }
612
                if (r == 0) {
10✔
UNCOV
613
                        log_syntax(NULL, LOG_WARNING, path, line, 0, "Bad syntax, ignoring line.");
×
614
                        continue;
×
615
                }
616
                if (isempty(p)) {
10✔
UNCOV
617
                        log_syntax(NULL, LOG_WARNING, path, line, 0, "Field '%s' without value, ignoring line.", field);
×
618
                        continue;
×
619
                }
620

621
                if (streq(field, "preferred"))
10✔
UNCOV
622
                        r = free_and_strdup(&config->preferred_pattern, p);
×
623
                else if (streq(field, "default"))
10✔
624
                        r = free_and_strdup(&config->default_pattern, p);
10✔
UNCOV
625
                else if (STR_IN_SET(field, "timeout", "editor", "auto-entries", "auto-firmware",
×
626
                                    "auto-poweroff", "auto-reboot", "beep", "reboot-for-bitlocker",
627
                                    "reboot-on-error", "secure-boot-enroll", "secure-boot-enroll-action",
628
                                    "secure-boot-enroll-timeout-sec", "console-mode", "log-level"))
UNCOV
629
                        r = 0; /* we don't parse these in userspace, but they are OK */
×
630
                else {
UNCOV
631
                        log_syntax(NULL, LOG_WARNING, path, line, 0, "Unknown line '%s', ignoring.", field);
×
632
                        continue;
×
633
                }
634
                if (r < 0)
10✔
UNCOV
635
                        return log_syntax(NULL, LOG_ERR, path, line, r, "Error while parsing: %m");
×
636
        }
637

638
        return 1;
45✔
639
}
640

641
static int boot_loader_read_conf_path(BootConfig *config, const char *root, const char *path) {
47✔
642
        _cleanup_free_ char *full = NULL;
47✔
643
        _cleanup_fclose_ FILE *f = NULL;
47✔
644
        int r;
47✔
645

646
        assert(config);
47✔
647
        assert(path);
47✔
648

649
        r = chase_and_fopen_unlocked(path, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, "re", &full, &f);
47✔
650
        config->loader_conf_status = r < 0 ? r : true;
47✔
651
        if (r == -ENOENT)
47✔
652
                return 0;
653
        if (r < 0)
45✔
UNCOV
654
                return log_error_errno(r, "Failed to open '%s/%s': %m", root, skip_leading_slash(path));
×
655

656
        return boot_loader_read_conf(config, f, full);
45✔
657
}
658

659
static unsigned boot_entry_profile(const BootEntry *a) {
48✔
660
        assert(a);
48✔
661

662
        return a->profile == UINT_MAX ? 0 : a->profile;
48✔
663
}
664

665
static int boot_entry_compare(const BootEntry *a, const BootEntry *b) {
36✔
666
        int r;
36✔
667

668
        assert(a);
36✔
669
        assert(b);
36✔
670

671
        /* This mimics a function of the same name in src/boot/efi/sd-boot.c */
672

673
        r = CMP(a->tries_left == 0, b->tries_left == 0);
36✔
674
        if (r != 0)
36✔
675
                return r;
676

677
        r = CMP(!a->sort_key, !b->sort_key);
36✔
678
        if (r != 0)
33✔
679
                return r;
680

681
        if (a->sort_key && b->sort_key) {
33✔
682
                r = strcmp(a->sort_key, b->sort_key);
30✔
683
                if (r != 0)
30✔
684
                        return r;
685

686
                r = strcmp_ptr(a->machine_id, b->machine_id);
29✔
687
                if (r != 0)
29✔
688
                        return r;
689

690
                r = -strverscmp_improved(a->version, b->version);
29✔
691
                if (r != 0)
29✔
692
                        return r;
693

694
                r = CMP(boot_entry_profile(a), boot_entry_profile(b));
24✔
UNCOV
695
                if (r != 0)
×
696
                        return r;
697
        }
698

699
        r = -strverscmp_improved(a->id_without_profile ?: a->id, b->id_without_profile ?: b->id);
3✔
700
        if (r != 0)
3✔
701
                return r;
702

UNCOV
703
        if (a->id_without_profile && b->id_without_profile) {
×
704
                /* The strverscmp_improved() call above already established that we are talking about the
705
                 * same image here, hence order by profile, if there is one */
UNCOV
706
                r = CMP(boot_entry_profile(a), boot_entry_profile(b));
×
707
                if (r != 0)
×
708
                        return r;
709
        }
710

UNCOV
711
        if (a->tries_left != UINT_MAX || b->tries_left != UINT_MAX)
×
712
                return 0;
713

UNCOV
714
        r = -CMP(a->tries_left, b->tries_left);
×
715
        if (r != 0)
×
716
                return r;
717

UNCOV
718
        return CMP(a->tries_done, b->tries_done);
×
719
}
720

721
static int config_check_inode_relevant_and_unseen(BootConfig *config, int fd, const char *fname) {
44✔
722
        _cleanup_free_ char *d = NULL;
44✔
723
        struct stat st;
44✔
724

725
        assert(config);
44✔
726
        assert(fd >= 0);
44✔
727
        assert(fname);
44✔
728

729
        /* So, here's the thing: because of the mess around /efi/ vs. /boot/ vs. /boot/efi/ it might be that
730
         * people have these dirs, or subdirs of them symlinked or bind mounted, and we might end up
731
         * iterating though some dirs multiple times. Let's thus rather be safe than sorry, and track the
732
         * inodes we already processed: let's ignore inodes we have seen already. This should be robust
733
         * against any form of symlinking or bind mounting, and effectively suppress any such duplicates. */
734

735
        if (fstat(fd, &st) < 0)
44✔
UNCOV
736
                return log_error_errno(errno, "Failed to stat('%s'): %m", fname);
×
737
        if (!S_ISREG(st.st_mode)) {
44✔
UNCOV
738
                log_debug("File '%s' is not a regular file, ignoring.", fname);
×
739
                return false;
740
        }
741

742
        if (set_contains(config->inodes_seen, &st)) {
44✔
UNCOV
743
                log_debug("Inode '%s' already seen before, ignoring.", fname);
×
744
                return false;
745
        }
746

747
        d = memdup(&st, sizeof(st));
44✔
748
        if (!d)
44✔
UNCOV
749
                return log_oom();
×
750

751
        if (set_ensure_consume(&config->inodes_seen, &inode_hash_ops, TAKE_PTR(d)) < 0)
44✔
UNCOV
752
                return log_oom();
×
753

754
        return true;
755
}
756

757
static int boot_entries_find_type1(
61✔
758
                BootConfig *config,
759
                const char *root,
760
                const BootEntrySource source,
761
                const char *dir) {
762

763
        _cleanup_free_ DirectoryEntries *dentries = NULL;
122✔
764
        _cleanup_free_ char *full = NULL;
61✔
765
        _cleanup_close_ int dir_fd = -EBADF;
61✔
766
        int r;
61✔
767

768
        assert(config);
61✔
769
        assert(root);
61✔
770
        assert(dir);
61✔
771

772
        dir_fd = chase_and_open(dir, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, O_DIRECTORY|O_CLOEXEC, &full);
61✔
773
        if (dir_fd == -ENOENT)
61✔
774
                return 0;
775
        if (dir_fd < 0)
47✔
UNCOV
776
                return log_error_errno(dir_fd, "Failed to open '%s/%s': %m", root, skip_leading_slash(dir));
×
777

778
        r = readdir_all(dir_fd, RECURSE_DIR_IGNORE_DOT, &dentries);
47✔
779
        if (r < 0)
47✔
UNCOV
780
                return log_error_errno(r, "Failed to read directory '%s': %m", full);
×
781

782
        FOREACH_ARRAY(i, dentries->entries, dentries->n_entries) {
67✔
783
                const struct dirent *de = *i;
20✔
784
                _cleanup_fclose_ FILE *f = NULL;
20✔
785

786
                if (!dirent_is_file(de))
20✔
UNCOV
787
                        continue;
×
788

789
                if (!endswith_no_case(de->d_name, ".conf"))
20✔
UNCOV
790
                        continue;
×
791

792
                r = xfopenat(dir_fd, de->d_name, "re", O_NOFOLLOW|O_NOCTTY, &f);
20✔
793
                if (r < 0) {
20✔
UNCOV
794
                        log_warning_errno(r, "Failed to open %s/%s, ignoring: %m", full, de->d_name);
×
795
                        continue;
×
796
                }
797

798
                r = config_check_inode_relevant_and_unseen(config, fileno(f), de->d_name);
20✔
799
                if (r < 0)
20✔
800
                        return r;
801
                if (r == 0) /* inode already seen or otherwise not relevant */
20✔
UNCOV
802
                        continue;
×
803

804
                r = boot_config_load_type1(config, f, root, source, full, de->d_name);
20✔
805
                if (r == -ENOMEM) /* ignore all other errors */
20✔
UNCOV
806
                        return log_oom();
×
807
        }
808

809
        return 0;
810
}
811

812
static void mangle_osrelease_string(char **s, const char *field) {
352✔
813
        assert(s);
352✔
814
        assert(field);
352✔
815

816
        if (!isempty(*s) && !string_has_cc(*s, /* ok= */ NULL) && utf8_is_valid(*s))
520✔
817
                return;
818

819
        if (*s) {
184✔
UNCOV
820
                log_debug("OS release field '%s' is not clean, suppressing.", field);
×
821
                *s = mfree(*s);
×
822
        }
823
}
824

825
int bootspec_extract_osrelease(
44✔
826
                const char *text,
827
                char **ret_good_name,
828
                char **ret_good_version,
829
                char **ret_good_sort_key,
830
                char **ret_os_id,
831
                char **ret_os_version_id,
832
                char **ret_image_id,
833
                char **ret_image_version) {
834

835
        int r;
44✔
836

837
        assert(text);
44✔
838

UNCOV
839
        _cleanup_free_ char *os_pretty_name = NULL, *image_id = NULL, *os_name = NULL, *os_id = NULL,
×
840
                *image_version = NULL, *os_version = NULL, *os_version_id = NULL, *os_build_id = NULL;
44✔
841
        r = parse_env_data(text, /* size= */ SIZE_MAX,
44✔
842
                           "os-release",
843
                           "PRETTY_NAME", &os_pretty_name,
844
                           "IMAGE_ID", &image_id,
845
                           "NAME", &os_name,
846
                           "ID", &os_id,
847
                           "IMAGE_VERSION", &image_version,
848
                           "VERSION", &os_version,
849
                           "VERSION_ID", &os_version_id,
850
                           "BUILD_ID", &os_build_id);
851
        if (r < 0)
44✔
852
                return r;
853

854
        mangle_osrelease_string(&os_pretty_name, "PRETTY_NAME");
44✔
855
        mangle_osrelease_string(&image_id, "IMAGE_ID");
44✔
856
        mangle_osrelease_string(&os_name, "NAME");
44✔
857
        mangle_osrelease_string(&os_id, "ID");
44✔
858
        mangle_osrelease_string(&image_version, "IMAGE_VERSION");
44✔
859
        mangle_osrelease_string(&os_version, "VERSION");
44✔
860
        mangle_osrelease_string(&os_version_id, "VERSION_ID");
44✔
861
        mangle_osrelease_string(&os_build_id, "BUILD_ID");
44✔
862

863
        const char *good_name, *good_version, *good_sort_key;
44✔
864
        if (!bootspec_pick_name_version_sort_key(
44✔
865
                            os_pretty_name,
866
                            image_id,
867
                            os_name,
868
                            os_id,
869
                            image_version,
870
                            os_version,
871
                            os_version_id,
872
                            os_build_id,
873
                            &good_name,
874
                            &good_version,
875
                            &good_sort_key))
876
                return -EBADMSG;
877

878
        _cleanup_free_ char *copy_good_name = NULL, *copy_good_version = NULL, *copy_good_sort_key = NULL;
44✔
879
        if (ret_good_name) {
44✔
880
                copy_good_name = strdup(good_name);
44✔
881
                if (!copy_good_name)
44✔
882
                        return -ENOMEM;
883
        }
884

885
        if (ret_good_version && good_version) {
44✔
886
                copy_good_version = strdup(good_version);
36✔
887
                if (!copy_good_version)
36✔
888
                        return -ENOMEM;
889
        }
890

891
        if (ret_good_sort_key && good_sort_key) {
44✔
892
                copy_good_sort_key = strdup(good_sort_key);
44✔
893
                if (!copy_good_sort_key)
44✔
894
                        return -ENOMEM;
895
        }
896

897
        if (ret_good_name)
44✔
898
                *ret_good_name = TAKE_PTR(copy_good_name);
44✔
899
        if (ret_good_version)
44✔
900
                *ret_good_version = TAKE_PTR(copy_good_version);
36✔
901
        if (ret_good_sort_key)
44✔
902
                *ret_good_sort_key = TAKE_PTR(copy_good_sort_key);
44✔
903

904
        if (ret_os_id)
44✔
905
                *ret_os_id = TAKE_PTR(os_id);
36✔
906
        if (ret_os_version_id)
44✔
907
                *ret_os_version_id = TAKE_PTR(os_version_id);
44✔
908
        if (ret_image_id)
44✔
UNCOV
909
                *ret_image_id = TAKE_PTR(image_id);
×
910
        if (ret_image_version)
44✔
911
                *ret_image_version = TAKE_PTR(image_version);
8✔
912

913
        return 0;
914
}
915

916
static int boot_entry_load_unified(
36✔
917
                const char *root,
918
                const BootEntrySource source,
919
                const char *path,
920
                unsigned profile,
921
                const char *osrelease_text,
922
                const char *profile_text,
923
                const char *cmdline_text,
924
                BootEntry *ret) {
925

926
        int r;
36✔
927

928
        assert(root);
36✔
929
        assert(path);
36✔
930
        assert(osrelease_text);
36✔
931
        assert(ret);
36✔
932

933
        const char *k = path_startswith(path, root);
36✔
934
        if (!k)
36✔
935
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Path is not below root: %s", path);
36✔
936

937
        _cleanup_free_ char *good_name = NULL, *good_version = NULL, *good_sort_key = NULL, *os_id = NULL, *os_version_id = NULL;
36✔
938
        r = bootspec_extract_osrelease(
36✔
939
                        osrelease_text,
940
                        &good_name,
941
                        &good_version,
942
                        &good_sort_key,
943
                        &os_id,
944
                        &os_version_id,
945
                        /* ret_image_id= */ NULL,
946
                        /* ret_image_version= */ NULL);
947
        if (r < 0)
36✔
UNCOV
948
                return log_error_errno(r, "Failed to extract name/version/sort-key from os-release data from unified kernel image %s, refusing: %m", path);
×
949

950
        _cleanup_free_ char *profile_id = NULL, *profile_title = NULL;
36✔
951
        if (profile_text) {
36✔
952
                r = parse_env_data(
36✔
953
                                profile_text, /* size= */ SIZE_MAX,
954
                                ".profile",
955
                                "ID", &profile_id,
956
                                "TITLE", &profile_title);
957
                if (r < 0)
36✔
UNCOV
958
                        return log_error_errno(r, "Failed to parse profile data from unified kernel image '%s': %m", path);
×
959
        }
960

961
        _cleanup_free_ char *fname = NULL;
36✔
962
        r = path_extract_filename(path, &fname);
36✔
963
        if (r < 0)
36✔
UNCOV
964
                return log_error_errno(r, "Failed to extract file name from '%s': %m", path);
×
965

966
        _cleanup_(boot_entry_free) BootEntry tmp = BOOT_ENTRY_INIT(BOOT_ENTRY_TYPE2, source);
36✔
967

968
        r = boot_filename_extract_tries(fname, &tmp.id, &tmp.tries_left, &tmp.tries_done);
36✔
969
        if (r < 0)
36✔
UNCOV
970
                return log_error_errno(r, "Failed to extract tries counters from '%s': %m", fname);
×
971

972
        if (!efi_loader_entry_name_valid(tmp.id))
36✔
UNCOV
973
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry name: %s", tmp.id);
×
974

975
        tmp.profile = profile;
36✔
976

977
        if (profile_id || profile > 0) {
36✔
978
                tmp.id_without_profile = TAKE_PTR(tmp.id);
36✔
979

980
                if (profile_id)
36✔
981
                        tmp.id = strjoin(tmp.id_without_profile, "@", profile_id);
36✔
982
                else
UNCOV
983
                        (void) asprintf(&tmp.id, "%s@%u", tmp.id_without_profile, profile);
×
984
                if (!tmp.id)
36✔
UNCOV
985
                        return log_oom();
×
986
        }
987

988
        if (os_id && os_version_id) {
36✔
UNCOV
989
                tmp.id_old = strjoin(os_id, "-", os_version_id);
×
990
                if (!tmp.id_old)
×
991
                        return log_oom();
×
992
        }
993

994
        tmp.path = strdup(path);
36✔
995
        if (!tmp.path)
36✔
UNCOV
996
                return log_oom();
×
997

998
        tmp.root = strdup(root);
36✔
999
        if (!tmp.root)
36✔
UNCOV
1000
                return log_oom();
×
1001

1002
        tmp.kernel = path_make_absolute(k, "/");
36✔
1003
        if (!tmp.kernel)
36✔
UNCOV
1004
                return log_oom();
×
1005

1006
        tmp.options = strv_new(cmdline_text);
36✔
1007
        if (!tmp.options)
36✔
UNCOV
1008
                return log_oom();
×
1009

1010
        if (profile_title)
36✔
1011
                tmp.title = strjoin(good_name, " (", profile_title, ")");
24✔
1012
        else if (profile_id)
12✔
1013
                tmp.title = strjoin(good_name, " (", profile_id, ")");
12✔
UNCOV
1014
        else if (profile > 0)
×
1015
                (void) asprintf(&tmp.title, "%s (@%u)", good_name, profile);
×
1016
        else
UNCOV
1017
                tmp.title = strdup(good_name);
×
1018
        if (!tmp.title)
36✔
UNCOV
1019
                return log_oom();
×
1020

1021
        if (good_sort_key) {
36✔
1022
                tmp.sort_key = strdup(good_sort_key);
36✔
1023
                if (!tmp.sort_key)
36✔
UNCOV
1024
                        return log_oom();
×
1025
        }
1026

1027
        if (good_version) {
36✔
1028
                tmp.version = strdup(good_version);
36✔
1029
                if (!tmp.version)
36✔
UNCOV
1030
                        return log_oom();
×
1031
        }
1032

1033
        *ret = TAKE_STRUCT(tmp);
36✔
1034
        return 0;
36✔
1035
}
1036

1037
static int pe_load_headers_and_sections(
75✔
1038
                int fd,
1039
                const char *path,
1040
                IMAGE_SECTION_HEADER **ret_sections,
1041
                PeHeader **ret_pe_header) {
1042

1043
        _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
150✔
UNCOV
1044
        _cleanup_free_ IMAGE_DOS_HEADER *dos_header = NULL;
×
1045
        _cleanup_free_ PeHeader *pe_header = NULL;
75✔
1046
        int r;
75✔
1047

1048
        assert(fd >= 0);
75✔
1049
        assert(path);
75✔
1050

1051
        r = pe_load_headers(fd, &dos_header, &pe_header);
75✔
1052
        if (r < 0)
75✔
UNCOV
1053
                return log_error_errno(r, "Failed to parse PE file '%s': %m", path);
×
1054

1055
        r = pe_load_sections(fd, dos_header, pe_header, &sections);
75✔
1056
        if (r < 0)
75✔
UNCOV
1057
                return log_error_errno(r, "Failed to parse PE sections of '%s': %m", path);
×
1058

1059
        if (ret_pe_header)
75✔
1060
                *ret_pe_header = TAKE_PTR(pe_header);
75✔
1061
        if (ret_sections)
75✔
1062
                *ret_sections = TAKE_PTR(sections);
75✔
1063

1064
        return 0;
1065
}
1066

1067
static const IMAGE_SECTION_HEADER* pe_find_profile_section_table(
107✔
1068
                const PeHeader *pe_header,
1069
                const IMAGE_SECTION_HEADER *sections,
1070
                unsigned profile,
1071
                size_t *ret_n_sections) {
1072

1073
        assert(pe_header);
107✔
1074

1075
        /* Looks for the part of the section table that defines the specified profile. If 'profile' is
1076
         * specified as UINT_MAX this will look for the base profile. */
1077

1078
        if (le16toh(pe_header->pe.NumberOfSections) == 0)
107✔
1079
                return NULL;
1080

1081
        assert(sections);
107✔
1082

1083
        const IMAGE_SECTION_HEADER
107✔
1084
                *p = sections,
107✔
1085
                *e = sections + le16toh(pe_header->pe.NumberOfSections),
107✔
1086
                *start = profile == UINT_MAX ? sections : NULL,
107✔
1087
                *end;
1088
        unsigned current_profile = UINT_MAX;
107✔
1089

1090
        for (;;) {
323✔
1091
                p = pe_section_table_find(p, e - p, ".profile");
215✔
1092
                if (!p) {
215✔
1093
                        end = e;
1094
                        break;
1095
                }
1096
                if (current_profile == profile) {
168✔
1097
                        end = p;
1098
                        break;
1099
                }
1100

1101
                if (current_profile == UINT_MAX)
108✔
1102
                        current_profile = 0;
1103
                else
1104
                        current_profile++;
60✔
1105

1106
                if (current_profile == profile)
108✔
1107
                        start = p;
36✔
1108

1109
                p++; /* Continue scanning after the .profile entry we just found */
108✔
1110
        }
1111

1112
        if (!start)
107✔
1113
                return NULL;
1114

1115
        if (ret_n_sections)
80✔
1116
                *ret_n_sections = end - start;
80✔
1117

1118
        return start;
1119
}
1120

1121
static int trim_cmdline(char **cmdline) {
56✔
1122
        assert(cmdline);
56✔
1123

1124
        /* Strips leading and trailing whitespace from command line */
1125

1126
        if (!*cmdline)
56✔
1127
                return 0;
1128

1129
        const char *skipped = skip_leading_chars(*cmdline, WHITESPACE);
56✔
1130

1131
        if (isempty(skipped)) {
56✔
UNCOV
1132
                *cmdline = mfree(*cmdline);
×
1133
                return 0;
×
1134
        }
1135

1136
        if (skipped != *cmdline) {
56✔
UNCOV
1137
                _cleanup_free_ char *c = strdup(skipped);
×
1138
                if (!c)
×
1139
                        return -ENOMEM;
×
1140

UNCOV
1141
                free_and_replace(*cmdline, c);
×
1142
        }
1143

1144
        delete_trailing_chars(*cmdline, WHITESPACE);
56✔
1145
        return 1;
56✔
1146
}
1147

1148
/* Maximum PE section we are willing to load (Note that sections we are not interested in may be larger, but
1149
 * the ones we do care about and we are willing to load into memory have this size limit.) */
1150
#define PE_SECTION_SIZE_MAX (4U*1024U*1024U)
1151

1152
int pe_find_uki_sections(
63✔
1153
                int fd,
1154
                const char *path,
1155
                unsigned profile,
1156
                char **ret_osrelease,
1157
                char **ret_profile,
1158
                char **ret_cmdline) {
1159

UNCOV
1160
        _cleanup_free_ char *osrelease_text = NULL, *profile_text = NULL, *cmdline_text = NULL;
×
1161
        _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
×
1162
        _cleanup_free_ PeHeader *pe_header = NULL;
63✔
1163
        int r;
63✔
1164

1165
        assert(fd >= 0);
63✔
1166
        assert(path);
63✔
1167
        assert(profile != UINT_MAX);
63✔
1168

1169
        r = pe_load_headers_and_sections(fd, path, &sections, &pe_header);
63✔
1170
        if (r < 0)
63✔
1171
                return r;
1172

1173
        if (!pe_is_uki(pe_header, sections))
63✔
UNCOV
1174
                return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Parsed PE file '%s' is not a UKI.", path);
×
1175

1176
        if (!pe_is_native(pe_header)) /* Don't process non-native UKIs */
63✔
UNCOV
1177
                goto nothing;
×
1178

1179
        /* Find part of the section table for this profile */
1180
        size_t n_psections = 0;
63✔
1181
        const IMAGE_SECTION_HEADER *psections = pe_find_profile_section_table(pe_header, sections, profile, &n_psections);
63✔
1182
        if (!psections && profile != 0) /* Profile not found? (Profile @0 needs no explicit .profile!) */
63✔
1183
                goto nothing;
19✔
1184

1185
        /* Find base profile part of section table */
1186
        size_t n_bsections;
44✔
1187
        const IMAGE_SECTION_HEADER *bsections = ASSERT_PTR(pe_find_profile_section_table(pe_header, sections, UINT_MAX, &n_bsections));
44✔
1188

1189
        struct {
44✔
1190
                const char *name;
1191
                char **data;
1192
        } table[] = {
44✔
1193
                { ".osrel",   &osrelease_text },
1194
                { ".profile", &profile_text   },
1195
                { ".cmdline", &cmdline_text   },
1196
        };
1197

1198
        FOREACH_ELEMENT(t, table) {
176✔
1199
                const IMAGE_SECTION_HEADER *found;
132✔
1200

1201
                /* First look in the profile part of the section table, and if we don't find anything there, look into the base part */
1202
                found = pe_section_table_find(psections, n_psections, t->name);
132✔
1203
                if (!found) {
132✔
1204
                        found = pe_section_table_find(bsections, n_bsections, t->name);
72✔
1205
                        if (!found)
72✔
1206
                                continue;
8✔
1207
                }
1208

1209
                /* Permit "masking" of sections in the base profile */
1210
                if (le32toh(found->VirtualSize) == 0)
124✔
UNCOV
1211
                        continue;
×
1212

1213
                r = pe_read_section_data(fd, found, PE_SECTION_SIZE_MAX, (void**) t->data, /* ret_size= */ NULL);
124✔
1214
                if (r < 0)
124✔
UNCOV
1215
                        return log_error_errno(r, "Failed to load contents of section '%s': %m", t->name);
×
1216
        }
1217

1218
        if (!osrelease_text)
44✔
UNCOV
1219
                return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Unified kernel image lacks .osrel data for profile @%u, refusing.", profile);
×
1220

1221
        if (trim_cmdline(&cmdline_text) < 0)
44✔
UNCOV
1222
                return log_oom();
×
1223

1224
        if (ret_osrelease)
44✔
1225
                *ret_osrelease = TAKE_PTR(osrelease_text);
44✔
1226
        if (ret_profile)
44✔
1227
                *ret_profile = TAKE_PTR(profile_text);
44✔
1228
        if (ret_cmdline)
44✔
1229
                *ret_cmdline = TAKE_PTR(cmdline_text);
36✔
1230
        return 1;
1231

1232
nothing:
19✔
1233
        if (ret_osrelease)
19✔
1234
                *ret_osrelease = NULL;
19✔
1235
        if (ret_profile)
19✔
1236
                *ret_profile = NULL;
19✔
1237
        if (ret_cmdline)
19✔
1238
                *ret_cmdline = NULL;
12✔
1239

1240
        return 0;
1241
}
1242

1243
static int pe_find_addon_sections(
12✔
1244
                int fd,
1245
                const char *path,
1246
                char **ret_cmdline) {
1247

1248
        _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
24✔
1249
        _cleanup_free_ PeHeader *pe_header = NULL;
12✔
1250
        int r;
12✔
1251

1252
        assert(fd >= 0);
12✔
1253
        assert(path);
12✔
1254
        assert(ret_cmdline);
12✔
1255

1256
        r = pe_load_headers_and_sections(fd, path, &sections, &pe_header);
12✔
1257
        if (r < 0)
12✔
1258
                return r;
1259

1260
        if (!pe_is_addon(pe_header, sections))
12✔
UNCOV
1261
                return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Parse PE file '%s' is not an add-on.", path);
×
1262

1263
        /* Define early, before the gotos below */
1264
        _cleanup_free_ char *cmdline_text = NULL;
12✔
1265

1266
        if (!pe_is_native(pe_header))
12✔
UNCOV
1267
                goto nothing;
×
1268

1269
        const IMAGE_SECTION_HEADER *found = pe_section_table_find(sections, le16toh(pe_header->pe.NumberOfSections), ".cmdline");
12✔
1270
        if (!found)
12✔
UNCOV
1271
                goto nothing;
×
1272

1273
        r = pe_read_section_data(fd, found, PE_SECTION_SIZE_MAX, (void**) &cmdline_text, /* ret_size= */ NULL);
12✔
1274
        if (r < 0)
12✔
UNCOV
1275
                return log_error_errno(r, "Failed to load contents of section '.cmdline': %m");
×
1276

1277
        if (trim_cmdline(&cmdline_text) < 0)
12✔
UNCOV
1278
                return log_oom();
×
1279

1280
        *ret_cmdline = TAKE_PTR(cmdline_text);
12✔
1281
        return 1;
12✔
1282

UNCOV
1283
nothing:
×
1284
        *ret_cmdline = NULL;
×
1285
        return 0;
×
1286
}
1287

1288
static int boot_entries_find_unified_extras(
280✔
1289
                BootConfig *config,
1290
                int d_fd,
1291
                const char *extra_dir,
1292
                BootEntryExtraType only_type,
1293
                const char *where,
1294
                bool suppress_seen,
1295
                BootEntryExtras *extras) {
1296

1297
        int r;
280✔
1298

1299
        assert(config);
280✔
1300
        assert(extras);
280✔
1301

1302
        _cleanup_closedir_ DIR *d = NULL;
280✔
1303
        r = chase_and_opendirat(
280✔
1304
                        /* root_fd= */ d_fd,
1305
                        /* dir_fd= */ d_fd,
1306
                        extra_dir,
1307
                        /* chase_flags= */ 0,
1308
                        /* ret_path= */ NULL,
1309
                        &d);
1310
        if (r == -ENOENT)
280✔
1311
                return 0;
1312
        if (r < 0)
12✔
UNCOV
1313
                return log_error_errno(r, "Failed to open '%s/%s': %m", where, skip_leading_slash(extra_dir));
×
1314

1315
        FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read '%s': %m", extra_dir)) {
48✔
1316
                if (!dirent_is_file(de))
12✔
UNCOV
1317
                        continue;
×
1318

1319
                BootEntryExtraType type = boot_entry_extra_type_from_filename(de->d_name);
12✔
1320
                if (type < 0) {
12✔
UNCOV
1321
                        log_debug_errno(type, "Unrecognized extra file '%s', skipping.", de->d_name);
×
1322
                        continue;
×
1323
                }
1324
                if (only_type >= 0 && type != only_type) {
12✔
UNCOV
1325
                        log_debug("Extra file '%s' type not permitted in '%s', skipping.", de->d_name, extra_dir);
×
1326
                        continue;
×
1327
                }
1328

1329
                _cleanup_free_ char *location = path_join(extra_dir, de->d_name);
24✔
1330
                if (!location)
12✔
UNCOV
1331
                        return log_oom();
×
1332

1333
                _cleanup_close_ int pin_fd = openat(dirfd(d), de->d_name, O_PATH|O_CLOEXEC|O_NOFOLLOW);
24✔
1334
                if (pin_fd < 0) {
12✔
UNCOV
1335
                        log_debug_errno(errno, "Failed to pin '%s', ignoring: %m", location);
×
1336
                        continue;
×
1337
                }
1338

1339
                r = fd_verify_regular(pin_fd);
12✔
1340
                if (r < 0) {
12✔
UNCOV
1341
                        log_debug_errno(r, "Unrecognized inode type of '%s', skipping.", location);
×
1342
                        continue;
×
1343
                }
1344

1345
                if (suppress_seen) {
12✔
1346
                        r = config_check_inode_relevant_and_unseen(config, pin_fd, location);
12✔
1347
                        if (r < 0)
12✔
1348
                                return r;
1349
                        if (r == 0) /* inode already seen or otherwise not relevant */
12✔
UNCOV
1350
                                continue;
×
1351
                }
1352

1353
                _cleanup_free_ char *cmdline = NULL;
12✔
1354
                if (type == BOOT_ENTRY_ADDON) {
12✔
1355
                        _cleanup_close_ int fd = fd_reopen(pin_fd, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
12✔
1356
                        if (fd < 0) {
12✔
UNCOV
1357
                                log_debug_errno(fd, "Failed to open '%s', ignoring: %m", location);
×
1358
                                continue;
×
1359
                        }
1360

1361
                        /* Try to extract the command line, but let's handle any failures gracefully, but
1362
                         * still mention the extra file exists. */
1363
                        (void) pe_find_addon_sections(fd, location, &cmdline);
12✔
1364
                }
1365

1366
                r = boot_entry_extras_add(extras, type, location, cmdline);
12✔
1367
                if (r < 0)
12✔
UNCOV
1368
                        return r;
×
1369
        }
1370

1371
        return 0;
1372
}
1373

1374
static int boot_entries_find_unified_global_extras(
244✔
1375
                BootConfig *config,
1376
                const char *where,
1377
                const char *extra_dir,
1378
                BootEntryExtraType only_type,
1379
                BootEntryExtras *extras) {
1380

1381
        assert(extras);
244✔
1382

1383
        _cleanup_close_ int where_fd = RET_NERRNO(open(where, O_DIRECTORY|O_CLOEXEC));
488✔
UNCOV
1384
        if (where_fd == -ENOENT)
×
1385
                return 0;
1386
        if (where_fd < 0)
244✔
UNCOV
1387
                return log_error_errno(where_fd, "Failed to open '%s': %m", where);
×
1388

1389
        return boot_entries_find_unified_extras(
244✔
1390
                        config,
1391
                        where_fd,
1392
                        extra_dir,
1393
                        only_type,
1394
                        where,
1395
                        /* suppress_seen= */ true,
1396
                        extras);
1397
}
1398

1399
static int boot_entries_find_unified_local_extras(
36✔
1400
                BootConfig *config,
1401
                int d_fd,
1402
                const char *uki,
1403
                const char *where,
1404
                BootEntry *ret) {
1405

1406
        _cleanup_free_ char *extra_dir = NULL;
36✔
1407

1408
        assert(ret);
36✔
1409

1410
        extra_dir = strjoin(uki, ".extra.d");
36✔
1411
        if (!extra_dir)
36✔
UNCOV
1412
                return log_oom();
×
1413

1414
        return boot_entries_find_unified_extras(
36✔
1415
                        config,
1416
                        d_fd,
1417
                        extra_dir,
1418
                        /* only_type= */ _BOOT_ENTRY_EXTRA_TYPE_INVALID,
1419
                        where,
1420
                        /* suppress_seen= */ false,
1421
                        &ret->local_extras);
1422
}
1423

1424
static int boot_entries_find_unified(
61✔
1425
                BootConfig *config,
1426
                const char *root,
1427
                BootEntrySource source,
1428
                const char *dir) {
1429

1430
        _cleanup_closedir_ DIR *d = NULL;
61✔
1431
        _cleanup_free_ char *full = NULL;
61✔
1432
        int r;
61✔
1433

1434
        assert(config);
61✔
1435
        assert(dir);
61✔
1436

1437
        r = chase_and_opendir(dir, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &full, &d);
61✔
1438
        if (r == -ENOENT)
61✔
1439
                return 0;
1440
        if (r < 0)
45✔
UNCOV
1441
                return log_error_errno(r, "Failed to open '%s/%s': %m", root, skip_leading_slash(dir));
×
1442

1443
        FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read %s: %m", full)) {
147✔
1444
                if (!dirent_is_file(de))
12✔
UNCOV
1445
                        continue;
×
1446

1447
                if (!endswith_no_case(de->d_name, ".efi"))
12✔
UNCOV
1448
                        continue;
×
1449

1450
                _cleanup_close_ int fd = openat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOFOLLOW|O_NOCTTY);
175✔
1451
                if (fd < 0) {
12✔
UNCOV
1452
                        log_warning_errno(errno, "Failed to open %s/%s, ignoring: %m", full, de->d_name);
×
1453
                        continue;
×
1454
                }
1455

1456
                r = config_check_inode_relevant_and_unseen(config, fd, de->d_name);
12✔
1457
                if (r < 0)
12✔
1458
                        return r;
1459
                if (r == 0) /* inode already seen or otherwise not relevant */
12✔
UNCOV
1460
                        continue;
×
1461

1462
                _cleanup_free_ char *j = path_join(full, de->d_name);
24✔
1463
                if (!j)
12✔
UNCOV
1464
                        return log_oom();
×
1465

1466
                for (unsigned p = 0; p < UNIFIED_PROFILES_MAX; p++) {
48✔
1467
                        _cleanup_free_ char *osrelease = NULL, *profile = NULL, *cmdline = NULL;
48✔
1468

1469
                        r = pe_find_uki_sections(fd, j, p, &osrelease, &profile, &cmdline);
48✔
1470
                        if (r == 0) /* this profile does not exist, we are done */
48✔
1471
                                break;
1472
                        if (r < 0)
36✔
UNCOV
1473
                                continue;
×
1474

1475
                        if (!GREEDY_REALLOC(config->entries, config->n_entries + 1))
36✔
UNCOV
1476
                                return log_oom();
×
1477

1478
                        BootEntry *entry = config->entries + config->n_entries;
36✔
1479

1480
                        if (boot_entry_load_unified(root, source, j, p, osrelease, profile, cmdline, entry) < 0)
36✔
UNCOV
1481
                                continue;
×
1482

1483
                        /* Look for .efi.extra.d/ */
1484
                        (void) boot_entries_find_unified_local_extras(config, dirfd(d), de->d_name, full, entry);
36✔
1485

1486
                        /* Set up the backpointer, so that we can find the global extras */
1487
                        entry->global_extras = &config->global_extras[source];
36✔
1488

1489
                        config->n_entries++;
36✔
1490
                }
1491
        }
1492

1493
        return 0;
1494
}
1495

1496
static bool find_nonunique(const BootEntry *entries, size_t n_entries, bool arr[]) {
28✔
1497
        bool non_unique = false;
28✔
1498

1499
        assert(entries || n_entries == 0);
28✔
1500
        assert(arr || n_entries == 0);
28✔
1501

1502
        for (size_t i = 0; i < n_entries; i++)
99✔
1503
                arr[i] = false;
71✔
1504

1505
        for (size_t i = 0; i < n_entries; i++)
99✔
1506
                for (size_t j = 0; j < n_entries; j++)
296✔
1507
                        if (i != j && streq(boot_entry_title(entries + i),
225✔
1508
                                            boot_entry_title(entries + j)))
1509
                                non_unique = arr[i] = arr[j] = true;
18✔
1510

1511
        return non_unique;
28✔
1512
}
1513

1514
static int boot_entries_uniquify(BootEntry *entries, size_t n_entries) {
47✔
1515
        _cleanup_free_ bool *arr = NULL;
47✔
1516
        char *s;
47✔
1517

1518
        assert(entries || n_entries == 0);
47✔
1519

1520
        if (n_entries == 0)
47✔
1521
                return 0;
1522

1523
        arr = new(bool, n_entries);
23✔
1524
        if (!arr)
23✔
1525
                return -ENOMEM;
1526

1527
        /* Find _all_ non-unique titles */
1528
        if (!find_nonunique(entries, n_entries, arr))
23✔
1529
                return 0;
1530

1531
        /* Add version to non-unique titles */
1532
        for (size_t i = 0; i < n_entries; i++)
17✔
1533
                if (arr[i] && entries[i].version) {
13✔
1534
                        if (asprintf(&s, "%s (%s)", boot_entry_title(entries + i), entries[i].version) < 0)
11✔
1535
                                return -ENOMEM;
1536

1537
                        free_and_replace(entries[i].show_title, s);
11✔
1538
                }
1539

1540
        if (!find_nonunique(entries, n_entries, arr))
4✔
1541
                return 0;
1542

1543
        /* Add machine-id to non-unique titles */
1544
        for (size_t i = 0; i < n_entries; i++)
3✔
1545
                if (arr[i] && entries[i].machine_id) {
2✔
1546
                        if (asprintf(&s, "%s (%s)", boot_entry_title(entries + i), entries[i].machine_id) < 0)
2✔
1547
                                return -ENOMEM;
1548

1549
                        free_and_replace(entries[i].show_title, s);
2✔
1550
                }
1551

1552
        if (!find_nonunique(entries, n_entries, arr))
1✔
1553
                return 0;
1554

1555
        /* Add file name to non-unique titles */
1556
        for (size_t i = 0; i < n_entries; i++)
3✔
1557
                if (arr[i]) {
2✔
1558
                        if (asprintf(&s, "%s (%s)", boot_entry_title(entries + i), entries[i].id) < 0)
2✔
1559
                                return -ENOMEM;
1560

1561
                        free_and_replace(entries[i].show_title, s);
2✔
1562
                }
1563

1564
        return 0;
1565
}
1566

1567
static int boot_config_find(const BootConfig *config, const char *id) {
44✔
1568
        assert(config);
44✔
1569

1570
        if (!id)
44✔
1571
                return -1;
1572

1573
        if (id[0] == '@') {
44✔
UNCOV
1574
                if (!strcaseeq(id, "@saved"))
×
1575
                        return -1;
UNCOV
1576
                if (!config->entry_selected)
×
1577
                        return -1;
1578
                id = config->entry_selected;
1579
        }
1580

1581
        for (size_t i = 0; i < config->n_entries; i++)
103✔
1582
                if (fnmatch(id, config->entries[i].id, FNM_CASEFOLD) == 0)
101✔
1583
                        return i;
42✔
1584

1585
        return -1;
1586
}
1587

1588
static int boot_entries_select_default(const BootConfig *config) {
45✔
1589
        int i;
45✔
1590

1591
        assert(config);
45✔
1592
        assert(config->entries || config->n_entries == 0);
45✔
1593

1594
        if (config->n_entries == 0) {
45✔
1595
                log_debug("Found no default boot entry :(");
14✔
1596
                return -1; /* -1 means "no default" */
1597
        }
1598

1599
        if (config->entry_oneshot) {
31✔
UNCOV
1600
                i = boot_config_find(config, config->entry_oneshot);
×
1601
                if (i >= 0) {
×
1602
                        log_debug("Found default: id \"%s\" is matched by LoaderEntryOneShot",
×
1603
                                  config->entries[i].id);
1604
                        return i;
1605
                }
1606
        }
1607

1608
        if (config->entry_preferred) {
31✔
UNCOV
1609
                i = boot_config_find(config, config->entry_preferred);
×
1610
                if (i >= 0) {
×
1611
                        log_debug("Found default: id \"%s\" is matched by LoaderEntryPreferred",
×
1612
                                  config->entries[i].id);
1613
                        return i;
1614
                }
1615
        }
1616

1617
        if (config->entry_default) {
31✔
1618
                i = boot_config_find(config, config->entry_default);
3✔
1619
                if (i >= 0) {
3✔
1620
                        log_debug("Found default: id \"%s\" is matched by LoaderEntryDefault",
3✔
1621
                                  config->entries[i].id);
1622
                        return i;
1623
                }
1624
        }
1625

1626
        if (config->preferred_pattern) {
28✔
UNCOV
1627
                i = boot_config_find(config, config->preferred_pattern);
×
1628
                if (i >= 0) {
×
1629
                        log_debug("Found preferred: id \"%s\" is matched by pattern \"%s\"",
×
1630
                                  config->entries[i].id, config->preferred_pattern);
1631
                        return i;
1632
                }
1633
        }
1634

1635
        if (config->default_pattern) {
28✔
1636
                i = boot_config_find(config, config->default_pattern);
10✔
1637
                if (i >= 0) {
10✔
1638
                        log_debug("Found default: id \"%s\" is matched by pattern \"%s\"",
10✔
1639
                                  config->entries[i].id, config->default_pattern);
1640
                        return i;
1641
                }
1642
        }
1643

1644
        log_debug("Found default: first entry \"%s\"", config->entries[0].id);
18✔
1645
        return 0;
1646
}
1647

1648
static int boot_entries_select_selected(const BootConfig *config) {
45✔
1649
        assert(config);
45✔
1650
        assert(config->entries || config->n_entries == 0);
45✔
1651

1652
        if (!config->entry_selected || config->n_entries == 0)
45✔
1653
                return -1;
1654

1655
        return boot_config_find(config, config->entry_selected);
31✔
1656
}
1657

1658
static int boot_load_efi_entry_pointers(BootConfig *config, bool skip_efivars) {
45✔
1659
        int r;
45✔
1660

1661
        assert(config);
45✔
1662

1663
        if (skip_efivars || !is_efi_boot())
45✔
1664
                return 0;
1665

1666
        /* Loads the three "pointers" to boot loader entries from their EFI variables */
1667

1668
        r = efi_get_variable_string(EFI_LOADER_VARIABLE_STR("LoaderEntryOneShot"), &config->entry_oneshot);
31✔
1669
        if (r == -ENOMEM)
31✔
UNCOV
1670
                return log_oom();
×
1671
        if (r < 0 && !IN_SET(r, -ENOENT, -ENODATA))
31✔
UNCOV
1672
                log_warning_errno(r, "Failed to read EFI variable \"LoaderEntryOneShot\", ignoring: %m");
×
1673

1674
        r = efi_get_variable_string(EFI_LOADER_VARIABLE_STR("LoaderEntryPreferred"), &config->entry_preferred);
31✔
1675
        if (r == -ENOMEM)
31✔
UNCOV
1676
                return log_oom();
×
1677
        if (r < 0 && !IN_SET(r, -ENOENT, -ENODATA))
31✔
UNCOV
1678
                log_warning_errno(r, "Failed to read EFI variable \"LoaderEntryPreferred\", ignoring: %m");
×
1679

1680
        r = efi_get_variable_string(EFI_LOADER_VARIABLE_STR("LoaderEntryDefault"), &config->entry_default);
31✔
1681
        if (r == -ENOMEM)
31✔
UNCOV
1682
                return log_oom();
×
1683
        if (r < 0 && !IN_SET(r, -ENOENT, -ENODATA))
31✔
UNCOV
1684
                log_warning_errno(r, "Failed to read EFI variable \"LoaderEntryDefault\", ignoring: %m");
×
1685

1686
        r = efi_get_variable_string(EFI_LOADER_VARIABLE_STR("LoaderEntrySelected"), &config->entry_selected);
31✔
1687
        if (r == -ENOMEM)
31✔
UNCOV
1688
                return log_oom();
×
1689
        if (r < 0 && !IN_SET(r, -ENOENT, -ENODATA))
31✔
UNCOV
1690
                log_warning_errno(r, "Failed to read EFI variable \"LoaderEntrySelected\", ignoring: %m");
×
1691

1692
        r = efi_get_variable_string(EFI_LOADER_VARIABLE_STR("LoaderEntrySysFail"), &config->entry_sysfail);
31✔
1693
        if (r == -ENOMEM)
31✔
UNCOV
1694
                return log_oom();
×
1695
        if (r < 0 && !IN_SET(r, -ENOENT, -ENODATA))
31✔
UNCOV
1696
                log_warning_errno(r, "Failed to read EFI variable \"LoaderEntrySysFail\", ignoring: %m");
×
1697

1698
        return 1;
1699
}
1700

1701
int boot_config_select_special_entries(BootConfig *config, bool skip_efivars) {
45✔
1702
        int r;
45✔
1703

1704
        assert(config);
45✔
1705

1706
        r = boot_load_efi_entry_pointers(config, skip_efivars);
45✔
1707
        if (r < 0)
45✔
1708
                return r;
1709

1710
        config->default_entry = boot_entries_select_default(config);
45✔
1711
        config->selected_entry = boot_entries_select_selected(config);
45✔
1712

1713
        return 0;
45✔
1714
}
1715

1716
int boot_config_finalize(BootConfig *config) {
47✔
1717
        int r;
47✔
1718

1719
        typesafe_qsort(config->entries, config->n_entries, boot_entry_compare);
47✔
1720

1721
        r = boot_entries_uniquify(config->entries, config->n_entries);
47✔
1722
        if (r < 0)
47✔
UNCOV
1723
                return log_error_errno(r, "Failed to uniquify boot entries: %m");
×
1724

1725
        return 0;
1726
}
1727

1728
static int boot_entries_load(
61✔
1729
                BootConfig *config,
1730
                BootEntrySource source,
1731
                const char *where) { /* Mount point of ESP/XBOOTLDR */
1732

1733
        int r;
61✔
1734

1735
        assert(config);
61✔
1736
        assert(source >= 0);
61✔
1737
        assert(source < _BOOT_ENTRY_SOURCE_MAX);
61✔
1738

1739
        if (!where)
61✔
1740
                return 0;
1741

1742
        r = boot_entries_find_type1(config, where, source, "/loader/entries");
61✔
1743
        if (r < 0)
61✔
1744
                return r;
1745

1746
        r = boot_entries_find_unified(config, where, source, "/EFI/Linux/");
61✔
1747
        if (r < 0)
61✔
1748
                return r;
1749

1750
        static const struct {
1751
                BootEntryExtraType extra_type;
1752
                const char *directory;
1753
        } table[] = {
1754
                { BOOT_ENTRY_ADDON,      "/loader/addons/"      },
1755
                { BOOT_ENTRY_CONFEXT,    "/loader/extensions/"  },
1756
                { BOOT_ENTRY_SYSEXT,     "/loader/extensions/"  },
1757
                { BOOT_ENTRY_CREDENTIAL, "/loader/credentials/" },
1758
        };
1759

1760
        FOREACH_ELEMENT(i, table) {
305✔
1761
                r = boot_entries_find_unified_global_extras(
488✔
1762
                                config,
1763
                                where,
1764
                                i->directory,
244✔
1765
                                i->extra_type,
244✔
1766
                                &config->global_extras[source]);
1767
                if (r < 0)
244✔
1768
                        return r;
1769
        }
1770

1771
        return 0;
1772
}
1773

1774
int boot_config_load(
47✔
1775
                BootConfig *config,
1776
                const char *esp_path,
1777
                const char *xbootldr_path) {
1778

1779
        int r;
47✔
1780

1781
        assert(config);
47✔
1782

1783
        if (esp_path) {
47✔
1784
                r = boot_loader_read_conf_path(config, esp_path, "/loader/loader.conf");
47✔
1785
                if (r < 0)
47✔
1786
                        return r;
1787

1788
                r = boot_entries_load(config, BOOT_ENTRY_ESP, esp_path);
47✔
1789
                if (r < 0)
47✔
1790
                        return r;
1791
        }
1792

1793
        if (xbootldr_path) {
47✔
1794
                r = boot_entries_load(config, BOOT_ENTRY_XBOOTLDR, xbootldr_path);
14✔
1795
                if (r < 0)
14✔
1796
                        return r;
1797
        }
1798

1799
        return boot_config_finalize(config);
47✔
1800
}
1801

1802
int boot_config_load_auto(
2✔
1803
                BootConfig *config,
1804
                const char *override_esp_path,
1805
                const char *override_xbootldr_path) {
1806

1807
        _cleanup_free_ char *esp_where = NULL, *xbootldr_where = NULL;
2✔
1808
        dev_t esp_devid = 0, xbootldr_devid = 0;
2✔
1809
        int r;
2✔
1810

1811
        assert(config);
2✔
1812

1813
        /* This function is similar to boot_entries_load_config(), however we automatically search for the
1814
         * ESP and the XBOOTLDR partition unless it is explicitly specified. Also, if the user did not pass
1815
         * an ESP or XBOOTLDR path directly, let's see if /run/boot-loader-entries/ exists. If so, let's
1816
         * read data from there, as if it was an ESP (i.e. loading both entries and loader.conf data from
1817
         * it). This allows other boot loaders to pass boot loader entry information to our tools if they
1818
         * want to. */
1819

1820
        if (!override_esp_path && !override_xbootldr_path) {
2✔
1821
                if (access("/run/boot-loader-entries/", F_OK) >= 0)
2✔
UNCOV
1822
                        return boot_config_load(config, "/run/boot-loader-entries/", NULL);
×
1823

1824
                if (errno != ENOENT)
2✔
UNCOV
1825
                        return log_error_errno(errno,
×
1826
                                               "Failed to determine whether /run/boot-loader-entries/ exists: %m");
1827
        }
1828

1829
        r = find_esp_and_warn_full(
2✔
1830
                        /* root= */ NULL,
1831
                        override_esp_path,
1832
                        /* unprivileged_mode= */ false,
1833
                        &esp_where,
1834
                        /* ret_fd= */ NULL,
1835
                        /* ret_part= */ NULL,
1836
                        /* ret_pstart= */ NULL,
1837
                        /* ret_psize= */ NULL,
1838
                        /* ret_uuid= */ NULL,
1839
                        &esp_devid);
1840
        if (r < 0) /* we don't log about ENOKEY here, but propagate it, leaving it to the caller to log */
2✔
1841
                return r;
1842

1843
        r = find_xbootldr_and_warn_full(
2✔
1844
                        /* root= */ NULL,
1845
                        override_xbootldr_path,
1846
                        /* unprivileged_mode= */ false,
1847
                        &xbootldr_where,
1848
                        /* ret_fd= */ NULL,
1849
                        /* ret_uuid= */ NULL,
1850
                        &xbootldr_devid);
1851
        if (r < 0 && r != -ENOKEY)
2✔
1852
                return r; /* It's fine if the XBOOTLDR partition doesn't exist, hence we ignore ENOKEY here */
1853

1854
        /* If both paths actually refer to the same inode, suppress the xbootldr path */
1855
        if (esp_where && xbootldr_where && devnum_set_and_equal(esp_devid, xbootldr_devid))
2✔
UNCOV
1856
                xbootldr_where = mfree(xbootldr_where);
×
1857

1858
        return boot_config_load(config, esp_where, xbootldr_where);
2✔
1859
}
1860

1861
int boot_config_augment_from_loader(
29✔
1862
                BootConfig *config,
1863
                char **found_by_loader,
1864
                bool auto_only) {
1865

1866
        static const BootEntryExtras no_extras = (BootEntryExtras) {};
29✔
1867
        static const char *const title_table[] = {
29✔
1868
                /* Pretty names for a few well-known automatically discovered entries. */
1869
                "auto-osx",                      "macOS",
1870
                "auto-windows",                  "Windows Boot Manager",
1871
                "auto-efi-shell",                "EFI Shell",
1872
                "auto-efi-default",              "EFI Default Loader",
1873
                "auto-poweroff",                 "Power Off The System",
1874
                "auto-reboot",                   "Reboot The System",
1875
                "auto-reboot-to-firmware-setup", "Reboot Into Firmware Interface",
1876
                NULL,
1877
        };
1878

1879
        assert(config);
29✔
1880

1881
        /* Let's add the entries discovered by the boot loader to the end of our list, unless they are
1882
         * already included there. */
1883

1884
        STRV_FOREACH(i, found_by_loader) {
145✔
1885
                BootEntry *existing;
116✔
1886
                _cleanup_free_ char *c = NULL, *t = NULL, *p = NULL;
96✔
1887

1888
                existing = boot_config_find_entry(config, *i);
116✔
1889
                if (existing) {
116✔
1890
                        existing->reported_by_loader = true;
20✔
1891
                        continue;
20✔
1892
                }
1893

1894
                if (auto_only && !startswith(*i, "auto-"))
96✔
UNCOV
1895
                        continue;
×
1896

1897
                c = strdup(*i);
96✔
1898
                if (!c)
96✔
UNCOV
1899
                        return log_oom();
×
1900

1901
                STRV_FOREACH_PAIR(a, b, title_table)
739✔
1902
                        if (streq(*a, *i)) {
672✔
1903
                                t = strdup(*b);
29✔
1904
                                if (!t)
29✔
UNCOV
1905
                                        return log_oom();
×
1906
                                break;
1907
                        }
1908

1909
                p = strdup(EFIVAR_PATH(EFI_LOADER_VARIABLE_STR("LoaderEntries")));
96✔
1910
                if (!p)
96✔
UNCOV
1911
                        return log_oom();
×
1912

1913
                if (!GREEDY_REALLOC0(config->entries, config->n_entries + 1))
96✔
UNCOV
1914
                        return log_oom();
×
1915

1916
                config->entries[config->n_entries++] = (BootEntry) {
192✔
1917
                        .type = startswith(*i, "auto-") ? BOOT_ENTRY_AUTO : BOOT_ENTRY_LOADER,
96✔
1918
                        .id = TAKE_PTR(c),
96✔
1919
                        .title = TAKE_PTR(t),
96✔
1920
                        .path = TAKE_PTR(p),
96✔
1921
                        .reported_by_loader = true,
1922
                        .tries_left = UINT_MAX,
1923
                        .tries_done = UINT_MAX,
1924
                        .profile = UINT_MAX,
1925
                        .global_extras = &no_extras,
1926
                };
1927
        }
1928

1929
        return 0;
1930
}
1931

1932
BootEntry* boot_config_find_entry(BootConfig *config, const char *id) {
120✔
1933
        assert(config);
120✔
1934
        assert(id);
120✔
1935

1936
        for (size_t j = 0; j < config->n_entries; j++)
385✔
1937
                if (strcaseeq_ptr(config->entries[j].id, id) ||
288✔
1938
                    strcaseeq_ptr(config->entries[j].id_old, id))
265✔
1939
                        return config->entries + j;
1940

1941
        return NULL;
1942
}
1943

1944
static void boot_entry_file_list(
18✔
1945
                const char *field,
1946
                const char *root,
1947
                const char *p,
1948
                int *pstatus) {
1949

1950
        assert(p);
18✔
1951
        assert(pstatus);
18✔
1952

1953
        int status = chase_and_access(p, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, F_OK, NULL);
18✔
1954

1955
        /* Note that this shows two '/' between the root and the file. This is intentional to highlight (in
1956
         * the absence of color support) to the user that the boot loader is only interested in the second
1957
         * part of the file. */
1958
        printf("%13s%s %s%s/%s", strempty(field), field ? ":" : " ", ansi_grey(), root, ansi_normal());
36✔
1959

1960
        if (status < 0) {
18✔
UNCOV
1961
                errno = -status;
×
1962
                printf("%s%s%s (%m)\n", ansi_highlight_red(), p, ansi_normal());
×
1963
        } else
1964
                printf("%s\n", p);
18✔
1965

1966
        if (*pstatus == 0 && status < 0)
18✔
UNCOV
1967
                *pstatus = status;
×
1968
}
18✔
1969

1970
static void print_extra(
9✔
1971
                const BootEntry *e,
1972
                const BootEntryExtra *extra,
1973
                const char *field,
1974
                int *status) {
1975

1976
        assert(e);
9✔
1977
        assert(extra);
9✔
1978

1979
        boot_entry_file_list(field, e->root, extra->location, status);
9✔
1980

1981
        if (extra->cmdline)
9✔
1982
                printf("      options: %s%s\n", glyph(GLYPH_TREE_RIGHT), extra->cmdline);
9✔
1983
}
9✔
1984

1985
static int indent_embedded_newlines(char *cmdline, char **ret_cmdline) {
18✔
1986
        _cleanup_free_ char *t = NULL;
18✔
1987
        _cleanup_strv_free_ char **ts = NULL;
18✔
1988

1989
        assert(ret_cmdline);
18✔
1990

1991
        ts = strv_split_newlines(cmdline);
18✔
1992
        if (!ts)
18✔
1993
                return -ENOMEM;
1994

1995
        t = strv_join(ts, "\n              ");
18✔
1996
        if (!t)
18✔
1997
                return -ENOMEM;
1998

1999
        *ret_cmdline = TAKE_PTR(t);
18✔
2000
        return 0;
18✔
2001
}
2002

2003
static int print_cmdline(const BootEntry *e, int *status) {
22✔
2004
        _cleanup_free_ char *options = NULL, *combined_cmdline = NULL, *t2 = NULL;
22✔
2005

2006
        assert(e);
22✔
2007

2008
        if (!strv_isempty(e->options)) {
22✔
2009
                _cleanup_free_ char *t = NULL;
9✔
2010

2011
                options = strv_join(e->options, " ");
9✔
2012
                if (!options)
9✔
UNCOV
2013
                        return log_oom();
×
2014

2015
                if (indent_embedded_newlines(options, &t) < 0)
9✔
UNCOV
2016
                        return log_oom();
×
2017

2018
                printf("      options: %s\n", t);
9✔
2019
                t2 = strdup(options);
9✔
2020
                if (!t2)
9✔
UNCOV
2021
                        return log_oom();
×
2022
        }
2023

2024
        FOREACH_ARRAY(extra, e->global_extras->items, e->global_extras->n_items) {
31✔
2025
                print_extra(e, extra, "extra", status);
9✔
2026

2027
                if (extra->cmdline)
9✔
2028
                        if (!strextend(&t2, " ", extra->cmdline))
9✔
UNCOV
2029
                                return log_oom();
×
2030
        }
2031

2032
        FOREACH_ARRAY(extra, e->local_extras.items, e->local_extras.n_items) {
22✔
UNCOV
2033
                print_extra(e, extra, "extra", status);
×
2034

UNCOV
2035
                if (extra->cmdline)
×
2036
                        if (!strextend(&t2, " ", extra->cmdline))
×
2037
                                return log_oom();
×
2038
        }
2039

2040
        /* Don't print the combined cmdline if it's same as options. */
2041
        if (streq_ptr(t2, options))
22✔
2042
                return 0;
2043

2044
        if (indent_embedded_newlines(t2, &combined_cmdline) < 0)
9✔
UNCOV
2045
                return log_oom();
×
2046

2047
        if (combined_cmdline)
9✔
2048
                printf("      cmdline: %s\n", combined_cmdline);
9✔
2049

2050
        return 0;
2051
}
2052

2053
static int json_addon(
3✔
2054
                const BootEntryExtra *extra,
2055
                const char *extra_str,
2056
                sd_json_variant **array) {
2057

2058
        int r;
3✔
2059

2060
        assert(extra);
3✔
2061
        assert(extra_str);
3✔
2062

2063
        r = sd_json_variant_append_arraybo(
3✔
2064
                        array,
2065
                        SD_JSON_BUILD_PAIR_STRING(extra_str, extra->location),
2066
                        JSON_BUILD_PAIR_STRING_NON_EMPTY("options", extra->cmdline));
2067
        if (r < 0)
3✔
UNCOV
2068
                return log_oom();
×
2069

2070
        return 0;
2071
}
2072

2073
static int json_cmdline(
18✔
2074
                const BootEntry *e,
2075
                const char *def_cmdline,
2076
                sd_json_variant **v) {
2077

2078
        _cleanup_free_ char *combined_cmdline = NULL;
18✔
2079
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *addons_array = NULL;
18✔
2080
        int r;
18✔
2081

2082
        assert(e);
18✔
2083

2084
        if (def_cmdline) {
18✔
2085
                combined_cmdline = strdup(def_cmdline);
3✔
2086
                if (!combined_cmdline)
3✔
UNCOV
2087
                        return log_oom();
×
2088
        }
2089

2090
        /* NB: these JSON fields are kinda obsolete, we want the more generic 'extra' ones to be used. */
2091
        FOREACH_ARRAY(extra, e->global_extras->items, e->global_extras->n_items) {
21✔
2092
                if (extra->type != BOOT_ENTRY_ADDON)
3✔
UNCOV
2093
                        continue;
×
2094

2095
                r = json_addon(extra, "globalAddon", &addons_array);
3✔
2096
                if (r < 0)
3✔
2097
                        return r;
2098

2099
                if (extra->cmdline)
3✔
2100
                        if (!strextend(&combined_cmdline, " ", extra->cmdline))
3✔
UNCOV
2101
                                return log_oom();
×
2102
        }
2103

2104
        FOREACH_ARRAY(extra, e->local_extras.items, e->local_extras.n_items) {
18✔
UNCOV
2105
                if (extra->type != BOOT_ENTRY_ADDON)
×
2106
                        continue;
×
2107

UNCOV
2108
                r = json_addon(extra, "localAddon", &addons_array);
×
2109
                if (r < 0)
×
2110
                        return r;
2111

UNCOV
2112
                if (extra->cmdline)
×
2113
                        if (!strextend(&combined_cmdline, " ", extra->cmdline))
×
2114
                                return log_oom();
×
2115
        }
2116

2117
        r = sd_json_variant_merge_objectbo(
18✔
2118
                        v,
2119
                        SD_JSON_BUILD_PAIR_VARIANT("addons", addons_array),
2120
                        SD_JSON_BUILD_PAIR_CONDITION(!!combined_cmdline, "cmdline", SD_JSON_BUILD_STRING(combined_cmdline)));
2121
        if (r < 0)
18✔
UNCOV
2122
                return log_oom();
×
2123
        return 0;
2124
}
2125

2126
int show_boot_entry(
22✔
2127
                const BootEntry *e,
2128
                bool show_as_default,
2129
                bool show_as_selected,
2130
                bool show_reported) {
2131

2132
        int status = 0, r = 0;
22✔
2133

2134
        /* Returns 0 on success, negative on processing error, and positive if something is wrong with the
2135
           boot entry itself. */
2136

2137
        assert(e);
22✔
2138

2139
        printf("         type: %s\n",
22✔
2140
               boot_entry_type_description_to_string(e->type));
22✔
2141

2142
        printf("        title: %s%s%s",
66✔
2143
               ansi_highlight(), boot_entry_title(e), ansi_normal());
2144

2145
        if (show_as_default)
22✔
2146
                printf(" %s(default)%s",
4✔
2147
                       ansi_highlight_green(), ansi_normal());
2148

2149
        if (show_as_selected)
22✔
2150
                printf(" %s(selected)%s",
4✔
2151
                       ansi_highlight_magenta(), ansi_normal());
2152

2153
        if (show_reported) {
22✔
2154
                if (e->type == BOOT_ENTRY_LOADER)
8✔
2155
                        printf(" %s(reported/absent)%s",
12✔
2156
                               ansi_highlight_red(), ansi_normal());
2157
                else if (!e->reported_by_loader && e->type != BOOT_ENTRY_AUTO)
2✔
UNCOV
2158
                        printf(" %s(not reported/new)%s",
×
2159
                               ansi_highlight_green(), ansi_normal());
2160
        }
2161

2162
        putchar('\n');
22✔
2163

2164
        if (e->id) {
22✔
2165
                printf("           id: %s", e->id);
22✔
2166

2167
                if (e->id_without_profile && !streq_ptr(e->id, e->id_without_profile))
22✔
2168
                        printf(" (without profile: %s)\n", e->id_without_profile);
9✔
2169
                else
2170
                        putchar('\n');
13✔
2171
        }
2172
        if (e->path) {
22✔
2173
                _cleanup_free_ char *text = NULL, *link = NULL;
22✔
2174

2175
                const char *p = e->root ? path_startswith(e->path, e->root) : NULL;
22✔
2176
                if (p) {
9✔
2177
                        text = strjoin(ansi_grey(), e->root, "/", ansi_normal(), "/", p);
18✔
2178
                        if (!text)
9✔
UNCOV
2179
                                return log_oom();
×
2180
                }
2181

2182
                /* Let's urlify the link to make it easy to view in an editor, but only if it is a text
2183
                 * file. Unified images are binary ELFs, and EFI variables are not pure text either. */
2184
                if (e->type == BOOT_ENTRY_TYPE1)
22✔
UNCOV
2185
                        (void) terminal_urlify_path(e->path, text, &link);
×
2186

2187
                printf("       source: %s (on the %s)\n",
22✔
2188
                       link ?: text ?: e->path,
22✔
2189
                       boot_entry_source_description_to_string(e->source));
22✔
2190
        }
2191
        if (e->tries_left != UINT_MAX) {
22✔
UNCOV
2192
                printf("        tries: %u left", e->tries_left);
×
2193

UNCOV
2194
                if (e->tries_done != UINT_MAX)
×
2195
                        printf("; %u done\n", e->tries_done);
×
2196
                else
UNCOV
2197
                        putchar('\n');
×
2198
        }
2199

2200
        if (e->sort_key)
22✔
2201
                printf("     sort-key: %s\n", e->sort_key);
9✔
2202
        if (e->version)
22✔
2203
                printf("      version: %s\n", e->version);
9✔
2204
        if (e->machine_id)
22✔
UNCOV
2205
                printf("   machine-id: %s\n", e->machine_id);
×
2206
        if (e->architecture)
22✔
UNCOV
2207
                printf(" architecture: %s\n", e->architecture);
×
2208
        if (e->kernel)
22✔
2209
                boot_entry_file_list("linux", e->root, e->kernel, &status);
9✔
2210
        if (e->efi)
22✔
UNCOV
2211
                boot_entry_file_list("efi", e->root, e->efi, &status);
×
2212
        if (e->uki)
22✔
UNCOV
2213
                boot_entry_file_list("uki", e->root, e->uki, &status);
×
2214
        if (e->uki_url)
22✔
UNCOV
2215
                printf("      uki-url: %s\n", e->uki_url);
×
2216
        if (e->profile != UINT_MAX)
22✔
2217
                printf("      profile: %u\n", e->profile);
9✔
2218

2219
        STRV_FOREACH(s, e->initrd)
22✔
UNCOV
2220
                boot_entry_file_list(s == e->initrd ? "initrd" : NULL,
×
2221
                                     e->root,
×
2222
                                     *s,
2223
                                     &status);
2224

2225
        r = print_cmdline(e, &status);
22✔
2226
        if (r < 0)
22✔
2227
                return r;
2228

2229
        if (e->device_tree)
22✔
UNCOV
2230
                boot_entry_file_list("devicetree", e->root, e->device_tree, &status);
×
2231

2232
        STRV_FOREACH(s, e->device_tree_overlay)
22✔
UNCOV
2233
                boot_entry_file_list(s == e->device_tree_overlay ? "devicetree-overlay" : NULL,
×
2234
                                     e->root,
×
2235
                                     *s,
2236
                                     &status);
2237

2238
        return -status;
22✔
2239
}
2240

2241
int boot_entry_to_json(const BootConfig *c, size_t i, sd_json_variant **ret) {
18✔
2242
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
18✔
2243
        _cleanup_free_ char *opts = NULL;
18✔
2244
        const BootEntry *e;
18✔
2245
        int r;
18✔
2246

2247
        assert(c);
18✔
2248
        assert(ret);
18✔
2249

2250
        if (i >= c->n_entries) {
18✔
UNCOV
2251
                *ret = NULL;
×
2252
                return 0;
×
2253
        }
2254

2255
        e = c->entries + i;
18✔
2256

2257
        if (!strv_isempty(e->options)) {
18✔
2258
                opts = strv_join(e->options, " ");
3✔
2259
                if (!opts)
3✔
UNCOV
2260
                        return log_oom();
×
2261
        }
2262

2263
        r = sd_json_variant_merge_objectbo(
18✔
2264
                        &v,
2265
                        SD_JSON_BUILD_PAIR_STRING("type", boot_entry_type_to_string(e->type)),
2266
                        SD_JSON_BUILD_PAIR_STRING("source", boot_entry_source_to_string(e->source)),
2267
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->id, "id", SD_JSON_BUILD_STRING(e->id)),
2268
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->path, "path", SD_JSON_BUILD_STRING(e->path)),
2269
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->root, "root", SD_JSON_BUILD_STRING(e->root)),
2270
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->title, "title", SD_JSON_BUILD_STRING(e->title)),
2271
                        SD_JSON_BUILD_PAIR_CONDITION(!!boot_entry_title(e), "showTitle", SD_JSON_BUILD_STRING(boot_entry_title(e))),
2272
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->sort_key, "sortKey", SD_JSON_BUILD_STRING(e->sort_key)),
2273
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->version, "version", SD_JSON_BUILD_STRING(e->version)),
2274
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->machine_id, "machineId", SD_JSON_BUILD_STRING(e->machine_id)),
2275
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->architecture, "architecture", SD_JSON_BUILD_STRING(e->architecture)),
2276
                        SD_JSON_BUILD_PAIR_CONDITION(!!opts, "options", SD_JSON_BUILD_STRING(opts)),
2277
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->kernel, "linux", SD_JSON_BUILD_STRING(e->kernel)),
2278
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->efi, "efi", SD_JSON_BUILD_STRING(e->efi)),
2279
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->uki, "uki", SD_JSON_BUILD_STRING(e->uki)),
2280
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->uki_url, "ukiUrl", SD_JSON_BUILD_STRING(e->uki_url)),
2281
                        SD_JSON_BUILD_PAIR_CONDITION(e->profile != UINT_MAX, "profile", SD_JSON_BUILD_UNSIGNED(e->profile)),
2282
                        SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(e->initrd), "initrd", SD_JSON_BUILD_STRV(e->initrd)));
2283
        if (r < 0)
18✔
UNCOV
2284
                return log_oom();
×
2285

2286
        /* Sanitizers (only memory sanitizer?) do not like function call with too many
2287
         * arguments and trigger false positive warnings. Let's not add too many json objects
2288
         * at once. */
2289
        r = sd_json_variant_merge_objectbo(
18✔
2290
                        &v,
2291
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->device_tree, "devicetree", SD_JSON_BUILD_STRING(e->device_tree)),
2292
                        SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(e->device_tree_overlay), "devicetreeOverlay", SD_JSON_BUILD_STRV(e->device_tree_overlay)),
2293
                        SD_JSON_BUILD_PAIR_BOOLEAN("isReported", e->reported_by_loader),
2294
                        SD_JSON_BUILD_PAIR_CONDITION(e->tries_left != UINT_MAX, "triesLeft", SD_JSON_BUILD_UNSIGNED(e->tries_left)),
2295
                        SD_JSON_BUILD_PAIR_CONDITION(e->tries_done != UINT_MAX, "triesDone", SD_JSON_BUILD_UNSIGNED(e->tries_done)),
2296
                        SD_JSON_BUILD_PAIR_CONDITION(c->default_entry >= 0, "isDefault", SD_JSON_BUILD_BOOLEAN(i == (size_t) c->default_entry)),
2297
                        SD_JSON_BUILD_PAIR_CONDITION(c->selected_entry >= 0, "isSelected", SD_JSON_BUILD_BOOLEAN(i == (size_t) c->selected_entry)));
2298
        if (r < 0)
18✔
UNCOV
2299
                return log_oom();
×
2300

2301
        r = json_cmdline(e, opts, &v);
18✔
2302
        if (r < 0)
18✔
UNCOV
2303
                return log_oom();
×
2304

2305
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *jextras = NULL;
18✔
2306
        FOREACH_ARRAY(extra, e->global_extras->items, e->global_extras->n_items) {
21✔
2307
                r = sd_json_variant_append_arrayb(&jextras, SD_JSON_BUILD_STRING(extra->location));
3✔
2308
                if (r < 0)
3✔
UNCOV
2309
                        return log_oom();
×
2310
        }
2311
        FOREACH_ARRAY(extra, e->local_extras.items, e->local_extras.n_items) {
18✔
UNCOV
2312
                r = sd_json_variant_append_arrayb(&jextras, SD_JSON_BUILD_STRING(extra->location));
×
2313
                if (r < 0)
×
2314
                        return log_oom();
×
2315
        }
2316

2317
        r = sd_json_variant_merge_objectbo(
18✔
2318
                        &v,
2319
                        SD_JSON_BUILD_PAIR_CONDITION(!!jextras, "extras", SD_JSON_BUILD_VARIANT(jextras)));
2320
        if (r < 0)
18✔
UNCOV
2321
                return log_oom();
×
2322

2323
        *ret = TAKE_PTR(v);
18✔
2324
        return 1;
18✔
2325
}
2326

2327
int show_boot_entries(const BootConfig *config, sd_json_format_flags_t json_format) {
9✔
2328
        int r;
9✔
2329

2330
        assert(config);
9✔
2331

2332
        if (sd_json_format_enabled(json_format)) {
9✔
2333
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL;
7✔
2334

2335
                for (size_t i = 0; i < config->n_entries; i++) {
20✔
2336
                        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
13✔
2337

2338
                        r = boot_entry_to_json(config, i, &v);
13✔
2339
                        if (r < 0)
13✔
UNCOV
2340
                                return log_oom();
×
2341

2342
                        r = sd_json_variant_append_array(&array, v);
13✔
2343
                        if (r < 0)
13✔
UNCOV
2344
                                return log_oom();
×
2345
                }
2346

2347
                return sd_json_variant_dump(array, json_format | SD_JSON_FORMAT_EMPTY_ARRAY, NULL, NULL);
7✔
2348
        } else
2349
                for (size_t n = 0; n < config->n_entries; n++) {
10✔
2350
                        r = show_boot_entry(
16✔
2351
                                        config->entries + n,
8✔
2352
                                        /* show_as_default= */  n == (size_t) config->default_entry,
8✔
2353
                                        /* show_as_selected= */ n == (size_t) config->selected_entry,
8✔
2354
                                        /* show_reported= */  true);
2355
                        if (r < 0)
8✔
2356
                                return r;
2357

2358
                        if (n+1 < config->n_entries)
8✔
2359
                                putchar('\n');
6✔
2360
                }
2361

2362
        return 0;
2363
}
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