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

systemd / systemd / 22121027152

17 Feb 2026 11:25PM UTC coverage: 72.452% (-0.2%) from 72.633%
22121027152

push

github

yuwata
pe-binary: fix missing le16toh() on NumberOfSections in pe_hash/uki_hash

pe_hash() and uki_hash() pass pe_header->pe.NumberOfSections directly
to typesafe_qsort() and FOREACH_ARRAY() without le16toh(). On
big-endian (s390x), NumberOfSections=3 gets read as 0x0300 (768),
while pe_load_sections() correctly converts it and only allocates 3
sections. This makes qsort process 768 elements on a 3-element
buffer, causing a heap-buffer-overflow (confirmed with ASAN on
native s390x).

Wrap all three raw usages with le16toh() to match pe_load_sections().

3 of 4 new or added lines in 1 file covered. (75.0%)

5909 existing lines in 86 files now uncovered.

312380 of 431157 relevant lines covered (72.45%)

1145272.29 hits per line

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

71.86
/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 "bootspec-fundamental.h"
11
#include "chase.h"
12
#include "devnum-util.h"
13
#include "dirent-util.h"
14
#include "efi-loader.h"
15
#include "efivars.h"
16
#include "env-file.h"
17
#include "extract-word.h"
18
#include "fd-util.h"
19
#include "fileio.h"
20
#include "find-esp.h"
21
#include "log.h"
22
#include "parse-util.h"
23
#include "path-util.h"
24
#include "pe-binary.h"
25
#include "pretty-print.h"
26
#include "recurse-dir.h"
27
#include "set.h"
28
#include "sort-util.h"
29
#include "stat-util.h"
30
#include "string-table.h"
31
#include "string-util.h"
32
#include "strv.h"
33
#include "uki.h"
34

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

42
DEFINE_STRING_TABLE_LOOKUP_TO_STRING(boot_entry_type_description, BootEntryType);
20✔
43

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

51
DEFINE_STRING_TABLE_LOOKUP(boot_entry_type, BootEntryType);
22✔
52

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

58
DEFINE_STRING_TABLE_LOOKUP_TO_STRING(boot_entry_source_description, BootEntrySource);
20✔
59

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

65
DEFINE_STRING_TABLE_LOOKUP_TO_STRING(boot_entry_source, BootEntrySource);
16✔
66

67
static void boot_entry_addons_done(BootEntryAddons *addons) {
267✔
68
        assert(addons);
267✔
69

70
        FOREACH_ARRAY(addon, addons->items, addons->n_items) {
277✔
71
                free(addon->cmdline);
10✔
72
                free(addon->location);
10✔
73
        }
74
        addons->items = mfree(addons->items);
267✔
75
        addons->n_items = 0;
267✔
76
}
267✔
77

78
static void boot_entry_free(BootEntry *entry) {
124✔
79
        assert(entry);
124✔
80

81
        free(entry->id);
124✔
82
        free(entry->id_old);
124✔
83
        free(entry->id_without_profile);
124✔
84
        free(entry->path);
124✔
85
        free(entry->root);
124✔
86
        free(entry->title);
124✔
87
        free(entry->show_title);
124✔
88
        free(entry->sort_key);
124✔
89
        free(entry->version);
124✔
90
        free(entry->machine_id);
124✔
91
        free(entry->architecture);
124✔
92
        strv_free(entry->options);
124✔
93
        boot_entry_addons_done(&entry->local_addons);
124✔
94
        free(entry->kernel);
124✔
95
        free(entry->efi);
124✔
96
        free(entry->uki);
124✔
97
        free(entry->uki_url);
124✔
98
        strv_free(entry->initrd);
124✔
99
        free(entry->device_tree);
124✔
100
        strv_free(entry->device_tree_overlay);
124✔
101
}
124✔
102

103
static int mangle_path(
×
104
                const char *fname,
105
                unsigned line,
106
                const char *field,
107
                const char *p,
108
                char **ret) {
109

110
        _cleanup_free_ char *c = NULL;
×
111

112
        assert(field);
×
113
        assert(p);
×
114
        assert(ret);
×
115

116
        /* Spec leaves open if prefixed with "/" or not, let's normalize that */
117
        c = path_make_absolute(p, "/");
×
118
        if (!c)
×
119
                return -ENOMEM;
120

121
        /* We only reference files, never directories */
122
        if (endswith(c, "/")) {
×
123
                log_syntax(NULL, LOG_WARNING, fname, line, 0, "Path in field '%s' has trailing slash, ignoring: %s", field, c);
×
124
                *ret = NULL;
×
125
                return 0;
×
126
        }
127

128
        /* Remove duplicate "/" */
129
        path_simplify(c);
×
130

131
        /* No ".." or "." or so */
132
        if (!path_is_normalized(c)) {
×
133
                log_syntax(NULL, LOG_WARNING, fname, line, 0, "Path in field '%s' is not normalized, ignoring: %s", field, c);
×
134
                *ret = NULL;
×
135
                return 0;
×
136
        }
137

138
        *ret = TAKE_PTR(c);
×
139
        return 1;
×
140
}
141

142
static int parse_path_one(
×
143
                const char *fname,
144
                unsigned line,
145
                const char *field,
146
                char **s,
147
                const char *p) {
148

149
        _cleanup_free_ char *c = NULL;
×
150
        int r;
×
151

152
        assert(field);
×
153
        assert(s);
×
154
        assert(p);
×
155

156
        r = mangle_path(fname, line, field, p, &c);
×
157
        if (r <= 0)
×
158
                return r;
159

160
        return free_and_replace(*s, c);
×
161
}
162

163
static int parse_path_strv(
×
164
                const char *fname,
165
                unsigned line,
166
                const char *field,
167
                char ***s,
168
                const char *p) {
169

170
        char *c;
×
171
        int r;
×
172

173
        assert(field);
×
174
        assert(s);
×
175
        assert(p);
×
176

177
        r = mangle_path(fname, line, field, p, &c);
×
178
        if (r <= 0)
×
179
                return r;
×
180

181
        return strv_consume(s, c);
×
182
}
183

184
static int parse_path_many(
×
185
                const char *fname,
186
                unsigned line,
187
                const char *field,
188
                char ***s,
189
                const char *p) {
190

191
        _cleanup_strv_free_ char **l = NULL, **f = NULL;
×
192
        int r;
×
193

194
        l = strv_split(p, NULL);
×
195
        if (!l)
×
196
                return -ENOMEM;
197

198
        STRV_FOREACH(i, l) {
×
199
                char *c;
×
200

201
                r = mangle_path(fname, line, field, *i, &c);
×
202
                if (r < 0)
×
203
                        return r;
×
204
                if (r == 0)
×
205
                        continue;
×
206

207
                r = strv_consume(&f, c);
×
208
                if (r < 0)
×
209
                        return r;
210
        }
211

212
        return strv_extend_strv_consume(s, TAKE_PTR(f), /* filter_duplicates= */ false);
×
213
}
214

215
static int parse_tries(const char *fname, const char **p, unsigned *ret) {
36✔
216
        _cleanup_free_ char *d = NULL;
36✔
217
        unsigned tries;
36✔
218
        size_t n;
36✔
219
        int r;
36✔
220

221
        assert(fname);
36✔
222
        assert(p);
36✔
223
        assert(*p);
36✔
224
        assert(ret);
36✔
225

226
        n = strspn(*p, DIGITS);
36✔
227
        if (n == 0) {
36✔
228
                *ret = UINT_MAX;
2✔
229
                return 0;
2✔
230
        }
231

232
        d = strndup(*p, n);
34✔
233
        if (!d)
34✔
234
                return log_oom();
×
235

236
        r = safe_atou_full(d, 10, &tries);
34✔
237
        if (r >= 0 && tries > INT_MAX) /* sd-boot allows INT_MAX, let's use the same limit */
34✔
238
                r = -ERANGE;
239
        if (r < 0)
32✔
240
                return log_error_errno(r, "Failed to parse tries counter of filename '%s': %m", fname);
2✔
241

242
        *p = *p + n;
32✔
243
        *ret = tries;
32✔
244
        return 1;
32✔
245
}
246

247
int boot_filename_extract_tries(
67✔
248
                const char *fname,
249
                char **ret_stripped,
250
                unsigned *ret_tries_left,
251
                unsigned *ret_tries_done) {
252

253
        unsigned tries_left = UINT_MAX, tries_done = UINT_MAX;
67✔
254
        _cleanup_free_ char *stripped = NULL;
67✔
255
        const char *p, *suffix, *m;
67✔
256
        int r;
67✔
257

258
        assert(fname);
67✔
259
        assert(ret_stripped);
67✔
260
        assert(ret_tries_left);
67✔
261
        assert(ret_tries_done);
67✔
262

263
        /* Be liberal with suffix, only insist on a dot. After all we want to cover any capitalization here
264
         * (vfat is case insensitive after all), and at least .efi and .conf as suffix. */
265
        suffix = strrchr(fname, '.');
67✔
266
        if (!suffix)
67✔
267
                goto nothing;
1✔
268

269
        p = m = memrchr(fname, '+', suffix - fname);
66✔
270
        if (!p)
66✔
271
                goto nothing;
43✔
272
        p++;
23✔
273

274
        r = parse_tries(fname, &p, &tries_left);
23✔
275
        if (r < 0)
23✔
276
                return r;
277
        if (r == 0)
22✔
278
                goto nothing;
2✔
279

280
        if (*p == '-') {
20✔
281
                p++;
13✔
282

283
                r = parse_tries(fname, &p, &tries_done);
13✔
284
                if (r < 0)
13✔
285
                        return r;
286
                if (r == 0)
12✔
287
                        goto nothing;
×
288
        }
289

290
        if (p != suffix)
19✔
291
                goto nothing;
3✔
292

293
        stripped = strndup(fname, m - fname);
16✔
294
        if (!stripped)
16✔
295
                return log_oom();
×
296

297
        if (!strextend(&stripped, suffix))
16✔
298
                return log_oom();
×
299

300
        *ret_stripped = TAKE_PTR(stripped);
16✔
301
        *ret_tries_left = tries_left;
16✔
302
        *ret_tries_done = tries_done;
16✔
303

304
        return 0;
16✔
305

306
nothing:
49✔
307
        stripped = strdup(fname);
49✔
308
        if (!stripped)
49✔
309
                return log_oom();
×
310

311
        *ret_stripped = TAKE_PTR(stripped);
49✔
312
        *ret_tries_left = *ret_tries_done = UINT_MAX;
49✔
313
        return 0;
49✔
314
}
315

