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

systemd / systemd / 14895667988

07 May 2025 08:57PM UTC coverage: 72.225% (-0.007%) from 72.232%
14895667988

push

github

yuwata
network: log_link_message_debug_errno() automatically append %m if necessary

Follow-up for d28746ef5.
Fixes CID#1609753.

0 of 1 new or added line in 1 file covered. (0.0%)

20297 existing lines in 338 files now uncovered.

297407 of 411780 relevant lines covered (72.22%)

695716.85 hits per line

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

66.64
/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 "alloc-util.h"
7
#include "bootspec.h"
8
#include "bootspec-fundamental.h"
9
#include "chase.h"
10
#include "conf-files.h"
11
#include "devnum-util.h"
12
#include "dirent-util.h"
13
#include "efi-loader.h"
14
#include "env-file.h"
15
#include "errno-util.h"
16
#include "fd-util.h"
17
#include "fileio.h"
18
#include "find-esp.h"
19
#include "log.h"
20
#include "parse-util.h"
21
#include "path-util.h"
22
#include "pe-binary.h"
23
#include "pretty-print.h"
24
#include "recurse-dir.h"
25
#include "sort-util.h"
26
#include "stat-util.h"
27
#include "string-table.h"
28
#include "strv.h"
29
#include "terminal-util.h"
30
#include "uki.h"
31
#include "unaligned.h"
32

33
static const char* const boot_entry_type_table[_BOOT_ENTRY_TYPE_MAX] = {
34
        [BOOT_ENTRY_CONF]        = "Boot Loader Specification Type #1 (.conf)",
35
        [BOOT_ENTRY_UNIFIED]     = "Boot Loader Specification Type #2 (.efi)",
36
        [BOOT_ENTRY_LOADER]      = "Reported by Boot Loader",
37
        [BOOT_ENTRY_LOADER_AUTO] = "Automatic",
38
};
39

40
DEFINE_STRING_TABLE_LOOKUP_TO_STRING(boot_entry_type, BootEntryType);
15✔
41

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

49
DEFINE_STRING_TABLE_LOOKUP_TO_STRING(boot_entry_type_json, BootEntryType);
9✔
50

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

56
DEFINE_STRING_TABLE_LOOKUP_TO_STRING(boot_entry_source, BootEntrySource);
15✔
57

58
static const char* const boot_entry_source_json_table[_BOOT_ENTRY_SOURCE_MAX] = {
59
        [BOOT_ENTRY_ESP]      = "esp",
60
        [BOOT_ENTRY_XBOOTLDR] = "xbootldr",
61
};
62

63
DEFINE_STRING_TABLE_LOOKUP_TO_STRING(boot_entry_source_json, BootEntrySource);
9✔
64

65
static void boot_entry_addons_done(BootEntryAddons *addons) {
258✔
66
        assert(addons);
258✔
67

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

76
static void boot_entry_free(BootEntry *entry) {
112✔
77
        assert(entry);
112✔
78

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

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

106
        _cleanup_free_ char *c = NULL;
×
107

108
        assert(field);
×
UNCOV
109
        assert(p);
×
UNCOV
110
        assert(ret);
×
111

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

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

124
        /* Remove duplicate "/" */
UNCOV
125
        path_simplify(c);
×
126

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

UNCOV
134
        *ret = TAKE_PTR(c);
×
UNCOV
135
        return 1;
×
136
}
137

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

UNCOV
145
        _cleanup_free_ char *c = NULL;
×
146
        int r;
×
147

148
        assert(field);
×
UNCOV
149
        assert(s);
×
150
        assert(p);
×
151

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

UNCOV
156
        return free_and_replace(*s, c);
×
157
}
158

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

UNCOV
166
        char *c;
×
167
        int r;
×
168

169
        assert(field);
×
UNCOV
170
        assert(s);
×
171
        assert(p);
×
172

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

UNCOV
177
        return strv_consume(s, c);
×
178
}
179

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

UNCOV
187
        _cleanup_strv_free_ char **l = NULL, **f = NULL;
×
188
        int r;
×
189

UNCOV
190
        l = strv_split(p, NULL);
×
UNCOV
191
        if (!l)
×
192
                return -ENOMEM;
193

UNCOV
194
        STRV_FOREACH(i, l) {
×
195
                char *c;
×
196

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

UNCOV
203
                r = strv_consume(&f, c);
×
UNCOV
204
                if (r < 0)
×
205
                        return r;
206
        }
207

UNCOV
208
        return strv_extend_strv_consume(s, TAKE_PTR(f), /* filter_duplicates= */ false);
×
209
}
210

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

217
        assert(fname);
36✔
218
        assert(p);
36✔
219
        assert(*p);
36✔
220
        assert(ret);
36✔
221

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

228
        d = strndup(*p, n);
34✔
229
        if (!d)
34✔
UNCOV
230
                return log_oom();
×
231

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

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

243
int boot_filename_extract_tries(
79✔
244
                const char *fname,
245
                char **ret_stripped,
246
                unsigned *ret_tries_left,
247
                unsigned *ret_tries_done) {
248

249
        unsigned tries_left = UINT_MAX, tries_done = UINT_MAX;
79✔
250
        _cleanup_free_ char *stripped = NULL;
79✔
251
        const char *p, *suffix, *m;
79✔
252
        int r;
79✔
253

254
        assert(fname);
79✔
255
        assert(ret_stripped);
79✔
256
        assert(ret_tries_left);
79✔
257
        assert(ret_tries_done);
79✔
258

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

265
        p = m = memrchr(fname, '+', suffix - fname);
78✔
266
        if (!p)
78✔
267
                goto nothing;
55✔
268
        p++;
23✔
269

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

276
        if (*p == '-') {
20✔
277
                p++;
13✔
278

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

286
        if (p != suffix)
19✔
287
                goto nothing;
3✔
288

289
        stripped = strndup(fname, m - fname);
16✔
290
        if (!stripped)
16✔
UNCOV
291
                return log_oom();
×
292

293
        if (!strextend(&stripped, suffix))
16✔
UNCOV
294
                return log_oom();
×
295

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

300
        return 0;
16✔
301

302
nothing:
61✔
303
        stripped = strdup(fname);
61✔
304
        if (!stripped)
61✔
UNCOV
305
                return log_oom();
×
306

307
        *ret_stripped = TAKE_PTR(stripped);
61✔
308
        *ret_tries_left = *ret_tries_done = UINT_MAX;
61✔
309
        return 0;
61✔
310
}
311

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

320
        _cleanup_(boot_entry_free) BootEntry tmp = BOOT_ENTRY_INIT(BOOT_ENTRY_CONF, source);
8✔
321
        char *c;
8✔
322
        int r;
8✔
323

324
        assert(f);
8✔
325
        assert(root);
8✔
326
        assert(dir);
8✔
327
        assert(fname);
8✔
328
        assert(ret);
8✔
329

330
        /* Loads a Type #1 boot menu entry from the specified FILE* object */
331

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

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

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

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

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

351
        tmp.root = strdup(root);
8✔
352
        if (!tmp.root)
8✔
UNCOV
353
                return log_oom();
×
354

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

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

366
                if (IN_SET(buf[0], '#', '\0'))
27✔
UNCOV
367
                        continue;
×
368

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

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

UNCOV
385
                        continue;
×
386
                }
