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

systemd / systemd / 15837872256

23 Jun 2025 09:28PM UTC coverage: 72.09% (-0.02%) from 72.105%
15837872256

push

github

bluca
test-cpu-set-util: fix check for CPUSet.allocated

The check was simply wrong and meaningless, as it always checked
CPUSet.allocated is greater than or equals to 1, as sizeof(__cpu_mask) is 8.

Let's make the test more strict.

300458 of 416781 relevant lines covered (72.09%)

709101.32 hits per line

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

66.54
/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_table[_BOOT_ENTRY_TYPE_MAX] = {
36
        [BOOT_ENTRY_CONF]        = "Boot Loader Specification Type #1 (.conf)",
37
        [BOOT_ENTRY_UNIFIED]     = "Boot Loader Specification Type #2 (.efi)",
38
        [BOOT_ENTRY_LOADER]      = "Reported by Boot Loader",
39
        [BOOT_ENTRY_LOADER_AUTO] = "Automatic",
40
};
41

42
DEFINE_STRING_TABLE_LOOKUP_TO_STRING(boot_entry_type, BootEntryType);
15✔
43

44
static const char* const boot_entry_type_json_table[_BOOT_ENTRY_TYPE_MAX] = {
45
        [BOOT_ENTRY_CONF]        = "type1",
46
        [BOOT_ENTRY_UNIFIED]     = "type2",
47
        [BOOT_ENTRY_LOADER]      = "loader",
48
        [BOOT_ENTRY_LOADER_AUTO] = "auto",
49
};
50

51
DEFINE_STRING_TABLE_LOOKUP_TO_STRING(boot_entry_type_json, BootEntryType);
9✔
52

53
static const char* const boot_entry_source_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, BootEntrySource);
15✔
59

60
static const char* const boot_entry_source_json_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_json, BootEntrySource);
9✔
66

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

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

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

81
        free(entry->id);
112✔
82
        free(entry->id_old);
112✔
83
        free(entry->id_without_profile);
112✔
84
        free(entry->path);
112✔
85
        free(entry->root);
112✔
86
        free(entry->title);
112✔
87
        free(entry->show_title);
112✔
88
        free(entry->sort_key);
112✔
89
        free(entry->version);
112✔
90
        free(entry->machine_id);
112✔
91
        free(entry->architecture);
112✔
92
        strv_free(entry->options);
112✔
93
        boot_entry_addons_done(&entry->local_addons);
112✔
94
        free(entry->kernel);
112✔
95
        free(entry->efi);
112✔
96
        strv_free(entry->initrd);
112✔
97
        free(entry->device_tree);
112✔
98
        strv_free(entry->device_tree_overlay);
112✔
99
}
112✔
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(
79✔
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;
79✔
252
        _cleanup_free_ char *stripped = NULL;
79✔
253
        const char *p, *suffix, *m;
79✔
254
        int r;
79✔
255

256
        assert(fname);
79✔
257
        assert(ret_stripped);
79✔
258
        assert(ret_tries_left);
79✔
259
        assert(ret_tries_done);
79✔
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, '.');
79✔
264
        if (!suffix)
79✔
265
                goto nothing;
1✔
266

267
        p = m = memrchr(fname, '+', suffix - fname);
78✔
268
        if (!p)
78✔
269
                goto nothing;
55✔
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:
61✔
305
        stripped = strdup(fname);
61✔
306
        if (!stripped)
61✔
307
                return log_oom();
×
308

309
        *ret_stripped = TAKE_PTR(stripped);
61✔
310
        *ret_tries_left = *ret_tries_done = UINT_MAX;
61✔
311
        return 0;
61✔
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_CONF, 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) {
30✔
455
        assert(config);
30✔
456

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

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

464
        FOREACH_ARRAY(i, config->entries, config->n_entries)
92✔
465
                boot_entry_free(i);
62✔
466
        free(config->entries);
30✔
467

468
        FOREACH_ELEMENT(i, config->global_addons)
90✔
469
                boot_entry_addons_done(i);
60✔
470

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

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

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

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

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

492
                if (IN_SET(buf[0], '#', '\0'))
54✔
493
                        continue;
54✔
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;
27✔
525
}
526

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

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

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

541
        return boot_loader_read_conf(config, f, full);
27✔
542
}
543

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

