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

systemd / systemd / 16610200353

29 Jul 2025 11:26PM UTC coverage: 72.278% (+0.06%) from 72.215%
16610200353

push

github

yuwata
po: Translated using Weblate (Chinese (Simplified) (zh_CN))

Currently translated at 100.0% (264 of 264 strings)

Co-authored-by: Jesse Guo <jesseguotech@outlook.com>
Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/zh_CN/
Translation: systemd/main

303003 of 419221 relevant lines covered (72.28%)

740914.66 hits per line

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

72.96
/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
        strv_free(entry->initrd);
152✔
97
        free(entry->device_tree);
152✔
98
        strv_free(entry->device_tree_overlay);
152✔
99
}
152✔
100

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

108
        _cleanup_free_ char *c = NULL;
×
109

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

302
        return 0;
16✔
303

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

387
                        continue;
×
388
                }
389

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

420
        *ret = TAKE_STRUCT(tmp);
8✔
421
        return 0;
8✔
422
}
423

424
int boot_config_load_type1(
8✔
425
                BootConfig *config,
426
                FILE *f,
427
                const char *root,
428
                const BootEntrySource source,
429
                const char *dir,
430
                const char *fname) {
431
        int r;
8✔
432

433
        assert(config);
8✔
434
        assert(f);
8✔
435
        assert(root);
8✔
436
        assert(dir);
8✔
437
        assert(fname);
8✔
438

439
        if (!GREEDY_REALLOC(config->entries, config->n_entries + 1))
8✔
440
                return log_oom();
×
441

442
        BootEntry *entry = config->entries + config->n_entries;
8✔
443

444
        r = boot_entry_load_type1(f, root, source, dir, fname, entry);
8✔
445
        if (r < 0)
8✔
446
                return r;
447
        config->n_entries++;
8✔
448

449
        entry->global_addons = &config->global_addons[source];
8✔
450

451
        return 0;
8✔
452
}
453

454
void boot_config_free(BootConfig *config) {
33✔
455
        assert(config);
33✔
456

457
        free(config->default_pattern);
33✔
458

459
        free(config->entry_oneshot);
33✔
460
        free(config->entry_default);
33✔
461
        free(config->entry_selected);
33✔
462
        free(config->entry_sysfail);
33✔
463

464
        FOREACH_ARRAY(i, config->entries, config->n_entries)
126✔
465
                boot_entry_free(i);
93✔
466
        free(config->entries);
33✔
467

468
        FOREACH_ELEMENT(i, config->global_addons)
99✔
469
                boot_entry_addons_done(i);
66✔
470

471
        set_free(config->inodes_seen);
33✔
472
}
33✔
473

