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

systemd / systemd / 18251268710

04 Oct 2025 09:35PM UTC coverage: 72.225% (-0.06%) from 72.28%
18251268710

push

github

web-flow
shared/bootspec: don't warn for new `loader.conf` options and correctly parse new `uki` and `profile` boot entry options (#39165)

Commit e2a3d5621 added the `uki` option
to sd-boot, and 1e9c9773b added
`profile`, but because these were not added in src/shared/bootspec,
bootctl still shows warnings like `Unknown line 'uki', ignoring.` when
parsing the config. This PR allows parsing and displaying them correctly
in `bootctl` output. It also stops it from printing a warning for any of
the new `loader.conf` options (`log-level`, `reboot-on-error`, etc.).
Note that `uki-url` is still not handled as I can't easily test it.

4 of 12 new or added lines in 2 files covered. (33.33%)

3065 existing lines in 68 files now uncovered.

303282 of 419915 relevant lines covered (72.22%)

1059441.35 hits per line

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

72.72
/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);
22✔
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);
15✔
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);
22✔
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);
15✔
66

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

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

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

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

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

109
        _cleanup_free_ char *c = NULL;
×
110

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

257
        assert(fname);
88✔
258
        assert(ret_stripped);
88✔
259
        assert(ret_tries_left);
88✔
260
        assert(ret_tries_done);
88✔
261

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

268
        p = m = memrchr(fname, '+', suffix - fname);
87✔
269
        if (!p)
87✔
270
                goto nothing;
64✔
271
        p++;
23✔
272

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

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

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

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

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

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

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

303
        return 0;
16✔
304

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

388
                        continue;
×
389
                }
390

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

425
        *ret = TAKE_STRUCT(tmp);
8✔
426
        return 0;
8✔
427
}
428

429
int boot_config_load_type1(
8✔
430
                BootConfig *config,
431
                FILE *f,
432
                const char *root,
433
                const BootEntrySource source,
434
                const char *dir,
435
                const char *fname) {
436
        int r;
8✔
437

438
        assert(config);
8✔
439
        assert(f);
8✔
440
        assert(root);
8✔
441
        assert(dir);
8✔
442
        assert(fname);
8✔
443

444
        if (!GREEDY_REALLOC(config->entries, config->n_entries + 1))
8✔
445
                return log_oom();
×
446

447
        BootEntry *entry = config->entries + config->n_entries;
8✔
448

449
        r = boot_entry_load_type1(f, root, source, dir, fname, entry);
8✔
450
        if (r < 0)
8✔
451
                return r;
452
        config->n_entries++;
8✔
453

454
        entry->global_addons = &config->global_addons[source];
8✔
455

456
        return 0;
8✔
457
}
458

459
void boot_config_free(BootConfig *config) {
33✔
460
        assert(config);
33✔
461

462
        free(config->default_pattern);
33✔
463

464
        free(config->entry_oneshot);
33✔
465
        free(config->entry_default);
33✔
466
        free(config->entry_selected);
33✔
467
        free(config->entry_sysfail);
33✔
468

469
        FOREACH_ARRAY(i, config->entries, config->n_entries)
126✔
470
                boot_entry_free(i);
93✔
471
        free(config->entries);
33✔
472

473
        FOREACH_ELEMENT(i, config->global_addons)
99✔
474
                boot_entry_addons_done(i);
66✔
475

476
        set_free(config->inodes_seen);
33✔
477
}
33✔
478