547
        assert(a);
36✔
548
        assert(b);
36✔
549

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

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

556
        r = CMP(!a->sort_key, !b->sort_key);
36✔
557
        if (r != 0)
33✔
558
                return r;
3✔
559

560
        if (a->sort_key && b->sort_key) {
33✔
561
                r = strcmp(a->sort_key, b->sort_key);
30✔
562
                if (r != 0)
30✔
563
                        return r;
564

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

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

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

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

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

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

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

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

600
        assert(config);
22✔
601
        assert(fd >= 0);
22✔
602
        assert(fname);
22✔
603

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

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

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

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

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

629
        return true;
630
}
631

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

638
        _cleanup_free_ DirectoryEntries *dentries = NULL;
88✔
639
        _cleanup_free_ char *full = NULL;
44✔
640
        _cleanup_close_ int dir_fd = -EBADF;
44✔
641
        int r;
44✔
642

643
        assert(config);
44✔
644
        assert(root);
44✔
645
        assert(dir);
44✔
646

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

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

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

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

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

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

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

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

684
        return 0;
685
}
686

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

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

703
        assert(root);
42✔
704
        assert(path);
42✔
705
        assert(osrelease_text);
42✔
706
        assert(ret);
42✔
707

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

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

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

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

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

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

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

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

762
        _cleanup_(boot_entry_free) BootEntry tmp = BOOT_ENTRY_INIT(BOOT_ENTRY_UNIFIED, source);
42✔
763

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

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

771
        tmp.profile = profile;
42✔
772

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

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

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

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

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

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

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

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

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

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

829
        *ret = TAKE_STRUCT(tmp);
42✔
830
        return 0;
42✔
831
}
832

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

839
        _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
112✔
840
        _cleanup_free_ IMAGE_DOS_HEADER *dos_header = NULL;
×
841
        _cleanup_free_ PeHeader *pe_header = NULL;
56✔
842
        int r;
56✔
843

844
        assert(fd >= 0);
56✔
845
        assert(path);
56✔
846

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

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

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

860
        return 0;
861
}
862

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

869
        assert(pe_header);
98✔
870

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

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

877
        assert(sections);
98✔
878

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

886
        for (;;) {
350✔
887
                p = pe_section_table_find(p, e - p, ".profile");
224✔
888
                if (!p) {
224✔
889
                        end = e;
890
                        break;
891
                }
892
                if (current_profile == profile) {
196✔
893
                        end = p;
894
                        break;
895
                }
896

897
                if (current_profile == UINT_MAX)
126✔
898
                        current_profile = 0;
899
                else
900
                        current_profile++;
70✔
901

902
                if (current_profile == profile)
126✔
903
                        start = p;
42✔
904

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

908
        if (!start)
98✔
909
                return NULL;
910

911
        if (ret_n_sections)
84✔
912
                *ret_n_sections = end - start;
84✔
913

914
        return start;
915
}
916

917
static int trim_cmdline(char **cmdline) {
42✔
918
        assert(cmdline);
42✔
919

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

922
        if (!*cmdline)
42✔
923
                return 0;
924

925
        const char *skipped = skip_leading_chars(*cmdline, WHITESPACE);
42✔
926

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

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

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

940
        delete_trailing_chars(*cmdline, WHITESPACE);
42✔
941
        return 1;
42✔
942
}
943

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

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

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

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

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

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

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

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

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

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