474
int boot_loader_read_conf(BootConfig *config, FILE *file, const char *path) {
31✔
475
        int r;
31✔
476

477
        assert(config);
31✔
478
        assert(file);
31✔
479
        assert(path);
31✔
480

481
        for (unsigned line = 1;; line++) {
62✔
482
                _cleanup_free_ char *buf = NULL, *field = NULL;
62✔
483

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

492
                if (IN_SET(buf[0], '#', '\0'))
62✔
493
                        continue;
62✔
494

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

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

524
        return 1;
31✔
525
}
526

527
static int boot_loader_read_conf_path(BootConfig *config, const char *root, const char *path) {
33✔
528
        _cleanup_free_ char *full = NULL;
33✔
529
        _cleanup_fclose_ FILE *f = NULL;
33✔
530
        int r;
33✔
531

532
        assert(config);
33✔
533
        assert(path);
33✔
534

535
        r = chase_and_fopen_unlocked(path, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, "re", &full, &f);
33✔
536
        config->loader_conf_status = r < 0 ? r : true;
33✔
537
        if (r == -ENOENT)
33✔
538
                return 0;
539
        if (r < 0)
31✔
540
                return log_error_errno(r, "Failed to open '%s/%s': %m", root, skip_leading_slash(path));
×
541

542
        return boot_loader_read_conf(config, f, full);
31✔
543
}
544

545
static int boot_entry_compare(const BootEntry *a, const BootEntry *b) {
42✔
546
        int r;
42✔
547

548
        assert(a);
42✔
549
        assert(b);
42✔
550

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

553
        r = CMP(a->tries_left == 0, b->tries_left == 0);
42✔
554
        if (r != 0)
42✔
555
                return r;
×
556

557
        r = CMP(!a->sort_key, !b->sort_key);
42✔
558
        if (r != 0)
39✔
559
                return r;
3✔
560

561
        if (a->sort_key && b->sort_key) {
39✔
562
                r = strcmp(a->sort_key, b->sort_key);
36✔
563
                if (r != 0)
36✔
564
                        return r;
565

566
                r = strcmp_ptr(a->machine_id, b->machine_id);
35✔
567
                if (r != 0)
35✔
568
                        return r;
569

570
                r = -strverscmp_improved(a->version, b->version);
35✔
571
                if (r != 0)
35✔
572
                        return r;
573
        }
574

575
        r = -strverscmp_improved(a->id_without_profile ?: a->id, b->id_without_profile ?: b->id);
37✔
576
        if (r != 0)
37✔
577
                return r;
578

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

587
        if (a->tries_left != UINT_MAX || b->tries_left != UINT_MAX)
×
588
                return 0;
589

590
        r = -CMP(a->tries_left, b->tries_left);
×
591
        if (r != 0)
×
592
                return r;
×
593

594
        return CMP(a->tries_done, b->tries_done);
×
595
}
596

597
static int config_check_inode_relevant_and_unseen(BootConfig *config, int fd, const char *fname) {
42✔
598
        _cleanup_free_ char *d = NULL;
42✔
599
        struct stat st;
42✔
600

601
        assert(config);
42✔
602
        assert(fd >= 0);
42✔
603
        assert(fname);
42✔
604

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

611
        if (fstat(fd, &st) < 0)
42✔
612
                return log_error_errno(errno, "Failed to stat('%s'): %m", fname);
×
613
        if (!S_ISREG(st.st_mode)) {
42✔
614
                log_debug("File '%s' is not a regular file, ignoring.", fname);
×
615
                return false;
×
616
        }
617

618
        if (set_contains(config->inodes_seen, &st)) {
42✔
619
                log_debug("Inode '%s' already seen before, ignoring.", fname);
×
620
                return false;
×
621
        }
622

623
        d = memdup(&st, sizeof(st));
42✔
624
        if (!d)
42✔
625
                return log_oom();
×
626

627
        if (set_ensure_consume(&config->inodes_seen, &inode_hash_ops, TAKE_PTR(d)) < 0)
42✔
628
                return log_oom();
×
629

630
        return true;
631
}
632

633
static int boot_entries_find_type1(
47✔
634
                BootConfig *config,
635
                const char *root,
636
                const BootEntrySource source,
637
                const char *dir) {
638

639
        _cleanup_free_ DirectoryEntries *dentries = NULL;
94✔
640
        _cleanup_free_ char *full = NULL;
47✔
641
        _cleanup_close_ int dir_fd = -EBADF;
47✔
642
        int r;
47✔
643

644
        assert(config);
47✔
645
        assert(root);
47✔
646
        assert(dir);
47✔
647

648
        dir_fd = chase_and_open(dir, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, O_DIRECTORY|O_CLOEXEC, &full);
47✔
649
        if (dir_fd == -ENOENT)
47✔
650
                return 0;
651
        if (dir_fd < 0)
33✔
652
                return log_error_errno(dir_fd, "Failed to open '%s/%s': %m", root, skip_leading_slash(dir));
×
653

654
        r = readdir_all(dir_fd, RECURSE_DIR_IGNORE_DOT, &dentries);
33✔
655
        if (r < 0)
33✔
656
                return log_error_errno(r, "Failed to read directory '%s': %m", full);
×
657

658
        FOREACH_ARRAY(i, dentries->entries, dentries->n_entries) {
41✔
659
                const struct dirent *de = *i;
8✔
660
                _cleanup_fclose_ FILE *f = NULL;
8✔
661

662
                if (!dirent_is_file(de))
8✔
663
                        continue;
×
664

665
                if (!endswith_no_case(de->d_name, ".conf"))
8✔
666
                        continue;
×
667

668
                r = xfopenat(dir_fd, de->d_name, "re", O_NOFOLLOW|O_NOCTTY, &f);
8✔
669
                if (r < 0) {
8✔
670
                        log_warning_errno(r, "Failed to open %s/%s, ignoring: %m", full, de->d_name);
×
671
                        continue;
×
672
                }
673

674
                r = config_check_inode_relevant_and_unseen(config, fileno(f), de->d_name);
8✔
675
                if (r < 0)
8✔
676
                        return r;
677
                if (r == 0) /* inode already seen or otherwise not relevant */
8✔
678
                        continue;
×
679

680
                r = boot_config_load_type1(config, f, root, source, full, de->d_name);
8✔
681
                if (r == -ENOMEM) /* ignore all other errors */
8✔
682
                        return log_oom();
×
683
        }
684

685
        return 0;
686
}
687

688
static int boot_entry_load_unified(
51✔
689
                const char *root,
690
                const BootEntrySource source,
691
                const char *path,
692
                unsigned profile,
693
                const char *osrelease_text,
694
                const char *profile_text,
695
                const char *cmdline_text,
696
                BootEntry *ret) {
697

698
        _cleanup_free_ char *fname = NULL, *os_pretty_name = NULL, *os_image_id = NULL, *os_name = NULL, *os_id = NULL,
×
699
                *os_image_version = NULL, *os_version = NULL, *os_version_id = NULL, *os_build_id = NULL;
51✔
700
        const char *k, *good_name, *good_version, *good_sort_key;
51✔
701
        _cleanup_fclose_ FILE *f = NULL;
51✔
702
        int r;
51✔
703

704
        assert(root);
51✔
705
        assert(path);
51✔
706
        assert(osrelease_text);
51✔
707
        assert(ret);
51✔
708

709
        k = path_startswith(path, root);
51✔
710
        if (!k)
51✔
711
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Path is not below root: %s", path);
×
712

713
        f = fmemopen_unlocked((void*) osrelease_text, strlen(osrelease_text), "r");
51✔
714
        if (!f)
51✔
715
                return log_oom();
×
716

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

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

743
        _cleanup_free_ char *profile_id = NULL, *profile_title = NULL;
51✔
744
        if (profile_text) {
51✔
745
                fclose(f);
51✔
746

747
                f = fmemopen_unlocked((void*) profile_text, strlen(profile_text), "r");
51✔
748
                if (!f)
51✔
749
                        return log_oom();
×
750

751
                r = parse_env_file(
51✔
752
                                f, "profile",
753
                                "ID", &profile_id,
754
                                "TITLE", &profile_title);
755
                if (r < 0)
51✔
756
                        return log_error_errno(r, "Failed to parse profile data from unified kernel image '%s': %m", path);
×
757
        }
758

759
        r = path_extract_filename(path, &fname);
51✔
760
        if (r < 0)
51✔
761
                return log_error_errno(r, "Failed to extract file name from '%s': %m", path);
×
762

763
        _cleanup_(boot_entry_free) BootEntry tmp = BOOT_ENTRY_INIT(BOOT_ENTRY_TYPE2, source);
51✔
764

765
        r = boot_filename_extract_tries(fname, &tmp.id, &tmp.tries_left, &tmp.tries_done);
51✔
766
        if (r < 0)
51✔
767
                return r;
768

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

772
        tmp.profile = profile;
51✔
773

774
        if (profile_id || profile > 0) {
51✔
775
                tmp.id_without_profile = TAKE_PTR(tmp.id);
51✔
776

777
                if (profile_id)
51✔
778
                        tmp.id = strjoin(tmp.id_without_profile, "@", profile_id);
51✔
779
                else
780
                        (void) asprintf(&tmp.id, "%s@%u", tmp.id_without_profile, profile);
×
781
                if (!tmp.id)
51✔
782
                        return log_oom();
×
783
        }
784

785
        if (os_id && os_version_id) {
51✔
786
                tmp.id_old = strjoin(os_id, "-", os_version_id);
×
787
                if (!tmp.id_old)
×
788
                        return log_oom();
×
789
        }
790

791
        tmp.path = strdup(path);
51✔
792
        if (!tmp.path)
51✔
793
                return log_oom();
×
794

795
        tmp.root = strdup(root);
51✔
796
        if (!tmp.root)
51✔
797
                return log_oom();
×
798

799
        tmp.kernel = path_make_absolute(k, "/");
51✔
800
        if (!tmp.kernel)
51✔
801
                return log_oom();
×
802

803
        tmp.options = strv_new(cmdline_text);
51✔
804
        if (!tmp.options)
51✔
805
                return log_oom();
×
806

807
        if (profile_title)
51✔
808
                tmp.title = strjoin(good_name, " (", profile_title, ")");
34✔
809
        else if (profile_id)
17✔
810
                tmp.title = strjoin(good_name, " (", profile_id, ")");
17✔
811
        else if (profile > 0)
×
812
                (void) asprintf(&tmp.title, "%s (@%u)", good_name, profile);
×
813
        else
814
                tmp.title = strdup(good_name);
×
815
        if (!tmp.title)
51✔
816
                return log_oom();
×
817

818
        if (good_sort_key) {
51✔
819
                tmp.sort_key = strdup(good_sort_key);
51✔
820
                if (!tmp.sort_key)
51✔
821
                        return log_oom();
×
822
        }
823

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

830
        *ret = TAKE_STRUCT(tmp);
51✔
831
        return 0;
51✔
832
}
833

834
static int pe_load_headers_and_sections(
85✔
835
                int fd,
836
                const char *path,
837
                IMAGE_SECTION_HEADER **ret_sections,
838
                PeHeader **ret_pe_header) {
839

840
        _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
170✔
841
        _cleanup_free_ IMAGE_DOS_HEADER *dos_header = NULL;
×
842
        _cleanup_free_ PeHeader *pe_header = NULL;
85✔
843
        int r;
85✔
844

845
        assert(fd >= 0);
85✔
846
        assert(path);
85✔
847

848
        r = pe_load_headers(fd, &dos_header, &pe_header);
85✔
849
        if (r < 0)
85✔
850
                return log_error_errno(r, "Failed to parse PE file '%s': %m", path);
×
851

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

856
        if (ret_pe_header)
85✔
857
                *ret_pe_header = TAKE_PTR(pe_header);
85✔
858
        if (ret_sections)
85✔
859
                *ret_sections = TAKE_PTR(sections);
85✔
860

861
        return 0;
862
}
863

864
static const IMAGE_SECTION_HEADER* pe_find_profile_section_table(
119✔
865
                const PeHeader *pe_header,
866
                const IMAGE_SECTION_HEADER *sections,
867
                unsigned profile,
868
                size_t *ret_n_sections) {
869

870
        assert(pe_header);
119✔
871

872
        /* Looks for the part of the section table that defines the specified profile. If 'profile' is
873
         * specified as UINT_MAX this will look for the base profile. */
874

875
        if (le16toh(pe_header->pe.NumberOfSections) == 0)
119✔
876
                return NULL;
877

878
        assert(sections);
119✔
879

880
        const IMAGE_SECTION_HEADER
119✔
881
                *p = sections,
119✔
882
                *e = sections + le16toh(pe_header->pe.NumberOfSections),
119✔
883
                *start = profile == UINT_MAX ? sections : NULL,
119✔
884
                *end;
885
        unsigned current_profile = UINT_MAX;
886

887
        for (;;) {
425✔
888
                p = pe_section_table_find(p, e - p, ".profile");
272✔
889
                if (!p) {
272✔
890
                        end = e;
891
                        break;
892
                }
893
                if (current_profile == profile) {
238✔
894
                        end = p;
895
                        break;
896
                }
897

898
                if (current_profile == UINT_MAX)
153✔
899
                        current_profile = 0;
900
                else
901
                        current_profile++;
85✔
902

903
                if (current_profile == profile)
153✔
904
                        start = p;
51✔
905

906
                p++; /* Continue scanning after the .profile entry we just found */
153✔
907
        }
908

909
        if (!start)
119✔
910
                return NULL;
911

912
        if (ret_n_sections)
102✔
913
                *ret_n_sections = end - start;
102✔
914

915
        return start;
916
}
917

918
static int trim_cmdline(char **cmdline) {
68✔
919
        assert(cmdline);
68✔
920

921
        /* Strips leading and trailing whitespace from command line */
922

923
        if (!*cmdline)
68✔
924
                return 0;
925

926
        const char *skipped = skip_leading_chars(*cmdline, WHITESPACE);
68✔
927

928
        if (isempty(skipped)) {
68✔
929
                *cmdline = mfree(*cmdline);
×
930
                return 0;
×
931
        }
932

933
        if (skipped != *cmdline) {
68✔
934
                _cleanup_free_ char *c = strdup(skipped);
×
935
                if (!c)
×
936
                        return -ENOMEM;
×
937

938
                free_and_replace(*cmdline, c);
×
939
        }
940

941
        delete_trailing_chars(*cmdline, WHITESPACE);
68✔
942
        return 1;
68✔
943
}
944

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

949
static int pe_find_uki_sections(
68✔
950
                int fd,
951
                const char *path,
952
                unsigned profile,
953
                char **ret_osrelease,
954
                char **ret_profile,
955
                char **ret_cmdline) {
956

957
        _cleanup_free_ char *osrelease_text = NULL, *profile_text = NULL, *cmdline_text = NULL;
×
958
        _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
×
959
        _cleanup_free_ PeHeader *pe_header = NULL;
68✔
960
        int r;
68✔
961

962
        assert(fd >= 0);
68✔
963
        assert(path);
68✔
964
        assert(profile != UINT_MAX);
68✔
965
        assert(ret_osrelease);
68✔
966
        assert(ret_profile);
68✔
967
        assert(ret_cmdline);
68✔
968

969
        r = pe_load_headers_and_sections(fd, path, &sections, &pe_header);
68✔
970
        if (r < 0)
68✔
971
                return r;
972

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

976
        if (!pe_is_native(pe_header)) /* Don't process non-native UKIs */
68✔
977
                goto nothing;
×
978

979
        /* Find part of the section table for this profile */
980
        size_t n_psections = 0;
68✔
981
        const IMAGE_SECTION_HEADER *psections = pe_find_profile_section_table(pe_header, sections, profile, &n_psections);
68✔
982
        if (!psections && profile != 0) /* Profile not found? (Profile @0 needs no explicit .profile!) */
68✔
983
                goto nothing;
17✔
984

985
        /* Find base profile part of section table */
986
        size_t n_bsections;
51✔
987
        const IMAGE_SECTION_HEADER *bsections = ASSERT_PTR(pe_find_profile_section_table(pe_header, sections, UINT_MAX, &n_bsections));
51✔
988

989
        struct {
51✔
990
                const char *name;
991
                char **data;
992
        } table[] = {
51✔
993
                { ".osrel",   &osrelease_text },
994
                { ".profile", &profile_text   },
995
                { ".cmdline", &cmdline_text   },
996
        };
997

998
        FOREACH_ELEMENT(t, table) {
204✔
999
                const IMAGE_SECTION_HEADER *found;
153✔
1000

1001
                /* First look in the profile part of the section table, and if we don't find anything there, look into the base part */
1002
                found = pe_section_table_find(psections, n_psections, t->name);
153✔
1003
                if (!found) {
153✔
1004
                        found = pe_section_table_find(bsections, n_bsections, t->name);
68✔
1005
                        if (!found)
68✔
1006
                                continue;
×
1007
                }
1008

1009
                /* Permit "masking" of sections in the base profile */
1010
                if (found->VirtualSize == 0)
153✔
1011
                        continue;
×
1012

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

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

1021
        if (trim_cmdline(&cmdline_text) < 0)
51✔
1022
                return log_oom();
×
1023

1024
        *ret_osrelease = TAKE_PTR(osrelease_text);
51✔
1025
        *ret_profile = TAKE_PTR(profile_text);
51✔
1026
        *ret_cmdline = TAKE_PTR(cmdline_text);
51✔
1027
        return 1;
51✔
1028

1029
nothing:
17✔
1030
        *ret_osrelease = *ret_profile = *ret_cmdline = NULL;
17✔
1031
        return 0;
17✔
1032
}
1033

1034
static int pe_find_addon_sections(
17✔
1035
                int fd,
1036
                const char *path,
1037
                char **ret_cmdline) {
1038

1039
        _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
34✔
1040
        _cleanup_free_ PeHeader *pe_header = NULL;
17✔
1041
        int r;
17✔
1042

1043
        assert(fd >= 0);
17✔
1044
        assert(path);
17✔
1045

1046
        r = pe_load_headers_and_sections(fd, path, &sections, &pe_header);
17✔
1047
        if (r < 0)
17✔
1048
                return r;
1049

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

1053
        /* Define early, before the gotos below */
1054
        _cleanup_free_ char *cmdline_text = NULL;
17✔
1055

1056
        if (!pe_is_native(pe_header))
17✔
1057
                goto nothing;
×
1058

1059
        const IMAGE_SECTION_HEADER *found = pe_section_table_find(sections, le16toh(pe_header->pe.NumberOfSections), ".cmdline");
17✔
1060
        if (!found)
17✔
1061
                goto nothing;
×
1062

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

1067
        if (trim_cmdline(&cmdline_text) < 0)
17✔
1068
                return log_oom();
×
1069

1070
        *ret_cmdline = TAKE_PTR(cmdline_text);
17✔
1071
        return 1;
17✔
1072

1073
nothing:
×
1074
        *ret_cmdline = NULL;
×
1075
        return 0;
×
1076
}
1077

1078
static int insert_boot_entry_addon(
17✔
1079
                BootEntryAddons *addons,
1080
                char *location,
1081
                char *cmdline) {
1082

1083
        assert(addons);
17✔
1084

1085
        if (!GREEDY_REALLOC(addons->items, addons->n_items + 1))
17✔
1086
                return log_oom();
×
1087

1088
        addons->items[addons->n_items++] = (BootEntryAddon) {
17✔
1089
                .location = location,
1090
                .cmdline = cmdline,
1091
        };
1092

1093
        return 0;
17✔
1094
}
1095

1096
static int boot_entries_find_unified_addons(
98✔
1097
                BootConfig *config,
1098
                int d_fd,
1099
                const char *addon_dir,
1100
                const char *root,
1101
                BootEntryAddons *ret_addons) {
1102

1103
        _cleanup_closedir_ DIR *d = NULL;
98✔
1104
        _cleanup_free_ char *full = NULL;
98✔
1105
        _cleanup_(boot_entry_addons_done) BootEntryAddons addons = {};
98✔
1106
        int r;
98✔
1107

1108
        assert(ret_addons);
98✔
1109
        assert(config);
98✔
1110

1111
        r = chase_and_opendirat(d_fd, addon_dir, CHASE_AT_RESOLVE_IN_ROOT, &full, &d);
98✔
1112
        if (r == -ENOENT)
98✔
1113
                return 0;
1114
        if (r < 0)
17✔
1115
                return log_error_errno(r, "Failed to open '%s/%s': %m", root, skip_leading_slash(addon_dir));
×
1116

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

1121
                if (!dirent_is_file(de))
17✔
1122
                        continue;
×
1123

1124
                if (!endswith_no_case(de->d_name, ".addon.efi"))
17✔
1125
                        continue;
×
1126

1127
                fd = openat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOFOLLOW|O_NOCTTY);
17✔
1128
                if (fd < 0) {
17✔
1129
                        log_warning_errno(errno, "Failed to open %s/%s, ignoring: %m", full, de->d_name);
×
1130
                        continue;
×
1131
                }
1132

1133
                r = config_check_inode_relevant_and_unseen(config, fd, de->d_name);
17✔
1134
                if (r < 0)
17✔
1135
                        return r;
1136
                if (r == 0) /* inode already seen or otherwise not relevant */
17✔
1137
                        continue;
×
1138

1139
                j = path_join(full, de->d_name);
17✔
1140
                if (!j)
17✔
1141
                        return log_oom();
×
1142

1143
                if (pe_find_addon_sections(fd, j, &cmdline) <= 0)
17✔
1144
                        continue;
×
1145

1146
                location = strdup(j);
17✔
1147
                if (!location)
17✔
1148
                        return log_oom();
×
1149

1150
                r = insert_boot_entry_addon(&addons, location, cmdline);
17✔
1151
                if (r < 0)
17✔
1152
                        return r;
1153

1154
                TAKE_PTR(location);
17✔
1155
                TAKE_PTR(cmdline);
17✔
1156
        }
1157

1158
        *ret_addons = TAKE_STRUCT(addons);
17✔
1159
        return 0;
17✔
1160
}
1161

