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

systemd / systemd / 25238955322

01 May 2026 10:09AM UTC coverage: 71.943% (-0.2%) from 72.134%
25238955322

push

github

bluca
po: Translated using Weblate (Greek)

Currently translated at 100.0% (266 of 266 strings)

Co-authored-by: Jim Spentzos <jimspentzos2000@gmail.com>
Translate-URL: https://translate.fedoraproject.org/projects/systemd/main/el/
Translation: systemd/main

324741 of 451384 relevant lines covered (71.94%)

1387736.3 hits per line

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

76.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 "errno-util.h"
18
#include "extract-word.h"
19
#include "fd-util.h"
20
#include "fileio.h"
21
#include "find-esp.h"
22
#include "json-util.h"
23
#include "log.h"
24
#include "parse-util.h"
25
#include "path-util.h"
26
#include "pe-binary.h"
27
#include "pretty-print.h"
28
#include "recurse-dir.h"
29
#include "set.h"
30
#include "sort-util.h"
31
#include "stat-util.h"
32
#include "string-table.h"
33
#include "string-util.h"
34
#include "strv.h"
35
#include "uki.h"
36
#include "utf8.h"
37

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

45
DEFINE_STRING_TABLE_LOOKUP_TO_STRING(boot_entry_type_description, BootEntryType);
20✔
46

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

54
DEFINE_STRING_TABLE_LOOKUP(boot_entry_type, BootEntryType);
27✔
55

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

61
DEFINE_STRING_TABLE_LOOKUP_TO_STRING(boot_entry_source_description, BootEntrySource);
20✔
62

63
static const char* const boot_entry_source_table[_BOOT_ENTRY_SOURCE_MAX] = {
64
        [BOOT_ENTRY_ESP]      = "esp",
65
        [BOOT_ENTRY_XBOOTLDR] = "xbootldr",
66
};
67

68
DEFINE_STRING_TABLE_LOOKUP_TO_STRING(boot_entry_source, BootEntrySource);
21✔
69

70
static BootEntryExtraType boot_entry_extra_type_from_filename(const char *path) {
13✔
71
        if (!path)
13✔
72
                return _BOOT_ENTRY_EXTRA_TYPE_INVALID;
73

74
        if (endswith_no_case(path, ".addon.efi"))
13✔
75
                return BOOT_ENTRY_ADDON;
76
        if (endswith_no_case(path, ".confext.raw"))
3✔
77
                return BOOT_ENTRY_CONFEXT;
78
        if (endswith_no_case(path, ".sysext.raw"))
2✔
79
                return BOOT_ENTRY_SYSEXT;
80
        if (endswith_no_case(path, ".cred"))
1✔
81
                return BOOT_ENTRY_CREDENTIAL;
1✔
82

83
        return _BOOT_ENTRY_EXTRA_TYPE_INVALID;
84
}
85

86
static void boot_entry_extras_done(BootEntryExtras *extras) {
274✔
87
        assert(extras);
274✔
88

89
        FOREACH_ARRAY(extra, extras->items, extras->n_items) {
287✔
90
                free(extra->location);
13✔
91
                free(extra->cmdline);
13✔
92
        }
93
        extras->items = mfree(extras->items);
274✔
94
        extras->n_items = 0;
274✔
95
}
274✔
96

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

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

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

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

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

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

128
        return 0;
13✔
129
}
130