316
static int boot_entry_load_type1(
8✔
317
                FILE *f,
318
                const char *root,
319
                const BootEntrySource source,
320
                const char *dir,
321
                const char *fname,
322
                BootEntry *ret) {
323

324
        _cleanup_(boot_entry_free) BootEntry tmp = BOOT_ENTRY_INIT(BOOT_ENTRY_TYPE1, source);
8✔
325
        char *c;
8✔
326
        int r;
8✔
327

328
        assert(f);
8✔
329
        assert(root);
8✔
330
        assert(dir);
8✔
331
        assert(fname);
8✔
332
        assert(ret);
8✔
333

334
        /* Loads a Type #1 boot menu entry from the specified FILE* object */
335

336
        r = boot_filename_extract_tries(fname, &tmp.id, &tmp.tries_left, &tmp.tries_done);
8✔
337
        if (r < 0)
8✔
338
                return r;
339

340
        if (!efi_loader_entry_name_valid(tmp.id))
8✔
341
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry name: %s", fname);
×
342

343
        c = endswith_no_case(tmp.id, ".conf");
8✔
344
        if (!c)
8✔
345
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry file suffix: %s", fname);
×
346

347
        tmp.id_old = strndup(tmp.id, c - tmp.id); /* Without .conf suffix */
8✔
348
        if (!tmp.id_old)
8✔
349
                return log_oom();
×
350

351
        tmp.path = path_join(dir, fname);
8✔
352
        if (!tmp.path)
8✔
353
                return log_oom();
×
354

355
        tmp.root = strdup(root);
8✔
356
        if (!tmp.root)
8✔
357
                return log_oom();
×
358

359
        for (unsigned line = 1;; line++) {
27✔
360
                _cleanup_free_ char *buf = NULL, *field = NULL;
27✔
361

362
                r = read_stripped_line(f, LONG_LINE_MAX, &buf);
35✔
363
                if (r == -ENOBUFS)
35✔
364
                        return log_syntax(NULL, LOG_ERR, tmp.path, line, r, "Line too long.");
×
365
                if (r < 0)
35✔
366
                        return log_syntax(NULL, LOG_ERR, tmp.path, line, r, "Error while reading: %m");
×
367
                if (r == 0)
35✔
368
                        break;
369

370
                if (IN_SET(buf[0], '#', '\0'))
27✔
371
                        continue;
×
372

373
                const char *p = buf;
27✔
374
                r = extract_first_word(&p, &field, NULL, 0);
27✔
375
                if (r < 0) {
27✔
376
                        log_syntax(NULL, LOG_WARNING, tmp.path, line, r, "Failed to parse, ignoring line: %m");
×
377
                        continue;
×
378
                }
379
                if (r == 0) {
27✔
380
                        log_syntax(NULL, LOG_WARNING, tmp.path, line, 0, "Bad syntax, ignoring line.");
×
381
                        continue;
×
382
                }
383

384
                if (isempty(p)) {
27✔
385
                        /* Some fields can reasonably have an empty value. In other cases warn. */
386
                        if (!STR_IN_SET(field, "options", "devicetree-overlay"))
×
387
                                log_syntax(NULL, LOG_WARNING, tmp.path, line, 0, "Field '%s' without value, ignoring line.", field);
×
388

389
                        continue;
×
390
                }
391

392
                if (streq(field, "title"))
27✔
393
                        r = free_and_strdup(&tmp.title, p);
8✔
394
                else if (streq(field, "sort-key"))
19✔
395
                        r = free_and_strdup(&tmp.sort_key, p);
3✔
396
                else if (streq(field, "version"))
16✔
397
                        r = free_and_strdup(&tmp.version, p);
8✔
398
                else if (streq(field, "machine-id"))
8✔
399
                        r = free_and_strdup(&tmp.machine_id, p);
8✔
400
                else if (streq(field, "architecture"))
×
401
                        r = free_and_strdup(&tmp.architecture, p);
×
402
                else if (streq(field, "options"))
×
403
                        r = strv_extend(&tmp.options, p);
×
404
                else if (streq(field, "linux"))
×
405
                        r = parse_path_one(tmp.path, line, field, &tmp.kernel, p);
×
406
                else if (streq(field, "efi"))
×
407
                        r = parse_path_one(tmp.path, line, field, &tmp.efi, p);
×
408
                else if (streq(field, "uki"))
×
409
                        r = parse_path_one(tmp.path, line, field, &tmp.uki, p);
×
410
                else if (streq(field, "uki-url"))
×
411
                        r = free_and_strdup(&tmp.uki_url, p);
×
412
                else if (streq(field, "profile"))
×
413
                        r = safe_atou_full(p, 10, &tmp.profile);
×
414
                else if (streq(field, "initrd"))
×
415
                        r = parse_path_strv(tmp.path, line, field, &tmp.initrd, p);
×
416
                else if (streq(field, "devicetree"))
×
417
                        r = parse_path_one(tmp.path, line, field, &tmp.device_tree, p);
×
418
                else if (streq(field, "devicetree-overlay"))
×
419
                        r = parse_path_many(tmp.path, line, field, &tmp.device_tree_overlay, p);
×
420
                else {
421
                        log_syntax(NULL, LOG_WARNING, tmp.path, line, 0, "Unknown line '%s', ignoring.", field);
×
422
                        continue;
×
423
                }
424
                if (r < 0)
27✔
425
                        return log_syntax(NULL, LOG_ERR, tmp.path, line, r, "Error while parsing: %m");
×
426
        }
427

428
        *ret = TAKE_STRUCT(tmp);
8✔
429
        return 0;
8✔
430
}
431

432
int boot_config_load_type1(
8✔
433
                BootConfig *config,
434
                FILE *f,
435
                const char *root,
436
                const BootEntrySource source,
437
                const char *dir,
438
                const char *filename) {
439
        int r;
8✔
440

441
        assert(config);
8✔
442
        assert(f);
8✔
443
        assert(root);
8✔
444
        assert(dir);
8✔
445
        assert(filename);
8✔
446

447
        if (!GREEDY_REALLOC(config->entries, config->n_entries + 1))
8✔
448
                return log_oom();
×
449

450
        BootEntry *entry = config->entries + config->n_entries;
8✔
451

452
        r = boot_entry_load_type1(f, root, source, dir, filename, entry);
8✔
453
        if (r < 0)
8✔
454
                return r;
455
        config->n_entries++;
8✔
456

457
        entry->global_addons = &config->global_addons[source];
8✔
458

459
        return 0;
8✔
460
}
461

462
void boot_config_free(BootConfig *config) {
33✔
463
        assert(config);
33✔
464

465
        free(config->preferred_pattern);
33✔
466
        free(config->default_pattern);
33✔
467

468
        free(config->entry_oneshot);
33✔
469
        free(config->entry_preferred);
33✔
470
        free(config->entry_default);
33✔
471
        free(config->entry_selected);
33✔
472
        free(config->entry_sysfail);
33✔
473

474
        FOREACH_ARRAY(i, config->entries, config->n_entries)
119✔
475
                boot_entry_free(i);
86✔
476
        free(config->entries);
33✔
477

478
        FOREACH_ELEMENT(i, config->global_addons)
99✔
479
                boot_entry_addons_done(i);
66✔
480

481
        set_free(config->inodes_seen);
33✔
482
}
33✔
483

484
int boot_loader_read_conf(BootConfig *config, FILE *file, const char *path) {
31✔
485
        int r;
31✔
486

487
        assert(config);
31✔
488
        assert(file);
31✔
489
        assert(path);
31✔
490

491
        for (unsigned line = 1;; line++) {
62✔
492
                _cleanup_free_ char *buf = NULL, *field = NULL;
62✔
493

494
                r = read_stripped_line(file, LONG_LINE_MAX, &buf);
93✔
495
                if (r == -ENOBUFS)
93✔
496
                        return log_syntax(NULL, LOG_ERR, path, line, r, "Line too long.");
×
497
                if (r < 0)
93✔
UNCOV
498
                        return log_syntax(NULL, LOG_ERR, path, line, r, "Error while reading: %m");
×
499
                if (r == 0)
93✔
500
                        break;
501

502
                if (IN_SET(buf[0], '#', '\0'))
62✔
503
                        continue;
62✔
504

505
                const char *p = buf;
×
506
                r = extract_first_word(&p, &field, NULL, 0);
×
507
                if (r < 0) {
×
UNCOV
508
                        log_syntax(NULL, LOG_WARNING, path, line, r, "Failed to parse, ignoring line: %m");
×
509
                        continue;
×
510
                }
511
                if (r == 0) {
×
UNCOV
512
                        log_syntax(NULL, LOG_WARNING, path, line, 0, "Bad syntax, ignoring line.");
×
513
                        continue;
×
514
                }
515
                if (isempty(p)) {
×
UNCOV
516
                        log_syntax(NULL, LOG_WARNING, path, line, 0, "Field '%s' without value, ignoring line.", field);
×
UNCOV
517
                        continue;
×
518
                }
519

520
                if (streq(field, "preferred"))
×
UNCOV
521
                        r = free_and_strdup(&config->preferred_pattern, p);
×
UNCOV
522
                if (streq(field, "default"))
×
UNCOV
523
                        r = free_and_strdup(&config->default_pattern, p);
×
524
                else if (STR_IN_SET(field, "timeout", "editor", "auto-entries", "auto-firmware",
×
525
                                    "auto-poweroff", "auto-reboot", "beep", "reboot-for-bitlocker",
526
                                    "reboot-on-error", "secure-boot-enroll", "secure-boot-enroll-action",
527
                                    "secure-boot-enroll-timeout-sec", "console-mode", "log-level"))
UNCOV
528
                        r = 0; /* we don't parse these in userspace, but they are OK */
×
529
                else {
530
                        log_syntax(NULL, LOG_WARNING, path, line, 0, "Unknown line '%s', ignoring.", field);
×
UNCOV
531
                        continue;
×
532
                }
UNCOV
533
                if (r < 0)
×
UNCOV
534
                        return log_syntax(NULL, LOG_ERR, path, line, r, "Error while parsing: %m");
×
535
        }
536

537
        return 1;
31✔
538
}
539

540
static int boot_loader_read_conf_path(BootConfig *config, const char *root, const char *path) {
33✔
541
        _cleanup_free_ char *full = NULL;
33✔
542
        _cleanup_fclose_ FILE *f = NULL;
33✔
543
        int r;
33✔
544

545
        assert(config);
33✔
546
        assert(path);
33✔
547

548
        r = chase_and_fopen_unlocked(path, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, "re", &full, &f);
33✔
549
        config->loader_conf_status = r < 0 ? r : true;
33✔
550
        if (r == -ENOENT)
33✔
551
                return 0;
552
        if (r < 0)
31✔
UNCOV
553
                return log_error_errno(r, "Failed to open '%s/%s': %m", root, skip_leading_slash(path));
×
554

555
        return boot_loader_read_conf(config, f, full);
31✔
556
}
557