1162
static int boot_entries_find_unified_global_addons(
47✔
1163
                BootConfig *config,
1164
                const char *root,
1165
                const char *d_name,
1166
                BootEntryAddons *ret_addons) {
1167

1168
        int r;
47✔
1169
        _cleanup_closedir_ DIR *d = NULL;
47✔
1170

1171
        assert(ret_addons);
47✔
1172

1173
        r = chase_and_opendir(root, NULL, CHASE_PROHIBIT_SYMLINKS, NULL, &d);
47✔
1174
        if (r == -ENOENT)
47✔
1175
                return 0;
1176
        if (r < 0)
47✔
1177
                return log_error_errno(r, "Failed to open '%s/%s': %m", root, skip_leading_slash(d_name));
×
1178

1179
        return boot_entries_find_unified_addons(config, dirfd(d), d_name, root, ret_addons);
47✔
1180
}
1181

1182
static int boot_entries_find_unified_local_addons(
51✔
1183
                BootConfig *config,
1184
                int d_fd,
1185
                const char *d_name,
1186
                const char *root,
1187
                BootEntry *ret) {
1188

1189
        _cleanup_free_ char *addon_dir = NULL;
51✔
1190

1191
        assert(ret);
51✔
1192

1193
        addon_dir = strjoin(d_name, ".extra.d");
51✔
1194
        if (!addon_dir)
51✔
1195
                return log_oom();
×
1196

1197
        return boot_entries_find_unified_addons(config, d_fd, addon_dir, root, &ret->local_addons);
51✔
1198
}
1199