131
static void boot_entry_free(BootEntry *entry) {
188✔
132
        assert(entry);
188✔
133

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

156
static int mangle_path(
15✔
157
                const char *fname,
158
                unsigned line,
159
                const char *field,
160
                const char *p,
161
                char **ret) {
162

163
        _cleanup_free_ char *c = NULL;
15✔
164

165
        assert(field);
15✔
166
        assert(p);
15✔
167
        assert(ret);
15✔
168

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

174
        /* We only reference files, never directories */
175
        if (endswith(c, "/")) {
15✔
176
                log_syntax(NULL, LOG_WARNING, fname, line, 0, "Path in field '%s' has trailing slash, ignoring: %s", field, c);
×
177
                *ret = NULL;
×
178
                return 0;
×
179
        }
180

181
        /* Remove duplicate "/" */
182
        path_simplify(c);
15✔
183

184
        /* No ".." or "." or so */
185
        if (!path_is_normalized(c)) {
15✔
186
                log_syntax(NULL, LOG_WARNING, fname, line, 0, "Path in field '%s' is not normalized, ignoring: %s", field, c);
×
187
                *ret = NULL;
×
188
                return 0;
×
189
        }
190

191
        *ret = TAKE_PTR(c);
15✔
192
        return 1;
15✔
193
}
194

195
static int parse_path_one(
12✔
196
                const char *fname,
197
                unsigned line,
198
                const char *field,
199
                char **s,
200
                const char *p) {
201

202
        _cleanup_free_ char *c = NULL;
12✔
203
        int r;
12✔
204

205
        assert(field);
12✔
206
        assert(s);
12✔
207
        assert(p);
12✔
208

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

213
        return free_and_replace(*s, c);
12✔
214
}
215

216
static int parse_path_strv(
×
217
                const char *fname,
218
                unsigned line,
219
                const char *field,
220
                char ***s,
221
                const char *p) {
222

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

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

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

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

237
static int parse_path_many(
×
238
                const char *fname,
239
                unsigned line,
240
                const char *field,
241
                char ***s,
242
                const char *p) {
243

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

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

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

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

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

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

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

275
        int r;
3✔
276

277
        assert(extras);
3✔
278

279
        _cleanup_strv_free_ char **l = strv_split(p, NULL);
6✔
280
        if (!l)
3✔
281
                return -ENOMEM;
282

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

291
                BootEntryExtraType type = boot_entry_extra_type_from_filename(c);
3✔
292
                if (type < 0) {
3✔
293
                        log_debug_errno(type, "Failed to determine boot entry extra type of '%s', skipping: %m", c);
×
294
                        continue;
×
295
                }
296

297
                /* Let's filter out EFI addons for now. We have no protocol for passing them from sd-boot to
298
                 * sd-stub, hence supporting them would require major plumbing first. */
299
                if (type == BOOT_ENTRY_ADDON) {
3✔
300
                        log_debug("EFI addons are currently not supported for Type #1 entries, skipping '%s'.", c);
×
301
                        continue;
×
302
                }
303

304
                r = boot_entry_extras_add(extras, type, c, /* cmdline= */ NULL);
3✔
305
                if (r < 0)
3✔
306
                        return r;
307
        }
308

309
        return 0;
310
}
311

312
static int parse_tries(const char *fname, const char **p, unsigned *ret) {
41✔
313
        _cleanup_free_ char *d = NULL;
41✔
314
        unsigned tries;
41✔
315
        size_t n;
41✔
316
        int r;
41✔
317

318
        assert(fname);
41✔
319
        assert(p);
41✔
320
        assert(*p);
41✔
321
        assert(ret);
41✔
322

323
        n = strspn(*p, DIGITS);
41✔
324
        if (n == 0) {
41✔
325
                *ret = UINT_MAX;
2✔
326
                return 0;
2✔
327
        }
328

329
        d = strndup(*p, n);
39✔
330
        if (!d)
39✔
331
                return -ENOMEM;
332

333
        r = safe_atou_full(d, 10, &tries);
39✔
334
        if (r < 0)
39✔
335
                return r;
336
        if (tries > INT_MAX) /* sd-boot allows INT_MAX, let's use the same limit */
39✔
337
                return -ERANGE;
338

339
        *p = *p + n;
37✔
340
        *ret = tries;
37✔
341
        return 1;
37✔
342
}
343

344
int boot_filename_extract_tries(
108✔
345
                const char *fname,
346
                char **ret_stripped,
347
                unsigned *ret_tries_left,
348
                unsigned *ret_tries_done) {
349

350
        unsigned tries_left = UINT_MAX, tries_done = UINT_MAX;
108✔
351
        _cleanup_free_ char *stripped = NULL;
108✔
352
        const char *p, *suffix, *m;
108✔
353
        int r;
108✔
354

355
        assert(fname);
108✔
356
        assert(ret_stripped);
108✔
357

358
        /* Be liberal with suffix, only insist on a dot. After all we want to cover any capitalization here
359
         * (vfat is case insensitive after all), and at least .efi and .conf as suffix. */
360
        suffix = strrchr(fname, '.');
108✔
361
        if (!suffix)
108✔
362
                goto nothing;
1✔
363

364
        p = m = memrchr(fname, '+', suffix - fname);
107✔
365
        if (!p)
107✔
366
                goto nothing;
79✔
367
        p++;
28✔
368

369
        r = parse_tries(fname, &p, &tries_left);
28✔
370
        if (r < 0)
28✔
371
                return r;
372
        if (r == 0)
27✔
373
                goto nothing;
2✔
374

375
        if (*p == '-') {
25✔
376
                p++;
13✔
377

378
                r = parse_tries(fname, &p, &tries_done);
13✔
379
                if (r < 0)
13✔
380
                        return r;
381
                if (r == 0)
12✔
382
                        goto nothing;
×
383
        }
384

385
        if (p != suffix)
24✔
386
                goto nothing;
3✔
387

388
        stripped = strndup(fname, m - fname);
21✔
389
        if (!stripped)
21✔
390
                return -ENOMEM;
391

392
        if (!strextend(&stripped, suffix))
21✔
393
                return -ENOMEM;
394

395
        *ret_stripped = TAKE_PTR(stripped);
21✔
396
        if (ret_tries_left)
21✔
397
                *ret_tries_left = tries_left;
17✔
398
        if (ret_tries_done)
21✔
399
                *ret_tries_done = tries_done;
17✔
400

401
        return 0;
402

403
nothing:
85✔
404
        stripped = strdup(fname);
85✔
405
        if (!stripped)
85✔
406
                return -ENOMEM;
407

408
        *ret_stripped = TAKE_PTR(stripped);
85✔
409
        if (ret_tries_left)
85✔
410
                *ret_tries_left = UINT_MAX;
60✔
411
        if (ret_tries_done)
85✔
412
                *ret_tries_done = UINT_MAX;
60✔
413
        return 0;
414
}
415

416
static int boot_entry_load_type1(
20✔
417
                FILE *f,
418
                const char *root,
419
                const BootEntrySource source,
420
                const char *dir,
421
                const char *fname,
422
                BootEntry *ret) {
423

424
        _cleanup_(boot_entry_free) BootEntry tmp = BOOT_ENTRY_INIT(BOOT_ENTRY_TYPE1, source);
20✔
425
        char *c;
20✔
426
        int r;
20✔
427

428
        assert(f);
20✔
429
        assert(root);
20✔
430
        assert(dir);
20✔
431
        assert(fname);
20✔
432
        assert(ret);
20✔
433

434
        /* Loads a Type #1 boot menu entry from the specified FILE* object */
435

436
        r = boot_filename_extract_tries(fname, &tmp.id, &tmp.tries_left, &tmp.tries_done);
20✔
437
        if (r < 0)
20✔
438
                return log_error_errno(r, "Failed to extract tries counters from '%s': %m", fname);
×
439

440
        if (!efi_loader_entry_name_valid(tmp.id))
20✔
441
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry name: %s", fname);
×
442

443
        c = endswith_no_case(tmp.id, ".conf");
20✔
444
        if (!c)
20✔
445
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry file suffix: %s", fname);
×
446

447
        tmp.id_old = strndup(tmp.id, c - tmp.id); /* Without .conf suffix */
20✔
448
        if (!tmp.id_old)
20✔
449
                return log_oom();
×
450

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

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

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

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

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

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

484
                if (isempty(p)) {
90✔
485
                        /* Some fields can reasonably have an empty value. In other cases warn. */
486
                        if (!STR_IN_SET(field, "options", "devicetree-overlay"))
×
487
                                log_syntax(NULL, LOG_WARNING, tmp.path, line, 0, "Field '%s' without value, ignoring line.", field);
×
488

489
                        continue;
×
490
                }
491

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

530
        *ret = TAKE_STRUCT(tmp);
20✔
531
        return 0;
20✔
532
}
533

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

543
        assert(config);
20✔
544
        assert(f);
20✔
545
        assert(root);
20✔
546
        assert(dir);
20✔
547
        assert(filename);
20✔
548

549
        if (!GREEDY_REALLOC(config->entries, config->n_entries + 1))
20✔
550
                return log_oom();
×
551

552
        BootEntry *entry = config->entries + config->n_entries;
20✔
553

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

559
        entry->global_extras = &config->global_extras[source];
20✔
560

561
        return 0;
20✔
562
}
563