558
static int boot_entry_compare(const BootEntry *a, const BootEntry *b) {
28✔
559
        int r;
28✔
560

561
        assert(a);
28✔
562
        assert(b);
28✔
563

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

566
        r = CMP(a->tries_left == 0, b->tries_left == 0);
28✔
567
        if (r != 0)
28✔
UNCOV
568
                return r;
×
569

570
        r = CMP(!a->sort_key, !b->sort_key);
28✔
571
        if (r != 0)
25✔
572
                return r;
3✔
573

574
        if (a->sort_key && b->sort_key) {
25✔
575
                r = strcmp(a->sort_key, b->sort_key);
22✔
576
                if (r != 0)
22✔
577
                        return r;
578

579
                r = strcmp_ptr(a->machine_id, b->machine_id);
21✔
580
                if (r != 0)
21✔
581
                        return r;
582

583
                r = -strverscmp_improved(a->version, b->version);
21✔
584
                if (r != 0)
21✔
585
                        return r;
586
        }
587

588
        r = -strverscmp_improved(a->id_without_profile ?: a->id, b->id_without_profile ?: b->id);
23✔
589
        if (r != 0)
23✔
590
                return r;
591

592
        if (a->id_without_profile && b->id_without_profile) {
20✔
593
                /* The strverscmp_improved() call above already established that we are talking about the
594
                 * same image here, hence order by profile, if there is one */
595
                r = CMP(a->profile, b->profile);
20✔
596
                if (r != 0)
×
597
                        return r;
20✔
598
        }
599

600
        if (a->tries_left != UINT_MAX || b->tries_left != UINT_MAX)
×
601
                return 0;
602

603
        r = -CMP(a->tries_left, b->tries_left);
×
UNCOV
604
        if (r != 0)
×
UNCOV
605
                return r;
×
606

UNCOV
607
        return CMP(a->tries_done, b->tries_done);
×
608
}
609

610
static int config_check_inode_relevant_and_unseen(BootConfig *config, int fd, const char *fname) {
28✔
611
        _cleanup_free_ char *d = NULL;
28✔
612
        struct stat st;
28✔
613

614
        assert(config);
28✔
615
        assert(fd >= 0);
28✔
616
        assert(fname);
28✔
617

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

624
        if (fstat(fd, &st) < 0)
28✔
UNCOV
625
                return log_error_errno(errno, "Failed to stat('%s'): %m", fname);
×
626
        if (!S_ISREG(st.st_mode)) {
28✔
UNCOV
627
                log_debug("File '%s' is not a regular file, ignoring.", fname);
×
628
                return false;
×
629
        }
630

631
        if (set_contains(config->inodes_seen, &st)) {
28✔
UNCOV
632
                log_debug("Inode '%s' already seen before, ignoring.", fname);
×
UNCOV
633
                return false;
×
634
        }
635

636
        d = memdup(&st, sizeof(st));
28✔
637
        if (!d)
28✔
UNCOV
638
                return log_oom();
×
639

640
        if (set_ensure_consume(&config->inodes_seen, &inode_hash_ops, TAKE_PTR(d)) < 0)
28✔
UNCOV
641
                return log_oom();
×
642

643
        return true;
644
}
645

646
static int boot_entries_find_type1(
47✔
647
                BootConfig *config,
648
                const char *root,
649
                const BootEntrySource source,
650
                const char *dir) {
651

652
        _cleanup_free_ DirectoryEntries *dentries = NULL;
94✔
653
        _cleanup_free_ char *full = NULL;
47✔
654
        _cleanup_close_ int dir_fd = -EBADF;
47✔
655
        int r;
47✔
656

657
        assert(config);
47✔
658
        assert(root);
47✔
659
        assert(dir);
47✔
660

661
        dir_fd = chase_and_open(dir, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, O_DIRECTORY|O_CLOEXEC, &full);
47✔
662
        if (dir_fd == -ENOENT)
47✔
663
                return 0;
664
        if (dir_fd < 0)
33✔
665
                return log_error_errno(dir_fd, "Failed to open '%s/%s': %m", root, skip_leading_slash(dir));
×
666

667
        r = readdir_all(dir_fd, RECURSE_DIR_IGNORE_DOT, &dentries);
33✔
668
        if (r < 0)
33✔
UNCOV
669
                return log_error_errno(r, "Failed to read directory '%s': %m", full);
×
670

671
        FOREACH_ARRAY(i, dentries->entries, dentries->n_entries) {
41✔
672
                const struct dirent *de = *i;
8✔
673
                _cleanup_fclose_ FILE *f = NULL;
8✔
674

675
                if (!dirent_is_file(de))
8✔
UNCOV
676
                        continue;
×
677

678
                if (!endswith_no_case(de->d_name, ".conf"))
8✔
679
                        continue;
×
680

681
                r = xfopenat(dir_fd, de->d_name, "re", O_NOFOLLOW|O_NOCTTY, &f);
8✔
682
                if (r < 0) {
8✔
UNCOV
683
                        log_warning_errno(r, "Failed to open %s/%s, ignoring: %m", full, de->d_name);
×
UNCOV
684
                        continue;
×
685
                }
686

687
                r = config_check_inode_relevant_and_unseen(config, fileno(f), de->d_name);
8✔
688
                if (r < 0)
8✔
689
                        return r;
690
                if (r == 0) /* inode already seen or otherwise not relevant */
8✔
691
                        continue;
×
692

693
                r = boot_config_load_type1(config, f, root, source, full, de->d_name);
8✔
694
                if (r == -ENOMEM) /* ignore all other errors */
8✔
UNCOV
695
                        return log_oom();
×
696
        }
697

698
        return 0;
699
}
700

701
static int boot_entry_load_unified(
30✔
702
                const char *root,
703
                const BootEntrySource source,
704
                const char *path,
705
                unsigned profile,
706
                const char *osrelease_text,
707
                const char *profile_text,
708
                const char *cmdline_text,
709
                BootEntry *ret) {
710

UNCOV
711
        _cleanup_free_ char *fname = NULL, *os_pretty_name = NULL, *os_image_id = NULL, *os_name = NULL, *os_id = NULL,
×
712
                *os_image_version = NULL, *os_version = NULL, *os_version_id = NULL, *os_build_id = NULL;
30✔
713
        const char *k, *good_name, *good_version, *good_sort_key;
30✔
714
        _cleanup_fclose_ FILE *f = NULL;
30✔
715
        int r;
30✔
716

717
        assert(root);
30✔
718
        assert(path);
30✔
719
        assert(osrelease_text);
30✔
720
        assert(ret);
30✔
721

722
        k = path_startswith(path, root);
30✔
723
        if (!k)
30✔
724
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Path is not below root: %s", path);
×
725

726
        f = fmemopen_unlocked((void*) osrelease_text, strlen(osrelease_text), "r");
30✔
727
        if (!f)
30✔
UNCOV
728
                return log_oom();
×
729

730
        r = parse_env_file(f, "os-release",
30✔
731
                           "PRETTY_NAME", &os_pretty_name,
732
                           "IMAGE_ID", &os_image_id,
733
                           "NAME", &os_name,
734
                           "ID", &os_id,
735
                           "IMAGE_VERSION", &os_image_version,
736
                           "VERSION", &os_version,
737
                           "VERSION_ID", &os_version_id,
738
                           "BUILD_ID", &os_build_id);
739
        if (r < 0)
30✔
UNCOV
740
                return log_error_errno(r, "Failed to parse os-release data from unified kernel image %s: %m", path);
×
741

742
        if (!bootspec_pick_name_version_sort_key(
30✔
743
                            os_pretty_name,
744
                            os_image_id,
745
                            os_name,
746
                            os_id,
747
                            os_image_version,
748
                            os_version,
749
                            os_version_id,
750
                            os_build_id,
751
                            &good_name,
752
                            &good_version,
753
                            &good_sort_key))
UNCOV
754
                return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Missing fields in os-release data from unified kernel image %s, refusing.", path);
×
755

756
        _cleanup_free_ char *profile_id = NULL, *profile_title = NULL;
30✔
757
        if (profile_text) {
30✔
758
                fclose(f);
30✔
759

760
                f = fmemopen_unlocked((void*) profile_text, strlen(profile_text), "r");
30✔
761
                if (!f)
30✔
UNCOV
762
                        return log_oom();
×
763

764
                r = parse_env_file(
30✔
765
                                f, "profile",
766
                                "ID", &profile_id,
767
                                "TITLE", &profile_title);
768
                if (r < 0)
30✔
UNCOV
769
                        return log_error_errno(r, "Failed to parse profile data from unified kernel image '%s': %m", path);
×
770
        }
771

772
        r = path_extract_filename(path, &fname);
30✔
773
        if (r < 0)
30✔
UNCOV
774
                return log_error_errno(r, "Failed to extract file name from '%s': %m", path);
×
775

776
        _cleanup_(boot_entry_free) BootEntry tmp = BOOT_ENTRY_INIT(BOOT_ENTRY_TYPE2, source);
30✔
777

778
        r = boot_filename_extract_tries(fname, &tmp.id, &tmp.tries_left, &tmp.tries_done);
30✔
779
        if (r < 0)
30✔
780
                return r;
781

782
        if (!efi_loader_entry_name_valid(tmp.id))
30✔
UNCOV
783
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry name: %s", tmp.id);
×
784

785
        tmp.profile = profile;
30✔
786

787
        if (profile_id || profile > 0) {
30✔
788
                tmp.id_without_profile = TAKE_PTR(tmp.id);
30✔
789

790
                if (profile_id)
30✔
791
                        tmp.id = strjoin(tmp.id_without_profile, "@", profile_id);
30✔
792
                else
UNCOV
793
                        (void) asprintf(&tmp.id, "%s@%u", tmp.id_without_profile, profile);
×
794
                if (!tmp.id)
30✔
795
                        return log_oom();
×
796
        }
797

798
        if (os_id && os_version_id) {
30✔
UNCOV
799
                tmp.id_old = strjoin(os_id, "-", os_version_id);
×
UNCOV
800
                if (!tmp.id_old)
×
UNCOV
801
                        return log_oom();
×
802
        }
803

804
        tmp.path = strdup(path);
30✔
805
        if (!tmp.path)
30✔
806
                return log_oom();
×
807

808
        tmp.root = strdup(root);
30✔
809
        if (!tmp.root)
30✔
810
                return log_oom();
×
811

812
        tmp.kernel = path_make_absolute(k, "/");
30✔
813
        if (!tmp.kernel)
30✔
814
                return log_oom();
×
815

816
        tmp.options = strv_new(cmdline_text);
30✔
817
        if (!tmp.options)
30✔
UNCOV
818
                return log_oom();
×
819

820
        if (profile_title)
30✔
821
                tmp.title = strjoin(good_name, " (", profile_title, ")");
20✔
822
        else if (profile_id)
10✔
823
                tmp.title = strjoin(good_name, " (", profile_id, ")");
10✔
UNCOV
824
        else if (profile > 0)
×
825
                (void) asprintf(&tmp.title, "%s (@%u)", good_name, profile);
×
826
        else
UNCOV
827
                tmp.title = strdup(good_name);
×
828
        if (!tmp.title)
30✔
UNCOV
829
                return log_oom();
×
830

831
        if (good_sort_key) {
30✔
832
                tmp.sort_key = strdup(good_sort_key);
30✔
833
                if (!tmp.sort_key)
30✔
UNCOV
834
                        return log_oom();
×
835
        }
836

837
        if (good_version) {
30✔
838
                tmp.version = strdup(good_version);
30✔
839
                if (!tmp.version)
30✔
UNCOV
840
                        return log_oom();
×
841
        }
842

843
        *ret = TAKE_STRUCT(tmp);
30✔
844
        return 0;
30✔
845
}
846