387

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

418
        *ret = TAKE_STRUCT(tmp);
8✔
419
        return 0;
8✔
420
}
421

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

431
        assert(config);
8✔
432
        assert(f);
8✔
433
        assert(root);
8✔
434
        assert(dir);
8✔
435
        assert(fname);
8✔
436

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

440
        BootEntry *entry = config->entries + config->n_entries;
8✔
441

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

447
        entry->global_addons = &config->global_addons[source];
8✔
448

449
        return 0;
8✔
450
}
451

452
void boot_config_free(BootConfig *config) {
30✔
453
        assert(config);
30✔
454

455
        free(config->default_pattern);
30✔
456

457
        free(config->entry_oneshot);
30✔
458
        free(config->entry_default);
30✔
459
        free(config->entry_selected);
30✔
460

461
        FOREACH_ARRAY(i, config->entries, config->n_entries)
92✔
462
                boot_entry_free(i);
62✔
463
        free(config->entries);
30✔
464

465
        FOREACH_ELEMENT(i, config->global_addons)
90✔
466
                boot_entry_addons_done(i);
60✔
467

468
        set_free(config->inodes_seen);
30✔
469
}
30✔
470

471
int boot_loader_read_conf(BootConfig *config, FILE *file, const char *path) {
27✔
472
        int r;
27✔
473

474
        assert(config);
27✔
475
        assert(file);
27✔
476
        assert(path);
27✔
477

478
        for (unsigned line = 1;; line++) {
54✔
479
                _cleanup_free_ char *buf = NULL, *field = NULL;
54✔
480

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

489
                if (IN_SET(buf[0], '#', '\0'))
54✔
490
                        continue;
54✔
491

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

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

521
        return 1;
27✔
522
}
523

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

529
        assert(config);
30✔
530
        assert(path);
30✔
531

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

538
        return boot_loader_read_conf(config, f, full);
27✔
539
}
540

541
static int boot_entry_compare(const BootEntry *a, const BootEntry *b) {
36✔
542
        int r;
36✔
543

544
        assert(a);
36✔
545
        assert(b);
36✔
546

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

549
        r = CMP(a->tries_left == 0, b->tries_left == 0);
36✔
550
        if (r != 0)
36✔
UNCOV
551
                return r;
×
552

553
        r = CMP(!a->sort_key, !b->sort_key);
36✔
554
        if (r != 0)
33✔
555
                return r;
3✔
556

557
        if (a->sort_key && b->sort_key) {
33✔
558
                r = strcmp(a->sort_key, b->sort_key);
30✔
559
                if (r != 0)
30✔
560
                        return r;
561

562
                r = strcmp_ptr(a->machine_id, b->machine_id);
29✔
563
                if (r != 0)
29✔
564
                        return r;
565

566
                r = -strverscmp_improved(a->version, b->version);
29✔
567
                if (r != 0)
29✔
568
                        return r;
569
        }
570

571
        r = -strverscmp_improved(a->id_without_profile ?: a->id, b->id_without_profile ?: b->id);
31✔
572
        if (r != 0)
31✔
573
                return r;
574

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

UNCOV
583
        if (a->tries_left != UINT_MAX || b->tries_left != UINT_MAX)
×
584
                return 0;
585

586
        r = -CMP(a->tries_left, b->tries_left);
×
UNCOV
587
        if (r != 0)
×
588
                return r;
×
589

UNCOV
590
        return CMP(a->tries_done, b->tries_done);
×
591
}
592

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

597
        assert(config);
22✔
598
        assert(fd >= 0);
22✔
599
        assert(fname);
22✔
600

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

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

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

619
        d = memdup(&st, sizeof(st));
22✔
620
        if (!d)
22✔
UNCOV
621
                return log_oom();
×
622

623
        if (set_ensure_consume(&config->inodes_seen, &inode_hash_ops, TAKE_PTR(d)) < 0)
22✔
UNCOV
624
                return log_oom();
×
625

626
        return true;
627
}
628

629
static int boot_entries_find_type1(
44✔
630
                BootConfig *config,
631
                const char *root,
632
                const BootEntrySource source,
633
                const char *dir) {
634

635
        _cleanup_free_ DirectoryEntries *dentries = NULL;
88✔
636
        _cleanup_free_ char *full = NULL;
44✔
637
        _cleanup_close_ int dir_fd = -EBADF;
44✔
638
        int r;
44✔
639

640
        assert(config);
44✔
641
        assert(root);
44✔
642
        assert(dir);
44✔
643

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

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

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

658
                if (!dirent_is_file(de))
8✔
UNCOV
659
                        continue;
×
660

661
                if (!endswith_no_case(de->d_name, ".conf"))
8✔
UNCOV
662
                        continue;
×
663

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

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

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

681
        return 0;
682
}
683

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

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

700
        assert(root);
42✔
701
        assert(path);
42✔
702
        assert(osrelease_text);
42✔
703
        assert(ret);
42✔
704

705
        k = path_startswith(path, root);
42✔
706
        if (!k)
42✔
UNCOV
707
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Path is not below root: %s", path);
×
708

709
        f = fmemopen_unlocked((void*) osrelease_text, strlen(osrelease_text), "r");
42✔
710
        if (!f)
42✔
UNCOV
711
                return log_oom();
×
712

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

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

739
        _cleanup_free_ char *profile_id = NULL, *profile_title = NULL;