479
int boot_loader_read_conf(BootConfig *config, FILE *file, const char *path) {
31✔
480
        int r;
31✔
481

482
        assert(config);
31✔
483
        assert(file);
31✔
484
        assert(path);
31✔
485

486
        for (unsigned line = 1;; line++) {
62✔
487
                _cleanup_free_ char *buf = NULL, *field = NULL;
62✔
488

489
                r = read_stripped_line(file, LONG_LINE_MAX, &buf);
93✔
490
                if (r == -ENOBUFS)
93✔
491
                        return log_syntax(NULL, LOG_ERR, path, line, r, "Line too long.");
×
492
                if (r < 0)
93✔
493
                        return log_syntax(NULL, LOG_ERR, path, line, r, "Error while reading: %m");
×
494
                if (r == 0)
93✔
495
                        break;
496

497
                if (IN_SET(buf[0], '#', '\0'))
62✔
498
                        continue;
62✔
499

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

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

530
        return 1;
31✔
531
}
532

533
static int boot_loader_read_conf_path(BootConfig *config, const char *root, const char *path) {
33✔
534
        _cleanup_free_ char *full = NULL;
33✔
535
        _cleanup_fclose_ FILE *f = NULL;
33✔
536
        int r;
33✔
537

538
        assert(config);
33✔
539
        assert(path);
33✔
540

541
        r = chase_and_fopen_unlocked(path, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, "re", &full, &f);
33✔
542
        config->loader_conf_status = r < 0 ? r : true;
33✔
543
        if (r == -ENOENT)
33✔
544
                return 0;
545
        if (r < 0)
31✔
546
                return log_error_errno(r, "Failed to open '%s/%s': %m", root, skip_leading_slash(path));
×
547

548
        return boot_loader_read_conf(config, f, full);
31✔
549
}
550

551
static int boot_entry_compare(const BootEntry *a, const BootEntry *b) {
42✔
552
        int r;
42✔
553

554
        assert(a);
42✔
555
        assert(b);
42✔
556

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

559
        r = CMP(a->tries_left == 0, b->tries_left == 0);
42✔
560
        if (r != 0)
42✔
561
                return r;
×
562

563
        r = CMP(!a->sort_key, !b->sort_key);
42✔
564
        if (r != 0)
39✔
565
                return r;
3✔
566

567
        if (a->sort_key && b->sort_key) {
39✔
568
                r = strcmp(a->sort_key, b->sort_key);
36✔
569
                if (r != 0)
36✔
570
                        return r;
571

572
                r = strcmp_ptr(a->machine_id, b->machine_id);
35✔
573
                if (r != 0)
35✔
574
                        return r;
575

576
                r = -strverscmp_improved(a->version, b->version);
35✔
577
                if (r != 0)
35✔
578
                        return r;
579
        }
580

581
        r = -strverscmp_improved(a->id_without_profile ?: a->id, b->id_without_profile ?: b->id);
37✔
582
        if (r != 0)
37✔
583
                return r;
584

585
        if (a->id_without_profile && b->id_without_profile) {
34✔
586
                /* The strverscmp_improved() call above already established that we are talking about the
587
                 * same image here, hence order by profile, if there is one */
588
                r = CMP(a->profile, b->profile);
34✔
589
                if (r != 0)
×
590
                        return r;
34✔
591
        }
592

593
        if (a->tries_left != UINT_MAX || b->tries_left != UINT_MAX)
×
594
                return 0;
595

596
        r = -CMP(a->tries_left, b->tries_left);
×
597
        if (r != 0)
×
598
                return r;
×
599

600
        return CMP(a->tries_done, b->tries_done);
×
601
}
602

603
static int config_check_inode_relevant_and_unseen(BootConfig *config, int fd, const char *fname) {
42✔
604
        _cleanup_free_ char *d = NULL;
42✔
605
        struct stat st;
42✔
606

607
        assert(config);
42✔
608
        assert(fd >= 0);
42✔
609
        assert(fname);
42✔
610

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

617
        if (fstat(fd, &st) < 0)
42✔
618
                return log_error_errno(errno, "Failed to stat('%s'): %m", fname);
×
619
        if (!S_ISREG(st.st_mode)) {
42✔
620
                log_debug("File '%s' is not a regular file, ignoring.", fname);
×
621
                return false;
×
622
        }
623

624
        if (set_contains(config->inodes_seen, &st)) {
42✔
625
                log_debug("Inode '%s' already seen before, ignoring.", fname);
×
626
                return false;
×
627
        }
628

629
        d = memdup(&st, sizeof(st));
42✔
630
        if (!d)
42✔
631
                return log_oom();
×
632

633
        if (set_ensure_consume(&config->inodes_seen, &inode_hash_ops, TAKE_PTR(d)) < 0)
42✔
634
                return log_oom();
×
635

636
        return true;
637
}
638

639
static int boot_entries_find_type1(
47✔
640
                BootConfig *config,
641
                const char *root,
642
                const BootEntrySource source,
643
                const char *dir) {
644

645
        _cleanup_free_ DirectoryEntries *dentries = NULL;
94✔
646
        _cleanup_free_ char *full = NULL;
47✔
647
        _cleanup_close_ int dir_fd = -EBADF;
47✔
648
        int r;
47✔
649

650
        assert(config);
47✔
651
        assert(root);
47✔
652
        assert(dir);
47✔
653

654
        dir_fd = chase_and_open(dir, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, O_DIRECTORY|O_CLOEXEC, &full);
47✔
655
        if (dir_fd == -ENOENT)
47✔
656
                return 0;
657
        if (dir_fd < 0)
33✔
658
                return log_error_errno(dir_fd, "Failed to open '%s/%s': %m", root, skip_leading_slash(dir));
×
659

660
        r = readdir_all(dir_fd, RECURSE_DIR_IGNORE_DOT, &dentries);
33✔
661
        if (r < 0)
33✔
662
                return log_error_errno(r, "Failed to read directory '%s': %m", full);
×
663

664
        FOREACH_ARRAY(i, dentries->entries, dentries->n_entries) {
41✔
665
                const struct dirent *de = *i;
8✔
666
                _cleanup_fclose_ FILE *f = NULL;
8✔
667

668
                if (!dirent_is_file(de))
8✔
669
                        continue;
×
670

671
                if (!endswith_no_case(de->d_name, ".conf"))
8✔
672
                        continue;
×
673

674
                r = xfopenat(dir_fd, de->d_name, "re", O_NOFOLLOW|O_NOCTTY, &f);
8✔
675
                if (r < 0) {
8✔
676
                        log_warning_errno(r, "Failed to open %s/%s, ignoring: %m", full, de->d_name);
×
677
                        continue;
×
678
                }
679

680
                r = config_check_inode_relevant_and_unseen(config, fileno(f), de->d_name);
8✔
681
                if (r < 0)
8✔
682
                        return r;
683
                if (r == 0) /* inode already seen or otherwise not relevant */
8✔
684
                        continue;
×
685

686
                r = boot_config_load_type1(config, f, root, source, full, de->d_name);
8✔
687
                if (r == -ENOMEM) /* ignore all other errors */
8✔
688
                        return log_oom();
×
689
        }
690

691
        return 0;
692
}
693

694
static int boot_entry_load_unified(
51✔
695
                const char *root,
696
                const BootEntrySource source,
697
                const char *path,
698
                unsigned profile,
699
                const char *osrelease_text,
700
                const char *profile_text,
701
                const char *cmdline_text,
702
                BootEntry *ret) {
703

704
        _cleanup_free_ char *fname = NULL, *os_pretty_name = NULL, *os_image_id = NULL, *os_name = NULL, *os_id = NULL,
×
705
                *os_image_version = NULL, *os_version = NULL, *os_version_id = NULL, *os_build_id = NULL;
51✔
706
        const char *k, *good_name, *good_version, *good_sort_key;
51✔
707
        _cleanup_fclose_ FILE *f = NULL;
51✔
708
        int r;
51✔
709

710
        assert(root);
51✔
711
        assert(path);
51✔
712
        assert(osrelease_text);
51✔
713
        assert(ret);
51✔
714

715
        k = path_startswith(path, root);
51✔
716
        if (!k)
51✔
717
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Path is not below root: %s", path);
×
718

719
        f = fmemopen_unlocked((void*) osrelease_text, strlen(osrelease_text), "r");
51✔
720
        if (!f)
51✔
721
                return log_oom();
×
722

723
        r = parse_env_file(f, "os-release",
51✔
724
                           "PRETTY_NAME", &os_pretty_name,
725
                           "IMAGE_ID", &os_image_id,
726
                           "NAME", &os_name,
727
                           "ID", &os_id,
728
                           "IMAGE_VERSION", &os_image_version,
729
                           "VERSION", &os_version,
730
                           "VERSION_ID", &os_version_id,
731
                           "BUILD_ID", &os_build_id);
732
        if (r < 0)
51✔
733
                return log_error_errno(r, "Failed to parse os-release data from unified kernel image %s: %m", path);
×
734

735
        if (!bootspec_pick_name_version_sort_key(
51✔
736
                            os_pretty_name,
737
                            os_image_id,
738
                            os_name,
739
                            os_id,
740
                            os_image_version,
741
                            os_version,
742
                            os_version_id,
743
                            os_build_id,
744
                            &good_name,
745
                            &good_version,
746
                            &good_sort_key))
747
                return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Missing fields in os-release data from unified kernel image %s, refusing.", path);
×
748

749
        _cleanup_free_ char *profile_id = NULL, *profile_title = NULL;
51✔
750
        if (profile_text) {
51✔
751
                fclose(f);
51✔
752

753
                f = fmemopen_unlocked((void*) profile_text, strlen(profile_text), "r");
51✔
754
                if (!f)
51✔
755
                        return log_oom();
×
756

757
                r = parse_env_file(
51✔
758
                                f, "profile",
759
                                "ID", &profile_id,
760
                                "TITLE", &profile_title);
761
                if (r < 0)
51✔
762
                        return log_error_errno(r, "Failed to parse profile data from unified kernel image '%s': %m", path);
×
763
        }
764

765
        r = path_extract_filename(path, &fname);
51✔
766
        if (r < 0)
51✔
767
                return log_error_errno(r, "Failed to extract file name from '%s': %m", path);
×
768

769
        _cleanup_(boot_entry_free) BootEntry tmp = BOOT_ENTRY_INIT(BOOT_ENTRY_TYPE2, source);
51✔
770

771
        r = boot_filename_extract_tries(fname, &tmp.id, &tmp.tries_left, &tmp.tries_done);
51✔
772
        if (r < 0)
51✔
773
                return r;
774

775
        if (!efi_loader_entry_name_valid(tmp.id))
51✔
776
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry name: %s", tmp.id);
×
777

778
        tmp.profile = profile;
51✔
779

780
        if (profile_id || profile > 0) {
51✔
781
                tmp.id_without_profile = TAKE_PTR(tmp.id);
51✔
782

783
                if (profile_id)
51✔
784
                        tmp.id = strjoin(tmp.id_without_profile, "@", profile_id);
51✔
785
                else
786
                        (void) asprintf(&tmp.id, "%s@%u", tmp.id_without_profile, profile);
×
787
                if (!tmp.id)
51✔
788
                        return log_oom();
×
789
        }
790

791
        if (os_id && os_version_id) {
51✔
792
                tmp.id_old = strjoin(os_id, "-", os_version_id);
×
793
                if (!tmp.id_old)
×
794
                        return log_oom();
×
795
        }
796

797
        tmp.path = strdup(path);
51✔
798
        if (!tmp.path)
51✔
799
                return log_oom();
×
800

801
        tmp.root = strdup(root);
51✔
802
        if (!tmp.root)
51✔
803
                return log_oom();
×
804

805
        tmp.kernel = path_make_absolute(k, "/");
51✔
806
        if (!tmp.kernel)
51✔
807
                return log_oom();
×
808

809
        tmp.options = strv_new(cmdline_text);
51✔
810
        if (!tmp.options)
51✔
811
                return log_oom();
×
812

813
        if (profile_title)
51✔
814
                tmp.title = strjoin(good_name, " (", profile_title, ")");
34✔
815
        else if (profile_id)
17✔
816
                tmp.title = strjoin(good_name, " (", profile_id, ")");
17✔
817
        else if (profile > 0)
×
818
                (void) asprintf(&tmp.title, "%s (@%u)", good_name, profile);
×
819
        else
820
                tmp.title = strdup(good_name);
×
821
        if (!tmp.title)
51✔
822
                return log_oom();
×
823

824
        if (good_sort_key) {
51✔
825
                tmp.sort_key = strdup(good_sort_key);
51✔
826
                if (!tmp.sort_key)
51✔
827
                        return log_oom();
×
828
        }
829

830
        if (good_version) {
51✔
831
                tmp.version = strdup(good_version);
51✔
832
                if (!tmp.version)
51✔
833
                        return log_oom();
×
834
        }
835

836
        *ret = TAKE_STRUCT(tmp);
51✔
837
        return 0;
51✔
838
}
839

840
static int pe_load_headers_and_sections(
85✔
841
                int fd,
842
                const char *path,
843
                IMAGE_SECTION_HEADER **ret_sections,
844
                PeHeader **ret_pe_header) {
845

846
        _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
170✔
847
        _cleanup_free_ IMAGE_DOS_HEADER *dos_header = NULL;
×
848
        _cleanup_free_ PeHeader *pe_header = NULL;
85✔
849
        int r;
85✔
850

851
        assert(fd >= 0);
85✔
852
        assert(path);
85✔
853

854
        r = pe_load_headers(fd, &dos_header, &pe_header);
85✔
855
        if (r < 0)
85✔
856
                return log_error_errno(r, "Failed to parse PE file '%s': %m", path);
×
857

858
        r = pe_load_sections(fd, dos_header, pe_header, &sections);
85✔
859
        if (r < 0)
85✔
860
                return log_error_errno(r, "Failed to parse PE sections of '%s': %m", path);
×
861

862
        if (ret_pe_header)
85✔
863
                *ret_pe_header = TAKE_PTR(pe_header);
85✔
864
        if (ret_sections)
85✔
865
                *ret_sections = TAKE_PTR(sections);
85✔
866

867
        return 0;
868
}
869

870
static const IMAGE_SECTION_HEADER* pe_find_profile_section_table(
119✔
871
                const PeHeader *pe_header,
872
                const IMAGE_SECTION_HEADER *sections,
873
                unsigned profile,
874
                size_t *ret_n_sections) {
875

876
        assert(pe_header);
119✔
877

878
        /* Looks for the part of the section table that defines the specified profile. If 'profile' is
879
         * specified as UINT_MAX this will look for the base profile. */
880

881
        if (le16toh(pe_header->pe.NumberOfSections) == 0)
119✔
882
                return NULL;
883

884
        assert(sections);
119✔
885

886
        const IMAGE_SECTION_HEADER
119✔
887
                *p = sections,
119✔
888
                *e = sections + le16toh(pe_header->pe.NumberOfSections),
119✔
889
                *start = profile == UINT_MAX ? sections : NULL,
119✔
890
                *end;
891
        unsigned current_profile = UINT_MAX;
892

893
        for (;;) {
425✔
894
                p = pe_section_table_find(p, e - p, ".profile");
272✔
895
                if (!p) {
272✔
896
                        end = e;
897
                        break;
898
                }
899
                if (current_profile == profile) {
238✔
900
                        end = p;
901
                        break;
902
                }
903

904
                if (current_profile == UINT_MAX)
153✔
905
                        current_profile = 0;
906
                else
907
                        current_profile++;
85✔
908

909
                if (current_profile == profile)
153✔
910
                        start = p;
51✔
911

912
                p++; /* Continue scanning after the .profile entry we just found */
153✔
913
        }
914

915
        if (!start)
119✔
916
                return NULL;
917

918
        if (ret_n_sections)
102✔
919
                *ret_n_sections = end - start;
102✔
920

921
        return start;
922
}
923

924
static int trim_cmdline(char **cmdline) {
68✔
925
        assert(cmdline);
68✔
926

927
        /* Strips leading and trailing whitespace from command line */
928

929
        if (!*cmdline)
68✔
930
                return 0;
931

932
        const char *skipped = skip_leading_chars(*cmdline, WHITESPACE);
68✔
933

934
        if (isempty(skipped)) {
68✔
935
                *cmdline = mfree(*cmdline);
×
936
                return 0;
×
937
        }
938

939
        if (skipped != *cmdline) {
68✔
940
                _cleanup_free_ char *c = strdup(skipped);
×
941
                if (!c)
×
942
                        return -ENOMEM;
×
943

944
                free_and_replace(*cmdline, c);
×
945
        }
946

947
        delete_trailing_chars(*cmdline, WHITESPACE);
68✔
948
        return 1;
68✔
949
}
950

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

955
static int pe_find_uki_sections(
68✔
956
                int fd,
957
                const char *path,
958
                unsigned profile,
959
                char **ret_osrelease,
960
                char **ret_profile,
961
                char **ret_cmdline) {
962

963
        _cleanup_free_ char *osrelease_text = NULL, *profile_text = NULL, *cmdline_text = NULL;
×
964
        _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
×
965
        _cleanup_free_ PeHeader *pe_header = NULL;
68✔
966
        int r;
68✔
967

968
        assert(fd >= 0);
68✔
969
        assert(path);
68✔
970
        assert(profile != UINT_MAX);
68✔
971
        assert(ret_osrelease);
68✔
972
        assert(ret_profile);
68✔
973
        assert(ret_cmdline);
68✔
974

975
        r = pe_load_headers_and_sections(fd, path, &sections, &pe_header);
68✔
976
        if (r < 0)
68✔
977
                return r;
978

979
        if (!pe_is_uki(pe_header, sections))
68✔
980
                return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Parsed PE file '%s' is not a UKI.", path);
×
981

982
        if (!pe_is_native(pe_header)) /* Don't process non-native UKIs */
68✔
983
                goto nothing;
×
984

985
        /* Find part of the section table for this profile */
986
        size_t n_psections = 0;
68✔
987
        const IMAGE_SECTION_HEADER *psections = pe_find_profile_section_table(pe_header, sections, profile, &n_psections);
68✔
988
        if (!psections && profile != 0) /* Profile not found? (Profile @0 needs no explicit .profile!) */
68✔
989
                goto nothing;
17✔
990

991
        /* Find base profile part of section table */
992
        size_t n_bsections;
51✔
993
        const IMAGE_SECTION_HEADER *bsections = ASSERT_PTR(pe_find_profile_section_table(pe_header, sections, UINT_MAX, &n_bsections));
51✔
994

995
        struct {
51✔
996
                const char *name;
997
                char **data;
998
        } table[] = {
51✔
999
                { ".osrel",   &osrelease_text },
1000
                { ".profile", &profile_text   },
1001
                { ".cmdline", &cmdline_text   },
1002
        };
1003

1004
        FOREACH_ELEMENT(t, table) {
204✔
1005
                const IMAGE_SECTION_HEADER *found;
153✔
1006

1007
                /* First look in the profile part of the section table, and if we don't find anything there, look into the base part */
1008
                found = pe_section_table_find(psections, n_psections, t->name);
153✔
1009
                if (!found) {
153✔
1010
                        found = pe_section_table_find(bsections, n_bsections, t->name);
68✔
1011
                        if (!found)
68✔
1012
                                continue;
×
1013
                }
1014

1015
                /* Permit "masking" of sections in the base profile */
1016
                if (found->VirtualSize == 0)
153✔
1017
                        continue;
×
1018

1019
                r = pe_read_section_data(fd, found, PE_SECTION_SIZE_MAX, (void**) t->data, /* ret_size= */ NULL);
153✔
1020
                if (r < 0)
153✔
1021
                        return log_error_errno(r, "Failed to load contents of section '%s': %m", t->name);
×
1022
        }
1023

1024
        if (!osrelease_text)
51✔
1025
                return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Unified kernel image lacks .osrel data for profile @%u, refusing.", profile);
×
1026

1027
        if (trim_cmdline(&cmdline_text) < 0)
51✔
1028
                return log_oom();
×
1029

1030
        *ret_osrelease = TAKE_PTR(osrelease_text);
51✔
1031
        *ret_profile = TAKE_PTR(profile_text);
51✔
1032
        *ret_cmdline = TAKE_PTR(cmdline_text);
51✔
1033
        return 1;
51✔
1034

1035
nothing:
17✔
1036
        *ret_osrelease = *ret_profile = *ret_cmdline = NULL;
17✔
1037
        return 0;
17✔
1038
}
1039

1040
static int pe_find_addon_sections(
17✔
1041
                int fd,
1042
                const char *path,
1043
                char **ret_cmdline) {
1044

1045
        _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
34✔
1046
        _cleanup_free_ PeHeader *pe_header = NULL;
17✔
1047
        int r;
17✔
1048

1049
        assert(fd >= 0);
17✔
1050
        assert(path);
17✔
1051

1052
        r = pe_load_headers_and_sections(fd, path, &sections, &pe_header);
17✔
1053
        if (r < 0)
17✔
1054
                return r;
1055

1056
        if (!pe_is_addon(pe_header, sections))
17✔
1057
                return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Parse PE file '%s' is not an add-on.", path);
×
1058

1059
        /* Define early, before the gotos below */
1060
        _cleanup_free_ char *cmdline_text = NULL;
17✔
1061

1062
        if (!pe_is_native(pe_header))
17✔
1063
                goto nothing;
×
1064

1065
        const IMAGE_SECTION_HEADER *found = pe_section_table_find(sections, le16toh(pe_header->pe.NumberOfSections), ".cmdline");
17✔
1066
        if (!found)
17✔
1067
                goto nothing;
×
1068

1069
        r = pe_read_section_data(fd, found, PE_SECTION_SIZE_MAX, (void**) &cmdline_text, /* ret_size= */ NULL);
17✔
1070
        if (r < 0)
17✔
1071
                return log_error_errno(r, "Failed to load contents of section '.cmdline': %m");
×
1072

1073
        if (trim_cmdline(&cmdline_text) < 0)
17✔
1074
                return log_oom();
×
1075

1076
        *ret_cmdline = TAKE_PTR(cmdline_text);
17✔
1077
        return 1;
17✔
1078

1079
nothing:
×
1080
        *ret_cmdline = NULL;
×
1081
        return 0;
×
1082
}
1083

1084
static int insert_boot_entry_addon(
17✔
1085
                BootEntryAddons *addons,
1086
                char *location,
1087
                char *cmdline) {
1088

1089
        assert(addons);
17✔
1090

1091
        if (!GREEDY_REALLOC(addons->items, addons->n_items + 1))
17✔
1092
                return log_oom();
×
1093

1094
        addons->items[addons->n_items++] = (BootEntryAddon) {
17✔
1095
                .location = location,
1096
                .cmdline = cmdline,
1097
        };
1098

1099
        return 0;
17✔
1100
}
1101

1102
static int boot_entries_find_unified_addons(
98✔
1103
                BootConfig *config,
1104
                int d_fd,
1105
                const char *addon_dir,
1106
                const char *root,
1107
                BootEntryAddons *ret_addons) {
1108

1109
        _cleanup_closedir_ DIR *d = NULL;
98✔
1110
        _cleanup_free_ char *full = NULL;
98✔
1111
        _cleanup_(boot_entry_addons_done) BootEntryAddons addons = {};
98✔
1112
        int r;
98✔
1113

1114
        assert(ret_addons);
98✔
1115
        assert(config);
98✔
1116

1117
        r = chase_and_opendirat(d_fd, addon_dir, CHASE_AT_RESOLVE_IN_ROOT, &full, &d);
98✔
1118
        if (r == -ENOENT)
98✔
1119
                return 0;
1120
        if (r < 0)
17✔
1121
                return log_error_errno(r, "Failed to open '%s/%s': %m", root, skip_leading_slash(addon_dir));
×
1122

1123
        FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read %s: %m", full)) {
68✔
1124
                _cleanup_free_ char *j = NULL, *cmdline = NULL, *location = NULL;
17✔
1125
                _cleanup_close_ int fd = -EBADF;
17✔
1126

1127
                if (!dirent_is_file(de))
17✔
1128
                        continue;
×
1129

1130
                if (!endswith_no_case(de->d_name, ".addon.efi"))
17✔
1131
                        continue;
×
1132

1133
                fd = openat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOFOLLOW|O_NOCTTY);
17✔
1134
                if (fd < 0) {
17✔
1135
                        log_warning_errno(errno, "Failed to open %s/%s, ignoring: %m", full, de->d_name);
×
1136
                        continue;
×
1137
                }
1138

1139
                r = config_check_inode_relevant_and_unseen(config, fd, de->d_name);
17✔
1140
                if (r < 0)
17✔
1141
                        return r;
1142
                if (r == 0) /* inode already seen or otherwise not relevant */
17✔
1143
                        continue;
×
1144

1145
                j = path_join(full, de->d_name);
17✔
1146
                if (!j)
17✔
1147
                        return log_oom();
×
1148

1149
                if (pe_find_addon_sections(fd, j, &cmdline) <= 0)
17✔
1150
                        continue;
×
1151

1152
                location = strdup(j);
17✔
1153
                if (!location)
17✔
1154
                        return log_oom();
×
1155

1156
                r = insert_boot_entry_addon(&addons, location, cmdline);
17✔
1157
                if (r < 0)
17✔
1158
                        return r;
1159

1160
                TAKE_PTR(location);
17✔
1161
                TAKE_PTR(cmdline);
17✔
1162
        }