564
void boot_config_free(BootConfig *config) {
43✔
565
        assert(config);
43✔
566

567
        free(config->preferred_pattern);
43✔
568
        free(config->default_pattern);
43✔
569

570
        free(config->entry_oneshot);
43✔
571
        free(config->entry_preferred);
43✔
572
        free(config->entry_default);
43✔
573
        free(config->entry_selected);
43✔
574
        free(config->entry_sysfail);
43✔
575

576
        FOREACH_ARRAY(i, config->entries, config->n_entries)
181✔
577
                boot_entry_free(i);
138✔
578
        free(config->entries);
43✔
579

580
        FOREACH_ELEMENT(i, config->global_extras)
129✔
581
                boot_entry_extras_done(i);
86✔
582

583
        set_free(config->inodes_seen);
43✔
584
}
43✔
585

586
int boot_loader_read_conf(BootConfig *config, FILE *file, const char *path) {
41✔
587
        int r;
41✔
588

589
        assert(config);
41✔
590
        assert(file);
41✔
591
        assert(path);
41✔
592

593
        for (unsigned line = 1;; line++) {
92✔
594
                _cleanup_free_ char *buf = NULL, *field = NULL;
92✔
595

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

604
                if (IN_SET(buf[0], '#', '\0'))
92✔
605
                        continue;
82✔
606

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

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

639
        return 1;
41✔
640
}
641

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

647
        assert(config);
43✔
648
        assert(path);
43✔
649

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

657
        return boot_loader_read_conf(config, f, full);
41✔
658
}
659

660
static unsigned boot_entry_profile(const BootEntry *a) {
40✔
661
        assert(a);
40✔
662

663
        return a->profile == UINT_MAX ? 0 : a->profile;
40✔
664
}
665

666
static int boot_entry_compare(const BootEntry *a, const BootEntry *b) {
32✔
667
        int r;
32✔
668

669
        assert(a);
32✔
670
        assert(b);
32✔
671

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

674
        r = CMP(a->tries_left == 0, b->tries_left == 0);
32✔
675
        if (r != 0)
32✔
676
                return r;
×
677

678
        r = CMP(!a->sort_key, !b->sort_key);
32✔
679
        if (r != 0)
29✔
680
                return r;
3✔
681

682
        if (a->sort_key && b->sort_key) {
29✔
683
                r = strcmp(a->sort_key, b->sort_key);
26✔
684
                if (r != 0)
26✔
685
                        return r;
686

687
                r = strcmp_ptr(a->machine_id, b->machine_id);
25✔
688
                if (r != 0)
25✔
689
                        return r;
690

691
                r = -strverscmp_improved(a->version, b->version);
25✔
692
                if (r != 0)
25✔
693
                        return r;
694

695
                r = CMP(boot_entry_profile(a), boot_entry_profile(b));
20✔
696
                if (r != 0)
×
697
                        return r;
20✔
698
        }
699

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

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

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

715
        r = -CMP(a->tries_left, b->tries_left);
×
716
        if (r != 0)
×
717
                return r;
×
718

719
        return CMP(a->tries_done, b->tries_done);
×
720
}
721

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

726
        assert(config);
40✔
727
        assert(fd >= 0);
40✔
728
        assert(fname);
40✔
729

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

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

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

748
        d = memdup(&st, sizeof(st));
40✔
749
        if (!d)
40✔
750
                return log_oom();
×
751

752
        if (set_ensure_consume(&config->inodes_seen, &inode_hash_ops, TAKE_PTR(d)) < 0)
40✔
753
                return log_oom();
×
754

755
        return true;
756
}
757

