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

systemd / systemd / 29378720657

14 Jul 2026 11:44PM UTC coverage: 72.936% (-0.004%) from 72.94%
29378720657

push

github

yuwata
sd-dhcp-relay: fix off-by-one when discarding BOOTREQUEST messages by hops count

According to RFC specifications
```
RFC 1542 section 4.1.1 states:
The relay agent MUST silently discard BOOTREQUEST messages whose 'hops'
   field exceeds the value 16."
```

"Exceeds the value 16" means hops > 16, i.e. a message that arrives with
hops == 16 is still valid and must be relayed (after which its hops field
becomes 17). The code used ">= 16", which silently dropped a valid message
that had legitimately traversed exactly 16 relay agents, one hop too early.

This matches the wording of the adjacent comment, which already says
"exceeds the value 16".

2 of 2 new or added lines in 2 files covered. (100.0%)

4253 existing lines in 80 files now uncovered.

345834 of 474164 relevant lines covered (72.94%)

1318659.74 hits per line

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

78.07
/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) {
20✔
70
        if (!path)
20✔
71
                return _BOOT_ENTRY_EXTRA_TYPE_INVALID;
72

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

82
        return _BOOT_ENTRY_EXTRA_TYPE_INVALID;
83
}
84

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

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

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

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

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

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

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

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

127
        return 0;
20✔
128
}
129

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

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

155
static int mangle_path(
23✔
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;
23✔
163

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

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

173
        /* We only reference files, never directories */
174
        if (endswith(c, "/")) {
23✔
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);
23✔
182

183
        /* No ".." or "." or so */
184
        if (!path_is_normalized(c)) {
23✔
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);
23✔
191
        return 1;
23✔
192
}
193

194
static int parse_path_one(
15✔
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;
15✔
202
        int r;
15✔
203

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

274
        int r;
8✔
275

276
        assert(extras);
8✔
277

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

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

290
                BootEntryExtraType type = boot_entry_extra_type_from_filename(c);
8✔
291
                if (type < 0) {
8✔
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) {
8✔
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);
8✔
304
                if (r < 0)
8✔
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(
120✔
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;
120✔
350
        _cleanup_free_ char *stripped = NULL;
120✔
351
        const char *p, *suffix, *m;
120✔
352
        int r;
120✔
353

354
        assert(fname);
120✔
355
        assert(ret_stripped);
120✔
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, '.');
120✔
360
        if (!suffix)
120✔
361
                goto nothing;
1✔
362

363
        p = m = memrchr(fname, '+', suffix - fname);
119✔
364
        if (!p)
119✔
365
                goto nothing;
91✔
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✔
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:
97✔
403
        stripped = strdup(fname);
97✔
404
        if (!stripped)
97✔
405
                return -ENOMEM;
406

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

415
static int boot_entry_load_type1(
23✔
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);
23✔
424
        char *c;
23✔
425
        int r;
23✔
426

427
        assert(f);
23✔
428
        assert(root);
23✔
429
        assert(dir);
23✔
430
        assert(fname);
23✔
431
        assert(ret);
23✔
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);
23✔
436
        if (r < 0)
23✔
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))
23✔
440
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry name: %s", fname);
×
441

442
        c = endswith_no_case(tmp.id, ".conf");
23✔
443
        if (!c)
23✔
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 */
23✔
447
        if (!tmp.id_old)
23✔
448
                return log_oom();
×
449

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

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

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

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

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

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

483
                if (isempty(p)) {
110✔
484
                        /* Some fields can reasonably have an empty value. In other cases warn. */
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

488
                        continue;
×
489
                }
490

491
                if (streq(field, "title"))
110✔
492
                        r = free_and_strdup(&tmp.title, p);
23✔
493
                else if (streq(field, "sort-key"))
87✔
494
                        r = free_and_strdup(&tmp.sort_key, p);
18✔
495
                else if (streq(field, "version")) {
69✔
496
                        if (!version_is_valid(p, VERSION_ALLOW_UNDERSCORE|VERSION_ALLOW_PLUS))
23✔
UNCOV
497
                                log_syntax(NULL, LOG_WARNING, tmp.path, line, 0, "Version string '%s' is not a valid version, accepting anyway.", p);
×
498

499
                        r = free_and_strdup(&tmp.version, p);
23✔
500
                } else if (streq(field, "machine-id"))
46✔
501
                        r = free_and_strdup(&tmp.machine_id, p);
23✔
502
                else if (streq(field, "architecture"))
23✔
UNCOV
503
                        r = free_and_strdup(&tmp.architecture, p);
×
504
                else if (streq(field, "options"))
23✔
UNCOV
505
                        r = strv_extend(&tmp.options, p);
×
506
                else if (streq(field, "linux"))
23✔
UNCOV
507
                        r = parse_path_one(tmp.path, line, field, &tmp.kernel, p);
×
508
                else if (streq(field, "efi"))
23✔
UNCOV
509
                        r = parse_path_one(tmp.path, line, field, &tmp.efi, p);
×
510
                else if (streq(field, "uki"))
23✔
511
                        r = parse_path_one(tmp.path, line, field, &tmp.uki, p);
15✔
512
                else if (streq(field, "uki-url"))
8✔
UNCOV
513
                        r = free_and_strdup(&tmp.uki_url, p);
×
514
                else if (streq(field, "profile"))
8✔
UNCOV
515
                        r = safe_atou_full(p, 10, &tmp.profile);
×
516
                else if (streq(field, "initrd"))
8✔
UNCOV
517
                        r = parse_path_strv(tmp.path, line, field, &tmp.initrd, p);
×
518
                else if (streq(field, "devicetree"))
8✔
UNCOV
519
                        r = parse_path_one(tmp.path, line, field, &tmp.device_tree, p);
×
520
                else if (streq(field, "devicetree-overlay"))
8✔
UNCOV
521
                        r = parse_path_many(tmp.path, line, field, &tmp.device_tree_overlay, p);
×
522
                else if (streq(field, "extra"))
8✔
523
                        r = parse_extra(tmp.path, line, field, &tmp.local_extras, p);