42✔
740
        if (profile_text) {
42✔
741
                fclose(f);
42✔
742

743
                f = fmemopen_unlocked((void*) profile_text, strlen(profile_text), "r");
42✔
744
                if (!f)
42✔
UNCOV
745
                        return log_oom();
×
746

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

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

759
        _cleanup_(boot_entry_free) BootEntry tmp = BOOT_ENTRY_INIT(BOOT_ENTRY_UNIFIED, source);
42✔
760

761
        r = boot_filename_extract_tries(fname, &tmp.id, &tmp.tries_left, &tmp.tries_done);
42✔
762
        if (r < 0)
42✔
763
                return r;
764

765
        if (!efi_loader_entry_name_valid(tmp.id))
42✔
UNCOV
766
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry name: %s", tmp.id);
×
767

768
        tmp.profile = profile;
42✔
769

770
        if (profile_id || profile > 0) {
42✔
771
                tmp.id_without_profile = TAKE_PTR(tmp.id);
42✔
772

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

781
        if (os_id && os_version_id) {
42✔
782
                tmp.id_old = strjoin(os_id, "-", os_version_id);
×
UNCOV
783
                if (!tmp.id_old)
×
UNCOV
784
                        return log_oom();
×
785
        }
786

787
        tmp.path = strdup(path);
42✔
788
        if (!tmp.path)
42✔
UNCOV
789
                return log_oom();
×
790

791
        tmp.root = strdup(root);
42✔
792
        if (!tmp.root)
42✔
UNCOV
793
                return log_oom();
×
794

795
        tmp.kernel = path_make_absolute(k, "/");
42✔
796
        if (!tmp.kernel)
42✔
UNCOV
797
                return log_oom();
×
798

799
        tmp.options = strv_new(cmdline_text);
42✔
800
        if (!tmp.options)
42✔
UNCOV
801
                return log_oom();
×
802

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

814
        if (good_sort_key) {
42✔
815
                tmp.sort_key = strdup(good_sort_key);
42✔
816
                if (!tmp.sort_key)
42✔
UNCOV
817
                        return log_oom();
×
818
        }
819

820
        if (good_version) {
42✔
821
                tmp.version = strdup(good_version);
42✔
822
                if (!tmp.version)
42✔
UNCOV
823
                        return log_oom();
×
824
        }
825

826
        *ret = TAKE_STRUCT(tmp);
42✔
827
        return 0;
42✔
828
}
829

830
static int pe_load_headers_and_sections(
56✔
831
                int fd,
832
                const char *path,
833
                IMAGE_SECTION_HEADER **ret_sections,
834
                PeHeader **ret_pe_header) {
835

836
        _cleanup_free_ IMAGE_DOS_HEADER *dos_header = NULL;
56✔
837
        IMAGE_SECTION_HEADER *sections;
56✔
838
        PeHeader *pe_header;
56✔
839
        int r;
56✔
840

841
        assert(fd >= 0);
56✔
842
        assert(path);
56✔
843

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

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

852
        if (ret_pe_header)
56✔
853
                *ret_pe_header = TAKE_PTR(pe_header);
56✔
854
        if (ret_sections)
56✔
855
                *ret_sections = TAKE_PTR(sections);
56✔
856

857
        return 0;
858
}
859

860
static const IMAGE_SECTION_HEADER* pe_find_profile_section_table(
98✔
861
                const PeHeader *pe_header,
862
                const IMAGE_SECTION_HEADER *sections,
863
                unsigned profile,
864
                size_t *ret_n_sections) {
865

866
        assert(pe_header);
98✔
867

868
        /* Looks for the part of the section table that defines the specified profile. If 'profile' is
869
         * specified as UINT_MAX this will look for the base profile. */
870

871
        if (le16toh(pe_header->pe.NumberOfSections) == 0)
98✔
872
                return NULL;
873

874
        assert(sections);
98✔
875

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

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

894
                if (current_profile == UINT_MAX)
126✔
895
                        current_profile = 0;
896
                else
897
                        current_profile++;
70✔
898

899
                if (current_profile == profile)
126✔
900
                        start = p;
42✔
901

902
                p++; /* Continue scanning after the .profile entry we just found */
126✔
903
        }
904

905
        if (!start)
98✔
906
                return NULL;
907

908
        if (ret_n_sections)
84✔
909
                *ret_n_sections = end - start;
84✔
910

911
        return start;
912
}
913

914
static int trim_cmdline(char **cmdline) {
42✔
915
        assert(cmdline);
42✔
916

917
        /* Strips leading and trailing whitespace from command line */
918

919
        if (!*cmdline)
42✔
920
                return 0;
921

922
        const char *skipped = skip_leading_chars(*cmdline, WHITESPACE);
42✔
923

924
        if (isempty(skipped)) {
42✔
UNCOV
925
                *cmdline = mfree(*cmdline);
×
UNCOV
926
                return 0;
×
927
        }
928

929
        if (skipped != *cmdline) {
42✔
930
                _cleanup_free_ char *c = strdup(skipped);
×
UNCOV
931
                if (!c)
×
932
                        return -ENOMEM;
×
933

UNCOV
934
                free_and_replace(*cmdline, c);
×
935
        }
936

937
        delete_trailing_chars(*cmdline, WHITESPACE);
42✔
938
        return 1;
42✔
939
}
940

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

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

UNCOV
953
        _cleanup_free_ char *osrelease_text = NULL, *profile_text = NULL, *cmdline_text = NULL;
×
UNCOV
954
        _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
×
955
        _cleanup_free_ PeHeader *pe_header = NULL;
56✔
956
        int r;
56✔
957

958
        assert(fd >= 0);
56✔
959
        assert(path);
56✔
960
        assert(profile != UINT_MAX);
56✔
961
        assert(ret_osrelease);
56✔
962
        assert(ret_profile);
56✔
963
        assert(ret_cmdline);
56✔
964

965
        r = pe_load_headers_and_sections(fd, path, &sections, &pe_header);
56✔
966
        if (r < 0)
56✔
967
                return r;
968

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

972
        if (!pe_is_native(pe_header)) /* Don't process non-native UKIs */
56✔
UNCOV
973
                goto nothing;
×
974

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

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

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

994
        FOREACH_ELEMENT(t, table) {
168✔
995
                const IMAGE_SECTION_HEADER *found;
126✔
996

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

1005
                /* Permit "masking" of sections in the base profile */
1006
                if (found->VirtualSize == 0)
126✔
UNCOV
1007
                        continue;
×
1008

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

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

1017
        if (trim_cmdline(&cmdline_text) < 0)
42✔
UNCOV
1018
                return log_oom();
×
1019

1020
        *ret_osrelease = TAKE_PTR(osrelease_text);
42✔
1021
        *ret_profile = TAKE_PTR(profile_text);
42✔
1022
        *ret_cmdline = TAKE_PTR(cmdline_text);
42✔
1023
        return 1;
42✔
1024

1025
nothing:
14✔
1026
        *ret_osrelease = *ret_profile = *ret_cmdline = NULL;
14✔
1027
        return 0;
14✔
1028
}
1029