758
static int boot_entries_find_type1(
57✔
759
                BootConfig *config,
760
                const char *root,
761
                const BootEntrySource source,
762
                const char *dir) {
763

764
        _cleanup_free_ DirectoryEntries *dentries = NULL;
114✔
765
        _cleanup_free_ char *full = NULL;
57✔
766
        _cleanup_close_ int dir_fd = -EBADF;
57✔
767
        int r;
57✔
768

769
        assert(config);
57✔
770
        assert(root);
57✔
771
        assert(dir);
57✔
772

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

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

783
        FOREACH_ARRAY(i, dentries->entries, dentries->n_entries) {
63✔
784
                const struct dirent *de = *i;
20✔
785
                _cleanup_fclose_ FILE *f = NULL;
20✔
786

787
                if (!dirent_is_file(de))
20✔
788
                        continue;
×
789

790
                if (!endswith_no_case(de->d_name, ".conf"))
20✔
791
                        continue;
×
792

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

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

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

810
        return 0;
811
}
812

813
static void mangle_osrelease_string(char **s, const char *field) {
304✔
814
        assert(s);
304✔
815
        assert(field);
304✔
816

817
        if (!isempty(*s) && !string_has_cc(*s, /* ok= */ NULL) && utf8_is_valid(*s))
448✔
818
                return;
819

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

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

836
        int r;
38✔
837

838
        assert(text);
38✔
839

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

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

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

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

886
        if (ret_good_version && good_version) {
38✔
887
                copy_good_version = strdup(good_version);
30✔
888
                if (!copy_good_version)
30✔
889
                        return -ENOMEM;
890
        }
891

892
        if (ret_good_sort_key && good_sort_key) {
38✔
893
                copy_good_sort_key = strdup(good_sort_key);
38✔
894
                if (!copy_good_sort_key)
38✔
895
                        return -ENOMEM;
896
        }
897

898
        if (ret_good_name)
38✔
899
                *ret_good_name = TAKE_PTR(copy_good_name);
38✔
900
        if (ret_good_version)
38✔
901
                *ret_good_version = TAKE_PTR(copy_good_version);
30✔
902
        if (ret_good_sort_key)
38✔
903
                *ret_good_sort_key = TAKE_PTR(copy_good_sort_key);
38✔
904

905
        if (ret_os_id)
38✔
906
                *ret_os_id = TAKE_PTR(os_id);
30✔
907
        if (ret_os_version_id)
38✔
908
                *ret_os_version_id = TAKE_PTR(os_version_id);
38✔
909
        if (ret_image_id)
38✔
910
                *ret_image_id = TAKE_PTR(image_id);
×
911
        if (ret_image_version)
38✔
912
                *ret_image_version = TAKE_PTR(image_version);
8✔
913

914
        return 0;
915
}
916

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

927
        int r;
30✔
928

929
        assert(root);
30✔
930
        assert(path);
30✔
931
        assert(osrelease_text);
30✔
932
        assert(ret);
30✔
933

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

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

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

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

967
        _cleanup_(boot_entry_free) BootEntry tmp = BOOT_ENTRY_INIT(BOOT_ENTRY_TYPE2, source);
30✔
968

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

973
        if (!efi_loader_entry_name_valid(tmp.id))
30✔
974
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry name: %s", tmp.id);
×
975

976
        tmp.profile = profile;
30✔
977

978
        if (profile_id || profile > 0) {
30✔
979
                tmp.id_without_profile = TAKE_PTR(tmp.id);
30✔
980

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

989
        if (os_id && os_version_id) {
30✔
990
                tmp.id_old = strjoin(os_id, "-", os_version_id);
×
991
                if (!tmp.id_old)
×
992
                        return log_oom();
×
993
        }
994

995
        tmp.path = strdup(path);
30✔
996
        if (!tmp.path)
30✔
997
                return log_oom();
×
998

999
        tmp.root = strdup(root);
30✔
1000
        if (!tmp.root)
30✔
1001
                return log_oom();
×
1002

1003
        tmp.kernel = path_make_absolute(k, "/");
30✔
1004
        if (!tmp.kernel)
30✔
1005
                return log_oom();
×
1006

1007
        tmp.options = strv_new(cmdline_text);
30✔
1008
        if (!tmp.options)
30✔
1009
                return log_oom();
×
1010

1011
        if (profile_title)
30✔
1012
                tmp.title = strjoin(good_name, " (", profile_title, ")");
20✔
1013
        else if (profile_id)
10✔
1014
                tmp.title = strjoin(good_name, " (", profile_id, ")");
10✔
1015
        else if (profile > 0)
×
1016
                (void) asprintf(&tmp.title, "%s (@%u)", good_name, profile);
×
1017
        else
1018
                tmp.title = strdup(good_name);
×
1019
        if (!tmp.title)
30✔
1020
                return log_oom();
×
1021

1022
        if (good_sort_key) {
30✔
1023
                tmp.sort_key = strdup(good_sort_key);
30✔
1024
                if (!tmp.sort_key)
30✔
1025
                        return log_oom();
×
1026
        }
1027

1028
        if (good_version) {
30✔
1029
                tmp.version = strdup(good_version);
30✔
1030
                if (!tmp.version)
30✔
1031
                        return log_oom();
×
1032
        }
1033

1034
        *ret = TAKE_STRUCT(tmp);
30✔
1035
        return 0;
30✔
1036
}
1037

1038
static int pe_load_headers_and_sections(
65✔
1039
                int fd,
1040
                const char *path,
1041
                IMAGE_SECTION_HEADER **ret_sections,
1042
                PeHeader **ret_pe_header) {
1043

1044
        _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
130✔
1045
        _cleanup_free_ IMAGE_DOS_HEADER *dos_header = NULL;
×
1046
        _cleanup_free_ PeHeader *pe_header = NULL;
65✔
1047
        int r;
65✔
1048

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

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

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

1060
        if (ret_pe_header)
65✔
1061
                *ret_pe_header = TAKE_PTR(pe_header);
65✔
1062
        if (ret_sections)
65✔
1063
                *ret_sections = TAKE_PTR(sections);
65✔
1064

1065
        return 0;
1066
}
1067

1068
static const IMAGE_SECTION_HEADER* pe_find_profile_section_table(
93✔
1069
                const PeHeader *pe_header,
1070
                const IMAGE_SECTION_HEADER *sections,
1071
                unsigned profile,
1072
                size_t *ret_n_sections) {
1073

1074
        assert(pe_header);
93✔
1075

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

1079
        if (le16toh(pe_header->pe.NumberOfSections) == 0)
93✔
1080
                return NULL;
1081

1082
        assert(sections);
93✔
1083

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

1091
        for (;;) {
273✔
1092
                p = pe_section_table_find(p, e - p, ".profile");
183✔
1093
                if (!p) {
183✔
1094
                        end = e;
1095
                        break;
1096
                }
1097
                if (current_profile == profile) {
140✔
1098
                        end = p;
1099
                        break;
1100
                }
1101

1102
                if (current_profile == UINT_MAX)
90✔
1103
                        current_profile = 0;
1104
                else
1105
                        current_profile++;
50✔
1106

1107
                if (current_profile == profile)
90✔
1108
                        start = p;
30✔
1109

1110
                p++; /* Continue scanning after the .profile entry we just found */
90✔
1111
        }
1112

1113
        if (!start)
93✔
1114
                return NULL;
1115

1116
        if (ret_n_sections)
68✔
1117
                *ret_n_sections = end - start;
68✔
1118

1119
        return start;
1120
}
1121

1122
static int trim_cmdline(char **cmdline) {
48✔
1123
        assert(cmdline);
48✔
1124

1125
        /* Strips leading and trailing whitespace from command line */
1126

1127
        if (!*cmdline)
48✔
1128
                return 0;
1129

1130
        const char *skipped = skip_leading_chars(*cmdline, WHITESPACE);
48✔
1131

1132
        if (isempty(skipped)) {
48✔
1133
                *cmdline = mfree(*cmdline);
×
1134
                return 0;
×
1135
        }
1136

1137
        if (skipped != *cmdline) {
48✔
1138
                _cleanup_free_ char *c = strdup(skipped);
×
1139
                if (!c)
×
1140
                        return -ENOMEM;
×
1141

1142
                free_and_replace(*cmdline, c);
×
1143
        }
1144

1145
        delete_trailing_chars(*cmdline, WHITESPACE);
48✔
1146
        return 1;
48✔
1147
}
1148

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

1153
int pe_find_uki_sections(
55✔
1154
                int fd,
1155
                const char *path,
1156
                unsigned profile,
1157
                char **ret_osrelease,
1158
                char **ret_profile,
1159
                char **ret_cmdline) {
1160

1161
        _cleanup_free_ char *osrelease_text = NULL, *profile_text = NULL, *cmdline_text = NULL;
×
1162
        _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
×
1163
        _cleanup_free_ PeHeader *pe_header = NULL;
55✔
1164
        int r;
55✔
1165

1166
        assert(fd >= 0);
55✔
1167
        assert(path);
55✔
1168
        assert(profile != UINT_MAX);
55✔
1169

1170
        r = pe_load_headers_and_sections(fd, path, &sections, &pe_header);
55✔
1171
        if (r < 0)
55✔
1172
                return r;
1173

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

1177
        if (!pe_is_native(pe_header)) /* Don't process non-native UKIs */
55✔
1178
                goto nothing;
×
1179

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

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

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

1199
        FOREACH_ELEMENT(t, table) {
152✔
1200
                const IMAGE_SECTION_HEADER *found;
114✔
1201

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

1210
                /* Permit "masking" of sections in the base profile */
1211
                if (le32toh(found->VirtualSize) == 0)
106✔
1212
                        continue;
×
1213

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

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

1222
        if (trim_cmdline(&cmdline_text) < 0)
38✔
1223
                return log_oom();
×
1224

1225
        if (ret_osrelease)
38✔
1226
                *ret_osrelease = TAKE_PTR(osrelease_text);
38✔
1227
        if (ret_profile)
38✔
1228
                *ret_profile = TAKE_PTR(profile_text);
38✔
1229
        if (ret_cmdline)
38✔
1230
                *ret_cmdline = TAKE_PTR(cmdline_text);
30✔
1231
        return 1;
1232

1233
nothing:
17✔
1234
        if (ret_osrelease)
17✔
1235
                *ret_osrelease = NULL;
17✔
1236
        if (ret_profile)
17✔
1237
                *ret_profile = NULL;
17✔
1238
        if (ret_cmdline)
17✔
1239
                *ret_cmdline = NULL;
10✔
1240

1241
        return 0;
1242
}
1243

1244
static int pe_find_addon_sections(
10✔
1245
                int fd,
1246
                const char *path,
1247
                char **ret_cmdline) {
1248

1249
        _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
20✔
1250
        _cleanup_free_ PeHeader *pe_header = NULL;
10✔
1251
        int r;
10✔
1252

1253
        assert(fd >= 0);
10✔
1254
        assert(path);
10✔
1255
        assert(ret_cmdline);
10✔
1256

1257
        r = pe_load_headers_and_sections(fd, path, &sections, &pe_header);
10✔
1258
        if (r < 0)
10✔
1259
                return r;
1260

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

1264
        /* Define early, before the gotos below */
1265
        _cleanup_free_ char *cmdline_text = NULL;
10✔
1266

1267
        if (!pe_is_native(pe_header))
10✔
1268
                goto nothing;
×
1269

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

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

1278
        if (trim_cmdline(&cmdline_text) < 0)
10✔
1279
                return log_oom();
×
1280

1281
        *ret_cmdline = TAKE_PTR(cmdline_text);
10✔
1282
        return 1;
10✔
1283

1284
nothing:
×
1285
        *ret_cmdline = NULL;
×
1286
        return 0;
×
1287
}
1288

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

1298
        int r;
258✔
1299

1300
        assert(config);
258✔
1301
        assert(extras);
258✔
1302

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

1316
        FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read '%s': %m", extra_dir)) {
40✔
1317
                if (!dirent_is_file(de))
10✔
1318
                        continue;
×
1319

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

1330
                _cleanup_free_ char *location = path_join(extra_dir, de->d_name);
20✔
1331
                if (!location)
10✔
1332
                        return log_oom();
×
1333

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

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

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

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

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

1367
                r = boot_entry_extras_add(extras, type, location, cmdline);
10✔
1368
                if (r < 0)
10✔
1369
                        return r;
×
1370
        }
1371

1372
        return 0;
1373
}
1374

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