1200
static int boot_entries_find_unified(
47✔
1201
                BootConfig *config,
1202
                const char *root,
1203
                BootEntrySource source,
1204
                const char *dir) {
1205

1206
        _cleanup_closedir_ DIR *d = NULL;
47✔
1207
        _cleanup_free_ char *full = NULL;
47✔
1208
        int r;
47✔
1209

1210
        assert(config);
47✔
1211
        assert(dir);
47✔
1212

1213
        r = chase_and_opendir(dir, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &full, &d);
47✔
1214
        if (r == -ENOENT)
47✔
1215
                return 0;
1216
        if (r < 0)
31✔
1217
                return log_error_errno(r, "Failed to open '%s/%s': %m", root, skip_leading_slash(dir));
×
1218

1219
        FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read %s: %m", full)) {
110✔
1220
                if (!dirent_is_file(de))
17✔
1221
                        continue;
×
1222

1223
                if (!endswith_no_case(de->d_name, ".efi"))
17✔
1224
                        continue;
×
1225

1226
                _cleanup_close_ int fd = openat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOFOLLOW|O_NOCTTY);
143✔
1227
                if (fd < 0) {
17✔
1228
                        log_warning_errno(errno, "Failed to open %s/%s, ignoring: %m", full, de->d_name);
×
1229
                        continue;
×
1230
                }
1231

1232
                r = config_check_inode_relevant_and_unseen(config, fd, de->d_name);
17✔
1233
                if (r < 0)
17✔
1234
                        return r;
1235
                if (r == 0) /* inode already seen or otherwise not relevant */
17✔
1236
                        continue;
×
1237

1238
                _cleanup_free_ char *j = path_join(full, de->d_name);
34✔
1239
                if (!j)
17✔
1240
                        return log_oom();
×
1241

1242
                for (unsigned p = 0; p < UNIFIED_PROFILES_MAX; p++) {
68✔
1243
                        _cleanup_free_ char *osrelease = NULL, *profile = NULL, *cmdline = NULL;
68✔
1244

1245
                        r = pe_find_uki_sections(fd, j, p, &osrelease, &profile, &cmdline);
68✔
1246
                        if (r == 0) /* this profile does not exist, we are done */
68✔
1247
                                break;
1248
                        if (r < 0)
51✔
1249
                                continue;
×
1250

1251
                        if (!GREEDY_REALLOC(config->entries, config->n_entries + 1))
51✔
1252
                                return log_oom();
×
1253

1254
                        BootEntry *entry = config->entries + config->n_entries;
51✔
1255

1256
                        if (boot_entry_load_unified(root, source, j, p, osrelease, profile, cmdline, entry) < 0)
51✔
1257
                                continue;
×
1258

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

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

1265
                        config->n_entries++;
51✔
1266
                }
1267
        }
1268

1269
        return 0;
1270
}
1271

1272
static bool find_nonunique(const BootEntry *entries, size_t n_entries, bool arr[]) {
56✔
1273
        bool non_unique = false;
56✔
1274

1275
        assert(entries || n_entries == 0);
56✔
1276
        assert(arr || n_entries == 0);
56✔
1277

1278
        for (size_t i = 0; i < n_entries; i++)
227✔
1279
                arr[i] = false;
171✔
1280

1281
        for (size_t i = 0; i < n_entries; i++)
227✔
1282
                for (size_t j = 0; j < n_entries; j++)
714✔
1283
                        if (i != j && streq(boot_entry_title(entries + i),
543✔
1284
                                            boot_entry_title(entries + j)))
1285
                                non_unique = arr[i] = arr[j] = true;
112✔
1286

1287
        return non_unique;
56✔
1288
}
1289