847
static int pe_load_headers_and_sections(
50✔
848
                int fd,
849
                const char *path,
850
                IMAGE_SECTION_HEADER **ret_sections,
851
                PeHeader **ret_pe_header) {
852

853
        _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
100✔
UNCOV
854
        _cleanup_free_ IMAGE_DOS_HEADER *dos_header = NULL;
×
855
        _cleanup_free_ PeHeader *pe_header = NULL;
50✔
856
        int r;
50✔
857

858
        assert(fd >= 0);
50✔
859
        assert(path);
50✔
860

861
        r = pe_load_headers(fd, &dos_header, &pe_header);
50✔
862
        if (r < 0)
50✔
863
                return log_error_errno(r, "Failed to parse PE file '%s': %m", path);
×
864

865
        r = pe_load_sections(fd, dos_header, pe_header, &sections);
50✔
866
        if (r < 0)
50✔
UNCOV
867
                return log_error_errno(r, "Failed to parse PE sections of '%s': %m", path);
×
868

869
        if (ret_pe_header)
50✔
870
                *ret_pe_header = TAKE_PTR(pe_header);
50✔
871
        if (ret_sections)
50✔
872
                *ret_sections = TAKE_PTR(sections);
50✔
873

874
        return 0;
875
}
876

877
static const IMAGE_SECTION_HEADER* pe_find_profile_section_table(
70✔
878
                const PeHeader *pe_header,
879
                const IMAGE_SECTION_HEADER *sections,
880
                unsigned profile,
881
                size_t *ret_n_sections) {
882

883
        assert(pe_header);
70✔
884

885
        /* Looks for the part of the section table that defines the specified profile. If 'profile' is
886
         * specified as UINT_MAX this will look for the base profile. */
887

888
        if (le16toh(pe_header->pe.NumberOfSections) == 0)
70✔
889
                return NULL;
890

891
        assert(sections);
70✔
892

893
        const IMAGE_SECTION_HEADER
70✔
894
                *p = sections,
70✔
895
                *e = sections + le16toh(pe_header->pe.NumberOfSections),
70✔
896
                *start = profile == UINT_MAX ? sections : NULL,
70✔
897
                *end;
898
        unsigned current_profile = UINT_MAX;
899

900
        for (;;) {
250✔
901
                p = pe_section_table_find(p, e - p, ".profile");
160✔
902
                if (!p) {
160✔
903
                        end = e;
904
                        break;
905
                }
906
                if (current_profile == profile) {
140✔
907
                        end = p;
908
                        break;
909
                }
910

911
                if (current_profile == UINT_MAX)
90✔
912
                        current_profile = 0;
913
                else
914
                        current_profile++;
50✔
915

916
                if (current_profile == profile)
90✔
917
                        start = p;
30✔
918

919
                p++; /* Continue scanning after the .profile entry we just found */
90✔
920
        }
921

922
        if (!start)
70✔
923
                return NULL;
924

925
        if (ret_n_sections)
60✔
926
                *ret_n_sections = end - start;
60✔
927

928
        return start;
929
}
930

931
static int trim_cmdline(char **cmdline) {
40✔
932
        assert(cmdline);
40✔
933

934
        /* Strips leading and trailing whitespace from command line */
935

936
        if (!*cmdline)
40✔
937
                return 0;
938

939
        const char *skipped = skip_leading_chars(*cmdline, WHITESPACE);
40✔
940

941
        if (isempty(skipped)) {
40✔
UNCOV
942
                *cmdline = mfree(*cmdline);
×
943
                return 0;
×
944
        }
945

946
        if (skipped != *cmdline) {
40✔
947
                _cleanup_free_ char *c = strdup(skipped);
×
UNCOV
948
                if (!c)
×
UNCOV
949
                        return -ENOMEM;
×
950

UNCOV
951
                free_and_replace(*cmdline, c);
×
952
        }
953

954
        delete_trailing_chars(*cmdline, WHITESPACE);
40✔
955
        return 1;
40✔
956
}
957

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

962
static int pe_find_uki_sections(
40✔
963
                int fd,
964
                const char *path,
965
                unsigned profile,
966
                char **ret_osrelease,
967
                char **ret_profile,
968
                char **ret_cmdline) {
969

UNCOV
970
        _cleanup_free_ char *osrelease_text = NULL, *profile_text = NULL, *cmdline_text = NULL;
×
UNCOV
971
        _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
×
972
        _cleanup_free_ PeHeader *pe_header = NULL;
40✔
973
        int r;
40✔
974

975
        assert(fd >= 0);
40✔
976
        assert(path);
40✔
977
        assert(profile != UINT_MAX);
40✔
978
        assert(ret_osrelease);
40✔
979
        assert(ret_profile);
40✔
980
        assert(ret_cmdline);
40✔
981

982
        r = pe_load_headers_and_sections(fd, path, &sections, &pe_header);
40✔
983
        if (r < 0)
40✔
984
                return r;
985

986
        if (!pe_is_uki(pe_header, sections))
40✔
UNCOV
987
                return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Parsed PE file '%s' is not a UKI.", path);
×
988

989
        if (!pe_is_native(pe_header)) /* Don't process non-native UKIs */
40✔
UNCOV
990
                goto nothing;
×
991

992
        /* Find part of the section table for this profile */
993
        size_t n_psections = 0;
40✔
994
        const IMAGE_SECTION_HEADER *psections = pe_find_profile_section_table(pe_header, sections, profile, &n_psections);
40✔
995
        if (!psections && profile != 0) /* Profile not found? (Profile @0 needs no explicit .profile!) */
40✔
996
                goto nothing;
10✔
997

998
        /* Find base profile part of section table */
999
        size_t n_bsections;
30✔
1000
        const IMAGE_SECTION_HEADER *bsections = ASSERT_PTR(pe_find_profile_section_table(pe_header, sections, UINT_MAX, &n_bsections));
30✔
1001

1002
        struct {
30✔
1003
                const char *name;
1004
                char **data;
1005
        } table[] = {
30✔
1006
                { ".osrel",   &osrelease_text },
1007
                { ".profile", &profile_text   },
1008
                { ".cmdline", &cmdline_text   },
1009
        };
1010

1011
        FOREACH_ELEMENT(t, table) {
120✔
1012
                const IMAGE_SECTION_HEADER *found;
90✔
1013

1014
                /* First look in the profile part of the section table, and if we don't find anything there, look into the base part */
1015
                found = pe_section_table_find(psections, n_psections, t->name);
90✔
1016
                if (!found) {
90✔
1017
                        found = pe_section_table_find(bsections, n_bsections, t->name);
40✔
1018
                        if (!found)
40✔
UNCOV
1019
                                continue;
×
1020
                }
1021

1022
                /* Permit "masking" of sections in the base profile */
1023
                if (found->VirtualSize == 0)
90✔
1024
                        continue;
×
1025

1026
                r = pe_read_section_data(fd, found, PE_SECTION_SIZE_MAX, (void**) t->data, /* ret_size= */ NULL);
90✔
1027
                if (r < 0)
90✔
1028
                        return log_error_errno(r, "Failed to load contents of section '%s': %m", t->name);
×
1029
        }
1030

1031
        if (!osrelease_text)
30✔
UNCOV
1032
                return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Unified kernel image lacks .osrel data for profile @%u, refusing.", profile);
×
1033

1034
        if (trim_cmdline(&cmdline_text) < 0)
30✔
UNCOV
1035
                return log_oom();
×
1036

1037
        *ret_osrelease = TAKE_PTR(osrelease_text);
30✔
1038
        *ret_profile = TAKE_PTR(profile_text);
30✔
1039
        *ret_cmdline = TAKE_PTR(cmdline_text);
30✔
1040
        return 1;
30✔
1041

1042
nothing:
10✔
1043
        *ret_osrelease = *ret_profile = *ret_cmdline = NULL;
10✔
1044
        return 0;
10✔
1045
}
1046

1047
static int pe_find_addon_sections(
10✔
1048
                int fd,
1049
                const char *path,
1050
                char **ret_cmdline) {
1051

1052
        _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
20✔
1053
        _cleanup_free_ PeHeader *pe_header = NULL;
10✔
1054
        int r;
10✔
1055

1056
        assert(fd >= 0);
10✔
1057
        assert(path);
10✔
1058

1059
        r = pe_load_headers_and_sections(fd, path, &sections, &pe_header);
10✔
1060
        if (r < 0)
10✔
1061
                return r;
1062

1063
        if (!pe_is_addon(pe_header, sections))
10✔
UNCOV
1064
                return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Parse PE file '%s' is not an add-on.", path);
×
1065

1066
        /* Define early, before the gotos below */
1067
        _cleanup_free_ char *cmdline_text = NULL;
10✔
1068

1069
        if (!pe_is_native(pe_header))
10✔
1070
                goto nothing;
×
1071

1072
        const IMAGE_SECTION_HEADER *found = pe_section_table_find(sections, le16toh(pe_header->pe.NumberOfSections), ".cmdline");
10✔
1073
        if (!found)
10✔
1074
                goto nothing;
×
1075

1076
        r = pe_read_section_data(fd, found, PE_SECTION_SIZE_MAX, (void**) &cmdline_text, /* ret_size= */ NULL);
10✔
1077
        if (r < 0)
10✔
UNCOV
1078
                return log_error_errno(r, "Failed to load contents of section '.cmdline': %m");
×
1079

1080
        if (trim_cmdline(&cmdline_text) < 0)
10✔
UNCOV
1081
                return log_oom();
×
1082

1083
        *ret_cmdline = TAKE_PTR(cmdline_text);
10✔
1084
        return 1;
10✔
1085

UNCOV
1086
nothing:
×
UNCOV
1087
        *ret_cmdline = NULL;
×
UNCOV
1088
        return 0;
×
1089
}
1090

1091
static int insert_boot_entry_addon(
10✔
1092
                BootEntryAddons *addons,
1093
                char *location,
1094
                char *cmdline) {
1095

1096
        assert(addons);
10✔
1097

1098
        if (!GREEDY_REALLOC(addons->items, addons->n_items + 1))
10✔
UNCOV
1099
                return log_oom();
×
1100

1101
        addons->items[addons->n_items++] = (BootEntryAddon) {
10✔
1102
                .location = location,
1103
                .cmdline = cmdline,
1104
        };
1105

1106
        return 0;
10✔
1107
}
1108