997
        FOREACH_ELEMENT(t, table) {
168✔
998
                const IMAGE_SECTION_HEADER *found;
126✔
999

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

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

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

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

1020
        if (trim_cmdline(&cmdline_text) < 0)
42✔
1021
                return log_oom();
×
1022

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

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

1033
static int pe_find_addon_sections(
×
1034
                int fd,
1035
                const char *path,
1036
                char **ret_cmdline) {
1037

1038
        _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
×
1039
        _cleanup_free_ PeHeader *pe_header = NULL;
×
1040
        int r;
×
1041

1042
        assert(fd >= 0);
×
1043
        assert(path);
×
1044

1045
        r = pe_load_headers_and_sections(fd, path, &sections, &pe_header);
×
1046
        if (r < 0)
×
1047
                return r;
1048

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

1052
        /* Define early, before the gotos below */
1053
        _cleanup_free_ char *cmdline_text = NULL;
×
1054

1055
        if (!pe_is_native(pe_header))
×
1056
                goto nothing;
×
1057

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

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

1066
        if (trim_cmdline(&cmdline_text) < 0)
×
1067
                return log_oom();
×
1068

1069
        *ret_cmdline = TAKE_PTR(cmdline_text);
×
1070
        return 1;
×
1071

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

1077
static int insert_boot_entry_addon(
×
1078
                BootEntryAddons *addons,
1079
                char *location,
1080
                char *cmdline) {
1081

1082
        assert(addons);
×
1083

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

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

1092
        return 0;
×
1093
}
1094

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

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

1107
        assert(ret_addons);
86✔
1108
        assert(config);
86✔
1109

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

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

1120
                if (!dirent_is_file(de))
×
1121
                        continue;
×
1122

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

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

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

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

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

1145
                location = strdup(j);
×
1146
                if (!location)
×
1147
                        return log_oom();
×
1148

1149
                r = insert_boot_entry_addon(&addons, location, cmdline);
×
1150
                if (r < 0)
×
1151
                        return r;
1152

1153
                TAKE_PTR(location);
×
1154
                TAKE_PTR(cmdline);
×
1155
        }
1156

1157
        *ret_addons = TAKE_STRUCT(addons);
×
1158
        return 0;
×
1159
}
1160

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

1167
        int r;
44✔
1168
        _cleanup_closedir_ DIR *d = NULL;
44✔
1169

1170
        assert(ret_addons);
44✔
1171

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

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

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

1188
        _cleanup_free_ char *addon_dir = NULL;
42✔
1189

1190
        assert(ret);
42✔
1191

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

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

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

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

1209
        assert(config);
44✔
1210
        assert(dir);
44✔
1211

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

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

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

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

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

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

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

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

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

1253
                        BootEntry *entry = config->entries + config->n_entries;
42✔
1254

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

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

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

1264
                        config->n_entries++;
42✔
1265
                }
1266
        }
1267

1268
        return 0;
1269
}
1270

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

1274
        assert(entries || n_entries == 0);
47✔
1275
        assert(arr || n_entries == 0);
47✔
1276

1277
        for (size_t i = 0; i < n_entries; i++)
191✔
1278
                arr[i] = false;
144✔
1279

1280
        for (size_t i = 0; i < n_entries; i++)
191✔
1281
                for (size_t j = 0; j < n_entries; j++)
606✔
1282
                        if (i != j && streq(boot_entry_title(entries + i),
462✔
1283
                                            boot_entry_title(entries + j)))
1284
                                non_unique = arr[i] = arr[j] = true;
94✔
1285

1286
        return non_unique;
47✔
1287
}
1288

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

1293
        assert(entries || n_entries == 0);
30✔
1294

1295
        if (n_entries == 0)
30✔
1296
                return 0;
1297

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

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

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

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

1315
        if (!find_nonunique(entries, n_entries, arr))
16✔
1316
                return 0;
1317

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

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

1327
        if (!find_nonunique(entries, n_entries, arr))
15✔
1328
                return 0;
1329

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

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

1339
        return 0;
1340
}
1341

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

1345
        if (!id)
9✔
1346
                return -1;
1347

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

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

1360
        return -1;
1361
}
1362

1363
static int boot_entries_select_default(const BootConfig *config) {
28✔
1364
        int i;
28✔
1365

1366
        assert(config);
28✔
1367
        assert(config->entries || config->n_entries == 0);
28✔
1368

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

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

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

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

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

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

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

1412
        return boot_config_find(config, config->entry_selected);
6✔
1413
}
1414

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

1418
        assert(config);
28✔
1419

1420
        if (skip_efivars || !is_efi_boot())
28✔
1421
                return 0;
22✔
1422

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

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

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

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

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

1449
        return 1;
1450
}
1451

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

1455
        assert(config);
28✔
1456

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

1461
        config->default_entry = boot_entries_select_default(config);
28✔
1462
        config->selected_entry = boot_entries_select_selected(config);
28✔
1463

1464
        return 0;
28✔
1465
}
1466

1467
int boot_config_finalize(BootConfig *config) {
30✔
1468
        int r;
30✔
1469

1470
        typesafe_qsort(config->entries, config->n_entries, boot_entry_compare);
30✔
1471

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

1476
        return 0;
1477
}
1478

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