8✔
524
                else {
UNCOV
525
                        log_syntax(NULL, LOG_WARNING, tmp.path, line, 0, "Unknown line '%s', ignoring.", field);
×
526
                        continue;
×
527
                }
528
                if (r < 0)
110✔
UNCOV
529
                        return log_syntax(NULL, LOG_ERR, tmp.path, line, r, "Error while parsing: %m");
×
530
        }
531

532
        *ret = TAKE_STRUCT(tmp);
23✔
533
        return 0;
23✔
534
}
535

536
int boot_config_load_type1(
23✔
537
                BootConfig *config,
538
                FILE *f,
539
                const char *root,
540
                const BootEntrySource source,
541
                const char *dir,
542
                const char *filename) {
543
        int r;
23✔
544

545
        assert(config);
23✔
546
        assert(f);
23✔
547
        assert(root);
23✔
548
        assert(dir);
23✔
549
        assert(filename);
23✔
550

551
        if (!GREEDY_REALLOC(config->entries, config->n_entries + 1))
23✔
UNCOV
552
                return log_oom();
×
553

554
        BootEntry *entry = config->entries + config->n_entries;
23✔
555

556
        r = boot_entry_load_type1(f, root, source, dir, filename, entry);
23✔
557
        if (r < 0)
23✔
558
                return r;
559
        config->n_entries++;
23✔
560

561
        entry->global_extras = &config->global_extras[source];
23✔
562

563
        return 0;
23✔
564
}
565

566
void boot_config_free(BootConfig *config) {
50✔
567
        assert(config);
50✔
568

569
        free(config->preferred_pattern);
50✔
570
        free(config->default_pattern);
50✔
571

572
        free(config->entry_oneshot);
50✔
573
        free(config->entry_preferred);
50✔
574
        free(config->entry_default);
50✔
575
        free(config->entry_selected);
50✔
576
        free(config->entry_sysfail);
50✔
577

578
        FOREACH_ARRAY(i, config->entries, config->n_entries)
217✔
579
                boot_entry_free(i);
167✔
580
        free(config->entries);
50✔
581

582
        FOREACH_ELEMENT(i, config->global_extras)
150✔
583
                boot_entry_extras_done(i);
100✔
584

585
        set_free(config->inodes_seen);
50✔
586
}
50✔
587