UNCOV
1030
static int pe_find_addon_sections(
×
1031
                int fd,
1032
                const char *path,
1033
                char **ret_cmdline) {
1034

1035
        _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
×
UNCOV
1036
        _cleanup_free_ PeHeader *pe_header = NULL;
×
1037
        int r;
×
1038

UNCOV
1039
        assert(fd >= 0);
×
1040
        assert(path);
×
1041

UNCOV
1042
        r = pe_load_headers_and_sections(fd, path, &sections, &pe_header);
×
UNCOV
1043
        if (r < 0)
×
1044
                return r;
1045

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

1049
        /* Define early, before the gotos below */
1050
        _cleanup_free_ char *cmdline_text = NULL;
×
1051

UNCOV
1052
        if (!pe_is_native(pe_header))
×
1053
                goto nothing;
×
1054

1055
        const IMAGE_SECTION_HEADER *found = pe_section_table_find(sections, le16toh(pe_header->pe.NumberOfSections), ".cmdline");
×
UNCOV
1056
        if (!found)
×
1057
                goto nothing;
×
1058

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

UNCOV
1063
        if (trim_cmdline(&cmdline_text) < 0)
×
1064
                return log_oom();
×
1065

UNCOV
1066
        *ret_cmdline = TAKE_PTR(cmdline_text);
×
1067
        return 1;
×
1068

1069
nothing:
×
UNCOV
1070
        *ret_cmdline = NULL;
×
UNCOV
1071
        return 0;
×
1072
}
1073

UNCOV
1074
static int insert_boot_entry_addon(
×
1075
                BootEntryAddons *addons,
1076
                char *location,
1077
                char *cmdline) {
1078

1079
        assert(addons);
×
1080

UNCOV
1081
        if (!GREEDY_REALLOC(addons->items, addons->n_items + 1))
×
1082
                return log_oom();
×
1083

UNCOV
1084
        addons->items[addons->n_items++] = (BootEntryAddon) {
×
1085
                .location = location,
1086
                .cmdline = cmdline,
1087
        };
1088

UNCOV
1089
        return 0;
×
1090
}
1091

1092
static int boot_entries_find_unified_addons(
86✔
1093
                BootConfig *config,
1094
                int d_fd,
1095
                const char *addon_dir,
1096
                const char *root,
1097
                BootEntryAddons *ret_addons) {
1098

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

1104
        assert(ret_addons);
86✔
1105
        assert(config);
86✔
1106

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

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

UNCOV
1117
                if (!dirent_is_file(de))
×
1118
                        continue;
×
1119

UNCOV
1120
                if (!endswith_no_case(de->d_name, ".addon.efi"))
×
1121
                        continue;
×
1122

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

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

1135
                j = path_join(full, de->d_name);
×
UNCOV
1136
                if (!j)
×
1137
                        return log_oom();
×
1138

UNCOV
1139
                if (pe_find_addon_sections(fd, j, &cmdline) <= 0)
×
1140
                        continue;
×
1141

1142
                location = strdup(j);
×
UNCOV
1143
                if (!location)
×
1144
                        return log_oom();
×
1145

UNCOV
1146
                r = insert_boot_entry_addon(&addons, location, cmdline);
×
UNCOV
1147
                if (r < 0)
×
1148
                        return r;
1149

UNCOV
1150
                TAKE_PTR(location);
×
UNCOV
1151
                TAKE_PTR(cmdline);
×
1152
        }
1153

UNCOV
1154
        *ret_addons = TAKE_STRUCT(addons);
×
UNCOV
1155
        return 0;
×
1156
}
1157

1158
static int boot_entries_find_unified_global_addons(
44✔
1159
                BootConfig *config,
1160
                const char *root,
1161
                const char *d_name,
1162
                BootEntryAddons *ret_addons) {
1163

1164
        int r;
44✔
1165
        _cleanup_closedir_ DIR *d = NULL;
44✔
1166

1167
        assert(ret_addons);
44✔
1168

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

1175
        return boot_entries_find_unified_addons(config, dirfd(d), d_name, root, ret_addons);
44✔
1176
}
1177

1178
static int boot_entries_find_unified_local_addons(
42✔
1179
                BootConfig *config,
1180
                int d_fd,
1181
                const char *d_name,
1182
                const char *root,
1183
                BootEntry *ret) {
1184

1185
        _cleanup_free_ char *addon_dir = NULL;
42✔
1186

1187
        assert(ret);
42✔
1188

1189
        addon_dir = strjoin(d_name, ".extra.d");
42✔
1190
        if (!addon_dir)
42✔
UNCOV
1191
                return log_oom();
×
1192

1193
        return boot_entries_find_unified_addons(config, d_fd, addon_dir, root, &ret->local_addons);
42✔
1194
}
1195

1196
static int boot_entries_find_unified(
44✔
1197
                BootConfig *config,
1198
                const char *root,
1199
                BootEntrySource source,
1200
                const char *dir) {
1201

1202
        _cleanup_closedir_ DIR *d = NULL;
44✔
1203
        _cleanup_free_ char *full = NULL;
44✔
1204
        int r;
44✔
1205

1206
        assert(config);
44✔
1207
        assert(dir);
44✔
1208

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

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

1219
                if (!endswith_no_case(de->d_name, ".efi"))
14✔
UNCOV
1220
                        continue;
×
1221

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

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

1234
                _cleanup_free_ char *j = path_join(full, de->d_name);
28✔
1235
                if (!j)
14✔
UNCOV
1236
                        return log_oom();
×
1237

1238
                for (unsigned p = 0; p < UNIFIED_PROFILES_MAX; p++) {
56✔
1239
                        _cleanup_free_ char *osrelease = NULL, *profile = NULL, *cmdline = NULL;
56✔
1240

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

1247
                        if (!GREEDY_REALLOC(config->entries, config->n_entries + 1))
42✔
UNCOV
1248
                                return log_oom();
×
1249

1250
                        BootEntry *entry = config->entries + config->n_entries;
42✔
1251

1252
                        if (boot_entry_load_unified(root, source, j, p, osrelease, profile, cmdline, entry) < 0)
42✔
UNCOV
1253
                                continue;
×
1254

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

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

1261
                        config->n_entries++;
42✔
1262
                }
1263
        }
1264

1265
        return 0;
1266
}
1267

1268
static bool find_nonunique(const BootEntry *entries, size_t n_entries, bool arr[]) {
47✔
1269
        bool non_unique = false;
47✔
1270

1271
        assert(entries || n_entries == 0);
47✔
1272
        assert(arr || n_entries == 0);
47✔
1273

1274
        for (size_t i = 0; i < n_entries; i++)
191✔
1275
                arr[i] = false;
144✔
1276

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

1283
        return non_unique;
47✔
1284
}
1285