1109
static int boot_entries_find_unified_addons(
77✔
1110
                BootConfig *config,
1111
                int d_fd,
1112
                const char *addon_dir,
1113
                const char *root,
1114
                BootEntryAddons *ret_addons) {
1115

1116
        _cleanup_closedir_ DIR *d = NULL;
77✔
1117
        _cleanup_free_ char *full = NULL;
77✔
1118
        _cleanup_(boot_entry_addons_done) BootEntryAddons addons = {};
77✔
1119
        int r;
77✔
1120

1121
        assert(ret_addons);
77✔
1122
        assert(config);
77✔
1123

1124
        r = chase_and_opendirat(d_fd, addon_dir, CHASE_AT_RESOLVE_IN_ROOT, &full, &d);
77✔
1125
        if (r == -ENOENT)
77✔
1126
                return 0;
1127
        if (r < 0)
10✔
UNCOV
1128
                return log_error_errno(r, "Failed to open '%s/%s': %m", root, skip_leading_slash(addon_dir));
×
1129

1130
        FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read %s: %m", full)) {
40✔
1131
                _cleanup_free_ char *j = NULL, *cmdline = NULL, *location = NULL;
10✔
1132
                _cleanup_close_ int fd = -EBADF;
10✔
1133

1134
                if (!dirent_is_file(de))
10✔
UNCOV
1135
                        continue;
×
1136

1137
                if (!endswith_no_case(de->d_name, ".addon.efi"))
10✔
1138
                        continue;
×
1139

1140
                fd = openat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOFOLLOW|O_NOCTTY);
10✔
1141
                if (fd < 0) {
10✔
UNCOV
1142
                        log_warning_errno(errno, "Failed to open %s/%s, ignoring: %m", full, de->d_name);
×
UNCOV
1143
                        continue;
×
1144
                }
1145

1146
                r = config_check_inode_relevant_and_unseen(config, fd, de->d_name);
10✔
1147
                if (r < 0)
10✔
1148
                        return r;
1149
                if (r == 0) /* inode already seen or otherwise not relevant */
10✔
1150
                        continue;
×
1151

1152
                j = path_join(full, de->d_name);
10✔
1153
                if (!j)
10✔
UNCOV
1154
                        return log_oom();
×
1155

1156
                if (pe_find_addon_sections(fd, j, &cmdline) <= 0)
10✔
1157
                        continue;
×
1158

1159
                location = strdup(j);
10✔
1160
                if (!location)
10✔
UNCOV
1161
                        return log_oom();
×
1162

1163
                r = insert_boot_entry_addon(&addons, location, cmdline);
10✔
1164
                if (r < 0)
10✔
1165
                        return r;
1166

1167
                TAKE_PTR(location);
10✔
1168
                TAKE_PTR(cmdline);
10✔
1169
        }
1170

1171
        *ret_addons = TAKE_STRUCT(addons);
10✔
1172
        return 0;
10✔
1173
}
1174

1175
static int boot_entries_find_unified_global_addons(
47✔
1176
                BootConfig *config,
1177
                const char *root,
1178
                const char *d_name,
1179
                BootEntryAddons *ret_addons) {
1180

1181
        int r;
47✔
1182
        _cleanup_closedir_ DIR *d = NULL;
47✔
1183

1184
        assert(ret_addons);
47✔
1185

1186
        r = chase_and_opendir(root, NULL, CHASE_PROHIBIT_SYMLINKS, NULL, &d);
47✔
1187
        if (r == -ENOENT)
47✔
1188
                return 0;
1189
        if (r < 0)
47✔
UNCOV
1190
                return log_error_errno(r, "Failed to open '%s/%s': %m", root, skip_leading_slash(d_name));
×
1191

1192
        return boot_entries_find_unified_addons(config, dirfd(d), d_name, root, ret_addons);
47✔
1193
}
1194

1195
static int boot_entries_find_unified_local_addons(
30✔
1196
                BootConfig *config,
1197
                int d_fd,
1198
                const char *d_name,
1199
                const char *root,
1200
                BootEntry *ret) {
1201

1202
        _cleanup_free_ char *addon_dir = NULL;
30✔
1203

1204
        assert(ret);
30✔
1205

1206
        addon_dir = strjoin(d_name, ".extra.d");
30✔
1207
        if (!addon_dir)
30✔
UNCOV
1208
                return log_oom();
×
1209

1210
        return boot_entries_find_unified_addons(config, d_fd, addon_dir, root, &ret->local_addons);
30✔
1211
}
1212

1213
static int boot_entries_find_unified(
47✔
1214
                BootConfig *config,
1215
                const char *root,
1216
                BootEntrySource source,
1217
                const char *dir) {
1218

1219
        _cleanup_closedir_ DIR *d = NULL;
47✔
1220
        _cleanup_free_ char *full = NULL;
47✔
1221
        int r;
47✔
1222

1223
        assert(config);
47✔
1224
        assert(dir);
47✔
1225

1226
        r = chase_and_opendir(dir, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &full, &d);
47✔
1227
        if (r == -ENOENT)
47✔
1228
                return 0;
1229
        if (r < 0)
31✔
1230
                return log_error_errno(r, "Failed to open '%s/%s': %m", root, skip_leading_slash(dir));
×
1231

1232
        FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read %s: %m", full)) {
103✔
1233
                if (!dirent_is_file(de))
10✔
UNCOV
1234
                        continue;
×
1235

1236
                if (!endswith_no_case(de->d_name, ".efi"))
10✔
1237
                        continue;
×
1238

1239
                _cleanup_close_ int fd = openat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOFOLLOW|O_NOCTTY);
129✔
1240
                if (fd < 0) {
10✔
UNCOV
1241
                        log_warning_errno(errno, "Failed to open %s/%s, ignoring: %m", full, de->d_name);
×
UNCOV
1242
                        continue;
×
1243
                }
1244

1245
                r = config_check_inode_relevant_and_unseen(config, fd, de->d_name);
10✔
1246
                if (r < 0)
10✔
1247
                        return r;
1248
                if (r == 0) /* inode already seen or otherwise not relevant */
10✔
1249
                        continue;
×
1250

1251
                _cleanup_free_ char *j = path_join(full, de->d_name);
20✔
1252
                if (!j)
10✔
UNCOV
1253
                        return log_oom();
×
1254

1255
                for (unsigned p = 0; p < UNIFIED_PROFILES_MAX; p++) {
40✔
1256
                        _cleanup_free_ char *osrelease = NULL, *profile = NULL, *cmdline = NULL;
40✔
1257

1258
                        r = pe_find_uki_sections(fd, j, p, &osrelease, &profile, &cmdline);
40✔
1259
                        if (r == 0) /* this profile does not exist, we are done */
40✔
1260
                                break;
1261
                        if (r < 0)
30✔
UNCOV
1262
                                continue;
×
1263

1264
                        if (!GREEDY_REALLOC(config->entries, config->n_entries + 1))
30✔
UNCOV
1265
                                return log_oom();
×
1266

1267
                        BootEntry *entry = config->entries + config->n_entries;
30✔
1268

1269
                        if (boot_entry_load_unified(root, source, j, p, osrelease, profile, cmdline, entry) < 0)
30✔
UNCOV
1270
                                continue;
×
1271

1272
                        /* look for .efi.extra.d */
1273
                        (void) boot_entries_find_unified_local_addons(config, dirfd(d), de->d_name, full, entry);
30✔
1274

1275
                        /* Set up the backpointer, so that we can find the global addons */
1276
                        entry->global_addons = &config->global_addons[source];
30✔
1277

1278
                        config->n_entries++;
30✔
1279
                }
1280
        }
1281

1282
        return 0;
1283
}
1284

1285
static bool find_nonunique(const BootEntry *entries, size_t n_entries, bool arr[]) {
35✔
1286
        bool non_unique = false;
35✔
1287

1288
        assert(entries || n_entries == 0);
35✔
1289
        assert(arr || n_entries == 0);
35✔
1290

1291
        for (size_t i = 0; i < n_entries; i++)
143✔
1292
                arr[i] = false;
108✔
1293

1294
        for (size_t i = 0; i < n_entries; i++)
143✔
1295
                for (size_t j = 0; j < n_entries; j++)
462✔
1296
                        if (i != j && streq(boot_entry_title(entries + i),
354✔
1297
                                            boot_entry_title(entries + j)))
1298
                                non_unique = arr[i] = arr[j] = true;
70✔
1299

1300
        return non_unique;
35✔
1301
}
1302

1303
static int boot_entries_uniquify(BootEntry *entries, size_t n_entries) {
33✔
1304
        _cleanup_free_ bool *arr = NULL;
33✔
1305
        char *s;
33✔
1306

1307
        assert(entries || n_entries == 0);
33✔
1308

1309
        if (n_entries == 0)
33✔
1310
                return 0;
1311

1312
        arr = new(bool, n_entries);
12✔
1313
        if (!arr)
12✔
1314
                return -ENOMEM;
1315

1316
        /* Find _all_ non-unique titles */
1317
        if (!find_nonunique(entries, n_entries, arr))
12✔
1318
                return 0;
1319

1320
        /* Add version to non-unique titles */
1321
        for (size_t i = 0; i < n_entries; i++)
50✔
1322
                if (arr[i] && entries[i].version) {
38✔
1323
                        if (asprintf(&s, "%s (%s)", boot_entry_title(entries + i), entries[i].version) < 0)
26✔
1324
                                return -ENOMEM;
1325

1326
                        free_and_replace(entries[i].show_title, s);
26✔
1327
                }
1328

1329
        if (!find_nonunique(entries, n_entries, arr))
12✔
1330
                return 0;
1331

1332
        /* Add machine-id to non-unique titles */
1333
        for (size_t i = 0; i < n_entries; i++)
43✔
1334
                if (arr[i] && entries[i].machine_id) {
32✔
1335
                        if (asprintf(&s, "%s (%s)", boot_entry_title(entries + i), entries[i].machine_id) < 0)
2✔
1336
                                return -ENOMEM;
1337

1338
                        free_and_replace(entries[i].show_title, s);
2✔
1339
                }
1340

1341
        if (!find_nonunique(entries, n_entries, arr))
11✔
1342
                return 0;
1343

1344
        /* Add file name to non-unique titles */
1345
        for (size_t i = 0; i < n_entries; i++)
43✔
1346
                if (arr[i]) {
32✔
1347
                        if (asprintf(&s, "%s (%s)", boot_entry_title(entries + i), entries[i].id) < 0)
22✔
1348
                                return -ENOMEM;
1349

1350
                        free_and_replace(entries[i].show_title, s);
22✔
1351
                }
1352

1353
        return 0;
1354
}
1355

1356
static int boot_config_find(const BootConfig *config, const char *id) {
20✔
1357
        assert(config);
20✔
1358

1359
        if (!id)
20✔
1360
                return -1;
1361

1362
        if (id[0] == '@') {
20✔
UNCOV
1363
                if (!strcaseeq(id, "@saved"))
×
1364
                        return -1;
UNCOV
1365
                if (!config->entry_selected)
×
1366
                        return -1;
1367
                id = config->entry_selected;
1368
        }
1369

1370
        for (size_t i = 0; i < config->n_entries; i++)
49✔
1371
                if (fnmatch(id, config->entries[i].id, FNM_CASEFOLD) == 0)
49✔
1372
                        return i;
20✔
1373

1374
        return -1;
1375
}
1376