1484
        int r;
30✔
1485

1486
        assert(config);
30✔
1487

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

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

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

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

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

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

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

1522
        return boot_config_finalize(config);
30✔
1523
}
1524

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

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

1534
        assert(config);
×
1535

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

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

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

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

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

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

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

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

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

1585
        assert(config);
6✔
1586

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

1590
        STRV_FOREACH(i, found_by_loader) {
30✔
1591
                BootEntry *existing;
24✔
1592
                _cleanup_free_ char *c = NULL, *t = NULL, *p = NULL;
12✔
1593

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

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

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

1607
                STRV_FOREACH_PAIR(a, b, title_table)
90✔
1608
                        if (streq(*a, *i)) {
84✔
1609
                                t = strdup(*b);
6✔
1610
                                if (!t)
6✔
1611
                                        return log_oom();
×
1612
                                break;
1613
                        }
1614

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

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

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

1634
        return 0;
1635
}
1636

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

1641
        for (size_t j = 0; j < config->n_entries; j++)
91✔
1642
                if (strcaseeq_ptr(config->entries[j].id, id) ||
78✔
1643
                    strcaseeq_ptr(config->entries[j].id_old, id))
63✔
1644
                        return config->entries + j;
1645

1646
        return NULL;
1647
}
1648

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

1655
        assert(p);
15✔
1656
        assert(ret_status);
15✔
1657

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

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

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

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

1675
static void print_addon(
×
1676
                BootEntryAddon *addon,
1677
                const char *addon_str) {
1678

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

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

1687
        assert(ret_cmdline);
15✔
1688

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

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

1697
        *ret_cmdline = TAKE_PTR(t);
15✔
1698

1699
        return 0;
15✔
1700
}
1701

1702
static int print_cmdline(const BootEntry *e) {
15✔
1703

1704
        _cleanup_free_ char *options = NULL, *combined_cmdline = NULL, *t2 = NULL;
15✔
1705

1706
        assert(e);
15✔
1707

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

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

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

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

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

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

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

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

1744
        if (combined_cmdline)
×
1745
                printf("      cmdline: %s\n", combined_cmdline);
×
1746

1747
        return 0;
1748
}
1749

1750
static int json_addon(
×
1751
                BootEntryAddon *addon,
1752
                const char *addon_str,
1753
                sd_json_variant **array) {
1754

1755
        int r;
×
1756

1757
        assert(addon);
×
1758
        assert(addon_str);
×
1759

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

1767
        return 0;
1768
}
1769

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

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

1779
        assert(e);
9✔
1780

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

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

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

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

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

1818
        int status = 0, r = 0;
15✔
1819

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

1823
        assert(e);
15✔
1824

1825
        printf("         type: %s\n",
15✔
1826
               boot_entry_type_to_string(e->type));
15✔
1827

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

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

1835
        if (show_as_selected)
15✔
1836
                printf(" %s(selected)%s",
×
1837
                       ansi_highlight_magenta(), ansi_normal());
1838

1839
        if (show_reported) {
15✔
1840
                if (e->type == BOOT_ENTRY_LOADER)
6✔
1841
                        printf(" %s(reported/absent)%s",
×
1842
                               ansi_highlight_red(), ansi_normal());
1843
                else if (!e->reported_by_loader && e->type != BOOT_ENTRY_LOADER_AUTO)
6✔
1844
                        printf(" %s(not reported/new)%s",
12✔
1845
                               ansi_highlight_green(), ansi_normal());
1846
        }
1847

1848
        putchar('\n');
15✔
1849

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

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

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

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

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

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

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

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

1905
        r = print_cmdline(e);
15✔
1906
        if (r < 0)
15✔
1907
                return r;
1908

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

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

1918
        return -status;
15✔
1919
}
1920

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

1927
        assert(c);
9✔
1928
        assert(ret);
9✔
1929

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

1935
        e = c->entries + i;
9✔
1936

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

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

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

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

1982
        *ret = TAKE_PTR(v);
9✔
1983
        return 1;
9✔
1984
}
1985

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

1989
        assert(config);
8✔
1990

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

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

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

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

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

2017
                        if (n+1 < config->n_entries)
6✔
2018
                                putchar('\n');
4✔
2019
                }
2020

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