1382
        assert(extras);
228✔
1383

1384
        _cleanup_close_ int where_fd = RET_NERRNO(open(where, O_DIRECTORY|O_CLOEXEC));
456✔
1385
        if (where_fd == -ENOENT)
×
1386
                return 0;
1387
        if (where_fd < 0)
228✔
1388
                return log_error_errno(where_fd, "Failed to open '%s': %m", where);
×
1389

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

1400
static int boot_entries_find_unified_local_extras(
30✔
1401
                BootConfig *config,
1402
                int d_fd,
1403
                const char *uki,
1404
                const char *where,
1405
                BootEntry *ret) {
1406

1407
        _cleanup_free_ char *extra_dir = NULL;
30✔
1408

1409
        assert(ret);
30✔
1410

1411
        extra_dir = strjoin(uki, ".extra.d");
30✔
1412
        if (!extra_dir)
30✔
1413
                return log_oom();
×
1414

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

1425
static int boot_entries_find_unified(
57✔
1426
                BootConfig *config,
1427
                const char *root,
1428
                BootEntrySource source,
1429
                const char *dir) {
1430

1431
        _cleanup_closedir_ DIR *d = NULL;
57✔
1432
        _cleanup_free_ char *full = NULL;
57✔
1433
        int r;
57✔
1434

1435
        assert(config);
57✔
1436
        assert(dir);
57✔
1437

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

1444
        FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read %s: %m", full)) {
133✔
1445
                if (!dirent_is_file(de))
10✔
1446
                        continue;
×
1447

1448
                if (!endswith_no_case(de->d_name, ".efi"))
10✔
1449
                        continue;
×
1450

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

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

1463
                _cleanup_free_ char *j = path_join(full, de->d_name);
20✔
1464
                if (!j)
10✔
1465
                        return log_oom();
×
1466

1467
                for (unsigned p = 0; p < UNIFIED_PROFILES_MAX; p++) {
40✔
1468
                        _cleanup_free_ char *osrelease = NULL, *profile = NULL, *cmdline = NULL;
40✔
1469

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

1476
                        if (!GREEDY_REALLOC(config->entries, config->n_entries + 1))
30✔
1477
                                return log_oom();
×
1478

1479
                        BootEntry *entry = config->entries + config->n_entries;
30✔
1480

1481
                        if (boot_entry_load_unified(root, source, j, p, osrelease, profile, cmdline, entry) < 0)
30✔
1482
                                continue;
×
1483

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

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

1490
                        config->n_entries++;
30✔
1491
                }
1492
        }
1493

1494
        return 0;
1495
}
1496