1377
static int boot_entries_select_default(const BootConfig *config) {
31✔
1378
        int i;
31✔
1379

1380
        assert(config);
31✔
1381
        assert(config->entries || config->n_entries == 0);
31✔
1382

1383
        if (config->n_entries == 0) {
31✔
1384
                log_debug("Found no default boot entry :(");
14✔
1385
                return -1; /* -1 means "no default" */
14✔
1386
        }
1387

1388
        if (config->entry_oneshot) {
17✔
1389
                i = boot_config_find(config, config->entry_oneshot);
×
UNCOV
1390
                if (i >= 0) {
×
UNCOV
1391
                        log_debug("Found default: id \"%s\" is matched by LoaderEntryOneShot",
×
1392
                                  config->entries[i].id);
UNCOV
1393
                        return i;
×
1394
                }
1395
        }
1396

1397
        if (config->entry_preferred) {
17✔
UNCOV
1398
                i = boot_config_find(config, config->entry_preferred);
×
UNCOV
1399
                if (i >= 0) {
×
UNCOV
1400
                        log_debug("Found default: id \"%s\" is matched by LoaderEntryPreferred",
×
1401
                                  config->entries[i].id);
UNCOV
1402
                        return i;
×
1403
                }
1404
        }
1405

1406
        if (config->entry_default) {
17✔
1407
                i = boot_config_find(config, config->entry_default);
3✔
1408
                if (i >= 0) {
3✔
1409
                        log_debug("Found default: id \"%s\" is matched by LoaderEntryDefault",
3✔
1410
                                  config->entries[i].id);
1411
                        return i;
3✔
1412
                }
1413
        }
1414

1415
        if (config->preferred_pattern) {
14✔
UNCOV
1416
                i = boot_config_find(config, config->preferred_pattern);
×
UNCOV
1417
                if (i >= 0) {
×
UNCOV
1418
                        log_debug("Found preferred: id \"%s\" is matched by pattern \"%s\"",
×
1419
                                  config->entries[i].id, config->preferred_pattern);
UNCOV
1420
                        return i;
×
1421
                }
1422
        }
1423

1424
        if (config->default_pattern) {
14✔
UNCOV
1425
                i = boot_config_find(config, config->default_pattern);
×
UNCOV
1426
                if (i >= 0) {
×
UNCOV
1427
                        log_debug("Found default: id \"%s\" is matched by pattern \"%s\"",
×
1428
                                  config->entries[i].id, config->default_pattern);
UNCOV
1429
                        return i;
×
1430
                }
1431
        }
1432

1433
        log_debug("Found default: first entry \"%s\"", config->entries[0].id);
14✔
1434
        return 0;
1435
}
1436

1437
static int boot_entries_select_selected(const BootConfig *config) {
31✔
1438
        assert(config);
31✔
1439
        assert(config->entries || config->n_entries == 0);
31✔
1440

1441
        if (!config->entry_selected || config->n_entries == 0)
31✔
1442
                return -1;
1443

1444
        return boot_config_find(config, config->entry_selected);
17✔
1445
}
1446

1447
static int boot_load_efi_entry_pointers(BootConfig *config, bool skip_efivars) {
31✔
1448
        int r;
31✔
1449

1450
        assert(config);
31✔
1451

1452
        if (skip_efivars || !is_efi_boot())
31✔
1453
                return 0;
14✔
1454

1455
        /* Loads the three "pointers" to boot loader entries from their EFI variables */
1456

1457
        r = efi_get_variable_string(EFI_LOADER_VARIABLE_STR("LoaderEntryOneShot"), &config->entry_oneshot);
17✔
1458
        if (r == -ENOMEM)
17✔
UNCOV
1459
                return log_oom();
×
1460
        if (r < 0 && !IN_SET(r, -ENOENT, -ENODATA))
17✔
UNCOV
1461
                log_warning_errno(r, "Failed to read EFI variable \"LoaderEntryOneShot\", ignoring: %m");
×
1462

1463
        r = efi_get_variable_string(EFI_LOADER_VARIABLE_STR("LoaderEntryPreferred"), &config->entry_preferred);
17✔
1464
        if (r == -ENOMEM)
17✔
UNCOV
1465
                return log_oom();
×
1466
        if (r < 0 && !IN_SET(r, -ENOENT, -ENODATA))
17✔
UNCOV
1467
                log_warning_errno(r, "Failed to read EFI variable \"LoaderEntryPreferred\", ignoring: %m");
×
1468

1469
        r = efi_get_variable_string(EFI_LOADER_VARIABLE_STR("LoaderEntryDefault"), &config->entry_default);
17✔
1470
        if (r == -ENOMEM)
17✔
UNCOV
1471
                return log_oom();
×
1472
        if (r < 0 && !IN_SET(r, -ENOENT, -ENODATA))
17✔
UNCOV
1473
                log_warning_errno(r, "Failed to read EFI variable \"LoaderEntryDefault\", ignoring: %m");
×
1474

1475
        r = efi_get_variable_string(EFI_LOADER_VARIABLE_STR("LoaderEntrySelected"), &config->entry_selected);
17✔
1476
        if (r == -ENOMEM)
17✔
UNCOV
1477
                return log_oom();
×
1478
        if (r < 0 && !IN_SET(r, -ENOENT, -ENODATA))
17✔
UNCOV
1479
                log_warning_errno(r, "Failed to read EFI variable \"LoaderEntrySelected\", ignoring: %m");
×
1480

1481
        r = efi_get_variable_string(EFI_LOADER_VARIABLE_STR("LoaderEntrySysFail"), &config->entry_sysfail);
17✔
1482
        if (r == -ENOMEM)
17✔
UNCOV
1483
                return log_oom();
×
1484
        if (r < 0 && !IN_SET(r, -ENOENT, -ENODATA))
17✔
UNCOV
1485
                log_warning_errno(r, "Failed to read EFI variable \"LoaderEntrySysFail\", ignoring: %m");
×
1486

1487
        return 1;
1488
}
1489

1490
int boot_config_select_special_entries(BootConfig *config, bool skip_efivars) {
31✔
1491
        int r;
31✔
1492

1493
        assert(config);
31✔
1494

1495
        r = boot_load_efi_entry_pointers(config, skip_efivars);
31✔
1496
        if (r < 0)
31✔
1497
                return r;
1498

1499
        config->default_entry = boot_entries_select_default(config);
31✔
1500
        config->selected_entry = boot_entries_select_selected(config);
31✔
1501

1502
        return 0;
31✔
1503
}
1504

1505
int boot_config_finalize(BootConfig *config) {
33✔
1506
        int r;
33✔
1507

1508
        typesafe_qsort(config->entries, config->n_entries, boot_entry_compare);
33✔
1509

1510
        r = boot_entries_uniquify(config->entries, config->n_entries);
33✔
1511
        if (r < 0)
33✔
UNCOV
1512
                return log_error_errno(r, "Failed to uniquify boot entries: %m");
×
1513

1514
        return 0;
1515
}
1516

1517
int boot_config_load(
33✔
1518
                BootConfig *config,
1519
                const char *esp_path,
1520
                const char *xbootldr_path) {
1521

1522
        int r;
33✔
1523

1524
        assert(config);
33✔
1525

1526
        if (esp_path) {
33✔
1527
                r = boot_loader_read_conf_path(config, esp_path, "/loader/loader.conf");
33✔
1528
                if (r < 0)
33✔
1529
                        return r;
1530

1531
                r = boot_entries_find_type1(config, esp_path, BOOT_ENTRY_ESP, "/loader/entries");
33✔
1532
                if (r < 0)
33✔
1533
                        return r;
1534

1535
                r = boot_entries_find_unified(config, esp_path, BOOT_ENTRY_ESP, "/EFI/Linux/");
33✔
1536
                if (r < 0)
33✔
1537
                        return r;
1538

1539
                r = boot_entries_find_unified_global_addons(config, esp_path, "/loader/addons/",
33✔
1540
                                                            &config->global_addons[BOOT_ENTRY_ESP]);
1541
                if (r < 0)
33✔
1542
                        return r;
1543
        }
1544

1545
        if (xbootldr_path) {
33✔
1546
                r = boot_entries_find_type1(config, xbootldr_path, BOOT_ENTRY_XBOOTLDR, "/loader/entries");
14✔
1547
                if (r < 0)
14✔
1548
                        return r;
1549

1550
                r = boot_entries_find_unified(config, xbootldr_path, BOOT_ENTRY_XBOOTLDR, "/EFI/Linux/");
14✔
1551
                if (r < 0)
14✔
1552
                        return r;
1553

1554
                r = boot_entries_find_unified_global_addons(config, xbootldr_path, "/loader/addons/",
14✔
1555
                                                            &config->global_addons[BOOT_ENTRY_XBOOTLDR]);
1556
                if (r < 0)
14✔
1557
                        return r;
1558
        }
1559

1560
        return boot_config_finalize(config);
33✔
1561
}
1562

1563
int boot_config_load_auto(
×
1564
                BootConfig *config,
1565
                const char *override_esp_path,
1566
                const char *override_xbootldr_path) {
1567

UNCOV
1568
        _cleanup_free_ char *esp_where = NULL, *xbootldr_where = NULL;
×
UNCOV
1569
        dev_t esp_devid = 0, xbootldr_devid = 0;
×
UNCOV
1570
        int r;
×
1571

1572
        assert(config);
×
1573

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

UNCOV
1581
        if (!override_esp_path && !override_xbootldr_path) {
×
UNCOV
1582
                if (access("/run/boot-loader-entries/", F_OK) >= 0)
×
UNCOV
1583
                        return boot_config_load(config, "/run/boot-loader-entries/", NULL);
×
1584

UNCOV
1585
                if (errno != ENOENT)
×
UNCOV
1586
                        return log_error_errno(errno,
×
1587
                                               "Failed to determine whether /run/boot-loader-entries/ exists: %m");
1588
        }
1589

UNCOV
1590
        r = find_esp_and_warn(NULL, override_esp_path, /* unprivileged_mode= */ false, &esp_where, NULL, NULL, NULL, NULL, &esp_devid);
×
UNCOV
1591
        if (r < 0) /* we don't log about ENOKEY here, but propagate it, leaving it to the caller to log */
×
1592
                return r;
1593

UNCOV
1594
        r = find_xbootldr_and_warn(NULL, override_xbootldr_path, /* unprivileged_mode= */ false, &xbootldr_where, NULL, &xbootldr_devid);
×
UNCOV
1595
        if (r < 0 && r != -ENOKEY)
×
1596
                return r; /* It's fine if the XBOOTLDR partition doesn't exist, hence we ignore ENOKEY here */
1597

1598
        /* If both paths actually refer to the same inode, suppress the xbootldr path */
UNCOV
1599
        if (esp_where && xbootldr_where && devnum_set_and_equal(esp_devid, xbootldr_devid))
×
UNCOV
1600
                xbootldr_where = mfree(xbootldr_where);
×
1601

UNCOV
1602
        return boot_config_load(config, esp_where, xbootldr_where);
×
1603
}
1604