1163

1164
        *ret_addons = TAKE_STRUCT(addons);
17✔
1165
        return 0;
17✔
1166
}
1167

1168
static int boot_entries_find_unified_global_addons(
47✔
1169
                BootConfig *config,
1170
                const char *root,
1171
                const char *d_name,
1172
                BootEntryAddons *ret_addons) {
1173

1174
        int r;
47✔
1175
        _cleanup_closedir_ DIR *d = NULL;
47✔
1176

1177
        assert(ret_addons);
47✔
1178

1179
        r = chase_and_opendir(root, NULL, CHASE_PROHIBIT_SYMLINKS, NULL, &d);
47✔
1180
        if (r == -ENOENT)
47✔
1181
                return 0;
1182
        if (r < 0)
47✔
1183
                return log_error_errno(r, "Failed to open '%s/%s': %m", root, skip_leading_slash(d_name));
×
1184

1185
        return boot_entries_find_unified_addons(config, dirfd(d), d_name, root, ret_addons);
47✔
1186
}
1187

1188
static int boot_entries_find_unified_local_addons(
51✔
1189
                BootConfig *config,
1190
                int d_fd,
1191
                const char *d_name,
1192
                const char *root,
1193
                BootEntry *ret) {
1194

1195
        _cleanup_free_ char *addon_dir = NULL;
51✔
1196

1197
        assert(ret);
51✔
1198

1199
        addon_dir = strjoin(d_name, ".extra.d");
51✔
1200
        if (!addon_dir)
51✔
1201
                return log_oom();
×
1202

1203
        return boot_entries_find_unified_addons(config, d_fd, addon_dir, root, &ret->local_addons);
51✔
1204
}
1205