1286
static int boot_entries_uniquify(BootEntry *entries, size_t n_entries) {
30✔
1287
        _cleanup_free_ bool *arr = NULL;
60✔
1288
        char *s;
30✔
1289

1290
        assert(entries || n_entries == 0);
30✔
1291

1292
        if (n_entries == 0)
30✔
1293
                return 0;
1294

1295
        arr = new(bool, n_entries);
16✔
1296
        if (!arr)
16✔
1297
                return -ENOMEM;
1298

1299
        /* Find _all_ non-unique titles */
1300
        if (!find_nonunique(entries, n_entries, arr))
16✔
1301
                return 0;
1302

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

1309
                        free_and_replace(entries[i].show_title, s);
34✔
1310
                }
1311

1312
        if (!find_nonunique(entries, n_entries, arr))
16✔
1313
                return 0;
1314

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

1321
                        free_and_replace(entries[i].show_title, s);
2✔
1322
                }
1323

1324
        if (!find_nonunique(entries, n_entries, arr))
15✔
1325
                return 0;
1326

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

1333
                        free_and_replace(entries[i].show_title, s);
30✔
1334
                }
1335

1336
        return 0;
1337
}
1338

1339
static int boot_config_find(const BootConfig *config, const char *id) {
9✔
1340
        assert(config);
9✔
1341

1342
        if (!id)
9✔
1343
                return -1;
1344

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

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

1357
        return -1;
1358
}
1359