1605
int boot_config_augment_from_loader(
17✔
1606
                BootConfig *config,
1607
                char **found_by_loader,
1608
                bool auto_only) {
1609

1610
        static const BootEntryAddons no_addons = (BootEntryAddons) {};
17✔
1611
        static const char *const title_table[] = {
17✔
1612
                /* Pretty names for a few well-known automatically discovered entries. */
1613
                "auto-osx",                      "macOS",
1614
                "auto-windows",                  "Windows Boot Manager",
1615
                "auto-efi-shell",                "EFI Shell",
1616
                "auto-efi-default",              "EFI Default Loader",
1617
                "auto-poweroff",                 "Power Off The System",
1618
                "auto-reboot",                   "Reboot The System",
1619
                "auto-reboot-to-firmware-setup", "Reboot Into Firmware Interface",
1620
                NULL,
1621
        };
1622

1623
        assert(config);
17✔
1624

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

1628
        STRV_FOREACH(i, found_by_loader) {
85✔
1629
                BootEntry *existing;
68✔
1630
                _cleanup_free_ char *c = NULL, *t = NULL, *p = NULL;
48✔
1631

1632
                existing = boot_config_find_entry(config, *i);
68✔
1633
                if (existing) {
68✔
1634
                        existing->reported_by_loader = true;
20✔
1635
                        continue;
20✔
1636
                }
1637

1638
                if (auto_only && !startswith(*i, "auto-"))
48✔
UNCOV
1639
                        continue;
×
1640

1641
                c = strdup(*i);
48✔
1642
                if (!c)
48✔
UNCOV
1643
                        return log_oom();
×
1644

1645
                STRV_FOREACH_PAIR(a, b, title_table)
367✔
1646
                        if (streq(*a, *i)) {
336✔
1647
                                t = strdup(*b);
17✔
1648
                                if (!t)
17✔
UNCOV
1649
                                        return log_oom();
×
1650
                                break;
1651
                        }
1652

1653
                p = strdup(EFIVAR_PATH(EFI_LOADER_VARIABLE_STR("LoaderEntries")));
48✔
1654
                if (!p)
48✔
UNCOV
1655
                        return log_oom();
×
1656

1657
                if (!GREEDY_REALLOC0(config->entries, config->n_entries + 1))
48✔
UNCOV
1658
                        return log_oom();
×
1659

1660
                config->entries[config->n_entries++] = (BootEntry) {
96✔
1661
                        .type = startswith(*i, "auto-") ? BOOT_ENTRY_AUTO : BOOT_ENTRY_LOADER,
48✔
1662
                        .id = TAKE_PTR(c),
48✔
1663
                        .title = TAKE_PTR(t),
48✔
1664
                        .path = TAKE_PTR(p),
48✔
1665
                        .reported_by_loader = true,
1666
                        .tries_left = UINT_MAX,
1667
                        .tries_done = UINT_MAX,
1668
                        .profile = UINT_MAX,
1669
                        .global_addons = &no_addons,
1670
                };
1671
        }
1672

1673
        return 0;
1674
}
1675

1676
BootEntry* boot_config_find_entry(BootConfig *config, const char *id) {
72✔
1677
        assert(config);
72✔
1678
        assert(id);
72✔
1679

1680
        for (size_t j = 0; j < config->n_entries; j++)
217✔
1681
                if (strcaseeq_ptr(config->entries[j].id, id) ||
168✔
1682
                    strcaseeq_ptr(config->entries[j].id_old, id))
145✔
1683
                        return config->entries + j;
1684

1685
        return NULL;
1686
}
1687

1688
static void boot_entry_file_list(
9✔
1689
                const char *field,
1690
                const char *root,
1691
                const char *p,
1692
                int *ret_status) {
1693

1694
        assert(p);
9✔
1695
        assert(ret_status);
9✔
1696

1697
        int status = chase_and_access(p, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, F_OK, NULL);
9✔
1698

1699
        /* Note that this shows two '/' between the root and the file. This is intentional to highlight (in
1700
         * the absence of color support) to the user that the boot loader is only interested in the second
1701
         * part of the file. */
1702
        printf("%13s%s %s%s/%s", strempty(field), field ? ":" : " ", ansi_grey(), root, ansi_normal());
18✔
1703

1704
        if (status < 0) {
9✔
UNCOV
1705
                errno = -status;
×
UNCOV
1706
                printf("%s%s%s (%m)\n", ansi_highlight_red(), p, ansi_normal());
×
1707
        } else
1708
                printf("%s\n", p);
9✔
1709

1710
        if (*ret_status == 0 && status < 0)
9✔
UNCOV
1711
                *ret_status = status;
×
1712
}
9✔
1713

1714
static void print_addon(
9✔
1715
                BootEntryAddon *addon,
1716
                const char *addon_str) {
1717

1718
        printf("  %s: %s\n", addon_str, addon->location);
9✔
1719
        printf("      options: %s%s\n", glyph(GLYPH_TREE_RIGHT), addon->cmdline);
9✔
1720
}
9✔
1721

1722
static int indent_embedded_newlines(char *cmdline, char **ret_cmdline) {
18✔
1723
        _cleanup_free_ char *t = NULL;
18✔
1724
        _cleanup_strv_free_ char **ts = NULL;
18✔
1725

1726
        assert(ret_cmdline);
18✔
1727

1728
        ts = strv_split_newlines(cmdline);
18✔
1729
        if (!ts)
18✔
1730
                return -ENOMEM;
1731

1732
        t = strv_join(ts, "\n              ");
18✔
1733
        if (!t)
18✔
1734
                return -ENOMEM;
1735

1736
        *ret_cmdline = TAKE_PTR(t);
18✔
1737

1738
        return 0;
18✔
1739
}
1740

1741
static int print_cmdline(const BootEntry *e) {
20✔
1742

1743
        _cleanup_free_ char *options = NULL, *combined_cmdline = NULL, *t2 = NULL;
20✔
1744

1745
        assert(e);
20✔
1746

1747
        if (!strv_isempty(e->options)) {
20✔
1748
                _cleanup_free_ char *t = NULL;
9✔
1749

1750
                options = strv_join(e->options, " ");
9✔
1751
                if (!options)
9✔
UNCOV
1752
                        return log_oom();
×
1753

1754
                if (indent_embedded_newlines(options, &t) < 0)
9✔
UNCOV
1755
                        return log_oom();
×
1756

1757
                printf("      options: %s\n", t);
9✔
1758
                t2 = strdup(options);
9✔
1759
                if (!t2)
9✔
UNCOV
1760
                        return log_oom();
×
1761
        }
1762

1763
        FOREACH_ARRAY(addon, e->global_addons->items, e->global_addons->n_items) {
29✔
1764
                print_addon(addon, "global-addon");
9✔
1765
                if (!strextend(&t2, " ", addon->cmdline))
9✔
UNCOV
1766
                        return log_oom();
×
1767
        }
1768

1769
        FOREACH_ARRAY(addon, e->local_addons.items, e->local_addons.n_items) {
20✔
1770
                /* Add space at the beginning of addon_str to align it correctly */
UNCOV
1771
                print_addon(addon, " local-addon");
×
UNCOV
1772
                if (!strextend(&t2, " ", addon->cmdline))
×
UNCOV
1773
                        return log_oom();
×
1774
        }
1775

1776
        /* Don't print the combined cmdline if it's same as options. */
1777
        if (streq_ptr(t2, options))
20✔
1778
                return 0;
1779

1780
        if (indent_embedded_newlines(t2, &combined_cmdline) < 0)
9✔
UNCOV
1781
                return log_oom();
×
1782

1783
        if (combined_cmdline)
9✔
1784
                printf("      cmdline: %s\n", combined_cmdline);
9✔
1785

1786
        return 0;
1787
}
1788

1789
static int json_addon(
3✔
1790
                BootEntryAddon *addon,
1791
                const char *addon_str,
1792
                sd_json_variant **array) {
1793

1794
        int r;
3✔
1795

1796
        assert(addon);
3✔
1797
        assert(addon_str);
3✔
1798

1799
        r = sd_json_variant_append_arraybo(
3✔
1800
                        array,
1801
                        SD_JSON_BUILD_PAIR_STRING(addon_str, addon->location),
1802
                        SD_JSON_BUILD_PAIR_STRING("options", addon->cmdline));
1803
        if (r < 0)
3✔
UNCOV
1804
                return log_oom();
×
1805

1806
        return 0;
1807
}
1808

1809
static int json_cmdline(
13✔
1810
                const BootEntry *e,
1811
                const char *def_cmdline,
1812
                sd_json_variant **v) {
1813

1814
        _cleanup_free_ char *combined_cmdline = NULL;
13✔
1815
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *addons_array = NULL;
13✔
1816
        int r;
13✔
1817

1818
        assert(e);
13✔
1819

1820
        if (def_cmdline) {
13✔
1821
                combined_cmdline = strdup(def_cmdline);
3✔
1822
                if (!combined_cmdline)
3✔
UNCOV
1823
                        return log_oom();
×
1824
        }
1825

1826
        FOREACH_ARRAY(addon, e->global_addons->items, e->global_addons->n_items) {
16✔
1827
                r = json_addon(addon, "globalAddon", &addons_array);
3✔
1828
                if (r < 0)
3✔
1829
                        return r;
1830
                if (!strextend(&combined_cmdline, " ", addon->cmdline))
3✔
UNCOV
1831
                        return log_oom();
×
1832
        }
1833

1834
        FOREACH_ARRAY(addon, e->local_addons.items, e->local_addons.n_items) {
13✔
UNCOV
1835
                r = json_addon(addon, "localAddon", &addons_array);
×
UNCOV
1836
                if (r < 0)
×
1837
                        return r;
UNCOV
1838
                if (!strextend(&combined_cmdline, " ", addon->cmdline))
×
UNCOV
1839
                        return log_oom();
×
1840
        }
1841

1842
        r = sd_json_variant_merge_objectbo(
13✔
1843
                        v,
1844
                        SD_JSON_BUILD_PAIR_VARIANT("addons", addons_array),
1845
                        SD_JSON_BUILD_PAIR_CONDITION(!!combined_cmdline, "cmdline", SD_JSON_BUILD_STRING(combined_cmdline)));
1846
        if (r < 0)
13✔
UNCOV
1847
                return log_oom();
×
1848
        return 0;
1849
}
1850