588
int boot_loader_read_conf(BootConfig *config, FILE *file, const char *path) {
48✔
589
        int r;
48✔
590

591
        assert(config);
48✔
592
        assert(file);
48✔
593
        assert(path);
48✔
594

595
        for (unsigned line = 1;; line++) {
109✔
596
                _cleanup_free_ char *buf = NULL, *field = NULL;
109✔
597

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

606
                if (IN_SET(buf[0], '#', '\0'))
109✔
607
                        continue;
96✔
608

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

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

641
        return 1;
48✔
642
}
643

644
static int boot_loader_read_conf_path(BootConfig *config, const char *root, const char *path) {
50✔
645
        _cleanup_free_ char *full = NULL;
50✔
646
        _cleanup_fclose_ FILE *f = NULL;
50✔
647
        int r;
50✔
648

649
        assert(config);
50✔
650
        assert(path);
50✔
651

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

659
        return boot_loader_read_conf(config, f, full);
48✔
660
}
661

662
static unsigned boot_entry_profile(const BootEntry *a) {
48✔
663
        assert(a);
48✔
664

665
        return a->profile == UINT_MAX ? 0 : a->profile;
48✔
666
}
667

668
static int boot_entry_compare(const BootEntry *a, const BootEntry *b) {
36✔
669
        int r;
36✔
670

671
        assert(a);
36✔
672
        assert(b);
36✔
673

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

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

680
        r = CMP(!a->sort_key, !b->sort_key);
36✔
681
        if (r != 0)
33✔
682
                return r;
683

684
        if (a->sort_key && b->sort_key) {
33✔
685
                r = strcmp(a->sort_key, b->sort_key);
30✔
686
                if (r != 0)
30✔
687
                        return r;
688

689
                r = strcmp_ptr(a->machine_id, b->machine_id);
29✔
690
                if (r != 0)
29✔
691
                        return r;
692

693
                r = -strverscmp_improved(a->version, b->version);
29✔
694
                if (r != 0)
29✔
695
                        return r;
696

697
                r = CMP(boot_entry_profile(a), boot_entry_profile(b));
24✔
UNCOV
698
                if (r != 0)
×
699
                        return r;
700
        }
701

702
        r = -strverscmp_improved(a->id_without_profile ?: a->id, b->id_without_profile ?: b->id);
3✔
703
        if (r != 0)
3✔
704
                return r;
705

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

714
        if (a->tries_left != UINT_MAX || b->tries_left != UINT_MAX)
×
715
                return 0;
716

UNCOV
717
        r = -CMP(a->tries_left, b->tries_left);
×
718
        if (r != 0)
×
719
                return r;
720

UNCOV
721
        return CMP(a->tries_done, b->tries_done);
×
722
}
723

724
static int config_check_inode_relevant_and_unseen(BootConfig *config, int fd, const char *fname) {
47✔
725
        _cleanup_free_ char *d = NULL;
47✔
726
        struct stat st;
47✔
727

728
        assert(config);
47✔
729
        assert(fd >= 0);
47✔
730
        assert(fname);
47✔
731

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

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

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

750
        d = memdup(&st, sizeof(st));
47✔
751
        if (!d)
47✔
752
                return log_oom();
×
753

754
        if (set_ensure_consume(&config->inodes_seen, &inode_hash_ops, TAKE_PTR(d)) < 0)
47✔
UNCOV
755
                return log_oom();
×
756

757
        return true;
758
}
759

760
static int boot_entries_find_type1(
64✔
761
                BootConfig *config,
762
                const char *root,
763
                const BootEntrySource source,
764
                const char *dir) {
765

766
        _cleanup_free_ DirectoryEntries *dentries = NULL;
128✔
767
        _cleanup_free_ char *full = NULL;
64✔
768
        _cleanup_close_ int dir_fd = -EBADF;
64✔
769
        int r;
64✔
770

771
        assert(config);
64✔
772
        assert(root);
64✔
773
        assert(dir);
64✔
774

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

781
        r = readdir_all(dir_fd, RECURSE_DIR_IGNORE_DOT, &dentries);
50✔
782
        if (r < 0)
50✔
UNCOV
783
                return log_error_errno(r, "Failed to read directory '%s': %m", full);
×
784

785
        FOREACH_ARRAY(i, dentries->entries, dentries->n_entries) {
73✔
786
                const struct dirent *de = *i;
23✔
787
                _cleanup_fclose_ FILE *f = NULL;
23✔
788

789
                if (!dirent_is_file(de))
23✔
790
                        continue;
×
791

792
                if (!endswith_no_case(de->d_name, ".conf"))
23✔
UNCOV
793
                        continue;
×
794

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

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

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

812
        return 0;
813
}
814

815
static void mangle_osrelease_string(char **s, const char *field) {
376✔
816
        assert(s);
376✔
817
        assert(field);
376✔
818

819
        if (!isempty(*s) && !string_has_cc(*s, /* ok= */ NULL) && utf8_is_valid(*s))
553✔
820
                return;
821

822
        if (*s) {
199✔
UNCOV
823
                log_debug("OS release field '%s' is not clean, suppressing.", field);
×
UNCOV
824
                *s = mfree(*s);
×
825
        }
826
}
827

828
int bootspec_extract_osrelease(
47✔
829
                const char *text,
830
                char **ret_good_name,
831
                char **ret_good_version,
832
                char **ret_good_sort_key,
833
                char **ret_os_id,
834
                char **ret_os_version_id,
835
                char **ret_image_id,
836
                char **ret_image_version) {
837

838
        int r;
47✔
839

840
        assert(text);
47✔
841

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

857
        mangle_osrelease_string(&os_pretty_name, "PRETTY_NAME");
47✔
858
        mangle_osrelease_string(&image_id, "IMAGE_ID");
47✔
859
        mangle_osrelease_string(&os_name, "NAME");
47✔
860
        mangle_osrelease_string(&os_id, "ID");
47✔
861
        mangle_osrelease_string(&image_version, "IMAGE_VERSION");
47✔
862
        mangle_osrelease_string(&os_version, "VERSION");
47✔
863
        mangle_osrelease_string(&os_version_id, "VERSION_ID");
47✔
864
        mangle_osrelease_string(&os_build_id, "BUILD_ID");
47✔
865

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

881
        _cleanup_free_ char *copy_good_name = NULL, *copy_good_version = NULL, *copy_good_sort_key = NULL;
47✔
882
        if (ret_good_name) {
47✔
883
                copy_good_name = strdup(good_name);
47✔
884
                if (!copy_good_name)
47✔
885
                        return -ENOMEM;
886
        }
887

888
        if (ret_good_version && good_version) {
47✔
889
                copy_good_version = strdup(good_version);
36✔
890
                if (!copy_good_version)
36✔
891
                        return -ENOMEM;
892
        }
893

894
        if (ret_good_sort_key && good_sort_key) {
47✔
895
                copy_good_sort_key = strdup(good_sort_key);
47✔
896
                if (!copy_good_sort_key)
47✔
897
                        return -ENOMEM;
898
        }
899

900
        if (ret_good_name)
47✔
901
                *ret_good_name = TAKE_PTR(copy_good_name);
47✔
902
        if (ret_good_version)
47✔
903
                *ret_good_version = TAKE_PTR(copy_good_version);
36✔
904
        if (ret_good_sort_key)
47✔
905
                *ret_good_sort_key = TAKE_PTR(copy_good_sort_key);
47✔
906

907
        if (ret_os_id)
47✔
908
                *ret_os_id = TAKE_PTR(os_id);
36✔
909
        if (ret_os_version_id)
47✔
910
                *ret_os_version_id = TAKE_PTR(os_version_id);
47✔
911
        if (ret_image_id)
47✔
UNCOV
912
                *ret_image_id = TAKE_PTR(image_id);
×
913
        if (ret_image_version)
47✔
914
                *ret_image_version = TAKE_PTR(image_version);
11✔
915

916
        return 0;
917
}
918

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

929
        int r;
36✔
930

931
        assert(root);
36✔
932
        assert(path);
36✔
933
        assert(osrelease_text);
36✔
934
        assert(ret);
36✔
935

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

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

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

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

969
        _cleanup_(boot_entry_free) BootEntry tmp = BOOT_ENTRY_INIT(BOOT_ENTRY_TYPE2, source);
36✔
970

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

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

978
        tmp.profile = profile;
36✔
979

980
        if (profile_id || profile > 0) {
36✔
981
                tmp.id_without_profile = TAKE_PTR(tmp.id);
36✔
982

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

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

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

1001
        tmp.root = strdup(root);
36✔
1002
        if (!tmp.root)
36✔
UNCOV
1003
                return log_oom();
×
1004

1005
        tmp.kernel = path_make_absolute(k, "/");
36✔
1006
        if (!tmp.kernel)
36✔
UNCOV
1007
                return log_oom();
×
1008

1009
        tmp.options = strv_new(cmdline_text);
36✔
1010
        if (!tmp.options)
36✔
UNCOV
1011
                return log_oom();
×
1012

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

1024
        if (good_sort_key) {
36✔
1025
                tmp.sort_key = strdup(good_sort_key);
36✔
1026
                if (!tmp.sort_key)
36✔
UNCOV
1027
                        return log_oom();
×
1028
        }
1029

1030
        if (good_version) {
36✔
1031
                tmp.version = strdup(good_version);
36✔
1032
                if (!tmp.version)
36✔
UNCOV
1033
                        return log_oom();
×
1034
        }
1035

1036
        *ret = TAKE_STRUCT(tmp);
36✔
1037
        return 0;
36✔
1038
}
1039

1040
static int pe_load_headers_and_sections(
81✔
1041
                int fd,
1042
                const char *path,
1043
                IMAGE_SECTION_HEADER **ret_sections,
1044
                PeHeader **ret_pe_header) {
1045

1046
        _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
162✔
UNCOV
1047
        _cleanup_free_ IMAGE_DOS_HEADER *dos_header = NULL;
×
1048
        _cleanup_free_ PeHeader *pe_header = NULL;
81✔
1049
        int r;
81✔
1050

1051
        assert(fd >= 0);
81✔
1052
        assert(path);
81✔
1053

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

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

1062
        if (ret_pe_header)
81✔
1063
                *ret_pe_header = TAKE_PTR(pe_header);
81✔
1064
        if (ret_sections)
81✔
1065
                *ret_sections = TAKE_PTR(sections);
81✔
1066

1067
        return 0;
1068
}
1069

1070
static const IMAGE_SECTION_HEADER* pe_find_profile_section_table(
116✔
1071
                const PeHeader *pe_header,
1072
                const IMAGE_SECTION_HEADER *sections,
1073
                unsigned profile,
1074
                size_t *ret_n_sections) {
1075

1076
        assert(pe_header);
116✔
1077

1078
        /* Looks for the part of the section table that defines the specified profile. If 'profile' is
1079
         * specified as UINT_MAX this will look for the base profile. */
1080

1081
        if (le16toh(pe_header->pe.NumberOfSections) == 0)
116✔
1082
                return NULL;
1083

1084
        assert(sections);
116✔
1085

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

1093
        for (;;) {
332✔
1094
                p = pe_section_table_find(p, e - p, ".profile");
224✔
1095
                if (!p) {
224✔
1096
                        end = e;
1097
                        break;
1098
                }
1099
                if (current_profile == profile) {
168✔
1100
                        end = p;
1101
                        break;
1102
                }
1103

1104
                if (current_profile == UINT_MAX)
108✔
1105
                        current_profile = 0;
1106
                else
1107
                        current_profile++;
60✔
1108

1109
                if (current_profile == profile)
108✔
1110
                        start = p;
36✔
1111

1112
                p++; /* Continue scanning after the .profile entry we just found */
108✔
1113
        }
1114

1115
        if (!start)
116✔
1116
                return NULL;
1117

1118
        if (ret_n_sections)
83✔
1119
                *ret_n_sections = end - start;
83✔
1120

1121
        return start;
1122
}
1123

1124
static int trim_cmdline(char **cmdline) {
59✔
1125
        assert(cmdline);
59✔
1126

1127
        /* Strips leading and trailing whitespace from command line */
1128

1129
        if (!*cmdline)
59✔
1130
                return 0;
1131

1132
        const char *skipped = skip_leading_chars(*cmdline, WHITESPACE);
59✔
1133

1134
        if (isempty(skipped)) {
59✔
UNCOV
1135
                *cmdline = mfree(*cmdline);
×
UNCOV
1136
                return 0;
×
1137
        }
1138

1139
        if (skipped != *cmdline) {
59✔
UNCOV
1140
                _cleanup_free_ char *c = strdup(skipped);
×
1141
                if (!c)
×
UNCOV
1142
                        return -ENOMEM;
×
1143

UNCOV
1144
                free_and_replace(*cmdline, c);
×
1145
        }
1146

1147
        delete_trailing_chars(*cmdline, WHITESPACE);
59✔
1148
        return 1;
59✔
1149
}
1150

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

1155
int pe_find_uki_sections(
69✔
1156
                int fd,
1157
                const char *path,
1158
                unsigned profile,
1159
                char **ret_osrelease,
1160
                char **ret_profile,
1161
                char **ret_cmdline) {
1162

UNCOV
1163
        _cleanup_free_ char *osrelease_text = NULL, *profile_text = NULL, *cmdline_text = NULL;
×
UNCOV
1164
        _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
×
1165
        _cleanup_free_ PeHeader *pe_header = NULL;
69✔
1166
        int r;
69✔
1167

1168
        assert(fd >= 0);
69✔
1169
        assert(path);
69✔
1170
        assert(profile != UINT_MAX);
69✔
1171

1172
        r = pe_load_headers_and_sections(fd, path, &sections, &pe_header);
69✔
1173
        if (r < 0)
69✔
1174
                return r;
1175

1176
        if (!pe_is_uki(pe_header, sections))
69✔
1177
                return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Parsed PE file '%s' is not a UKI.", path);
×
1178

1179
        if (!pe_is_native(pe_header)) /* Don't process non-native UKIs */
69✔
UNCOV
1180
                goto nothing;
×
1181

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

1188
        /* Find base profile part of section table */
1189
        size_t n_bsections;
47✔
1190
        const IMAGE_SECTION_HEADER *bsections = ASSERT_PTR(pe_find_profile_section_table(pe_header, sections, UINT_MAX, &n_bsections));
47✔
1191

1192
        struct {
47✔
1193
                const char *name;
1194
                char **data;
1195
        } table[] = {
47✔
1196
                { ".osrel",   &osrelease_text },
1197
                { ".profile", &profile_text   },
1198
                { ".cmdline", &cmdline_text   },
1199
        };
1200

1201
        FOREACH_ELEMENT(t, table) {
188✔
1202
                const IMAGE_SECTION_HEADER *found;
141✔
1203

1204
                /* First look in the profile part of the section table, and if we don't find anything there, look into the base part */
1205
                found = pe_section_table_find(psections, n_psections, t->name);
141✔
1206
                if (!found) {
141✔
1207
                        found = pe_section_table_find(bsections, n_bsections, t->name);
81✔
1208
                        if (!found)
81✔
1209
                                continue;
11✔
1210
                }
1211

1212
                /* Permit "masking" of sections in the base profile */
1213
                if (le32toh(found->VirtualSize) == 0)
130✔
UNCOV
1214
                        continue;
×
1215

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

1221
        if (!osrelease_text)
47✔
1222
                return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Unified kernel image lacks .osrel data for profile @%u, refusing.", profile);
×
1223

1224
        if (trim_cmdline(&cmdline_text) < 0)
47✔
UNCOV
1225
                return log_oom();
×
1226

1227
        if (ret_osrelease)
47✔
1228
                *ret_osrelease = TAKE_PTR(osrelease_text);
47✔
1229
        if (ret_profile)
47✔
1230
                *ret_profile = TAKE_PTR(profile_text);
47✔
1231
        if (ret_cmdline)
47✔
1232
                *ret_cmdline = TAKE_PTR(cmdline_text);
36✔
1233
        return 1;
1234

1235
nothing:
22✔
1236
        if (ret_osrelease)
22✔
1237
                *ret_osrelease = NULL;
22✔
1238
        if (ret_profile)
22✔
1239
                *ret_profile = NULL;
22✔
1240
        if (ret_cmdline)
22✔
1241
                *ret_cmdline = NULL;
12✔
1242

1243
        return 0;
1244
}
1245

1246
static int pe_find_addon_sections(
12✔
1247
                int fd,
1248
                const char *path,
1249
                char **ret_cmdline) {
1250

1251
        _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
24✔
1252
        _cleanup_free_ PeHeader *pe_header = NULL;
12✔
1253
        int r;
12✔
1254

1255
        assert(fd >= 0);
12✔
1256
        assert(path);
12✔
1257
        assert(ret_cmdline);
12✔
1258

1259
        r = pe_load_headers_and_sections(fd, path, &sections, &pe_header);
12✔
1260
        if (r < 0)
12✔
1261
                return r;
1262

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

1266
        /* Define early, before the gotos below */
1267
        _cleanup_free_ char *cmdline_text = NULL;
12✔
1268

1269
        if (!pe_is_native(pe_header))
12✔
UNCOV
1270
                goto nothing;
×
1271

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

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

1280
        if (trim_cmdline(&cmdline_text) < 0)
12✔
UNCOV
1281
                return log_oom();
×
1282

1283
        *ret_cmdline = TAKE_PTR(cmdline_text);
12✔
1284
        return 1;
12✔
1285

UNCOV
1286
nothing:
×
UNCOV
1287
        *ret_cmdline = NULL;
×
UNCOV
1288
        return 0;
×
1289
}
1290

1291
static int boot_entries_find_unified_extras(
292✔
1292
                BootConfig *config,
1293
                int d_fd,
1294
                const char *extra_dir,
1295
                BootEntryExtraType only_type,
1296
                const char *where,
1297
                bool suppress_seen,
1298
                BootEntryExtras *extras) {
1299

1300
        int r;
292✔
1301

1302
        assert(config);
292✔
1303
        assert(extras);
292✔
1304

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

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

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

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

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

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

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

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

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

1369
                r = boot_entry_extras_add(extras, type, location, cmdline);
12✔
1370
                if (r < 0)
12✔
UNCOV
1371
                        return r;
×
1372
        }
1373

1374
        return 0;
1375
}
1376

1377
static int boot_entries_find_unified_global_extras(
256✔
1378
                BootConfig *config,
1379
                const char *where,
1380
                const char *extra_dir,
1381
                BootEntryExtraType only_type,
1382
                BootEntryExtras *extras) {
1383

1384
        assert(extras);
256✔
1385

1386
        _cleanup_close_ int where_fd = RET_NERRNO(open(where, O_DIRECTORY|O_CLOEXEC));
512✔
1387
        if (where_fd == -ENOENT)
×
1388
                return 0;
1389
        if (where_fd < 0)
256✔
UNCOV
1390
                return log_error_errno(where_fd, "Failed to open '%s': %m", where);
×
1391

1392
        return boot_entries_find_unified_extras(
256✔
1393
                        config,
1394
                        where_fd,
1395
                        extra_dir,
1396
                        only_type,
1397
                        where,
1398
                        /* suppress_seen= */ true,
1399
                        extras);
1400
}
1401

1402
static int boot_entries_find_unified_local_extras(
36✔
1403
                BootConfig *config,
1404
                int d_fd,
1405
                const char *uki,
1406
                const char *where,
1407
                BootEntry *ret) {
1408

1409
        _cleanup_free_ char *extra_dir = NULL;
36✔
1410

1411
        assert(ret);
36✔
1412

1413
        extra_dir = strjoin(uki, ".extra.d");
36✔
1414
        if (!extra_dir)
36✔
UNCOV
1415
                return log_oom();
×
1416

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

1427
static int boot_entries_find_unified(
64✔
1428
                BootConfig *config,
1429
                const char *root,
1430
                BootEntrySource source,
1431
                const char *dir) {
1432

1433
        _cleanup_closedir_ DIR *d = NULL;
64✔
1434
        _cleanup_free_ char *full = NULL;
64✔
1435
        int r;
64✔
1436

1437
        assert(config);
64✔
1438
        assert(dir);
64✔
1439

1440
        r = chase_and_opendir(dir, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &full, &d);
64✔
1441
        if (r == -ENOENT)
64✔
1442
                return 0;
1443
        if (r < 0)
48✔
UNCOV
1444
                return log_error_errno(r, "Failed to open '%s/%s': %m", root, skip_leading_slash(dir));
×
1445

1446
        FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read %s: %m", full)) {
156✔
1447
                if (!dirent_is_file(de))
12✔
1448
                        continue;
×
1449

1450
                if (!endswith_no_case(de->d_name, ".efi"))
12✔
UNCOV
1451
                        continue;
×
1452

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

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

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

1469
                for (unsigned p = 0; p < UNIFIED_PROFILES_MAX; p++) {
48✔
1470
                        _cleanup_free_ char *osrelease = NULL, *profile = NULL, *cmdline = NULL;
48✔
1471

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

1478
                        if (!GREEDY_REALLOC(config->entries, config->n_entries + 1))
36✔
UNCOV
1479
                                return log_oom();
×
1480

1481
                        BootEntry *entry = config->entries + config->n_entries;
36✔
1482

1483
                        if (boot_entry_load_unified(root, source, j, p, osrelease, profile, cmdline, entry) < 0)
36✔
UNCOV
1484
                                continue;
×
1485

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

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

1492
                        config->n_entries++;
36✔
1493
                }
1494
        }
1495

1496
        return 0;
1497
}
1498

1499
static bool find_nonunique(const BootEntry *entries, size_t n_entries, bool arr[]) {
31✔
1500
        bool non_unique = false;
31✔
1501

1502
        assert(entries || n_entries == 0);
31✔
1503
        assert(arr || n_entries == 0);
31✔
1504

1505
        for (size_t i = 0; i < n_entries; i++)
105✔
1506
                arr[i] = false;
74✔
1507

1508
        for (size_t i = 0; i < n_entries; i++)
105✔
1509
                for (size_t j = 0; j < n_entries; j++)
302✔
1510
                        if (i != j && streq(boot_entry_title(entries + i),
228✔
1511
                                            boot_entry_title(entries + j)))
1512
                                non_unique = arr[i] = arr[j] = true;
18✔
1513

1514
        return non_unique;
31✔
1515
}
1516

1517
static int boot_entries_uniquify(BootEntry *entries, size_t n_entries) {
50✔
1518
        _cleanup_free_ bool *arr = NULL;
50✔
1519
        char *s;
50✔
1520

1521
        assert(entries || n_entries == 0);
50✔
1522

1523
        if (n_entries == 0)
50✔
1524
                return 0;
1525

1526
        arr = new(bool, n_entries);
26✔
1527
        if (!arr)
26✔
1528
                return -ENOMEM;
1529

1530
        /* Find _all_ non-unique titles */
1531
        if (!find_nonunique(entries, n_entries, arr))
26✔
1532
                return 0;
1533

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

1540
                        free_and_replace(entries[i].show_title, s);
11✔
1541
                }
1542

1543
        if (!find_nonunique(entries, n_entries, arr))
4✔
1544
                return 0;
1545

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

1552
                        free_and_replace(entries[i].show_title, s);
2✔
1553
                }
1554

1555
        if (!find_nonunique(entries, n_entries, arr))
1✔
1556
                return 0;
1557

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

1564
                        free_and_replace(entries[i].show_title, s);
2✔
1565
                }