1497
static bool find_nonunique(const BootEntry *entries, size_t n_entries, bool arr[]) {
26✔
1498
        bool non_unique = false;
26✔
1499

1500
        assert(entries || n_entries == 0);
26✔
1501
        assert(arr || n_entries == 0);
26✔
1502

1503
        for (size_t i = 0; i < n_entries; i++)
91✔
1504
                arr[i] = false;
65✔
1505

1506
        for (size_t i = 0; i < n_entries; i++)
91✔
1507
                for (size_t j = 0; j < n_entries; j++)
272✔
1508
                        if (i != j && streq(boot_entry_title(entries + i),
207✔
1509
                                            boot_entry_title(entries + j)))
1510
                                non_unique = arr[i] = arr[j] = true;
18✔
1511

1512
        return non_unique;
26✔
1513
}
1514

1515
static int boot_entries_uniquify(BootEntry *entries, size_t n_entries) {
43✔
1516
        _cleanup_free_ bool *arr = NULL;
43✔
1517
        char *s;
43✔
1518

1519
        assert(entries || n_entries == 0);
43✔
1520

1521
        if (n_entries == 0)
43✔
1522
                return 0;
1523

1524
        arr = new(bool, n_entries);
21✔
1525
        if (!arr)
21✔
1526
                return -ENOMEM;
1527

1528
        /* Find _all_ non-unique titles */
1529
        if (!find_nonunique(entries, n_entries, arr))
21✔
1530
                return 0;
1531

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

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

1541
        if (!find_nonunique(entries, n_entries, arr))
4✔
1542
                return 0;
1543

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

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

1553
        if (!find_nonunique(entries, n_entries, arr))
1✔
1554
                return 0;
1555

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

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

1565
        return 0;
1566
}
1567