1290
static int boot_entries_uniquify(BootEntry *entries, size_t n_entries) {
33✔
1291
        _cleanup_free_ bool *arr = NULL;
33✔
1292
        char *s;
33✔
1293

1294
        assert(entries || n_entries == 0);
33✔
1295

1296
        if (n_entries == 0)
33✔
1297
                return 0;
1298

1299
        arr = new(bool, n_entries);
19✔
1300
        if (!arr)
19✔
1301
                return -ENOMEM;
1302

1303
        /* Find _all_ non-unique titles */
1304
        if (!find_nonunique(entries, n_entries, arr))
19✔
1305
                return 0;
1306

1307
        /* Add version to non-unique titles */
1308
        for (size_t i = 0; i < n_entries; i++)
78✔
1309
                if (arr[i] && entries[i].version) {
59✔
1310
                        if (asprintf(&s, "%s (%s)", boot_entry_title(entries + i), entries[i].version) < 0)
40✔
1311
                                return -ENOMEM;
1312

1313
                        free_and_replace(entries[i].show_title, s);
40✔
1314
                }
1315

1316
        if (!find_nonunique(entries, n_entries, arr))
19✔
1317
                return 0;
1318

1319
        /* Add machine-id to non-unique titles */
1320
        for (size_t i = 0; i < n_entries; i++)
71✔
1321
                if (arr[i] && entries[i].machine_id) {
53✔
1322
                        if (asprintf(&s, "%s (%s)", boot_entry_title(entries + i), entries[i].machine_id) < 0)
2✔
1323
                                return -ENOMEM;
1324

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

1328
        if (!find_nonunique(entries, n_entries, arr))
18✔
1329
                return 0;
1330

1331
        /* Add file name to non-unique titles */
1332
        for (size_t i = 0; i < n_entries; i++)
71✔
1333
                if (arr[i]) {
53✔
1334
                        if (asprintf(&s, "%s (%s)", boot_entry_title(entries + i), entries[i].id) < 0)
36✔
1335
                                return -ENOMEM;
1336

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

1340
        return 0;
1341
}
1342

1343
static int boot_config_find(const BootConfig *config, const char *id) {
20✔
1344
        assert(config);
20✔
1345

1346
        if (!id)
20✔
1347
                return -1;
1348

1349
        if (id[0] == '@') {
20✔
1350
                if (!strcaseeq(id, "@saved"))
×
1351
                        return -1;
1352
                if (!config->entry_selected)
×
1353
                        return -1;
1354
                id = config->entry_selected;
1355
        }
1356

1357
        for (size_t i = 0; i < config->n_entries; i++)
70✔
1358
                if (fnmatch(id, config->entries[i].id, FNM_CASEFOLD) == 0)
70✔
1359
                        return i;
20✔
1360

1361
        return -1;
1362
}
1363

1364
static int boot_entries_select_default(const BootConfig *config) {
31✔
1365
        int i;
31✔
1366

1367
        assert(config);
31✔
1368
        assert(config->entries || config->n_entries == 0);
31✔
1369

1370
        if (config->n_entries == 0) {
31✔
1371
                log_debug("Found no default boot entry :(");
14✔
1372
                return -1; /* -1 means "no default" */
14✔
1373
        }
1374

1375
        if (config->entry_oneshot) {
17✔
1376
                i = boot_config_find(config, config->entry_oneshot);
×
1377
                if (i >= 0) {
×
1378
                        log_debug("Found default: id \"%s\" is matched by LoaderEntryOneShot",
×
1379
                                  config->entries[i].id);
1380
                        return i;
×
1381
                }
1382
        }
1383

1384
        if (config->entry_default) {
17✔
1385
                i = boot_config_find(config, config->entry_default);
3✔
1386
                if (i >= 0) {
3✔
1387
                        log_debug("Found default: id \"%s\" is matched by LoaderEntryDefault",
3✔
1388
                                  config->entries[i].id);
1389
                        return i;
3✔
1390
                }
1391
        }
1392

1393
        if (config->default_pattern) {
14✔
1394
                i = boot_config_find(config, config->default_pattern);
×
1395
                if (i >= 0) {
×
1396
                        log_debug("Found default: id \"%s\" is matched by pattern \"%s\"",
×
1397
                                  config->entries[i].id, config->default_pattern);
1398
                        return i;
×
1399
                }
1400
        }
1401

1402
        log_debug("Found default: first entry \"%s\"", config->entries[0].id);
14✔
1403
        return 0;
1404
}
1405

1406
static int boot_entries_select_selected(const BootConfig *config) {
31✔
1407
        assert(config);
31✔
1408
        assert(config->entries || config->n_entries == 0);
31✔
1409

1410
        if (!config->entry_selected || config->n_entries == 0)
31✔
1411
                return -1;
1412

1413
        return boot_config_find(config, config->entry_selected);
17✔
1414
}
1415

1416
static int boot_load_efi_entry_pointers(BootConfig *config, bool skip_efivars) {
31✔
1417
        int r;
31✔
1418

1419
        assert(config);
31✔
1420

1421
        if (skip_efivars || !is_efi_boot())
31✔
1422
                return 0;
14✔
1423

1424
        /* Loads the three "pointers" to boot loader entries from their EFI variables */
1425

1426
        r = efi_get_variable_string(EFI_LOADER_VARIABLE_STR("LoaderEntryOneShot"), &config->entry_oneshot);
17✔
1427
        if (r == -ENOMEM)
17✔
1428
                return log_oom();
×
1429
        if (r < 0 && !IN_SET(r, -ENOENT, -ENODATA))
17✔
1430
                log_warning_errno(r, "Failed to read EFI variable \"LoaderEntryOneShot\", ignoring: %m");
×
1431

1432
        r = efi_get_variable_string(EFI_LOADER_VARIABLE_STR("LoaderEntryDefault"), &config->entry_default);
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 \"LoaderEntryDefault\", ignoring: %m");
×
1437

1438
        r = efi_get_variable_string(EFI_LOADER_VARIABLE_STR("LoaderEntrySelected"), &config->entry_selected);
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 \"LoaderEntrySelected\", ignoring: %m");
×
1443

1444
        r = efi_get_variable_string(EFI_LOADER_VARIABLE_STR("LoaderEntrySysFail"), &config->entry_sysfail);
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 \"LoaderEntrySysFail\", ignoring: %m");
×
1449

1450
        return 1;
1451
}
1452

1453
int boot_config_select_special_entries(BootConfig *config, bool skip_efivars) {
31✔
1454
        int r;
31✔
1455

1456
        assert(config);
31✔
1457

1458
        r = boot_load_efi_entry_pointers(config, skip_efivars);
31✔
1459
        if (r < 0)
31✔
1460
                return r;
1461

1462
        config->default_entry = boot_entries_select_default(config);
31✔
1463
        config->selected_entry = boot_entries_select_selected(config);
31✔
1464

1465
        return 0;
31✔
1466
}
1467

1468
int boot_config_finalize(BootConfig *config) {
33✔
1469
        int r;
33✔
1470

1471
        typesafe_qsort(config->entries, config->n_entries, boot_entry_compare);
33✔
1472

1473
        r = boot_entries_uniquify(config->entries, config->n_entries);
33✔
1474
        if (r < 0)
33✔
1475
                return log_error_errno(r, "Failed to uniquify boot entries: %m");
×
1476

1477
        return 0;
1478
}
1479