1360
static int boot_entries_select_default(const BootConfig *config) {
28✔
1361
        int i;
28✔
1362

1363
        assert(config);
28✔
1364
        assert(config->entries || config->n_entries == 0);
28✔
1365

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

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

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

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

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

1402
static int boot_entries_select_selected(const BootConfig *config) {
28✔
1403
        assert(config);
28✔
1404
        assert(config->entries || config->n_entries == 0);
28✔
1405

1406
        if (!config->entry_selected || config->n_entries == 0)
28✔
1407
                return -1;
1408

1409
        return boot_config_find(config, config->entry_selected);
6✔
1410
}
1411

1412
static int boot_load_efi_entry_pointers(BootConfig *config, bool skip_efivars) {
28✔
1413
        int r;
28✔
1414

1415
        assert(config);
28✔
1416

1417
        if (skip_efivars || !is_efi_boot())
28✔
1418
                return 0;
22✔
1419

1420
        /* Loads the three "pointers" to boot loader entries from their EFI variables */
1421

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

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

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

1440
        return 1;
1441
}
1442

1443
int boot_config_select_special_entries(BootConfig *config, bool skip_efivars) {
28✔
1444
        int r;
28✔
1445

1446
        assert(config);
28✔
1447

1448
        r = boot_load_efi_entry_pointers(config, skip_efivars);
28✔
1449
        if (r < 0)
28✔
1450
                return r;
1451

1452
        config->default_entry = boot_entries_select_default(config);
28✔
1453
        config->selected_entry = boot_entries_select_selected(config);
28✔
1454

1455
        return 0;
28✔
1456
}
1457

1458
int boot_config_finalize(BootConfig *config) {
30✔
1459
        int r;
30✔
1460

1461
        typesafe_qsort(config->entries, config->n_entries, boot_entry_compare);
30✔
1462

1463
        r = boot_entries_uniquify(config->entries, config->n_entries);
30✔
1464
        if (r < 0)
30✔
UNCOV
1465
                return log_error_errno(r, "Failed to uniquify boot entries: %m");
×
1466

1467
        return 0;
1468
}
1469

1470
int boot_config_load(
30✔
1471
                BootConfig *config,
1472
                const char *esp_path,
1473
                const char *xbootldr_path) {
1474

1475
        int r;
30✔
1476

1477
        assert(config);
30✔
1478

1479
        if (esp_path) {
30✔
1480
                r = boot_loader_read_conf_path(config, esp_path, "/loader/loader.conf");
30✔
1481
                if (r < 0)
30✔
1482
                        return r;
1483

1484
                r = boot_entries_find_type1(config, esp_path, BOOT_ENTRY_ESP, "/loader/entries");
30✔
1485
                if (r < 0)
30✔
1486
                        return r;
1487

1488
                r = boot_entries_find_unified(config, esp_path, BOOT_ENTRY_ESP, "/EFI/Linux/");
30✔
1489
                if (r < 0)
30✔
1490
                        return r;
1491

1492
                r = boot_entries_find_unified_global_addons(config, esp_path, "/loader/addons/",
30✔
1493
                                                            &config->global_addons[BOOT_ENTRY_ESP]);
1494
                if (r < 0)
30✔
1495
                        return r;
1496
        }
1497

1498
        if (xbootldr_path) {
30✔
1499
                r = boot_entries_find_type1(config, xbootldr_path, BOOT_ENTRY_XBOOTLDR, "/loader/entries");
14✔
1500
                if (r < 0)
14✔
1501
                        return r;
1502

1503
                r = boot_entries_find_unified(config, xbootldr_path, BOOT_ENTRY_XBOOTLDR, "/EFI/Linux/");
14✔
1504
                if (r < 0)
14✔
1505
                        return r;
1506

1507
                r = boot_entries_find_unified_global_addons(config, xbootldr_path, "/loader/addons/",
14✔
1508
                                                            &config->global_addons[BOOT_ENTRY_XBOOTLDR]);
1509
                if (r < 0)
14✔
1510
                        return r;
1511
        }
1512

1513
        return boot_config_finalize(config);
30✔
1514
}
1515

UNCOV
1516
int boot_config_load_auto(
×
1517
                BootConfig *config,
1518
                const char *override_esp_path,
1519
                const char *override_xbootldr_path) {
1520

1521
        _cleanup_free_ char *esp_where = NULL, *xbootldr_where = NULL;
×
UNCOV
1522
        dev_t esp_devid = 0, xbootldr_devid = 0;
×
1523
        int r;
×
1524

UNCOV
1525
        assert(config);
×
1526

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

1534
        if (!override_esp_path && !override_xbootldr_path) {
×
UNCOV
1535
                if (access("/run/boot-loader-entries/", F_OK) >= 0)
×
1536
                        return boot_config_load(config, "/run/boot-loader-entries/", NULL);
×
1537

UNCOV
1538
                if (errno != ENOENT)
×
UNCOV
1539
                        return log_error_errno(errno,
×
1540
                                               "Failed to determine whether /run/boot-loader-entries/ exists: %m");
1541
        }
1542

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

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

1551
        /* If both paths actually refer to the same inode, suppress the xbootldr path */
UNCOV
1552
        if (esp_where && xbootldr_where && devnum_set_and_equal(esp_devid, xbootldr_devid))
×
1553
                xbootldr_where = mfree(xbootldr_where);
×
1554

UNCOV
1555
        return boot_config_load(config, esp_where, xbootldr_where);
×
1556
}
1557

1558
int boot_config_augment_from_loader(
6✔
1559
                BootConfig *config,
1560
                char **found_by_loader,
1561
                bool only_auto) {
1562

1563
        static const BootEntryAddons no_addons = (BootEntryAddons) {};
6✔
1564
        static const char *const title_table[] = {
6✔
1565
                /* Pretty names for a few well-known automatically discovered entries. */
1566
                "auto-osx",                      "macOS",
1567
                "auto-windows",                  "Windows Boot Manager",
1568
                "auto-efi-shell",                "EFI Shell",
1569
                "auto-efi-default",              "EFI Default Loader",
1570
                "auto-poweroff",                 "Power Off The System",
1571
                "auto-reboot",                   "Reboot The System",
1572
                "auto-reboot-to-firmware-setup", "Reboot Into Firmware Interface",
1573
                NULL,
1574
        };
1575

1576
        assert(config);
6✔
1577

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

1581
        STRV_FOREACH(i, found_by_loader) {
30✔
1582
                BootEntry *existing;
24✔
1583
                _cleanup_free_ char *c = NULL, *t = NULL, *p = NULL;
12✔
1584

1585
                existing = boot_config_find_entry(config, *i);
24✔
1586
                if (existing) {
24✔
1587
                        existing->reported_by_loader = true;
12✔
1588
                        continue;
12✔
1589
                }
1590

1591
                if (only_auto && !startswith(*i, "auto-"))
12✔
UNCOV
1592
                        continue;
×
1593

1594
                c = strdup(*i);
12✔
1595
                if (!c)
12✔
UNCOV
1596
                        return log_oom();
×
1597

1598
                STRV_FOREACH_PAIR(a, b, title_table)
90✔
1599
                        if (streq(*a, *i)) {
84✔
1600
                                t = strdup(*b);
6✔
1601
                                if (!t)
6✔
UNCOV
1602
                                        return log_oom();
×
1603
                                break;
1604
                        }
1605

1606
                p = strdup(EFIVAR_PATH(EFI_LOADER_VARIABLE_STR("LoaderEntries")));
12✔
1607
                if (!p)
12✔
UNCOV
1608
                        return log_oom();
×
1609

1610
                if (!GREEDY_REALLOC0(config->entries, config->n_entries + 1))
12✔
UNCOV
1611
                        return log_oom();
×
1612

1613
                config->entries[config->n_entries++] = (BootEntry) {
24✔
1614
                        .type = startswith(*i, "auto-") ? BOOT_ENTRY_LOADER_AUTO : BOOT_ENTRY_LOADER,
12✔
1615
                        .id = TAKE_PTR(c),
12✔
1616
                        .title = TAKE_PTR(t),
12✔
1617
                        .path = TAKE_PTR(p),
12✔
1618
                        .reported_by_loader = true,
1619
                        .tries_left = UINT_MAX,
1620
                        .tries_done = UINT_MAX,
1621
                        .global_addons = &no_addons,
1622
                };
1623
        }
1624

1625
        return 0;
1626
}
1627

1628
BootEntry* boot_config_find_entry(BootConfig *config, const char *id) {
28✔
1629
        assert(config);
28✔
1630
        assert(id);
28✔
1631

1632
        for (size_t j = 0; j < config->n_entries; j++)
91✔
1633
                if (strcaseeq_ptr(config->entries[j].id, id) ||
78✔
1634
                    strcaseeq_ptr(config->entries[j].id_old, id))
63✔
1635
                        return config->entries + j;
1636

1637
        return NULL;
1638
}
1639

1640
static void boot_entry_file_list(
15✔
1641
                const char *field,
1642
                const char *root,
1643
                const char *p,
1644
                int *ret_status) {
1645

1646
        assert(p);
15✔
1647
        assert(ret_status);
15✔
1648

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

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

1656
        if (status < 0) {
15✔
UNCOV
1657
                errno = -status;
×
UNCOV
1658
                printf("%s%s%s (%m)\n", ansi_highlight_red(), p, ansi_normal());
×
1659
        } else
1660
                printf("%s\n", p);
15✔
1661

1662
        if (*ret_status == 0 && status < 0)
15✔
UNCOV
1663
                *ret_status = status;
×
1664
}
15✔
1665

UNCOV
1666
static void print_addon(
×
1667
                BootEntryAddon *addon,
1668
                const char *addon_str) {
1669

1670
        printf("  %s: %s\n", addon_str, addon->location);
×
UNCOV
1671
        printf("      options: %s%s\n", glyph(GLYPH_TREE_RIGHT), addon->cmdline);
×
UNCOV
1672
}
×
1673

1674
static int indent_embedded_newlines(char *cmdline, char **ret_cmdline) {
15✔
1675
        _cleanup_free_ char *t = NULL;
15✔
1676
        _cleanup_strv_free_ char **ts = NULL;
15✔
1677

1678
        assert(ret_cmdline);
15✔
1679

1680
        ts = strv_split_newlines(cmdline);
15✔
1681
        if (!ts)
15✔
1682
                return -ENOMEM;
1683

1684
        t = strv_join(ts, "\n              ");
15✔
1685
        if (!t)
15✔
1686
                return -ENOMEM;
1687

1688
        *ret_cmdline = TAKE_PTR(t);
15✔
1689

1690
        return 0;
15✔
1691
}
1692

1693
static int print_cmdline(const BootEntry *e) {
15✔
1694

1695
        _cleanup_free_ char *options = NULL, *combined_cmdline = NULL, *t2 = NULL;
15✔
1696

1697
        assert(e);
15✔
1698

1699
        if (!strv_isempty(e->options)) {
15✔
1700
                _cleanup_free_ char *t = NULL;
15✔
1701

1702
                options = strv_join(e->options, " ");
15✔
1703
                if (!options)
15✔
UNCOV
1704
                        return log_oom();
×
1705

1706
                if (indent_embedded_newlines(options, &t) < 0)
15✔
UNCOV
1707
                        return log_oom();
×
1708

1709
                printf("      options: %s\n", t);
15✔
1710
                t2 = strdup(options);
15✔
1711
                if (!t2)
15✔
UNCOV
1712
                        return log_oom();
×
1713
        }
1714

1715
        FOREACH_ARRAY(addon, e->global_addons->items, e->global_addons->n_items) {
15✔
1716
                print_addon(addon, "global-addon");
×
UNCOV
1717
                if (!strextend(&t2, " ", addon->cmdline))
×
UNCOV
1718
                        return log_oom();
×
1719
        }
1720

1721
        FOREACH_ARRAY(addon, e->local_addons.items, e->local_addons.n_items) {
15✔
1722
                /* Add space at the beginning of addon_str to align it correctly */
1723
                print_addon(addon, " local-addon");
×
UNCOV
1724
                if (!strextend(&t2, " ", addon->cmdline))
×
UNCOV
1725
                        return log_oom();
×
1726
        }
1727

1728
        /* Don't print the combined cmdline if it's same as options. */
1729
        if (streq_ptr(t2, options))
15✔
1730
                return 0;
1731

UNCOV
1732
        if (indent_embedded_newlines(t2, &combined_cmdline) < 0)
×
1733
                return log_oom();
×
1734

UNCOV
1735
        if (combined_cmdline)
×
UNCOV
1736
                printf("      cmdline: %s\n", combined_cmdline);
×
1737

1738
        return 0;
1739
}
1740

UNCOV
1741
static int json_addon(
×
1742
                BootEntryAddon *addon,
1743
                const char *addon_str,
1744
                sd_json_variant **array) {
1745

1746
        int r;
×
1747

UNCOV
1748
        assert(addon);
×
1749
        assert(addon_str);
×
1750

UNCOV
1751
        r = sd_json_variant_append_arraybo(
×
1752
                        array,
1753
                        SD_JSON_BUILD_PAIR(addon_str, SD_JSON_BUILD_STRING(addon->location)),
1754
                        SD_JSON_BUILD_PAIR("options", SD_JSON_BUILD_STRING(addon->cmdline)));
UNCOV
1755
        if (r < 0)
×
UNCOV
1756
                return log_oom();
×
1757

1758
        return 0;
1759
}
1760

1761
static int json_cmdline(
9✔
1762
                const BootEntry *e,
1763
                const char *def_cmdline,
1764
                sd_json_variant **v) {
1765

1766
        _cleanup_free_ char *combined_cmdline = NULL;
9✔
1767
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *addons_array = NULL;
9✔
1768
        int r;
9✔
1769

1770
        assert(e);
9✔
1771

1772
        if (def_cmdline) {
9✔
1773
                combined_cmdline = strdup(def_cmdline);
9✔
1774
                if (!combined_cmdline)
9✔
UNCOV
1775
                        return log_oom();
×
1776
        }
1777

1778
        FOREACH_ARRAY(addon, e->global_addons->items, e->global_addons->n_items) {
9✔
UNCOV
1779
                r = json_addon(addon, "globalAddon", &addons_array);
×
1780
                if (r < 0)
×
1781
                        return r;
UNCOV
1782
                if (!strextend(&combined_cmdline, " ", addon->cmdline))
×
UNCOV
1783
                        return log_oom();
×
1784
        }
1785

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

1794
        r = sd_json_variant_merge_objectbo(
9✔
1795
                        v,
1796
                        SD_JSON_BUILD_PAIR("addons", SD_JSON_BUILD_VARIANT(addons_array)),
1797
                        SD_JSON_BUILD_PAIR_CONDITION(!!combined_cmdline, "cmdline", SD_JSON_BUILD_STRING(combined_cmdline)));
1798
        if (r < 0)
9✔
UNCOV
1799
                return log_oom();
×
1800
        return 0;
1801
}
1802

1803
int show_boot_entry(
15✔
1804
                const BootEntry *e,
1805
                bool show_as_default,
1806
                bool show_as_selected,
1807
                bool show_reported) {
1808

1809
        int status = 0, r = 0;
15✔
1810

1811
        /* Returns 0 on success, negative on processing error, and positive if something is wrong with the
1812
           boot entry itself. */
1813

1814
        assert(e);
15✔
1815

1816
        printf("         type: %s\n",
15✔
1817
               boot_entry_type_to_string(e->type));
15✔
1818

1819
        printf("        title: %s%s%s",
45✔
1820
               ansi_highlight(), boot_entry_title(e), ansi_normal());
1821

1822
        if (show_as_default)
15✔
1823
                printf(" %s(default)%s",
4✔
1824
                       ansi_highlight_green(), ansi_normal());
1825

1826
        if (show_as_selected)
15✔
UNCOV
1827
                printf(" %s(selected)%s",
×
1828
                       ansi_highlight_magenta(), ansi_normal());
1829

1830
        if (show_reported) {
15✔
1831
                if (e->type == BOOT_ENTRY_LOADER)
6✔
UNCOV
1832
                        printf(" %s(reported/absent)%s",
×
1833
                               ansi_highlight_red(), ansi_normal());
1834
                else if (!e->reported_by_loader && e->type != BOOT_ENTRY_LOADER_AUTO)
6✔
1835
                        printf(" %s(not reported/new)%s",
12✔
1836
                               ansi_highlight_green(), ansi_normal());
1837
        }
1838

1839
        putchar('\n');
15✔
1840

1841
        if (e->id) {
15✔
1842
                printf("           id: %s", e->id);
15✔
1843

1844
                if (e->id_without_profile && !streq_ptr(e->id, e->id_without_profile))
15✔
1845
                        printf(" (without profile: %s)\n", e->id_without_profile);
15✔
1846
                else
UNCOV
1847
                        putchar('\n');
×
1848
        }
1849
        if (e->path) {
15✔
1850
                _cleanup_free_ char *text = NULL, *link = NULL;
15✔
1851

1852
                const char *p = e->root ? path_startswith(e->path, e->root) : NULL;
15✔
1853
                if (p) {
15✔
1854
                        text = strjoin(ansi_grey(), e->root, "/", ansi_normal(), "/", p);
30✔
1855
                        if (!text)
15✔
UNCOV
1856
                                return log_oom();
×
1857
                }
1858

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

1864
                printf("       source: %s (on the %s)\n",
15✔
1865
                       link ?: text ?: e->path,
15✔
1866
                       boot_entry_source_to_string(e->source));
15✔
1867
        }
1868
        if (e->tries_left != UINT_MAX) {
15✔
1869
                printf("        tries: %u left", e->tries_left);
×
1870

UNCOV
1871
                if (e->tries_done != UINT_MAX)
×
1872
                        printf("; %u done\n", e->tries_done);
×
1873
                else
UNCOV
1874
                        putchar('\n');
×
1875
        }
1876

1877
        if (e->sort_key)
15✔
1878
                printf("     sort-key: %s\n", e->sort_key);
15✔
1879
        if (e->version)
15✔
1880
                printf("      version: %s\n", e->version);
15✔
1881
        if (e->machine_id)
15✔
1882
                printf("   machine-id: %s\n", e->machine_id);
×
1883
        if (e->architecture)
15✔
UNCOV
1884
                printf(" architecture: %s\n", e->architecture);
×
1885
        if (e->kernel)
15✔
1886
                boot_entry_file_list("linux", e->root, e->kernel, &status);
15✔
1887
        if (e->efi)
15✔
UNCOV
1888
                boot_entry_file_list("efi", e->root, e->efi, &status);
×
1889

1890
        STRV_FOREACH(s, e->initrd)
15✔
UNCOV
1891
                boot_entry_file_list(s == e->initrd ? "initrd" : NULL,
×
UNCOV
1892
                                     e->root,
×
1893
                                     *s,
1894
                                     &status);
1895

1896
        r = print_cmdline(e);
15✔
1897
        if (r < 0)
15✔
1898
                return r;
1899

1900
        if (e->device_tree)
15✔
UNCOV
1901
                boot_entry_file_list("devicetree", e->root, e->device_tree, &status);
×
1902

1903
        STRV_FOREACH(s, e->device_tree_overlay)
15✔
UNCOV
1904
                boot_entry_file_list(s == e->device_tree_overlay ? "devicetree-overlay" : NULL,
×
UNCOV
1905
                                     e->root,
×
1906
                                     *s,
1907
                                     &status);
1908

1909
        return -status;
15✔
1910
}
1911

1912
int boot_entry_to_json(const BootConfig *c, size_t i, sd_json_variant **ret) {
9✔
1913
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
9✔
1914
        _cleanup_free_ char *opts = NULL;
9✔
1915
        const BootEntry *e;
9✔
1916
        int r;
9✔
1917

1918
        assert(c);
9✔
1919
        assert(ret);
9✔
1920

1921
        if (i >= c->n_entries) {
9✔
UNCOV
1922
                *ret = NULL;
×
UNCOV
1923
                return 0;
×
1924
        }
1925

1926
        e = c->entries + i;
9✔
1927

1928
        if (!strv_isempty(e->options)) {
9✔
1929
                opts = strv_join(e->options, " ");
9✔
1930
                if (!opts)
9✔
UNCOV
1931
                        return log_oom();
×
1932
        }
1933

1934
        r = sd_json_variant_merge_objectbo(
9✔
1935
                        &v,
1936
                        SD_JSON_BUILD_PAIR("type", SD_JSON_BUILD_STRING(boot_entry_type_json_to_string(e->type))),
1937
                        SD_JSON_BUILD_PAIR("source", SD_JSON_BUILD_STRING(boot_entry_source_json_to_string(e->source))),
1938
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->id, "id", SD_JSON_BUILD_STRING(e->id)),
1939
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->path, "path", SD_JSON_BUILD_STRING(e->path)),
1940
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->root, "root", SD_JSON_BUILD_STRING(e->root)),
1941
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->title, "title", SD_JSON_BUILD_STRING(e->title)),
1942
                        SD_JSON_BUILD_PAIR_CONDITION(!!boot_entry_title(e), "showTitle", SD_JSON_BUILD_STRING(boot_entry_title(e))),