1206
static int boot_entries_find_unified(
47✔
1207
                BootConfig *config,
1208
                const char *root,
1209
                BootEntrySource source,
1210
                const char *dir) {
1211

1212
        _cleanup_closedir_ DIR *d = NULL;
47✔
1213
        _cleanup_free_ char *full = NULL;
47✔
1214
        int r;
47✔
1215

1216
        assert(config);
47✔
1217
        assert(dir);
47✔
1218

1219
        r = chase_and_opendir(dir, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &full, &d);
47✔
1220
        if (r == -ENOENT)
47✔
1221
                return 0;
1222
        if (r < 0)
31✔
1223
                return log_error_errno(r, "Failed to open '%s/%s': %m", root, skip_leading_slash(dir));
×
1224

1225
        FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read %s: %m", full)) {
110✔
1226
                if (!dirent_is_file(de))
17✔
1227
                        continue;
×
1228

1229
                if (!endswith_no_case(de->d_name, ".efi"))
17✔
1230
                        continue;
×
1231

1232
                _cleanup_close_ int fd = openat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOFOLLOW|O_NOCTTY);
143✔
1233
                if (fd < 0) {
17✔
1234
                        log_warning_errno(errno, "Failed to open %s/%s, ignoring: %m", full, de->d_name);
×
1235
                        continue;
×
1236
                }
1237

1238
                r = config_check_inode_relevant_and_unseen(config, fd, de->d_name);
17✔
1239
                if (r < 0)
17✔
1240
                        return r;
1241
                if (r == 0) /* inode already seen or otherwise not relevant */
17✔
1242
                        continue;
×
1243

1244
                _cleanup_free_ char *j = path_join(full, de->d_name);
34✔
1245
                if (!j)
17✔
1246
                        return log_oom();
×
1247

1248
                for (unsigned p = 0; p < UNIFIED_PROFILES_MAX; p++) {
68✔
1249
                        _cleanup_free_ char *osrelease = NULL, *profile = NULL, *cmdline = NULL;
68✔
1250

1251
                        r = pe_find_uki_sections(fd, j, p, &osrelease, &profile, &cmdline);
68✔
1252
                        if (r == 0) /* this profile does not exist, we are done */
68✔
1253
                                break;
1254
                        if (r < 0)
51✔
1255
                                continue;
×
1256

1257
                        if (!GREEDY_REALLOC(config->entries, config->n_entries + 1))
51✔
1258
                                return log_oom();
×
1259

1260
                        BootEntry *entry = config->entries + config->n_entries;
51✔
1261

1262
                        if (boot_entry_load_unified(root, source, j, p, osrelease, profile, cmdline, entry) < 0)
51✔
1263
                                continue;
×
1264

1265
                        /* look for .efi.extra.d */
1266
                        (void) boot_entries_find_unified_local_addons(config, dirfd(d), de->d_name, full, entry);
51✔
1267

1268
                        /* Set up the backpointer, so that we can find the global addons */
1269
                        entry->global_addons = &config->global_addons[source];
51✔
1270

1271
                        config->n_entries++;
51✔
1272
                }