1480
int boot_config_load(
33✔
1481
                BootConfig *config,
1482
                const char *esp_path,
1483
                const char *xbootldr_path) {
1484

1485
        int r;
33✔
1486

1487
        assert(config);
33✔
1488

1489
        if (esp_path) {
33✔
1490
                r = boot_loader_read_conf_path(config, esp_path, "/loader/loader.conf");
33✔
1491
                if (r < 0)
33✔
1492
                        return r;
1493

1494
                r = boot_entries_find_type1(config, esp_path, BOOT_ENTRY_ESP, "/loader/entries");
33✔
1495
                if (r < 0)
33✔
1496
                        return r;
1497

1498
                r = boot_entries_find_unified(config, esp_path, BOOT_ENTRY_ESP, "/EFI/Linux/");
33✔
1499
                if (r < 0)
33✔
1500
                        return r;
1501

1502
                r = boot_entries_find_unified_global_addons(config, esp_path, "/loader/addons/",
33✔
1503
                                                            &config->global_addons[BOOT_ENTRY_ESP]);
1504
                if (r < 0)
33✔
1505
                        return r;
1506
        }
1507

1508
        if (xbootldr_path) {
33✔
1509
                r = boot_entries_find_type1(config, xbootldr_path, BOOT_ENTRY_XBOOTLDR, "/loader/entries");
14✔
1510
                if (r < 0)
14✔
1511
                        return r;
1512

1513
                r = boot_entries_find_unified(config, xbootldr_path, BOOT_ENTRY_XBOOTLDR, "/EFI/Linux/");
14✔
1514
                if (r < 0)
14✔
1515
                        return r;
1516

1517
                r = boot_entries_find_unified_global_addons(config, xbootldr_path, "/loader/addons/",
14✔
1518
                                                            &config->global_addons[BOOT_ENTRY_XBOOTLDR]);
1519
                if (r < 0)
14✔
1520
                        return r;
1521
        }
1522

1523
        return boot_config_finalize(config);
33✔
1524
}
1525

1526
int boot_config_load_auto(
×
1527
                BootConfig *config,
1528
                const char *override_esp_path,
1529
                const char *override_xbootldr_path) {
1530

1531
        _cleanup_free_ char *esp_where = NULL, *xbootldr_where = NULL;
×
1532
        dev_t esp_devid = 0, xbootldr_devid = 0;
×
1533
        int r;
×
1534

1535
        assert(config);
×
1536

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

1544
        if (!override_esp_path && !override_xbootldr_path) {
×
1545
                if (access("/run/boot-loader-entries/", F_OK) >= 0)
×
1546
                        return boot_config_load(config, "/run/boot-loader-entries/", NULL);
×
1547

1548
                if (errno != ENOENT)
×
1549
                        return log_error_errno(errno,
×
1550
                                               "Failed to determine whether /run/boot-loader-entries/ exists: %m");
1551
        }
1552

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

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

1561
        /* If both paths actually refer to the same inode, suppress the xbootldr path */
1562
        if (esp_where && xbootldr_where && devnum_set_and_equal(esp_devid, xbootldr_devid))
×
1563
                xbootldr_where = mfree(xbootldr_where);
×
1564

1565
        return boot_config_load(config, esp_where, xbootldr_where);
×
1566
}
1567

1568
int boot_config_augment_from_loader(
17✔
1569
                BootConfig *config,
1570
                char **found_by_loader,
1571
                bool auto_only) {
1572

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

1586
        assert(config);
17✔
1587

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

1591
        STRV_FOREACH(i, found_by_loader) {
85✔
1592
                BootEntry *existing;
68✔
1593
                _cleanup_free_ char *c = NULL, *t = NULL, *p = NULL;
34✔
1594

1595
                existing = boot_config_find_entry(config, *i);
68✔
1596
                if (existing) {
68✔
1597
                        existing->reported_by_loader = true;
34✔
1598
                        continue;
34✔
1599
                }
1600

1601
                if (auto_only && !startswith(*i, "auto-"))
34✔
1602
                        continue;
×
1603

1604
                c = strdup(*i);
34✔
1605
                if (!c)
34✔
1606
                        return log_oom();
×
1607

1608
                STRV_FOREACH_PAIR(a, b, title_table)
255✔
1609
                        if (streq(*a, *i)) {
238✔
1610
                                t = strdup(*b);
17✔
1611
                                if (!t)
17✔
1612
                                        return log_oom();
×
1613
                                break;
1614
                        }
1615

1616
                p = strdup(EFIVAR_PATH(EFI_LOADER_VARIABLE_STR("LoaderEntries")));
34✔
1617
                if (!p)
34✔
1618
                        return log_oom();
×
1619

1620
                if (!GREEDY_REALLOC0(config->entries, config->n_entries + 1))
34✔
1621
                        return log_oom();
×
1622

1623
                config->entries[config->n_entries++] = (BootEntry) {
68✔
1624
                        .type = startswith(*i, "auto-") ? BOOT_ENTRY_AUTO : BOOT_ENTRY_LOADER,
34✔
1625
                        .id = TAKE_PTR(c),
34✔
1626
                        .title = TAKE_PTR(t),
34✔
1627
                        .path = TAKE_PTR(p),
34✔
1628
                        .reported_by_loader = true,
1629
                        .tries_left = UINT_MAX,
1630
                        .tries_done = UINT_MAX,
1631
                        .global_addons = &no_addons,
1632
                };
1633
        }
1634

1635
        return 0;
1636
}
1637

1638
BootEntry* boot_config_find_entry(BootConfig *config, const char *id) {
72✔
1639
        assert(config);
72✔
1640
        assert(id);
72✔
1641

1642
        for (size_t j = 0; j < config->n_entries; j++)
245✔
1643
                if (strcaseeq_ptr(config->entries[j].id, id) ||
210✔
1644
                    strcaseeq_ptr(config->entries[j].id_old, id))
173✔
1645
                        return config->entries + j;
1646

1647
        return NULL;
1648
}
1649

1650
static void boot_entry_file_list(
18✔
1651
                const char *field,
1652
                const char *root,
1653
                const char *p,
1654
                int *ret_status) {
1655

1656
        assert(p);
18✔
1657
        assert(ret_status);
18✔
1658

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

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

1666
        if (status < 0) {
18✔
1667
                errno = -status;
×
1668
                printf("%s%s%s (%m)\n", ansi_highlight_red(), p, ansi_normal());
×
1669
        } else
1670
                printf("%s\n", p);
18✔
1671

1672
        if (*ret_status == 0 && status < 0)
18✔
1673
                *ret_status = status;
×
1674
}
18✔
1675

1676
static void print_addon(
18✔
1677
                BootEntryAddon *addon,
1678
                const char *addon_str) {
1679

1680
        printf("  %s: %s\n", addon_str, addon->location);
18✔
1681
        printf("      options: %s%s\n", glyph(GLYPH_TREE_RIGHT), addon->cmdline);
18✔
1682
}
18✔
1683

1684
static int indent_embedded_newlines(char *cmdline, char **ret_cmdline) {
36✔
1685
        _cleanup_free_ char *t = NULL;
36✔
1686
        _cleanup_strv_free_ char **ts = NULL;
36✔
1687

1688
        assert(ret_cmdline);
36✔
1689

1690
        ts = strv_split_newlines(cmdline);
36✔
1691
        if (!ts)
36✔
1692
                return -ENOMEM;
1693

1694
        t = strv_join(ts, "\n              ");
36✔
1695
        if (!t)
36✔
1696
                return -ENOMEM;
1697

1698
        *ret_cmdline = TAKE_PTR(t);
36✔
1699

1700
        return 0;
36✔
1701
}
1702