1943
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->sort_key, "sortKey", SD_JSON_BUILD_STRING(e->sort_key)),
1944
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->version, "version", SD_JSON_BUILD_STRING(e->version)),
1945
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->machine_id, "machineId", SD_JSON_BUILD_STRING(e->machine_id)),
1946
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->architecture, "architecture", SD_JSON_BUILD_STRING(e->architecture)),
1947
                        SD_JSON_BUILD_PAIR_CONDITION(!!opts, "options", SD_JSON_BUILD_STRING(opts)),
1948
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->kernel, "linux", SD_JSON_BUILD_STRING(e->kernel)),
1949
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->efi, "efi", SD_JSON_BUILD_STRING(e->efi)),
1950
                        SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(e->initrd), "initrd", SD_JSON_BUILD_STRV(e->initrd)),
1951
                        SD_JSON_BUILD_PAIR_CONDITION(!!e->device_tree, "devicetree", SD_JSON_BUILD_STRING(e->device_tree)),
1952
                        SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(e->device_tree_overlay), "devicetreeOverlay", SD_JSON_BUILD_STRV(e->device_tree_overlay)));
1953
        if (r < 0)
9✔
UNCOV
1954
                return log_oom();
×
1955

1956
        /* Sanitizers (only memory sanitizer?) do not like function call with too many
1957
         * arguments and trigger false positive warnings. Let's not add too many json objects
1958
         * at once. */