1273
        }
1274

1275
        return 0;
1276
}
1277

1278
static bool find_nonunique(const BootEntry *entries, size_t n_entries, bool arr[]) {
56✔
1279
        bool non_unique = false;
56✔
1280

1281
        assert(entries || n_entries == 0);
56✔
1282
        assert(arr || n_entries == 0);
56✔
1283

1284
        for (size_t i = 0; i < n_entries; i++)
227✔
1285
                arr[i] = false;
171✔
1286

1287
        for (size_t i = 0; i < n_entries; i++)
227✔
1288
                for (size_t j = 0; j < n_entries; j++)
714✔
1289
                        if (i != j && streq(boot_entry_title(entries + i),
543✔
1290
                                            boot_entry_title(entries + j)))
1291
                                non_unique = arr[i] = arr[j] = true;
112✔
1292

1293
        return non_unique;
56✔
1294
}
1295

1296
static int boot_entries_uniquify(BootEntry *entries, size_t n_entries) {
33✔
1297
        _cleanup_free_ bool *arr = NULL;
33✔
1298
        char *s;
33✔
1299

1300
        assert(entries || n_entries == 0);
33✔
1301

1302
        if (n_entries == 0)
33✔
1303
                return 0;
1304

1305
        arr = new(bool, n_entries);
19✔
1306
        if (!arr)
19✔
1307
                return -ENOMEM;
1308

1309
        /* Find _all_ non-unique titles */
1310
        if (!find_nonunique(entries, n_entries, arr))
19✔
1311
                return 0;
1312

1313
        /* Add version to non-unique titles */
1314
        for (size_t i = 0; i < n_entries; i++)
78✔
1315
                if (arr[i] && entries[i].version) {
59✔
1316
                        if (asprintf(&s, "%s (%s)", boot_entry_title(entries + i), entries[i].version) < 0)
40✔
1317
                                return -ENOMEM;
1318

1319
                        free_and_replace(entries[i].show_title, s);
40✔
1320
                }
1321

1322
        if (!find_nonunique(entries, n_entries, arr))
19✔
1323
                return 0;
1324

1325
        /* Add machine-id to non-unique titles */
1326
        for (size_t i = 0; i < n_entries; i++)
71✔
1327
                if (arr[i] && entries[i].machine_id) {
53✔
1328
                        if (asprintf(&s, "%s (%s)", boot_entry_title(entries + i), entries[i].machine_id) < 0)
2✔
1329
                                return -ENOMEM;
1330

1331
                        free_and_replace(entries[i].show_title, s);
2✔
1332
                }
1333

1334
        if (!find_nonunique(entries, n_entries, arr))
18✔
1335
                return 0;
1336

1337
        /* Add file name to non-unique titles */
1338
        for (size_t i = 0; i < n_entries; i++)
71✔
1339
                if (arr[i]) {
53✔
1340
                        if (asprintf(&s, "%s (%s)", boot_entry_title(entries + i), entries[i].id) < 0)
36✔
1341
                                return -ENOMEM;
1342

1343
                        free_and_replace(entries[i].show_title, s);
36✔
1344
                }
1345

1346
        return 0;
1347
}
1348

1349
static int boot_config_find(const BootConfig *config, const char *id) {
20✔
1350
        assert(config);
20✔
1351

1352
        if (!id)
20✔
1353
                return -1;
1354

1355
        if (id[0] == '@') {
20✔
1356
                if (!strcaseeq(id, "@saved"))
×
1357
                        return -1;
1358
                if (!config->entry_selected)
×
1359
                        return -1;
1360
                id = config->entry_selected;
1361
        }
1362

1363
        for (size_t i = 0; i < config->n_entries; i++)
70✔
1364
                if (fnmatch(id, config->entries[i].id, FNM_CASEFOLD) == 0)
70✔
1365
                        return i;
20✔
1366

1367
        return -1;
1368
}
1369

1370
static int boot_entries_select_default(const BootConfig *config) {
31✔
1371
        int i;
31✔
1372

1373
        assert(config);
31✔
1374
        assert(config->entries || config->n_entries == 0);
31✔
1375

1376
        if (config->n_entries == 0) {
31✔
1377
                log_debug("Found no default boot entry :(");
14✔
1378
                return -1; /* -1 means "no default" */
14✔
1379
        }
1380

1381
        if (config->entry_oneshot) {
17✔
1382
                i = boot_config_find(config, config->entry_oneshot);
×
1383
                if (i >= 0) {
×
1384
                        log_debug("Found default: id \"%s\" is matched by LoaderEntryOneShot",
×
1385
                                  config->entries[i].id);
1386
                        return i;
×
1387
                }
1388
        }
1389

1390
        if (config->entry_default) {
17✔
1391
                i = boot_config_find(config, config->entry_default);
3✔
1392
                if (i >= 0) {
3✔
1393
                        log_debug("Found default: id \"%s\" is matched by LoaderEntryDefault",
3✔
1394
                                  config->entries[i].id);
1395
                        return i;
3✔
1396
                }
1397
        }
1398

1399
        if (config->default_pattern) {
14✔
1400
                i = boot_config_find(config, config->default_pattern);
×
1401
                if (i >= 0) {
×
1402
                        log_debug("Found default: id \"%s\" is matched by pattern \"%s\"",
×
1403
                                  config->entries[i].id, config->default_pattern);
1404
                        return i;
×
1405
                }
1406
        }
1407

1408
        log_debug("Found default: first entry \"%s\"", config->entries[0].id);
14✔
1409
        return 0;
1410
}
1411

1412
static int boot_entries_select_selected(const BootConfig *config) {
31✔
1413
        assert(config);
31✔
1414
        assert(config->entries || config->n_entries == 0);
31✔
1415

1416
        if (!config->entry_selected || config->n_entries == 0)
31✔
1417
                return -1;
1418

1419
        return boot_config_find(config, config->entry_selected);
17✔
1420
}
1421

1422
static int boot_load_efi_entry_pointers(BootConfig *config, bool skip_efivars) {
31✔
1423
        int r;
31✔
1424

1425
        assert(config);
31✔
1426

1427
        if (skip_efivars || !is_efi_boot())
31✔
1428
                return 0;
14✔
1429

1430
        /* Loads the three "pointers" to boot loader entries from their EFI variables */
1431

1432
        r = efi_get_variable_string(EFI_LOADER_VARIABLE_STR("LoaderEntryOneShot"), &config->entry_oneshot);
17✔
1433
        if (r == -ENOMEM)
17✔
1434
                return log_oom();
×
1435
        if (r < 0 && !IN_SET(r, -ENOENT, -ENODATA))
17✔
1436
                log_warning_errno(r, "Failed to read EFI variable \"LoaderEntryOneShot\", ignoring: %m");
×
1437

1438
        r = efi_get_variable_string(EFI_LOADER_VARIABLE_STR("LoaderEntryDefault"), &config->entry_default);
17✔
1439
        if (r == -ENOMEM)
17✔
1440
                return log_oom();
×
1441
        if (r < 0 && !IN_SET(r, -ENOENT, -ENODATA))
17✔
1442
                log_warning_errno(r, "Failed to read EFI variable \"LoaderEntryDefault\", ignoring: %m");
×
1443

1444
        r = efi_get_variable_string(EFI_LOADER_VARIABLE_STR("LoaderEntrySelected"), &config->entry_selected);
17✔
1445
        if (r == -ENOMEM)
17✔
1446
                return log_oom();
×
1447
        if (r < 0 && !IN_SET(r, -ENOENT, -ENODATA))
17✔
1448
                log_warning_errno(r, "Failed to read EFI variable \"LoaderEntrySelected\", ignoring: %m");
×
1449

1450
        r = efi_get_variable_string(EFI_LOADER_VARIABLE_STR("LoaderEntrySysFail"), &config->entry_sysfail);
17✔
1451
        if (r == -ENOMEM)
17✔
1452
                return log_oom();
×
1453
        if (r < 0 && !IN_SET(r, -ENOENT, -ENODATA))
17✔
1454
                log_warning_errno(r, "Failed to read EFI variable \"LoaderEntrySysFail\", ignoring: %m");
×
1455

1456
        return 1;
1457
}
1458