1703
static int print_cmdline(const BootEntry *e) {
22✔
1704

1705
        _cleanup_free_ char *options = NULL, *combined_cmdline = NULL, *t2 = NULL;
22✔
1706

1707
        assert(e);
22✔
1708

1709
        if (!strv_isempty(e->options)) {
22✔
1710
                _cleanup_free_ char *t = NULL;
18✔
1711

1712
                options = strv_join(e->options, " ");
18✔
1713
                if (!options)
18✔
1714
                        return log_oom();
×
1715

1716
                if (indent_embedded_newlines(options, &t) < 0)
18✔
1717
                        return log_oom();
×
1718

1719
                printf("      options: %s\n", t);
18✔
1720
                t2 = strdup(options);
18✔
1721
                if (!t2)
18✔
1722
                        return log_oom();
×
1723
        }
1724

1725
        FOREACH_ARRAY(addon, e->global_addons->items, e->global_addons->n_items) {
40✔
1726
                print_addon(addon, "global-addon");
18✔
1727
                if (!strextend(&t2, " ", addon->cmdline))
18✔
1728
                        return log_oom();
×
1729
        }
1730

1731
        FOREACH_ARRAY(addon, e->local_addons.items, e->local_addons.n_items) {
22✔
1732
                /* Add space at the beginning of addon_str to align it correctly */
1733
                print_addon(addon, " local-addon");
×
1734
                if (!strextend(&t2, " ", addon->cmdline))
×
1735
                        return log_oom();
×
1736
        }
1737

1738
        /* Don't print the combined cmdline if it's same as options. */
1739
        if (streq_ptr(t2, options))
22✔
1740
                return 0;
1741

1742
        if (indent_embedded_newlines(t2, &combined_cmdline) < 0)
18✔
1743
                return log_oom();
×
1744

1745
        if (combined_cmdline)
18✔
1746
                printf("      cmdline: %s\n", combined_cmdline);
18✔
1747

1748
        return 0;
1749
}
1750

1751
static int json_addon(
9✔
1752
                BootEntryAddon *addon,
1753
                const char *addon_str,
1754
                sd_json_variant **array) {
1755

1756
        int r;
9✔
1757

1758
        assert(addon);
9✔
1759
        assert(addon_str);
9✔
1760

1761
        r = sd_json_variant_append_arraybo(
9✔
1762
                        array,
1763
                        SD_JSON_BUILD_PAIR(addon_str, SD_JSON_BUILD_STRING(addon->location)),
1764
                        SD_JSON_BUILD_PAIR("options", SD_JSON_BUILD_STRING(addon->cmdline)));
1765
        if (r < 0)
9✔
1766
                return log_oom();
×
1767

1768
        return 0;
1769
}
1770

1771
static int json_cmdline(
15✔
1772
                const BootEntry *e,
1773
                const char *def_cmdline,
1774
                sd_json_variant **v) {
1775

1776
        _cleanup_free_ char *combined_cmdline = NULL;
15✔
1777
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *addons_array = NULL;
15✔
1778
        int r;
15✔
1779

1780
        assert(e);
15✔
1781

1782
        if (def_cmdline) {
15✔
1783
                combined_cmdline = strdup(def_cmdline);
9✔
1784
                if (!combined_cmdline)
9✔
1785
                        return log_oom();
×
1786
        }
1787

1788
        FOREACH_ARRAY(addon, e->global_addons->items, e->global_addons->n_items) {
24✔
1789
                r = json_addon(addon, "globalAddon", &addons_array);
9✔
1790
                if (r < 0)
9✔
1791
                        return r;
1792
                if (!strextend(&combined_cmdline, " ", addon->cmdline))
9✔
1793
                        return log_oom();
×
1794
        }
1795

1796
        FOREACH_ARRAY(addon, e->local_addons.items, e->local_addons.n_items) {
15✔
1797
                r = json_addon(addon, "localAddon", &addons_array);
×
1798
                if (r < 0)
×
1799
                        return r;
1800
                if (!strextend(&combined_cmdline, " ", addon->cmdline))
×
1801
                        return log_oom();
×
1802
        }
1803

1804
        r = sd_json_variant_merge_objectbo(
15✔
1805
                        v,
1806
                        SD_JSON_BUILD_PAIR("addons", SD_JSON_BUILD_VARIANT(addons_array)),
1807
                        SD_JSON_BUILD_PAIR_CONDITION(!!combined_cmdline, "cmdline", SD_JSON_BUILD_STRING(combined_cmdline)));
1808
        if (r < 0)
15✔
1809
                return log_oom();
×
1810
        return 0;
1811
}
1812

1813
int show_boot_entry(
22✔
1814
                const BootEntry *e,
1815
                bool show_as_default,
1816
                bool show_as_selected,
1817
                bool show_reported) {
1818

1819
        int status = 0, r = 0;
22✔
1820

1821
        /* Returns 0 on success, negative on processing error, and positive if something is wrong with the
1822
           boot entry itself. */
1823

1824
        assert(e);
22✔
1825

1826
        printf("         type: %s\n",
22✔
1827
               boot_entry_type_description_to_string(e->type));
22✔
1828

1829
        printf("        title: %s%s%s",
66✔
1830
               ansi_highlight(), boot_entry_title(e), ansi_normal());
1831

1832
        if (show_as_default)
22✔
1833
                printf(" %s(default)%s",
4✔
1834
                       ansi_highlight_green(), ansi_normal());
1835

1836
        if (show_as_selected)
22✔
1837
                printf(" %s(selected)%s",
4✔
1838
                       ansi_highlight_magenta(), ansi_normal());
1839

1840
        if (show_reported) {
22✔
1841
                if (e->type == BOOT_ENTRY_LOADER)
10✔
1842
                        printf(" %s(reported/absent)%s",
4✔
1843
                               ansi_highlight_red(), ansi_normal());
1844
                else if (!e->reported_by_loader && e->type != BOOT_ENTRY_AUTO)
8✔
1845
                        printf(" %s(not reported/new)%s",
4✔
1846
                               ansi_highlight_green(), ansi_normal());
1847
        }
1848

1849
        putchar('\n');
22✔
1850

1851
        if (e->id) {
22✔
1852
                printf("           id: %s", e->id);
22✔
1853

1854
                if (e->id_without_profile && !streq_ptr(e->id, e->id_without_profile))
22✔
1855
                        printf(" (without profile: %s)\n", e->id_without_profile);
18✔
1856
                else
1857
                        putchar('\n');
4✔
1858
        }
1859
        if (e->path) {
22✔
1860
                _cleanup_free_ char *text = NULL, *link = NULL;
22✔
1861

1862
                const char *p = e->root ? path_startswith(e->path, e->root) : NULL;
22✔
1863
                if (p) {
18✔
1864
                        text = strjoin(ansi_grey(), e->root, "/", ansi_normal(), "/", p);
36✔
1865
                        if (!text)
18✔
1866
                                return log_oom();
×
1867
                }
1868

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

1874
                printf("       source: %s (on the %s)\n",
22✔
1875
                       link ?: text ?: e->path,
22✔
1876
                       boot_entry_source_description_to_string(e->source));
22✔
1877
        }
1878
        if (e->tries_left != UINT_MAX) {
22✔
1879
                printf("        tries: %u left", e->tries_left);
×
1880

1881
                if (e->tries_done != UINT_MAX)
×
1882
                        printf("; %u done\n", e->tries_done);
×
1883
                else
1884
                        putchar('\n');
×
1885
        }
1886

1887
        if (e->sort_key)
22✔
1888
                printf("     sort-key: %s\n", e->sort_key);
18✔
1889
        if (e->version)
22✔
1890
                printf("      version: %s\n", e->version);
18✔
1891
        if (e->machine_id)
22✔
1892
                printf("   machine-id: %s\n", e->machine_id);
×
1893
        if (e->architecture)
22✔
1894
                printf(" architecture: %s\n", e->architecture);
×
1895
        if (e->kernel)
22✔
1896
                boot_entry_file_list("linux", e->root, e->kernel, &status);
18✔
1897
        if (e->efi)
22✔
1898
                boot_entry_file_list("efi", e->root, e->efi, &status);
×
1899

1900
        STRV_FOREACH(s, e->initrd)
22✔
1901
                boot_entry_file_list(s == e->initrd ? "initrd" : NULL,
×
1902
                                     e->root,
×
1903
                                     *s,
1904
                                     &status);
1905

1906
        r = print_cmdline(e);
22✔
1907
        if (r < 0)
22✔
1908
                return r;
1909

1910
        if (e->device_tree)
22✔
1911
                boot_entry_file_list("devicetree", e->root, e->device_tree, &status);
×
1912

1913
        STRV_FOREACH(s, e->device_tree_overlay)
22✔
1914
                boot_entry_file_list(s == e->device_tree_overlay ? "devicetree-overlay" : NULL,
×
1915
                                     e->root,
×
1916
                                     *s,
1917
                                     &status);
1918

1919
        return -status;
22✔
1920
}
1921