1566

1567
        return 0;
1568
}
1569

1570
static int boot_config_find(const BootConfig *config, const char *id) {
50✔
1571
        assert(config);
50✔
1572

1573
        if (!id)
50✔
1574
                return -1;
1575

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

1584
        for (size_t i = 0; i < config->n_entries; i++)
115✔
1585
                if (fnmatch(id, config->entries[i].id, FNM_CASEFOLD) == 0)
113✔
1586
                        return i;
48✔
1587

1588
        return -1;
1589
}
1590

1591
static int boot_entries_select_default(const BootConfig *config) {
48✔
1592
        int i;
48✔
1593

1594
        assert(config);
48✔
1595
        assert(config->entries || config->n_entries == 0);
48✔
1596

1597
        if (config->n_entries == 0) {
48✔
1598
                log_debug("Found no default boot entry :(");
14✔
1599
                return -1; /* -1 means "no default" */
1600
        }
1601

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

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

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

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

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

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

1651
static int boot_entries_select_selected(const BootConfig *config) {
48✔
1652
        assert(config);
48✔
1653
        assert(config->entries || config->n_entries == 0);
48✔
1654

1655
        if (!config->entry_selected || config->n_entries == 0)
48✔
1656
                return -1;
1657

1658
        return boot_config_find(config, config->entry_selected);
34✔
1659
}
1660

1661
static int boot_load_efi_entry_pointers(BootConfig *config, bool skip_efivars) {
48✔
1662
        int r;
48✔
1663

1664
        assert(config);
48✔
1665

1666
        if (skip_efivars || !is_efi_boot())
48✔
1667
                return 0;
1668

1669
        /* Loads the three "pointers" to boot loader entries from their EFI variables */
1670

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

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

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

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

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

1701
        return 1;
1702
}
1703

1704
int boot_config_select_special_entries(BootConfig *config, bool skip_efivars) {
48✔
1705
        int r;
48✔
1706

1707
        assert(config);
48✔
1708

1709
        r = boot_load_efi_entry_pointers(config, skip_efivars);
48✔
1710
        if (r < 0)
48✔
1711
                return r;
1712

1713
        config->default_entry = boot_entries_select_default(config);
48✔
1714
        config->selected_entry = boot_entries_select_selected(config);
48✔
1715

1716
        return 0;
48✔
1717
}
1718

1719
int boot_config_finalize(BootConfig *config) {
50✔
1720
        int r;
50✔
1721

1722
        typesafe_qsort(config->entries, config->n_entries, boot_entry_compare);
50✔
1723

1724
        r = boot_entries_uniquify(config->entries, config->n_entries);
50✔
1725
        if (r < 0)
50✔
UNCOV
1726
                return log_error_errno(r, "Failed to uniquify boot entries: %m");
×
1727

1728
        return 0;
1729
}
1730

1731
static int boot_entries_load(
64✔
1732
                BootConfig *config,
1733
                BootEntrySource source,
1734
                const char *where) { /* Mount point of ESP/XBOOTLDR */
1735

1736
        int r;
64✔
1737

1738
        assert(config);
64✔
1739
        assert(source >= 0);
64✔
1740
        assert(source < _BOOT_ENTRY_SOURCE_MAX);
64✔
1741

1742
        if (!where)
64✔
1743
                return 0;
1744

1745
        r = boot_entries_find_type1(config, where, source, "/loader/entries");
64✔
1746
        if (r < 0)
64✔
1747
                return r;
1748

1749
        r = boot_entries_find_unified(config, where, source, "/EFI/Linux/");
64✔
1750
        if (r < 0)
64✔
1751
                return r;
1752

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

1763
        FOREACH_ELEMENT(i, table) {
320✔
1764
                r = boot_entries_find_unified_global_extras(
512✔
1765
                                config,
1766
                                where,
1767
                                i->directory,
256✔
1768
                                i->extra_type,
256✔
1769
                                &config->global_extras[source]);
1770
                if (r < 0)
256✔
1771
                        return r;
1772
        }
1773

1774
        return 0;
1775
}
1776

1777
int boot_config_load(
50✔
1778
                BootConfig *config,
1779
                const char *esp_path,
1780
                const char *xbootldr_path) {
1781

1782
        int r;
50✔
1783

1784
        assert(config);
50✔
1785

1786
        if (esp_path) {
50✔
1787
                r = boot_loader_read_conf_path(config, esp_path, "/loader/loader.conf");
50✔
1788
                if (r < 0)
50✔
1789
                        return r;
1790

1791
                r = boot_entries_load(config, BOOT_ENTRY_ESP, esp_path);
50✔
1792
                if (r < 0)
50✔
1793
                        return r;
1794
        }
1795

1796
        if (xbootldr_path) {
50✔
1797
                r = boot_entries_load(config, BOOT_ENTRY_XBOOTLDR, xbootldr_path);
14✔
1798
                if (r < 0)
14✔
1799
                        return r;
1800
        }
1801

1802
        return boot_config_finalize(config);
50✔
1803
}
1804

1805
int boot_config_load_auto(
2✔
1806
                BootConfig *config,
1807
                const char *override_esp_path,
1808
                const char *override_xbootldr_path) {
1809

1810
        _cleanup_free_ char *esp_where = NULL, *xbootldr_where = NULL;
2✔
1811
        dev_t esp_devid = 0, xbootldr_devid = 0;
2✔
1812
        int r;
2✔
1813

1814
        assert(config);
2✔
1815

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

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

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

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

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

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

1861
        return boot_config_load(config, esp_where, xbootldr_where);
2✔
1862
}
1863

1864
int boot_config_augment_from_loader(
32✔
1865
                BootConfig *config,
1866
                char **found_by_loader,
1867
                bool auto_only) {
1868

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

1882
        assert(config);
32✔
1883

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

1887
        STRV_FOREACH(i, found_by_loader) {
160✔
1888
                BootEntry *existing;
128✔
1889
                _cleanup_free_ char *c = NULL, *t = NULL, *p = NULL;
108✔
1890

1891
                existing = boot_config_find_entry(config, *i);
128✔
1892
                if (existing) {
128✔
1893
                        existing->reported_by_loader = true;
20✔
1894
                        continue;
20✔
1895
                }
1896

1897
                if (auto_only && !startswith(*i, "auto-"))
108✔
UNCOV
1898
                        continue;
×
1899

1900
                c = strdup(*i);
108✔
1901
                if (!c)
108✔
UNCOV
1902
                        return log_oom();
×
1903

1904
                STRV_FOREACH_PAIR(a, b, title_table)
832✔
1905
                        if (streq(*a, *i)) {
756✔
1906
                                t = strdup(*b);
32✔
1907
                                if (!t)
32✔
UNCOV
1908
                                        return log_oom();
×
1909
                                break;
1910
                        }
1911

1912
                p = strdup(EFIVAR_PATH(EFI_LOADER_VARIABLE_STR("LoaderEntries")));
108✔
1913
                if (!p)
108✔
1914
                        return log_oom();
×
1915

1916
                if (!GREEDY_REALLOC0(config->entries, config->n_entries + 1))
108✔
UNCOV
1917
                        return log_oom();
×
1918

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

1932
        return 0;
1933
}
1934

1935
BootEntry* boot_config_find_entry(BootConfig *config, const char *id) {
132✔
1936
        assert(config);
132✔
1937
        assert(id);
132✔
1938

1939
        for (size_t j = 0; j < config->n_entries; j++)
427✔
1940
                if (strcaseeq_ptr(config->entries[j].id, id) ||
318✔
1941
                    strcaseeq_ptr(config->entries[j].id_old, id))
295✔
1942
                        return config->entries + j;
1943

1944
        return NULL;
1945
}
1946

1947
static void boot_entry_file_list(
18✔
1948
                const char *field,
1949
                const char *root,
1950
                const char *p,
1951
                int *pstatus) {
1952

1953
        assert(p);
18✔
1954
        assert(pstatus);
18✔
1955

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

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

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

1969
        if (*pstatus == 0 && status < 0)
18✔
UNCOV
1970
                *pstatus = status;
×
1971
}
18✔
1972

1973
static void print_extra(
9✔
1974
                const BootEntry *e,
1975
                const BootEntryExtra *extra,
1976
                const char *field,
1977
                int *status) {
1978

1979
        assert(e);
9✔
1980
        assert(extra);
9✔
1981

1982
        boot_entry_file_list(field, e->root, extra->location, status);
9✔
1983

1984
        if (extra->cmdline)
9✔
1985
                printf("      options: %s%s\n", glyph(GLYPH_TREE_RIGHT), extra->cmdline);
9✔
1986
}
9✔
1987

1988
static int indent_embedded_newlines(char *cmdline, char **ret_cmdline) {
18✔
1989
        _cleanup_free_ char *t = NULL;
18✔
1990
        _cleanup_strv_free_ char **ts = NULL;
18✔
1991

1992
        assert(ret_cmdline);
18✔
1993

1994
        ts = strv_split_newlines(cmdline);
18✔
1995
        if (!ts)
18✔
1996
                return -ENOMEM;
1997

1998
        t = strv_join(ts, "\n              ");
18✔
1999
        if (!t)
18✔
2000
                return -ENOMEM;
2001

2002
        *ret_cmdline = TAKE_PTR(t);
18✔
2003
        return 0;
18✔
2004
}
2005

2006
static int print_cmdline(const BootEntry *e, int *status) {
22✔
2007
        _cleanup_free_ char *options = NULL, *combined_cmdline = NULL, *t2 = NULL;
22✔
2008

2009
        assert(e);
22✔
2010

2011
        if (!strv_isempty(e->options)) {
22✔
2012
                _cleanup_free_ char *t = NULL;
9✔
2013

2014
                options = strv_join(e->options, " ");
9✔
2015
                if (!options)
9✔
2016
                        return log_oom();
×
2017

2018
                if (indent_embedded_newlines(options, &t) < 0)
9✔
UNCOV
2019
                        return log_oom();
×
2020

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

2027
        FOREACH_ARRAY(extra, e->global_extras->items, e->global_extras->n_items) {
31✔
2028
                print_extra(e, extra, "extra", status);
9✔
2029

2030
                if (extra->cmdline)
9✔
2031
                        if (!strextend(&t2, " ", extra->cmdline))
9✔
UNCOV
2032
                                return log_oom();
×
2033
        }
2034

2035
        FOREACH_ARRAY(extra, e->local_extras.items, e->local_extras.n_items) {
22✔
2036
                print_extra(e, extra, "extra", status);
×
2037

UNCOV
2038
                if (extra->cmdline)
×
UNCOV
2039
                        if (!strextend(&t2, " ", extra->cmdline))
×
UNCOV
2040
                                return log_oom();
×
2041
        }
2042

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

2047
        if (indent_embedded_newlines(t2, &combined_cmdline) < 0)
9✔
UNCOV
2048
                return log_oom();
×
2049

2050
        if (combined_cmdline)
9✔
2051
                printf("      cmdline: %s\n", combined_cmdline);
9✔
2052

2053
        return 0;
2054
}
2055

2056
static int json_addon(
3✔
2057
                const BootEntryExtra *extra,
2058
                const char *extra_str,
2059
                sd_json_variant **array) {
2060

2061
        int r;
3✔
2062

2063
        assert(extra);
3✔
2064
        assert(extra_str);
3✔
2065

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

2073
        return 0;
2074
}
2075

2076
static int json_cmdline(
18✔
2077
                const BootEntry *e,
2078
                const char *def_cmdline,
2079
                sd_json_variant **v) {
2080

2081
        _cleanup_free_ char *combined_cmdline = NULL;
18✔
2082
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *addons_array = NULL;
18✔
2083
        int r;
18✔
2084

2085
        assert(e);
18✔
2086

2087
        if (def_cmdline) {
18✔
2088
                combined_cmdline = strdup(def_cmdline);
3✔
2089
                if (!combined_cmdline)
3✔
UNCOV
2090
                        return log_oom();
×
2091
        }
2092

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

2098
                r = json_addon(extra, "globalAddon", &addons_array);
3✔
2099
                if (r < 0)
3✔
2100
                        return r;
2101

2102
                if (extra->cmdline)
3✔
2103
                        if (!strextend(&combined_cmdline, " ", extra->cmdline))
3✔
UNCOV
2104
                                return log_oom();
×
2105
        }
2106

2107
        FOREACH_ARRAY(extra, e->local_extras.items, e->local_extras.n_items) {
18✔
2108
                if (extra->type != BOOT_ENTRY_ADDON)
×
2109
                        continue;
×
2110

UNCOV
2111
                r = json_addon(extra, "localAddon", &addons_array);
×
2112
                if (r < 0)
×
2113
                        return r;
2114

UNCOV
2115
                if (extra->cmdline)
×
UNCOV
2116
                        if (!strextend(&combined_cmdline, " ", extra->cmdline))
×
UNCOV
2117
                                return log_oom();
×
2118
        }
2119

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

2129
int show_boot_entry(
22✔
2130
                const BootEntry *e,
2131
                bool show_as_default,
2132
                bool show_as_selected,
2133
                bool show_reported) {
2134

2135
        int status = 0, r = 0;
22✔
2136

2137
        /* Returns 0 on success, negative on processing error, and positive if something is wrong with the
2138
           boot entry itself. */
2139

2140
        assert(e);
22✔
2141

2142
        printf("         type: %s\n",
22✔
2143
               boot_entry_type_description_to_string(e->type));
22✔
2144

2145
        printf("        title: %s%s%s",
66✔
2146
               ansi_highlight(), boot_entry_title(e), ansi_normal());
2147

2148
        if (show_as_default)
22✔
2149
                printf(" %s(default)%s",
4✔
2150
                       ansi_highlight_green(), ansi_normal());
2151

2152
        if (show_as_selected)
22✔
2153
                printf(" %s(selected)%s",
4✔
2154
                       ansi_highlight_magenta(), ansi_normal());
2155

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

2165
        putchar('\n');
22✔
2166

2167
        if (e->id) {
22✔
2168
                printf("           id: %s", e->id);
22✔
2169

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

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

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

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

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

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

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

2228
        r = print_cmdline(e, &status);
22✔
2229
        if (r < 0)
22✔
2230
                return r;
2231

2232
        if (e->device_tree)
22✔
2233
                boot_entry_file_list("devicetree", e->root, e->device_tree, &status);
×
2234

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

2241
        return -status;
22✔
2242
}
2243

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

2250
        assert(c);
18✔
2251
        assert(ret);
18✔
2252

2253
        if (i >= c->n_entries) {
18✔
UNCOV
2254
                *ret = NULL;
×
UNCOV
2255
                return 0;
×
2256
        }
2257

2258
        e = c->entries + i;
18✔
2259

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

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

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

2304
        r = json_cmdline(e, opts, &v);
18✔
2305
        if (r < 0)
18✔
UNCOV
2306
                return log_oom();
×
2307

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

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

2326
        *ret = TAKE_PTR(v);
18✔
2327
        return 1;
18✔
2328
}
2329

2330
int show_boot_entries(const BootConfig *config, sd_json_format_flags_t json_format) {
9✔
2331
        int r;
9✔
2332

2333
        assert(config);
9✔
2334

2335
        if (sd_json_format_enabled(json_format)) {
9✔
2336
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL;
7✔
2337

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

2341
                        r = boot_entry_to_json(config, i, &v);
13✔
2342
                        if (r < 0)
13✔
UNCOV
2343
                                return log_oom();
×
2344

2345
                        r = sd_json_variant_append_array(&array, v);
13✔
2346
                        if (r < 0)
13✔
UNCOV
2347
                                return log_oom();
×
2348
                }
2349

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

2361
                        if (n+1 < config->n_entries)
8✔
2362
                                putchar('\n');
6✔
2363
                }
2364

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

© 2026 Coveralls, Inc