1459
int boot_config_select_special_entries(BootConfig *config, bool skip_efivars) {
31✔
1460
        int r;
31✔
1461

1462
        assert(config);
31✔
1463

1464
        r = boot_load_efi_entry_pointers(config, skip_efivars);
31✔
1465
        if (r < 0)
31✔
1466
                return r;
1467

1468
        config->default_entry = boot_entries_select_default(config);
31✔
1469
        config->selected_entry = boot_entries_select_selected(config);
31✔
1470

1471
        return 0;
31✔
1472
}
1473

1474
int boot_config_finalize(BootConfig *config) {
33✔
1475
        int r;
33✔
1476

1477
        typesafe_qsort(config->entries, config->n_entries, boot_entry_compare);
33✔
1478

1479
        r = boot_entries_uniquify(config->entries, config->n_entries);
33✔
1480
        if (r < 0)
33✔
1481
                return log_error_errno(r, "Failed to uniquify boot entries: %m");
×
1482

1483
        return 0;
1484
}
1485

1486
int boot_config_load(
33✔
1487
                BootConfig *config,
1488
                const char *esp_path,
1489
                const char *xbootldr_path) {
1490

1491
        int r;
33✔
1492

1493
        assert(config);
33✔
1494

1495
        if (esp_path) {
33✔
1496
                r = boot_loader_read_conf_path(config, esp_path, "/loader/loader.conf");
33✔
1497
                if (r < 0)
33✔
1498
                        return r;
1499

1500
                r = boot_entries_find_type1(config, esp_path, BOOT_ENTRY_ESP, "/loader/entries");
33✔
1501
                if (r < 0)
33✔
1502
                        return r;
1503

1504
                r = boot_entries_find_unified(config, esp_path, BOOT_ENTRY_ESP, "/EFI/Linux/");
33✔
1505
                if (r < 0)
33✔
1506
                        return r;
1507

1508
                r = boot_entries_find_unified_global_addons(config, esp_path, "/loader/addons/",
33✔
1509
                                                            &config->global_addons[BOOT_ENTRY_ESP]);
1510
                if (r < 0)
33✔
1511
                        return r;
1512
        }
1513

1514
        if (xbootldr_path) {
33✔
1515
                r = boot_entries_find_type1(config, xbootldr_path, BOOT_ENTRY_XBOOTLDR, "/loader/entries");
14✔
1516
                if (r < 0)
14✔
1517
                        return r;
1518

1519
                r = boot_entries_find_unified(config, xbootldr_path, BOOT_ENTRY_XBOOTLDR, "/EFI/Linux/");
14✔
1520
                if (r < 0)
14✔
1521
                        return r;
1522

1523
                r = boot_entries_find_unified_global_addons(config, xbootldr_path, "/loader/addons/",
14✔
1524
                                                            &config->global_addons[BOOT_ENTRY_XBOOTLDR]);
1525
                if (r < 0)
14✔
1526
                        return r;
1527
        }
1528

1529
        return boot_config_finalize(config);
33✔
1530
}
1531

1532
int boot_config_load_auto(
×
1533
                BootConfig *config,
1534
                const char *override_esp_path,
1535
                const char *override_xbootldr_path) {
1536

1537
        _cleanup_free_ char *esp_where = NULL, *xbootldr_where = NULL;
×
1538
        dev_t esp_devid = 0, xbootldr_devid = 0;
×
1539
        int r;
×
1540

1541
        assert(config);
×
1542

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

1550
        if (!override_esp_path && !override_xbootldr_path) {
×
1551
                if (access("/run/boot-loader-entries/", F_OK) >= 0)
×
1552
                        return boot_config_load(config, "/run/boot-loader-entries/", NULL);
×
1553

1554
                if (errno != ENOENT)
×
1555
                        return log_error_errno(errno,
×
1556
                                               "Failed to determine whether /run/boot-loader-entries/ exists: %m");
1557
        }
1558

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

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

1567
        /* If both paths actually refer to the same inode, suppress the xbootldr path */
1568
        if (esp_where && xbootldr_where && devnum_set_and_equal(esp_devid, xbootldr_devid))
×
1569
                xbootldr_where = mfree(xbootldr_where);
×
1570

1571
        return boot_config_load(config, esp_where, xbootldr_where);
×
1572
}
1573

1574
int boot_config_augment_from_loader(
17✔
1575
                BootConfig *config,
1576
                char **found_by_loader,
1577
                bool auto_only) {
1578

1579
        static const BootEntryAddons no_addons = (BootEntryAddons) {};
17✔
1580
        static const char *const title_table[] = {
17✔
1581
                /* Pretty names for a few well-known automatically discovered entries. */
1582
                "auto-osx",                      "macOS",
1583
                "auto-windows",                  "Windows Boot Manager",
1584
                "auto-efi-shell",                "EFI Shell",
1585
                "auto-efi-default",              "EFI Default Loader",
1586
                "auto-poweroff",                 "Power Off The System",
1587
                "auto-reboot",                   "Reboot The System",
1588
                "auto-reboot-to-firmware-setup", "Reboot Into Firmware Interface",
1589
                NULL,
1590
        };
1591

1592
        assert(config);
17✔
1593

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

1597
        STRV_FOREACH(i, found_by_loader) {
85✔
1598
                BootEntry *existing;
68✔
1599
                _cleanup_free_ char *c = NULL, *t = NULL, *p = NULL;
34✔
1600

1601
                existing = boot_config_find_entry(config, *i);
68✔
1602
                if (existing) {
68✔
1603
                        existing->reported_by_loader = true;
34✔
1604
                        continue;
34✔
1605
                }
1606

1607
                if (auto_only && !startswith(*i, "auto-"))
34✔
1608
                        continue;
×
1609

1610
                c = strdup(*i);
34✔
1611
                if (!c)
34✔
1612
                        return log_oom();
×
1613

1614
                STRV_FOREACH_PAIR(a, b, title_table)
255✔
1615
                        if (streq(*a, *i)) {
238✔
1616
                                t = strdup(*b);
17✔
1617
                                if (!t)
17✔
1618
                                        return log_oom();
×
1619
                                break;
1620
                        }
1621

1622
                p = strdup(EFIVAR_PATH(EFI_LOADER_VARIABLE_STR("LoaderEntries")));
34✔
1623
                if (!p)
34✔
1624
                        return log_oom();
×
1625

1626
                if (!GREEDY_REALLOC0(config->entries, config->n_entries + 1))
34✔
1627
                        return log_oom();
×
1628

1629
                config->entries[config->n_entries++] = (BootEntry) {
68✔
1630
                        .type = startswith(*i, "auto-") ? BOOT_ENTRY_AUTO : BOOT_ENTRY_LOADER,
34✔
1631
                        .id = TAKE_PTR(c),
34✔
1632
                        .title = TAKE_PTR(t),
34✔
1633
                        .path = TAKE_PTR(p),
34✔
1634
                        .reported_by_loader = true,
1635
                        .tries_left = UINT_MAX,
1636
                        .tries_done = UINT_MAX,
1637
                        .profile = UINT_MAX,
1638
                        .global_addons = &no_addons,
1639
                };
1640
        }
1641

1642
        return 0;
1643
}
1644

1645
BootEntry* boot_config_find_entry(BootConfig *config, const char *id) {
72✔
1646
        assert(config);
72✔
1647
        assert(id);
72✔
1648

1649
        for (size_t j = 0; j < config->n_entries; j++)
245✔
1650
                if (strcaseeq_ptr(config->entries[j].id, id) ||
210✔
1651
                    strcaseeq_ptr(config->entries[j].id_old, id))
173✔
1652
                        return config->entries + j;
1653

1654
        return NULL;
1655
}
1656

1657
static void boot_entry_file_list(
18✔
1658
                const char *field,
1659
                const char *root,
1660
                const char *p,
1661
                int *ret_status) {
1662

1663
        assert(p);
18✔
1664
        assert(ret_status);
18✔
1665

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

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

1673
        if (status < 0) {
18✔
1674
                errno = -status;
×
1675
                printf("%s%s%s (%m)\n", ansi_highlight_red(), p, ansi_normal());
×
1676
        } else
1677
                printf("%s\n", p);
18✔
1678

1679
        if (*ret_status == 0 && status < 0)
18✔
1680
                *ret_status = status;
×
1681
}
18✔
1682