1922
int boot_entry_to_json(const BootConfig *c, size_t i, sd_json_variant **ret) {
15✔
1923
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
15✔
1924
        _cleanup_free_ char *opts = NULL;
15✔
1925
        const BootEntry *e;
15✔
1926
        int r;
15✔
1927

1928
        assert(c);
15✔
1929
        assert(ret);
15✔
1930

1931
        if (i >= c->n_entries) {
15✔
1932
                *ret = NULL;
×
1933
                return 0;
×
1934
        }
1935

1936
        e = c->entries + i;
15✔
1937

1938
        if (!strv_isempty(e->options)) {
15✔
1939
                opts = strv_join(e->options, " ");
9✔
1940
                if (!opts)
9✔
1941
                        return log_oom();
×
1942
        }
1943

1944
        r = sd_json_variant_merge_objectbo(
15✔
1945
                        &v,
1946
                        SD_JSON_BUILD_PAIR("type", SD_JSON_BUILD_STRING(boot_entry_type_to_string(e->type))),
1947
                        SD_JSON_BUILD_PAIR("source", SD_JSON_BUILD_STRING(boot_entry_source_to_string(e->source))),
1948
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->id, "id", SD_JSON_BUILD_STRING(e->id)),
1949
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->path, "path", SD_JSON_BUILD_STRING(e->path)),
1950
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->root, "root", SD_JSON_BUILD_STRING(e->root)),
1951
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->title, "title", SD_JSON_BUILD_STRING(e->title)),
1952
                        SD_JSON_BUILD_PAIR_CONDITION(!!boot_entry_title(e), "showTitle", SD_JSON_BUILD_STRING(boot_entry_title(e))),
1953
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->sort_key, "sortKey", SD_JSON_BUILD_STRING(e->sort_key)),
1954
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->version, "version", SD_JSON_BUILD_STRING(e->version)),
1955
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->machine_id, "machineId", SD_JSON_BUILD_STRING(e->machine_id)),
1956
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->architecture, "architecture", SD_JSON_BUILD_STRING(e->architecture)),
1957
                        SD_JSON_BUILD_PAIR_CONDITION(!!opts, "options", SD_JSON_BUILD_STRING(opts)),
1958
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->kernel, "linux", SD_JSON_BUILD_STRING(e->kernel)),
1959
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->efi, "efi", SD_JSON_BUILD_STRING(e->efi)),
1960
                        SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(e->initrd), "initrd", SD_JSON_BUILD_STRV(e->initrd)),
1961
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->device_tree, "devicetree", SD_JSON_BUILD_STRING(e->device_tree)),
1962
                        SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(e->device_tree_overlay), "devicetreeOverlay", SD_JSON_BUILD_STRV(e->device_tree_overlay)));
1963
        if (r < 0)
15✔
1964
                return log_oom();
×
1965

1966
        /* Sanitizers (only memory sanitizer?) do not like function call with too many
1967
         * arguments and trigger false positive warnings. Let's not add too many json objects
1968
         * at once. */
1969
        r = sd_json_variant_merge_objectbo(
15✔
1970
                        &v,
1971
                        SD_JSON_BUILD_PAIR("isReported", SD_JSON_BUILD_BOOLEAN(e->reported_by_loader)),
1972
                        SD_JSON_BUILD_PAIR_CONDITION(e->tries_left != UINT_MAX, "triesLeft", SD_JSON_BUILD_UNSIGNED(e->tries_left)),
1973
                        SD_JSON_BUILD_PAIR_CONDITION(e->tries_done != UINT_MAX, "triesDone", SD_JSON_BUILD_UNSIGNED(e->tries_done)),
1974
                        SD_JSON_BUILD_PAIR_CONDITION(c->default_entry >= 0, "isDefault", SD_JSON_BUILD_BOOLEAN(i == (size_t) c->default_entry)),
1975
                        SD_JSON_BUILD_PAIR_CONDITION(c->selected_entry >= 0, "isSelected", SD_JSON_BUILD_BOOLEAN(i == (size_t) c->selected_entry)));
1976
        if (r < 0)
15✔
1977
                return log_oom();
×
1978

1979
        r = json_cmdline(e, opts, &v);
15✔
1980
        if (r < 0)
15✔
1981
                return log_oom();
×
1982

1983
        *ret = TAKE_PTR(v);
15✔
1984
        return 1;
15✔
1985
}
1986

1987
int show_boot_entries(const BootConfig *config, sd_json_format_flags_t json_format) {
8✔
1988
        int r;
8✔
1989

1990
        assert(config);
8✔
1991

1992
        if (sd_json_format_enabled(json_format)) {
8✔
1993
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL;
6✔
1994

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

1998
                        r = boot_entry_to_json(config, i, &v);
10✔
1999
                        if (r < 0)
10✔
2000
                                return log_oom();
×
2001

2002
                        r = sd_json_variant_append_array(&array, v);
10✔
2003
                        if (r < 0)
10✔
2004
                                return log_oom();
×
2005
                }
2006

2007
                return sd_json_variant_dump(array, json_format | SD_JSON_FORMAT_EMPTY_ARRAY, NULL, NULL);
6✔
2008
        } else
2009
                for (size_t n = 0; n < config->n_entries; n++) {
12✔
2010
                        r = show_boot_entry(
20✔
2011
                                        config->entries + n,
10✔
2012
                                        /* show_as_default= */  n == (size_t) config->default_entry,
10✔
2013
                                        /* show_as_selected= */ n == (size_t) config->selected_entry,
10✔
2014
                                        /* show_reported= */  true);
2015
                        if (r < 0)
10✔
2016
                                return r;
2017

2018
                        if (n+1 < config->n_entries)
10✔
2019
                                putchar('\n');
8✔
2020
                }
2021

2022
        return 0;
2023
}
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