1568
static int boot_config_find(const BootConfig *config, const char *id) {
40✔
1569
        assert(config);
40✔
1570

1571
        if (!id)
40✔
1572
                return -1;
1573

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

1582
        for (size_t i = 0; i < config->n_entries; i++)
93✔
1583
                if (fnmatch(id, config->entries[i].id, FNM_CASEFOLD) == 0)
93✔
1584
                        return i;
40✔
1585

1586
        return -1;
1587
}
1588

1589
static int boot_entries_select_default(const BootConfig *config) {
41✔
1590
        int i;
41✔
1591

1592
        assert(config);
41✔
1593
        assert(config->entries || config->n_entries == 0);
41✔
1594

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

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

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

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

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

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

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

1649
static int boot_entries_select_selected(const BootConfig *config) {
41✔
1650
        assert(config);
41✔
1651
        assert(config->entries || config->n_entries == 0);
41✔
1652

1653
        if (!config->entry_selected || config->n_entries == 0)
41✔
1654
                return -1;
1655

1656
        return boot_config_find(config, config->entry_selected);
27✔
1657
}
1658

1659
static int boot_load_efi_entry_pointers(BootConfig *config, bool skip_efivars) {
41✔
1660
        int r;
41✔
1661

1662
        assert(config);
41✔
1663

1664
        if (skip_efivars || !is_efi_boot())
41✔
1665
                return 0;
14✔
1666

1667
        /* Loads the three "pointers" to boot loader entries from their EFI variables */
1668

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

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

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

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

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

1699
        return 1;
1700
}
1701

1702
int boot_config_select_special_entries(BootConfig *config, bool skip_efivars) {
41✔
1703
        int r;
41✔
1704

1705
        assert(config);
41✔
1706

1707
        r = boot_load_efi_entry_pointers(config, skip_efivars);
41✔
1708
        if (r < 0)
41✔
1709
                return r;
1710

1711
        config->default_entry = boot_entries_select_default(config);
41✔
1712
        config->selected_entry = boot_entries_select_selected(config);
41✔
1713

1714
        return 0;
41✔
1715
}
1716

1717
int boot_config_finalize(BootConfig *config) {
43✔
1718
        int r;
43✔
1719

1720
        typesafe_qsort(config->entries, config->n_entries, boot_entry_compare);
43✔
1721

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

1726
        return 0;
1727
}
1728

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

1734
        int r;
57✔
1735

1736
        assert(config);
57✔
1737
        assert(source >= 0);
57✔
1738
        assert(source < _BOOT_ENTRY_SOURCE_MAX);
57✔
1739

1740
        if (!where)
57✔
1741
                return 0;
1742

1743
        r = boot_entries_find_type1(config, where, source, "/loader/entries");
57✔
1744
        if (r < 0)
57✔
1745
                return r;
1746

1747
        r = boot_entries_find_unified(config, where, source, "/EFI/Linux/");
57✔
1748
        if (r < 0)
57✔
1749
                return r;
1750

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

1761
        FOREACH_ELEMENT(i, table) {
285✔
1762
                r = boot_entries_find_unified_global_extras(
456✔
1763
                                config,
1764
                                where,
1765
                                i->directory,
228✔
1766
                                i->extra_type,
228✔
1767
                                &config->global_extras[source]);
1768
                if (r < 0)
228✔
1769
                        return r;
1770
        }
1771

1772
        return 0;
1773
}
1774

1775
int boot_config_load(
43✔
1776
                BootConfig *config,
1777
                const char *esp_path,
1778
                const char *xbootldr_path) {
1779

1780
        int r;
43✔
1781

1782
        assert(config);
43✔
1783

1784
        if (esp_path) {
43✔
1785
                r = boot_loader_read_conf_path(config, esp_path, "/loader/loader.conf");
43✔
1786
                if (r < 0)
43✔
1787
                        return r;
1788

1789
                r = boot_entries_load(config, BOOT_ENTRY_ESP, esp_path);
43✔
1790
                if (r < 0)
43✔
1791
                        return r;
1792
        }
1793

1794
        if (xbootldr_path) {
43✔
1795
                r = boot_entries_load(config, BOOT_ENTRY_XBOOTLDR, xbootldr_path);
14✔
1796
                if (r < 0)
14✔
1797
                        return r;
1798
        }
1799

1800
        return boot_config_finalize(config);
43✔
1801
}
1802

1803
int boot_config_load_auto(
×
1804
                BootConfig *config,
1805
                const char *override_esp_path,
1806
                const char *override_xbootldr_path) {
1807

1808
        _cleanup_free_ char *esp_where = NULL, *xbootldr_where = NULL;
×
1809
        dev_t esp_devid = 0, xbootldr_devid = 0;
×
1810
        int r;
×
1811

1812
        assert(config);
×
1813

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

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

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

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

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

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

1859
        return boot_config_load(config, esp_where, xbootldr_where);
×
1860
}
1861