1683
static void print_addon(
18✔
1684
                BootEntryAddon *addon,
1685
                const char *addon_str) {
1686

1687
        printf("  %s: %s\n", addon_str, addon->location);
18✔
1688
        printf("      options: %s%s\n", glyph(GLYPH_TREE_RIGHT), addon->cmdline);
18✔
1689
}
18✔
1690

1691
static int indent_embedded_newlines(char *cmdline, char **ret_cmdline) {
36✔
1692
        _cleanup_free_ char *t = NULL;
36✔
1693
        _cleanup_strv_free_ char **ts = NULL;
36✔
1694

1695
        assert(ret_cmdline);
36✔
1696

1697
        ts = strv_split_newlines(cmdline);
36✔
1698
        if (!ts)
36✔
1699
                return -ENOMEM;
1700

1701
        t = strv_join(ts, "\n              ");
36✔
1702
        if (!t)
36✔
1703
                return -ENOMEM;
1704

1705
        *ret_cmdline = TAKE_PTR(t);
36✔
1706

1707
        return 0;
36✔
1708
}
1709

1710
static int print_cmdline(const BootEntry *e) {
22✔
1711

1712
        _cleanup_free_ char *options = NULL, *combined_cmdline = NULL, *t2 = NULL;
22✔
1713

1714
        assert(e);
22✔
1715

1716
        if (!strv_isempty(e->options)) {
22✔
1717
                _cleanup_free_ char *t = NULL;
18✔
1718

1719
                options = strv_join(e->options, " ");
18✔
1720
                if (!options)
18✔
1721
                        return log_oom();
×
1722

1723
                if (indent_embedded_newlines(options, &t) < 0)
18✔
1724
                        return log_oom();
×
1725

1726
                printf("      options: %s\n", t);
18✔
1727
                t2 = strdup(options);
18✔
1728
                if (!t2)
18✔
1729
                        return log_oom();
×
1730
        }
1731

1732
        FOREACH_ARRAY(addon, e->global_addons->items, e->global_addons->n_items) {
40✔
1733
                print_addon(addon, "global-addon");
18✔
1734
                if (!strextend(&t2, " ", addon->cmdline))
18✔
1735
                        return log_oom();
×
1736
        }
1737

1738
        FOREACH_ARRAY(addon, e->local_addons.items, e->local_addons.n_items) {
22✔
1739
                /* Add space at the beginning of addon_str to align it correctly */
1740
                print_addon(addon, " local-addon");
×
1741
                if (!strextend(&t2, " ", addon->cmdline))
×
1742
                        return log_oom();
×
1743
        }
1744

1745
        /* Don't print the combined cmdline if it's same as options. */
1746
        if (streq_ptr(t2, options))
22✔
1747
                return 0;
1748

1749
        if (indent_embedded_newlines(t2, &combined_cmdline) < 0)
18✔
1750
                return log_oom();
×
1751

1752
        if (combined_cmdline)
18✔
1753
                printf("      cmdline: %s\n", combined_cmdline);
18✔
1754

1755
        return 0;
1756
}
1757

1758
static int json_addon(
9✔
1759
                BootEntryAddon *addon,
1760
                const char *addon_str,
1761
                sd_json_variant **array) {
1762

1763
        int r;
9✔
1764

1765
        assert(addon);
9✔
1766
        assert(addon_str);
9✔
1767

1768
        r = sd_json_variant_append_arraybo(
9✔
1769
                        array,
1770
                        SD_JSON_BUILD_PAIR(addon_str, SD_JSON_BUILD_STRING(addon->location)),
1771
                        SD_JSON_BUILD_PAIR("options", SD_JSON_BUILD_STRING(addon->cmdline)));
1772
        if (r < 0)
9✔
1773
                return log_oom();
×
1774

1775
        return 0;
1776
}
1777

1778
static int json_cmdline(
15✔
1779
                const BootEntry *e,
1780
                const char *def_cmdline,
1781
                sd_json_variant **v) {
1782

1783
        _cleanup_free_ char *combined_cmdline = NULL;
15✔
1784
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *addons_array = NULL;
15✔
1785
        int r;
15✔
1786

1787
        assert(e);
15✔
1788

1789
        if (def_cmdline) {
15✔
1790
                combined_cmdline = strdup(def_cmdline);
9✔
1791
                if (!combined_cmdline)
9✔
1792
                        return log_oom();
×
1793
        }
1794

1795
        FOREACH_ARRAY(addon, e->global_addons->items, e->global_addons->n_items) {
24✔
1796
                r = json_addon(addon, "globalAddon", &addons_array);
9✔
1797
                if (r < 0)
9✔
1798
                        return r;
1799
                if (!strextend(&combined_cmdline, " ", addon->cmdline))
9✔
1800
                        return log_oom();
×
1801
        }
1802

1803
        FOREACH_ARRAY(addon, e->local_addons.items, e->local_addons.n_items) {
15✔
1804
                r = json_addon(addon, "localAddon", &addons_array);
×
1805
                if (r < 0)
×
1806
                        return r;
1807
                if (!strextend(&combined_cmdline, " ", addon->cmdline))
×
1808
                        return log_oom();
×
1809
        }
1810

1811
        r = sd_json_variant_merge_objectbo(
15✔
1812
                        v,
1813
                        SD_JSON_BUILD_PAIR("addons", SD_JSON_BUILD_VARIANT(addons_array)),
1814
                        SD_JSON_BUILD_PAIR_CONDITION(!!combined_cmdline, "cmdline", SD_JSON_BUILD_STRING(combined_cmdline)));
1815
        if (r < 0)
15✔
1816
                return log_oom();
×
1817
        return 0;
1818
}
1819

1820
int show_boot_entry(
22✔
1821
                const BootEntry *e,
1822
                bool show_as_default,
1823
                bool show_as_selected,
1824
                bool show_reported) {
1825

1826
        int status = 0, r = 0;
22✔
1827

1828
        /* Returns 0 on success, negative on processing error, and positive if something is wrong with the
1829
           boot entry itself. */
1830

1831
        assert(e);
22✔
1832

1833
        printf("         type: %s\n",
22✔
1834
               boot_entry_type_description_to_string(e->type));
22✔
1835

1836
        printf("        title: %s%s%s",
66✔
1837
               ansi_highlight(), boot_entry_title(e), ansi_normal());
1838

1839
        if (show_as_default)
22✔
1840
                printf(" %s(default)%s",
4✔
1841
                       ansi_highlight_green(), ansi_normal());
1842

1843
        if (show_as_selected)
22✔
1844
                printf(" %s(selected)%s",
4✔
1845
                       ansi_highlight_magenta(), ansi_normal());
1846

1847
        if (show_reported) {
22✔
1848
                if (e->type == BOOT_ENTRY_LOADER)
10✔
1849
                        printf(" %s(reported/absent)%s",
4✔
1850
                               ansi_highlight_red(), ansi_normal());
1851
                else if (!e->reported_by_loader && e->type != BOOT_ENTRY_AUTO)
8✔
1852
                        printf(" %s(not reported/new)%s",
4✔
1853
                               ansi_highlight_green(), ansi_normal());
1854
        }
1855

1856
        putchar('\n');
22✔
1857

1858
        if (e->id) {
22✔
1859
                printf("           id: %s", e->id);
22✔
1860

1861
                if (e->id_without_profile && !streq_ptr(e->id, e->id_without_profile))
22✔
1862
                        printf(" (without profile: %s)\n", e->id_without_profile);
18✔
1863
                else
1864
                        putchar('\n');
4✔
1865
        }
1866
        if (e->path) {
22✔
1867
                _cleanup_free_ char *text = NULL, *link = NULL;
22✔
1868

1869
                const char *p = e->root ? path_startswith(e->path, e->root) : NULL;
22✔
1870
                if (p) {
18✔
1871
                        text = strjoin(ansi_grey(), e->root, "/", ansi_normal(), "/", p);
36✔
1872
                        if (!text)
18✔
1873
                                return log_oom();
×
1874
                }
1875

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

1881
                printf("       source: %s (on the %s)\n",
22✔
1882
                       link ?: text ?: e->path,
22✔
1883
                       boot_entry_source_description_to_string(e->source));
22✔
1884
        }
1885
        if (e->tries_left != UINT_MAX) {
22✔
1886
                printf("        tries: %u left", e->tries_left);
×
1887

1888
                if (e->tries_done != UINT_MAX)
×
1889
                        printf("; %u done\n", e->tries_done);
×
1890
                else
1891
                        putchar('\n');
×
1892
        }
1893

1894
        if (e->sort_key)
22✔
1895
                printf("     sort-key: %s\n", e->sort_key);
18✔
1896
        if (e->version)
22✔
1897
                printf("      version: %s\n", e->version);
18✔
1898
        if (e->machine_id)
22✔
1899
                printf("   machine-id: %s\n", e->machine_id);
×
1900
        if (e->architecture)
22✔
1901
                printf(" architecture: %s\n", e->architecture);
×
1902
        if (e->kernel)
22✔
1903
                boot_entry_file_list("linux", e->root, e->kernel, &status);
18✔
1904
        if (e->efi)
22✔
1905
                boot_entry_file_list("efi", e->root, e->efi, &status);
×
1906
        if (e->uki)
22✔
NEW
1907
                boot_entry_file_list("uki", e->root, e->uki, &status);
×
1908
        if (e->profile != UINT_MAX)
22✔
1909
                printf("      profile: %u\n", e->profile);
18✔
1910

1911
        STRV_FOREACH(s, e->initrd)
22✔
1912
                boot_entry_file_list(s == e->initrd ? "initrd" : NULL,
×
1913
                                     e->root,
×
1914
                                     *s,
1915
                                     &status);
1916

1917
        r = print_cmdline(e);
22✔
1918
        if (r < 0)
22✔
1919
                return r;
1920

1921
        if (e->device_tree)
22✔
1922
                boot_entry_file_list("devicetree", e->root, e->device_tree, &status);
×
1923

1924
        STRV_FOREACH(s, e->device_tree_overlay)
22✔
1925
                boot_entry_file_list(s == e->device_tree_overlay ? "devicetree-overlay" : NULL,
×
1926
                                     e->root,
×
1927
                                     *s,
1928
                                     &status);
1929

1930
        return -status;
22✔
1931
}
1932