1851
int show_boot_entry(
20✔
1852
                const BootEntry *e,
1853
                bool show_as_default,
1854
                bool show_as_selected,
1855
                bool show_reported) {
1856

1857
        int status = 0, r = 0;
20✔
1858

1859
        /* Returns 0 on success, negative on processing error, and positive if something is wrong with the
1860
           boot entry itself. */
1861

1862
        assert(e);
20✔
1863

1864
        printf("         type: %s\n",
20✔
1865
               boot_entry_type_description_to_string(e->type));
20✔
1866

1867
        printf("        title: %s%s%s",
60✔
1868
               ansi_highlight(), boot_entry_title(e), ansi_normal());
1869

1870
        if (show_as_default)
20✔
1871
                printf(" %s(default)%s",
4✔
1872
                       ansi_highlight_green(), ansi_normal());
1873

1874
        if (show_as_selected)
20✔
1875
                printf(" %s(selected)%s",
4✔
1876
                       ansi_highlight_magenta(), ansi_normal());
1877

1878
        if (show_reported) {
20✔
1879
                if (e->type == BOOT_ENTRY_LOADER)
8✔
1880
                        printf(" %s(reported/absent)%s",
12✔
1881
                               ansi_highlight_red(), ansi_normal());
1882
                else if (!e->reported_by_loader && e->type != BOOT_ENTRY_AUTO)
2✔
UNCOV
1883
                        printf(" %s(not reported/new)%s",
×
1884
                               ansi_highlight_green(), ansi_normal());
1885
        }
1886

1887
        putchar('\n');
20✔
1888

1889
        if (e->id) {
20✔
1890
                printf("           id: %s", e->id);
20✔
1891

1892
                if (e->id_without_profile && !streq_ptr(e->id, e->id_without_profile))
20✔
1893
                        printf(" (without profile: %s)\n", e->id_without_profile);
9✔
1894
                else
1895
                        putchar('\n');
11✔
1896
        }
1897
        if (e->path) {
20✔
1898
                _cleanup_free_ char *text = NULL, *link = NULL;
20✔
1899

1900
                const char *p = e->root ? path_startswith(e->path, e->root) : NULL;
20✔
1901
                if (p) {
9✔
1902
                        text = strjoin(ansi_grey(), e->root, "/", ansi_normal(), "/", p);
18✔
1903
                        if (!text)
9✔
1904
                                return log_oom();
×
1905
                }
1906

1907
                /* Let's urlify the link to make it easy to view in an editor, but only if it is a text
1908
                 * file. Unified images are binary ELFs, and EFI variables are not pure text either. */
1909
                if (e->type == BOOT_ENTRY_TYPE1)
20✔
1910
                        (void) terminal_urlify_path(e->path, text, &link);
×
1911

1912
                printf("       source: %s (on the %s)\n",
20✔
1913
                       link ?: text ?: e->path,
20✔
1914
                       boot_entry_source_description_to_string(e->source));
20✔
1915
        }
1916
        if (e->tries_left != UINT_MAX) {
20✔
1917
                printf("        tries: %u left", e->tries_left);
×
1918

UNCOV
1919
                if (e->tries_done != UINT_MAX)
×
UNCOV
1920
                        printf("; %u done\n", e->tries_done);
×
1921
                else
UNCOV
1922
                        putchar('\n');
×
1923
        }
1924

1925
        if (e->sort_key)
20✔
1926
                printf("     sort-key: %s\n", e->sort_key);
9✔
1927
        if (e->version)
20✔
1928
                printf("      version: %s\n", e->version);
9✔
1929
        if (e->machine_id)
20✔
1930
                printf("   machine-id: %s\n", e->machine_id);
×
1931
        if (e->architecture)
20✔
UNCOV
1932
                printf(" architecture: %s\n", e->architecture);
×
1933
        if (e->kernel)
20✔
1934
                boot_entry_file_list("linux", e->root, e->kernel, &status);
9✔
1935
        if (e->efi)
20✔
UNCOV
1936
                boot_entry_file_list("efi", e->root, e->efi, &status);
×
1937
        if (e->uki)
20✔
UNCOV
1938
                boot_entry_file_list("uki", e->root, e->uki, &status);
×
1939
        if (e->uki_url)
20✔
UNCOV
1940
                printf("      uki-url: %s\n", e->uki_url);
×
1941
        if (e->profile != UINT_MAX)
20✔
1942
                printf("      profile: %u\n", e->profile);
9✔
1943

1944
        STRV_FOREACH(s, e->initrd)
20✔
UNCOV
1945
                boot_entry_file_list(s == e->initrd ? "initrd" : NULL,
×
UNCOV
1946
                                     e->root,
×
1947
                                     *s,
1948
                                     &status);
1949

1950
        r = print_cmdline(e);
20✔
1951
        if (r < 0)
20✔
1952
                return r;
1953

1954
        if (e->device_tree)
20✔
UNCOV
1955
                boot_entry_file_list("devicetree", e->root, e->device_tree, &status);
×
1956

1957
        STRV_FOREACH(s, e->device_tree_overlay)
20✔
UNCOV
1958
                boot_entry_file_list(s == e->device_tree_overlay ? "devicetree-overlay" : NULL,
×
UNCOV
1959
                                     e->root,
×
1960
                                     *s,
1961
                                     &status);
1962

1963
        return -status;
20✔
1964
}
1965

1966
int boot_entry_to_json(const BootConfig *c, size_t i, sd_json_variant **ret) {
13✔
1967
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
13✔
1968
        _cleanup_free_ char *opts = NULL;
13✔
1969
        const BootEntry *e;
13✔
1970
        int r;
13✔
1971

1972
        assert(c);
13✔
1973
        assert(ret);
13✔
1974

1975
        if (i >= c->n_entries) {
13✔
UNCOV
1976
                *ret = NULL;
×
UNCOV
1977
                return 0;
×
1978
        }
1979

1980
        e = c->entries + i;
13✔
1981

1982
        if (!strv_isempty(e->options)) {
13✔
1983
                opts = strv_join(e->options, " ");
3✔
1984
                if (!opts)
3✔
UNCOV
1985
                        return log_oom();
×
1986
        }
1987

1988
        r = sd_json_variant_merge_objectbo(
13✔
1989
                        &v,
1990
                        SD_JSON_BUILD_PAIR_STRING("type", boot_entry_type_to_string(e->type)),
1991
                        SD_JSON_BUILD_PAIR_STRING("source", boot_entry_source_to_string(e->source)),
1992
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->id, "id", SD_JSON_BUILD_STRING(e->id)),
1993
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->path, "path", SD_JSON_BUILD_STRING(e->path)),
1994
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->root, "root", SD_JSON_BUILD_STRING(e->root)),
1995
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->title, "title", SD_JSON_BUILD_STRING(e->title)),
1996
                        SD_JSON_BUILD_PAIR_CONDITION(!!boot_entry_title(e), "showTitle", SD_JSON_BUILD_STRING(boot_entry_title(e))),
1997
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->sort_key, "sortKey", SD_JSON_BUILD_STRING(e->sort_key)),
1998
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->version, "version", SD_JSON_BUILD_STRING(e->version)),
1999
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->machine_id, "machineId", SD_JSON_BUILD_STRING(e->machine_id)),
2000
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->architecture, "architecture", SD_JSON_BUILD_STRING(e->architecture)),
2001
                        SD_JSON_BUILD_PAIR_CONDITION(!!opts, "options", SD_JSON_BUILD_STRING(opts)),
2002
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->kernel, "linux", SD_JSON_BUILD_STRING(e->kernel)),
2003
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->efi, "efi", SD_JSON_BUILD_STRING(e->efi)),
2004
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->uki, "uki", SD_JSON_BUILD_STRING(e->uki)),
2005
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->uki_url, "ukiUrl", SD_JSON_BUILD_STRING(e->uki_url)),
2006
                        SD_JSON_BUILD_PAIR_CONDITION(e->profile != UINT_MAX, "profile", SD_JSON_BUILD_UNSIGNED(e->profile)),
2007
                        SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(e->initrd), "initrd", SD_JSON_BUILD_STRV(e->initrd)));
2008
        if (r < 0)
13✔
UNCOV
2009
                return log_oom();
×
2010

2011
        /* Sanitizers (only memory sanitizer?) do not like function call with too many
2012
         * arguments and trigger false positive warnings. Let's not add too many json objects
2013
         * at once. */
2014
        r = sd_json_variant_merge_objectbo(
13✔
2015
                        &v,
2016
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->device_tree, "devicetree", SD_JSON_BUILD_STRING(e->device_tree)),
2017
                        SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(e->device_tree_overlay), "devicetreeOverlay", SD_JSON_BUILD_STRV(e->device_tree_overlay)),
2018
                        SD_JSON_BUILD_PAIR_BOOLEAN("isReported", e->reported_by_loader),
2019
                        SD_JSON_BUILD_PAIR_CONDITION(e->tries_left != UINT_MAX, "triesLeft", SD_JSON_BUILD_UNSIGNED(e->tries_left)),
2020
                        SD_JSON_BUILD_PAIR_CONDITION(e->tries_done != UINT_MAX, "triesDone", SD_JSON_BUILD_UNSIGNED(e->tries_done)),
2021
                        SD_JSON_BUILD_PAIR_CONDITION(c->default_entry >= 0, "isDefault", SD_JSON_BUILD_BOOLEAN(i == (size_t) c->default_entry)),
2022
                        SD_JSON_BUILD_PAIR_CONDITION(c->selected_entry >= 0, "isSelected", SD_JSON_BUILD_BOOLEAN(i == (size_t) c->selected_entry)));
2023
        if (r < 0)
13✔
UNCOV
2024
                return log_oom();
×
2025

2026
        r = json_cmdline(e, opts, &v);
13✔
2027
        if (r < 0)
13✔
UNCOV
2028
                return log_oom();
×
2029

2030
        *ret = TAKE_PTR(v);
13✔
2031
        return 1;
13✔
2032
}
2033

2034
int show_boot_entries(const BootConfig *config, sd_json_format_flags_t json_format) {
8✔
2035
        int r;
8✔
2036

2037
        assert(config);
8✔
2038

2039
        if (sd_json_format_enabled(json_format)) {
8✔
2040
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL;
6✔
2041

2042
                for (size_t i = 0; i < config->n_entries; i++) {
14✔
2043
                        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
8✔
2044

2045
                        r = boot_entry_to_json(config, i, &v);
8✔
2046
                        if (r < 0)
8✔
UNCOV
2047
                                return log_oom();
×
2048

2049
                        r = sd_json_variant_append_array(&array, v);
8✔
2050
                        if (r < 0)
8✔
UNCOV
2051
                                return log_oom();
×
2052
                }
2053

2054
                return sd_json_variant_dump(array, json_format | SD_JSON_FORMAT_EMPTY_ARRAY, NULL, NULL);
6✔
2055
        } else
2056
                for (size_t n = 0; n < config->n_entries; n++) {
10✔
2057
                        r = show_boot_entry(
16✔
2058
                                        config->entries + n,
8✔
2059
                                        /* show_as_default= */  n == (size_t) config->default_entry,
8✔
2060
                                        /* show_as_selected= */ n == (size_t) config->selected_entry,
8✔
2061
                                        /* show_reported= */  true);
2062
                        if (r < 0)
8✔
2063
                                return r;
2064

2065
                        if (n+1 < config->n_entries)
8✔
2066
                                putchar('\n');
6✔
2067
                }
2068

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

© 2026 Coveralls, Inc