1862
int boot_config_augment_from_loader(
27✔
1863
                BootConfig *config,
1864
                char **found_by_loader,
1865
                bool auto_only) {
1866

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

1880
        assert(config);
27✔
1881

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

1885
        STRV_FOREACH(i, found_by_loader) {
135✔
1886
                BootEntry *existing;
108✔
1887
                _cleanup_free_ char *c = NULL, *t = NULL, *p = NULL;
88✔
1888

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

1895
                if (auto_only && !startswith(*i, "auto-"))
88✔
1896
                        continue;
×
1897

1898
                c = strdup(*i);
88✔
1899
                if (!c)
88✔
1900
                        return log_oom();
×
1901

1902
                STRV_FOREACH_PAIR(a, b, title_table)
677✔
1903
                        if (streq(*a, *i)) {
616✔
1904
                                t = strdup(*b);
27✔
1905
                                if (!t)
27✔
1906
                                        return log_oom();
×
1907
                                break;
1908
                        }
1909

1910
                p = strdup(EFIVAR_PATH(EFI_LOADER_VARIABLE_STR("LoaderEntries")));
88✔
1911
                if (!p)
88✔
1912
                        return log_oom();
×
1913

1914
                if (!GREEDY_REALLOC0(config->entries, config->n_entries + 1))
88✔
1915
                        return log_oom();
×
1916

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

1930
        return 0;
1931
}
1932

1933
BootEntry* boot_config_find_entry(BootConfig *config, const char *id) {
112✔
1934
        assert(config);
112✔
1935
        assert(id);
112✔
1936

1937
        for (size_t j = 0; j < config->n_entries; j++)
365✔
1938
                if (strcaseeq_ptr(config->entries[j].id, id) ||
276✔
1939
                    strcaseeq_ptr(config->entries[j].id_old, id))
253✔
1940
                        return config->entries + j;
1941

1942
        return NULL;
1943
}
1944

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

1951
        assert(p);
18✔
1952
        assert(pstatus);
18✔
1953

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

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

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

1967
        if (*pstatus == 0 && status < 0)
18✔
1968
                *pstatus = status;
×
1969
}
18✔
1970

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

1977
        assert(e);
9✔
1978
        assert(extra);
9✔
1979

1980
        boot_entry_file_list(field, e->root, extra->location, status);
9✔
1981

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

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

1990
        assert(ret_cmdline);
18✔
1991

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

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

2000
        *ret_cmdline = TAKE_PTR(t);
18✔
2001
        return 0;
18✔
2002
}
2003

2004
static int print_cmdline(const BootEntry *e, int *status) {
20✔
2005
        _cleanup_free_ char *options = NULL, *combined_cmdline = NULL, *t2 = NULL;
20✔
2006

2007
        assert(e);
20✔
2008

2009
        if (!strv_isempty(e->options)) {
20✔
2010
                _cleanup_free_ char *t = NULL;
9✔
2011

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

2016
                if (indent_embedded_newlines(options, &t) < 0)
9✔
2017
                        return log_oom();
×
2018

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

2025
        FOREACH_ARRAY(extra, e->global_extras->items, e->global_extras->n_items) {
29✔
2026
                print_extra(e, extra, "extra", status);
9✔
2027

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

2033
        FOREACH_ARRAY(extra, e->local_extras.items, e->local_extras.n_items) {
20✔
2034
                print_extra(e, extra, "extra", status);
×
2035

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

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

2045
        if (indent_embedded_newlines(t2, &combined_cmdline) < 0)
9✔
2046
                return log_oom();
×
2047

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

2051
        return 0;
2052
}
2053

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

2059
        int r;
3✔
2060

2061
        assert(extra);
3✔
2062
        assert(extra_str);
3✔
2063

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

2071
        return 0;
2072
}
2073

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

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

2083
        assert(e);
18✔
2084

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

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

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

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

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

2109
                r = json_addon(extra, "localAddon", &addons_array);
×
2110
                if (r < 0)
×
2111
                        return r;
2112

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

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

2127
int show_boot_entry(
20✔
2128
                const BootEntry *e,
2129
                bool show_as_default,
2130
                bool show_as_selected,
2131
                bool show_reported) {
2132

2133
        int status = 0, r = 0;
20✔
2134

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

2138
        assert(e);
20✔
2139

2140
        printf("         type: %s\n",
20✔
2141
               boot_entry_type_description_to_string(e->type));
20✔
2142

2143
        printf("        title: %s%s%s",
60✔
2144
               ansi_highlight(), boot_entry_title(e), ansi_normal());
2145

2146
        if (show_as_default)
20✔
2147
                printf(" %s(default)%s",
4✔
2148
                       ansi_highlight_green(), ansi_normal());
2149

2150
        if (show_as_selected)
20✔
2151
                printf(" %s(selected)%s",
4✔
2152
                       ansi_highlight_magenta(), ansi_normal());
2153

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

2163
        putchar('\n');
20✔
2164

2165
        if (e->id) {
20✔
2166
                printf("           id: %s", e->id);
20✔
2167

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

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

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

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

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

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

2220
        STRV_FOREACH(s, e->initrd)
20✔
2221
                boot_entry_file_list(s == e->initrd ? "initrd" : NULL,
×
2222
                                     e->root,
×
2223
                                     *s,
2224
                                     &status);
2225

2226
        r = print_cmdline(e, &status);
20✔
2227
        if (r < 0)
20✔
2228
                return r;
2229

2230
        if (e->device_tree)
20✔
2231
                boot_entry_file_list("devicetree", e->root, e->device_tree, &status);
×
2232

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

2239
        return -status;
20✔
2240
}
2241

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

2248
        assert(c);
18✔
2249
        assert(ret);
18✔
2250

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

2256
        e = c->entries + i;
18✔
2257

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

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

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

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

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

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

2324
        *ret = TAKE_PTR(v);
18✔
2325
        return 1;
18✔
2326
}
2327

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

2331
        assert(config);
9✔
2332

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

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

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

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

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

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

2363
        return 0;
2364
}
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