1933
int boot_entry_to_json(const BootConfig *c, size_t i, sd_json_variant **ret) {
15✔
1934
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
15✔
1935
        _cleanup_free_ char *opts = NULL;
15✔
1936
        const BootEntry *e;
15✔
1937
        int r;
15✔
1938

1939
        assert(c);
15✔
1940
        assert(ret);
15✔
1941

1942
        if (i >= c->n_entries) {
15✔
1943
                *ret = NULL;
×
1944
                return 0;
×
1945
        }
1946

1947
        e = c->entries + i;
15✔
1948

1949
        if (!strv_isempty(e->options)) {
15✔
1950
                opts = strv_join(e->options, " ");
9✔
1951
                if (!opts)
9✔
1952
                        return log_oom();
×
1953
        }
1954

1955
        r = sd_json_variant_merge_objectbo(
15✔
1956
                        &v,
1957
                        SD_JSON_BUILD_PAIR("type", SD_JSON_BUILD_STRING(boot_entry_type_to_string(e->type))),
1958
                        SD_JSON_BUILD_PAIR("source", SD_JSON_BUILD_STRING(boot_entry_source_to_string(e->source))),
1959
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->id, "id", SD_JSON_BUILD_STRING(e->id)),
1960
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->path, "path", SD_JSON_BUILD_STRING(e->path)),
1961
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->root, "root", SD_JSON_BUILD_STRING(e->root)),
1962
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->title, "title", SD_JSON_BUILD_STRING(e->title)),
1963
                        SD_JSON_BUILD_PAIR_CONDITION(!!boot_entry_title(e), "showTitle", SD_JSON_BUILD_STRING(boot_entry_title(e))),
1964
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->sort_key, "sortKey", SD_JSON_BUILD_STRING(e->sort_key)),
1965
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->version, "version", SD_JSON_BUILD_STRING(e->version)),
1966
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->machine_id, "machineId", SD_JSON_BUILD_STRING(e->machine_id)),
1967
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->architecture, "architecture", SD_JSON_BUILD_STRING(e->architecture)),
1968
                        SD_JSON_BUILD_PAIR_CONDITION(!!opts, "options", SD_JSON_BUILD_STRING(opts)),
1969
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->kernel, "linux", SD_JSON_BUILD_STRING(e->kernel)),
1970
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->efi, "efi", SD_JSON_BUILD_STRING(e->efi)),
1971
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->uki, "uki", SD_JSON_BUILD_STRING(e->uki)),
1972
                        SD_JSON_BUILD_PAIR_CONDITION(e->profile != UINT_MAX, "profile", SD_JSON_BUILD_UNSIGNED(e->profile)),
1973
                        SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(e->initrd), "initrd", SD_JSON_BUILD_STRV(e->initrd)));
1974
        if (r < 0)
15✔
1975
                return log_oom();
×
1976

1977
        /* Sanitizers (only memory sanitizer?) do not like function call with too many
1978
         * arguments and trigger false positive warnings. Let's not add too many json objects
1979
         * at once. */
1980
        r = sd_json_variant_merge_objectbo(
15✔
1981
                        &v,
1982
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->device_tree, "devicetree", SD_JSON_BUILD_STRING(e->device_tree)),
1983
                        SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(e->device_tree_overlay), "devicetreeOverlay", SD_JSON_BUILD_STRV(e->device_tree_overlay)),
1984
                        SD_JSON_BUILD_PAIR("isReported", SD_JSON_BUILD_BOOLEAN(e->reported_by_loader)),
1985
                        SD_JSON_BUILD_PAIR_CONDITION(e->tries_left != UINT_MAX, "triesLeft", SD_JSON_BUILD_UNSIGNED(e->tries_left)),
1986
                        SD_JSON_BUILD_PAIR_CONDITION(e->tries_done != UINT_MAX, "triesDone", SD_JSON_BUILD_UNSIGNED(e->tries_done)),
1987
                        SD_JSON_BUILD_PAIR_CONDITION(c->default_entry >= 0, "isDefault", SD_JSON_BUILD_BOOLEAN(i == (size_t) c->default_entry)),
1988
                        SD_JSON_BUILD_PAIR_CONDITION(c->selected_entry >= 0, "isSelected", SD_JSON_BUILD_BOOLEAN(i == (size_t) c->selected_entry)));
1989
        if (r < 0)
15✔
1990
                return log_oom();
×
1991

1992
        r = json_cmdline(e, opts, &v);
15✔
1993
        if (r < 0)
15✔
1994
                return log_oom();
×
1995

1996
        *ret = TAKE_PTR(v);
15✔
1997
        return 1;
15✔
1998
}
1999

2000
int show_boot_entries(const BootConfig *config, sd_json_format_flags_t json_format) {
8✔
2001
        int r;
8✔
2002

2003
        assert(config);
8✔
2004

2005
        if (sd_json_format_enabled(json_format)) {
8✔
2006
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL;
6✔
2007

2008
                for (size_t i = 0; i < config->n_entries; i++) {
16✔
2009
                        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
10✔
2010

2011
                        r = boot_entry_to_json(config, i, &v);
10✔
2012
                        if (r < 0)
10✔
2013
                                return log_oom();
×
2014

2015
                        r = sd_json_variant_append_array(&array, v);
10✔
2016
                        if (r < 0)
10✔
2017
                                return log_oom();
×
2018
                }
2019

2020
                return sd_json_variant_dump(array, json_format | SD_JSON_FORMAT_EMPTY_ARRAY, NULL, NULL);
6✔
2021
        } else
2022
                for (size_t n = 0; n < config->n_entries; n++) {
12✔
2023
                        r = show_boot_entry(
20✔
2024
                                        config->entries + n,
10✔
2025
                                        /* show_as_default= */  n == (size_t) config->default_entry,
10✔
2026
                                        /* show_as_selected= */ n == (size_t) config->selected_entry,
10✔
2027
                                        /* show_reported= */  true);
2028
                        if (r < 0)
10✔
2029
                                return r;
2030

2031
                        if (n+1 < config->n_entries)
10✔
2032
                                putchar('\n');
8✔
2033
                }
2034

2035
        return 0;
2036
}
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