1959
        r = sd_json_variant_merge_objectbo(
9✔
1960
                        &v,
1961
                        SD_JSON_BUILD_PAIR("isReported", SD_JSON_BUILD_BOOLEAN(e->reported_by_loader)),
1962
                        SD_JSON_BUILD_PAIR_CONDITION(e->tries_left != UINT_MAX, "triesLeft", SD_JSON_BUILD_UNSIGNED(e->tries_left)),
1963
                        SD_JSON_BUILD_PAIR_CONDITION(e->tries_done != UINT_MAX, "triesDone", SD_JSON_BUILD_UNSIGNED(e->tries_done)),
1964
                        SD_JSON_BUILD_PAIR_CONDITION(c->default_entry >= 0, "isDefault", SD_JSON_BUILD_BOOLEAN(i == (size_t) c->default_entry)),
1965
                        SD_JSON_BUILD_PAIR_CONDITION(c->selected_entry >= 0, "isSelected", SD_JSON_BUILD_BOOLEAN(i == (size_t) c->selected_entry)));
1966
        if (r < 0)
9✔
UNCOV
1967
                return log_oom();
×
1968

1969
        r = json_cmdline(e, opts, &v);
9✔
1970
        if (r < 0)
9✔
UNCOV
1971
                return log_oom();
×
1972

1973
        *ret = TAKE_PTR(v);
9✔
1974
        return 1;
9✔
1975
}
1976

1977
int show_boot_entries(const BootConfig *config, sd_json_format_flags_t json_format) {
8✔
1978
        int r;
8✔
1979

1980
        assert(config);
8✔
1981

1982
        if (sd_json_format_enabled(json_format)) {
8✔
1983
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL;
6✔
1984

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

1988
                        r = boot_entry_to_json(config, i, &v);
6✔
1989
                        if (r < 0)
6✔
UNCOV
1990
                                return log_oom();
×
1991

1992
                        r = sd_json_variant_append_array(&array, v);
6✔
1993
                        if (r < 0)
6✔
UNCOV
1994
                                return log_oom();
×
1995
                }
1996

1997
                return sd_json_variant_dump(array, json_format | SD_JSON_FORMAT_EMPTY_ARRAY, NULL, NULL);
6✔
1998
        } else
1999
                for (size_t n = 0; n < config->n_entries; n++) {
8✔
2000
                        r = show_boot_entry(
12✔
2001
                                        config->entries + n,
6✔
2002
                                        /* show_as_default= */  n == (size_t) config->default_entry,
6✔
2003
                                        /* show_as_selected= */ n == (size_t) config->selected_entry,
6✔
2004
                                        /* show_reported= */  true);
2005
                        if (r < 0)
6✔
2006
                                return r;
2007

2008
                        if (n+1 < config->n_entries)
6✔
2009
                                putchar('\n');
4✔
2010
                }
2011

2012
        return 0;
2013
}
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