1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <unistd.h>
4
5 #include "bootspec-fundamental.h"
6 #include "bootspec.h"
7 #include "conf-files.h"
8 #include "devnum-util.h"
9 #include "dirent-util.h"
10 #include "efi-loader.h"
11 #include "env-file.h"
12 #include "errno-util.h"
13 #include "fd-util.h"
14 #include "fileio.h"
15 #include "find-esp.h"
16 #include "path-util.h"
17 #include "pe-header.h"
18 #include "pretty-print.h"
19 #include "recurse-dir.h"
20 #include "sort-util.h"
21 #include "string-table.h"
22 #include "strv.h"
23 #include "terminal-util.h"
24 #include "unaligned.h"
25
26 static const char* const boot_entry_type_table[_BOOT_ENTRY_TYPE_MAX] = {
27 [BOOT_ENTRY_CONF] = "Boot Loader Specification Type #1 (.conf)",
28 [BOOT_ENTRY_UNIFIED] = "Boot Loader Specification Type #2 (.efi)",
29 [BOOT_ENTRY_LOADER] = "Reported by Boot Loader",
30 [BOOT_ENTRY_LOADER_AUTO] = "Automatic",
31 };
32
33 DEFINE_STRING_TABLE_LOOKUP_TO_STRING(boot_entry_type, BootEntryType);
34
boot_entry_free(BootEntry * entry)35 static void boot_entry_free(BootEntry *entry) {
36 assert(entry);
37
38 free(entry->id);
39 free(entry->id_old);
40 free(entry->path);
41 free(entry->root);
42 free(entry->title);
43 free(entry->show_title);
44 free(entry->sort_key);
45 free(entry->version);
46 free(entry->machine_id);
47 free(entry->architecture);
48 strv_free(entry->options);
49 free(entry->kernel);
50 free(entry->efi);
51 strv_free(entry->initrd);
52 free(entry->device_tree);
53 strv_free(entry->device_tree_overlay);
54 }
55
boot_entry_load_type1(FILE * f,const char * root,const char * dir,const char * id,BootEntry * entry)56 static int boot_entry_load_type1(
57 FILE *f,
58 const char *root,
59 const char *dir,
60 const char *id,
61 BootEntry *entry) {
62
63 _cleanup_(boot_entry_free) BootEntry tmp = {
64 .type = BOOT_ENTRY_CONF,
65 };
66
67 unsigned line = 1;
68 char *c;
69 int r;
70
71 assert(f);
72 assert(root);
73 assert(dir);
74 assert(id);
75 assert(entry);
76
77 /* Loads a Type #1 boot menu entry from the specified FILE* object */
78
79 if (!efi_loader_entry_name_valid(id))
80 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry name: %s", id);
81
82 c = endswith_no_case(id, ".conf");
83 if (!c)
84 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry file suffix: %s", id);
85
86 tmp.id = strdup(id);
87 if (!tmp.id)
88 return log_oom();
89
90 tmp.id_old = strndup(id, c - id); /* Without .conf suffix */
91 if (!tmp.id_old)
92 return log_oom();
93
94 tmp.path = path_join(dir, id);
95 if (!tmp.path)
96 return log_oom();
97
98 tmp.root = strdup(root);
99 if (!tmp.root)
100 return log_oom();
101
102 for (;;) {
103 _cleanup_free_ char *buf = NULL, *field = NULL;
104 const char *p;
105
106 r = read_line(f, LONG_LINE_MAX, &buf);
107 if (r == 0)
108 break;
109 if (r == -ENOBUFS)
110 return log_error_errno(r, "%s:%u: Line too long", tmp.path, line);
111 if (r < 0)
112 return log_error_errno(r, "%s:%u: Error while reading: %m", tmp.path, line);
113
114 line++;
115
116 if (IN_SET(*strstrip(buf), '#', '\0'))
117 continue;
118
119 p = buf;
120 r = extract_first_word(&p, &field, " \t", 0);
121 if (r < 0) {
122 log_error_errno(r, "Failed to parse config file %s line %u: %m", tmp.path, line);
123 continue;
124 }
125 if (r == 0) {
126 log_warning("%s:%u: Bad syntax", tmp.path, line);
127 continue;
128 }
129
130 if (isempty(p)) {
131 /* Some fields can reasonably have an empty value. In other cases warn. */
132 if (!STR_IN_SET(field, "options", "devicetree-overlay"))
133 log_warning("%s:%u: Field %s without value", tmp.path, line, field);
134 continue;
135 }
136
137 if (streq(field, "title"))
138 r = free_and_strdup(&tmp.title, p);
139 else if (streq(field, "sort-key"))
140 r = free_and_strdup(&tmp.sort_key, p);
141 else if (streq(field, "version"))
142 r = free_and_strdup(&tmp.version, p);
143 else if (streq(field, "machine-id"))
144 r = free_and_strdup(&tmp.machine_id, p);
145 else if (streq(field, "architecture"))
146 r = free_and_strdup(&tmp.architecture, p);
147 else if (streq(field, "options"))
148 r = strv_extend(&tmp.options, p);
149 else if (streq(field, "linux"))
150 r = free_and_strdup(&tmp.kernel, p);
151 else if (streq(field, "efi"))
152 r = free_and_strdup(&tmp.efi, p);
153 else if (streq(field, "initrd"))
154 r = strv_extend(&tmp.initrd, p);
155 else if (streq(field, "devicetree"))
156 r = free_and_strdup(&tmp.device_tree, p);
157 else if (streq(field, "devicetree-overlay")) {
158 _cleanup_strv_free_ char **l = NULL;
159
160 l = strv_split(p, NULL);
161 if (!l)
162 return log_oom();
163
164 r = strv_extend_strv(&tmp.device_tree_overlay, l, false);
165 } else {
166 log_notice("%s:%u: Unknown line \"%s\", ignoring.", tmp.path, line, field);
167 continue;
168 }
169 if (r < 0)
170 return log_error_errno(r, "%s:%u: Error while reading: %m", tmp.path, line);
171 }
172
173 *entry = tmp;
174 tmp = (BootEntry) {};
175 return 0;
176 }
177
boot_config_load_type1(BootConfig * config,FILE * f,const char * root,const char * dir,const char * id)178 int boot_config_load_type1(
179 BootConfig *config,
180 FILE *f,
181 const char *root,
182 const char *dir,
183 const char *id) {
184 int r;
185
186 assert(config);
187 assert(f);
188 assert(root);
189 assert(dir);
190 assert(id);
191
192 if (!GREEDY_REALLOC0(config->entries, config->n_entries + 1))
193 return log_oom();
194
195 r = boot_entry_load_type1(f, root, dir, id, config->entries + config->n_entries);
196 if (r < 0)
197 return r;
198
199 config->n_entries++;
200 return 0;
201 }
202
boot_config_free(BootConfig * config)203 void boot_config_free(BootConfig *config) {
204 assert(config);
205
206 free(config->default_pattern);
207 free(config->timeout);
208 free(config->editor);
209 free(config->auto_entries);
210 free(config->auto_firmware);
211 free(config->console_mode);
212 free(config->random_seed_mode);
213 free(config->beep);
214
215 free(config->entry_oneshot);
216 free(config->entry_default);
217 free(config->entry_selected);
218
219 for (size_t i = 0; i < config->n_entries; i++)
220 boot_entry_free(config->entries + i);
221 free(config->entries);
222
223 set_free(config->inodes_seen);
224 }
225
boot_loader_read_conf(BootConfig * config,FILE * file,const char * path)226 int boot_loader_read_conf(BootConfig *config, FILE *file, const char *path) {
227 unsigned line = 1;
228 int r;
229
230 assert(config);
231 assert(file);
232 assert(path);
233
234 for (;;) {
235 _cleanup_free_ char *buf = NULL, *field = NULL;
236 const char *p;
237
238 r = read_line(file, LONG_LINE_MAX, &buf);
239 if (r == 0)
240 break;
241 if (r == -ENOBUFS)
242 return log_error_errno(r, "%s:%u: Line too long", path, line);
243 if (r < 0)
244 return log_error_errno(r, "%s:%u: Error while reading: %m", path, line);
245
246 line++;
247
248 if (IN_SET(*strstrip(buf), '#', '\0'))
249 continue;
250
251 p = buf;
252 r = extract_first_word(&p, &field, " \t", 0);
253 if (r < 0) {
254 log_error_errno(r, "Failed to parse config file %s line %u: %m", path, line);
255 continue;
256 }
257 if (r == 0) {
258 log_warning("%s:%u: Bad syntax", path, line);
259 continue;
260 }
261
262 if (streq(field, "default"))
263 r = free_and_strdup(&config->default_pattern, p);
264 else if (streq(field, "timeout"))
265 r = free_and_strdup(&config->timeout, p);
266 else if (streq(field, "editor"))
267 r = free_and_strdup(&config->editor, p);
268 else if (streq(field, "auto-entries"))
269 r = free_and_strdup(&config->auto_entries, p);
270 else if (streq(field, "auto-firmware"))
271 r = free_and_strdup(&config->auto_firmware, p);
272 else if (streq(field, "console-mode"))
273 r = free_and_strdup(&config->console_mode, p);
274 else if (streq(field, "random-seed-mode"))
275 r = free_and_strdup(&config->random_seed_mode, p);
276 else if (streq(field, "beep"))
277 r = free_and_strdup(&config->beep, p);
278 else {
279 log_notice("%s:%u: Unknown line \"%s\", ignoring.", path, line, field);
280 continue;
281 }
282 if (r < 0)
283 return log_error_errno(r, "%s:%u: Error while reading: %m", path, line);
284 }
285
286 return 1;
287 }
288
boot_loader_read_conf_path(BootConfig * config,const char * path)289 static int boot_loader_read_conf_path(BootConfig *config, const char *path) {
290 _cleanup_fclose_ FILE *f = NULL;
291
292 assert(config);
293 assert(path);
294
295 f = fopen(path, "re");
296 if (!f) {
297 if (errno == ENOENT)
298 return 0;
299
300 return log_error_errno(errno, "Failed to open \"%s\": %m", path);
301 }
302
303 return boot_loader_read_conf(config, f, path);
304 }
305
boot_entry_compare(const BootEntry * a,const BootEntry * b)306 static int boot_entry_compare(const BootEntry *a, const BootEntry *b) {
307 int r;
308
309 assert(a);
310 assert(b);
311
312 r = CMP(!a->sort_key, !b->sort_key);
313 if (r != 0)
314 return r;
315
316 if (a->sort_key && b->sort_key) {
317 r = strcmp(a->sort_key, b->sort_key);
318 if (r != 0)
319 return r;
320
321 r = strcmp_ptr(a->machine_id, b->machine_id);
322 if (r != 0)
323 return r;
324
325 r = -strverscmp_improved(a->version, b->version);
326 if (r != 0)
327 return r;
328 }
329
330 return -strverscmp_improved(a->id, b->id);
331 }
332
inode_hash_func(const struct stat * q,struct siphash * state)333 static void inode_hash_func(const struct stat *q, struct siphash *state) {
334 siphash24_compress(&q->st_dev, sizeof(q->st_dev), state);
335 siphash24_compress(&q->st_ino, sizeof(q->st_ino), state);
336 }
337
inode_compare_func(const struct stat * a,const struct stat * b)338 static int inode_compare_func(const struct stat *a, const struct stat *b) {
339 int r;
340
341 r = CMP(a->st_dev, b->st_dev);
342 if (r != 0)
343 return r;
344
345 return CMP(a->st_ino, b->st_ino);
346 }
347
348 DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(inode_hash_ops, struct stat, inode_hash_func, inode_compare_func, free);
349
config_check_inode_relevant_and_unseen(BootConfig * config,int fd,const char * fname)350 static int config_check_inode_relevant_and_unseen(BootConfig *config, int fd, const char *fname) {
351 _cleanup_free_ char *d = NULL;
352 struct stat st;
353
354 assert(config);
355 assert(fd >= 0);
356 assert(fname);
357
358 /* So, here's the thing: because of the mess around /efi/ vs. /boot/ vs. /boot/efi/ it might be that
359 * people have these dirs, or subdirs of them symlinked or bind mounted, and we might end up
360 * iterating though some dirs multiple times. Let's thus rather be safe than sorry, and track the
361 * inodes we already processed: let's ignore inodes we have seen already. This should be robust
362 * against any form of symlinking or bind mounting, and effectively suppress any such duplicates. */
363
364 if (fstat(fd, &st) < 0)
365 return log_error_errno(errno, "Failed to stat('%s'): %m", fname);
366 if (!S_ISREG(st.st_mode)) {
367 log_debug("File '%s' is not a reguar file, ignoring.", fname);
368 return false;
369 }
370
371 if (set_contains(config->inodes_seen, &st)) {
372 log_debug("Inode '%s' already seen before, ignoring.", fname);
373 return false;
374 }
375 d = memdup(&st, sizeof(st));
376 if (!d)
377 return log_oom();
378 if (set_ensure_put(&config->inodes_seen, &inode_hash_ops, d) < 0)
379 return log_oom();
380
381 TAKE_PTR(d);
382 return true;
383 }
384
boot_entries_find_type1(BootConfig * config,const char * root,const char * dir)385 static int boot_entries_find_type1(
386 BootConfig *config,
387 const char *root,
388 const char *dir) {
389
390 _cleanup_free_ DirectoryEntries *dentries = NULL;
391 _cleanup_close_ int dir_fd = -1;
392 int r;
393
394 assert(config);
395 assert(root);
396 assert(dir);
397
398 dir_fd = open(dir, O_DIRECTORY|O_CLOEXEC);
399 if (dir_fd < 0) {
400 if (errno == ENOENT)
401 return 0;
402
403 return log_error_errno(errno, "Failed to open '%s': %m", dir);
404 }
405
406 r = readdir_all(dir_fd, RECURSE_DIR_IGNORE_DOT, &dentries);
407 if (r < 0)
408 return log_error_errno(r, "Failed to read directory '%s': %m", dir);
409
410 for (size_t i = 0; i < dentries->n_entries; i++) {
411 const struct dirent *de = dentries->entries[i];
412 _cleanup_fclose_ FILE *f = NULL;
413
414 if (!dirent_is_file(de))
415 continue;
416
417 if (!endswith_no_case(de->d_name, ".conf"))
418 continue;
419
420 r = xfopenat(dir_fd, de->d_name, "re", 0, &f);
421 if (r < 0) {
422 log_warning_errno(r, "Failed to open %s/%s, ignoring: %m", dir, de->d_name);
423 continue;
424 }
425
426 r = config_check_inode_relevant_and_unseen(config, fileno(f), de->d_name);
427 if (r < 0)
428 return r;
429 if (r == 0) /* inode already seen or otherwise not relevant */
430 continue;
431
432 r = boot_config_load_type1(config, f, root, dir, de->d_name);
433 if (r == -ENOMEM)
434 return r;
435 }
436
437 return 0;
438 }
439
boot_entry_load_unified(const char * root,const char * path,const char * osrelease,const char * cmdline,BootEntry * ret)440 static int boot_entry_load_unified(
441 const char *root,
442 const char *path,
443 const char *osrelease,
444 const char *cmdline,
445 BootEntry *ret) {
446
447 _cleanup_free_ char *os_pretty_name = NULL, *os_image_id = NULL, *os_name = NULL, *os_id = NULL,
448 *os_image_version = NULL, *os_version = NULL, *os_version_id = NULL, *os_build_id = NULL;
449 _cleanup_(boot_entry_free) BootEntry tmp = {
450 .type = BOOT_ENTRY_UNIFIED,
451 };
452 const char *k, *good_name, *good_version, *good_sort_key;
453 _cleanup_fclose_ FILE *f = NULL;
454 int r;
455
456 assert(root);
457 assert(path);
458 assert(osrelease);
459
460 k = path_startswith(path, root);
461 if (!k)
462 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Path is not below root: %s", path);
463
464 f = fmemopen_unlocked((void*) osrelease, strlen(osrelease), "r");
465 if (!f)
466 return log_error_errno(errno, "Failed to open os-release buffer: %m");
467
468 r = parse_env_file(f, "os-release",
469 "PRETTY_NAME", &os_pretty_name,
470 "IMAGE_ID", &os_image_id,
471 "NAME", &os_name,
472 "ID", &os_id,
473 "IMAGE_VERSION", &os_image_version,
474 "VERSION", &os_version,
475 "VERSION_ID", &os_version_id,
476 "BUILD_ID", &os_build_id);
477 if (r < 0)
478 return log_error_errno(r, "Failed to parse os-release data from unified kernel image %s: %m", path);
479
480 if (!bootspec_pick_name_version_sort_key(
481 os_pretty_name,
482 os_image_id,
483 os_name,
484 os_id,
485 os_image_version,
486 os_version,
487 os_version_id,
488 os_build_id,
489 &good_name,
490 &good_version,
491 &good_sort_key))
492 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Missing fields in os-release data from unified kernel image %s, refusing.", path);
493
494 r = path_extract_filename(path, &tmp.id);
495 if (r < 0)
496 return log_error_errno(r, "Failed to extract file name from '%s': %m", path);
497
498 if (!efi_loader_entry_name_valid(tmp.id))
499 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry name: %s", tmp.id);
500
501 if (os_id && os_version_id) {
502 tmp.id_old = strjoin(os_id, "-", os_version_id);
503 if (!tmp.id_old)
504 return log_oom();
505 }
506
507 tmp.path = strdup(path);
508 if (!tmp.path)
509 return log_oom();
510
511 tmp.root = strdup(root);
512 if (!tmp.root)
513 return log_oom();
514
515 tmp.kernel = strdup(skip_leading_chars(k, "/"));
516 if (!tmp.kernel)
517 return log_oom();
518
519 tmp.options = strv_new(skip_leading_chars(cmdline, WHITESPACE));
520 if (!tmp.options)
521 return log_oom();
522
523 delete_trailing_chars(tmp.options[0], WHITESPACE);
524
525 tmp.title = strdup(good_name);
526 if (!tmp.title)
527 return log_oom();
528
529 tmp.sort_key = strdup(good_sort_key);
530 if (!tmp.sort_key)
531 return log_oom();
532
533 tmp.version = strdup(good_version);
534 if (!tmp.version)
535 return log_oom();
536
537 *ret = tmp;
538 tmp = (BootEntry) {};
539 return 0;
540 }
541
542 /* Maximum PE section we are willing to load (Note that sections we are not interested in may be larger, but
543 * the ones we do care about and we are willing to load into memory have this size limit.) */
544 #define PE_SECTION_SIZE_MAX (4U*1024U*1024U)
545
find_sections(int fd,char ** ret_osrelease,char ** ret_cmdline)546 static int find_sections(
547 int fd,
548 char **ret_osrelease,
549 char **ret_cmdline) {
550
551 _cleanup_free_ struct PeSectionHeader *sections = NULL;
552 _cleanup_free_ char *osrelease = NULL, *cmdline = NULL;
553 ssize_t n;
554
555 struct DosFileHeader dos;
556 n = pread(fd, &dos, sizeof(dos), 0);
557 if (n < 0)
558 return log_error_errno(errno, "Failed read DOS header: %m");
559 if (n != sizeof(dos))
560 return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short read while reading DOS header, refusing.");
561
562 if (dos.Magic[0] != 'M' || dos.Magic[1] != 'Z')
563 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "DOS executable magic missing, refusing.");
564
565 uint64_t start = unaligned_read_le32(&dos.ExeHeader);
566
567 struct PeHeader pe;
568 n = pread(fd, &pe, sizeof(pe), start);
569 if (n < 0)
570 return log_error_errno(errno, "Failed to read PE header: %m");
571 if (n != sizeof(pe))
572 return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short read while reading PE header, refusing.");
573
574 if (pe.Magic[0] != 'P' || pe.Magic[1] != 'E' || pe.Magic[2] != 0 || pe.Magic[3] != 0)
575 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "PE executable magic missing, refusing.");
576
577 size_t n_sections = unaligned_read_le16(&pe.FileHeader.NumberOfSections);
578 if (n_sections > 96)
579 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "PE header has too many sections, refusing.");
580
581 sections = new(struct PeSectionHeader, n_sections);
582 if (!sections)
583 return log_oom();
584
585 n = pread(fd, sections,
586 n_sections * sizeof(struct PeSectionHeader),
587 start + sizeof(pe) + unaligned_read_le16(&pe.FileHeader.SizeOfOptionalHeader));
588 if (n < 0)
589 return log_error_errno(errno, "Failed to read section data: %m");
590 if ((size_t) n != n_sections * sizeof(struct PeSectionHeader))
591 return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short read while reading sections, refusing.");
592
593 for (size_t i = 0; i < n_sections; i++) {
594 _cleanup_free_ char *k = NULL;
595 uint32_t offset, size;
596 char **b;
597
598 if (strneq((char*) sections[i].Name, ".osrel", sizeof(sections[i].Name)))
599 b = &osrelease;
600 else if (strneq((char*) sections[i].Name, ".cmdline", sizeof(sections[i].Name)))
601 b = &cmdline;
602 else
603 continue;
604
605 if (*b)
606 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Duplicate section %s, refusing.", sections[i].Name);
607
608 offset = unaligned_read_le32(§ions[i].PointerToRawData);
609 size = unaligned_read_le32(§ions[i].VirtualSize);
610
611 if (size > PE_SECTION_SIZE_MAX)
612 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Section %s too large, refusing.", sections[i].Name);
613
614 k = new(char, size+1);
615 if (!k)
616 return log_oom();
617
618 n = pread(fd, k, size, offset);
619 if (n < 0)
620 return log_error_errno(errno, "Failed to read section payload: %m");
621 if ((size_t) n != size)
622 return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short read while reading section payload, refusing:");
623
624 /* Allow one trailing NUL byte, but nothing more. */
625 if (size > 0 && memchr(k, 0, size - 1))
626 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Section contains embedded NUL byte: %m");
627
628 k[size] = 0;
629 *b = TAKE_PTR(k);
630 }
631
632 if (!osrelease)
633 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Image lacks .osrel section, refusing.");
634
635 if (ret_osrelease)
636 *ret_osrelease = TAKE_PTR(osrelease);
637 if (ret_cmdline)
638 *ret_cmdline = TAKE_PTR(cmdline);
639
640 return 0;
641 }
642
boot_entries_find_unified(BootConfig * config,const char * root,const char * dir)643 static int boot_entries_find_unified(
644 BootConfig *config,
645 const char *root,
646 const char *dir) {
647
648 _cleanup_(closedirp) DIR *d = NULL;
649 int r;
650
651 assert(config);
652 assert(dir);
653
654 d = opendir(dir);
655 if (!d) {
656 if (errno == ENOENT)
657 return 0;
658
659 return log_error_errno(errno, "Failed to open %s: %m", dir);
660 }
661
662 FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read %s: %m", dir)) {
663 _cleanup_free_ char *j = NULL, *osrelease = NULL, *cmdline = NULL;
664 _cleanup_close_ int fd = -1;
665
666 if (!dirent_is_file(de))
667 continue;
668
669 if (!endswith_no_case(de->d_name, ".efi"))
670 continue;
671
672 if (!GREEDY_REALLOC0(config->entries, config->n_entries + 1))
673 return log_oom();
674
675 fd = openat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC|O_NONBLOCK);
676 if (fd < 0) {
677 log_warning_errno(errno, "Failed to open %s/%s, ignoring: %m", dir, de->d_name);
678 continue;
679 }
680
681 r = config_check_inode_relevant_and_unseen(config, fd, de->d_name);
682 if (r < 0)
683 return r;
684 if (r == 0) /* inode already seen or otherwise not relevant */
685 continue;
686
687 if (find_sections(fd, &osrelease, &cmdline) < 0)
688 continue;
689
690 j = path_join(dir, de->d_name);
691 if (!j)
692 return log_oom();
693
694 r = boot_entry_load_unified(root, j, osrelease, cmdline, config->entries + config->n_entries);
695 if (r < 0)
696 continue;
697
698 config->n_entries++;
699 }
700
701 return 0;
702 }
703
find_nonunique(const BootEntry * entries,size_t n_entries,bool arr[])704 static bool find_nonunique(const BootEntry *entries, size_t n_entries, bool arr[]) {
705 bool non_unique = false;
706
707 assert(entries || n_entries == 0);
708 assert(arr || n_entries == 0);
709
710 for (size_t i = 0; i < n_entries; i++)
711 arr[i] = false;
712
713 for (size_t i = 0; i < n_entries; i++)
714 for (size_t j = 0; j < n_entries; j++)
715 if (i != j && streq(boot_entry_title(entries + i),
716 boot_entry_title(entries + j)))
717 non_unique = arr[i] = arr[j] = true;
718
719 return non_unique;
720 }
721
boot_entries_uniquify(BootEntry * entries,size_t n_entries)722 static int boot_entries_uniquify(BootEntry *entries, size_t n_entries) {
723 _cleanup_free_ bool *arr = NULL;
724 char *s;
725
726 assert(entries || n_entries == 0);
727
728 if (n_entries == 0)
729 return 0;
730
731 arr = new(bool, n_entries);
732 if (!arr)
733 return -ENOMEM;
734
735 /* Find _all_ non-unique titles */
736 if (!find_nonunique(entries, n_entries, arr))
737 return 0;
738
739 /* Add version to non-unique titles */
740 for (size_t i = 0; i < n_entries; i++)
741 if (arr[i] && entries[i].version) {
742 if (asprintf(&s, "%s (%s)", boot_entry_title(entries + i), entries[i].version) < 0)
743 return -ENOMEM;
744
745 free_and_replace(entries[i].show_title, s);
746 }
747
748 if (!find_nonunique(entries, n_entries, arr))
749 return 0;
750
751 /* Add machine-id to non-unique titles */
752 for (size_t i = 0; i < n_entries; i++)
753 if (arr[i] && entries[i].machine_id) {
754 if (asprintf(&s, "%s (%s)", boot_entry_title(entries + i), entries[i].machine_id) < 0)
755 return -ENOMEM;
756
757 free_and_replace(entries[i].show_title, s);
758 }
759
760 if (!find_nonunique(entries, n_entries, arr))
761 return 0;
762
763 /* Add file name to non-unique titles */
764 for (size_t i = 0; i < n_entries; i++)
765 if (arr[i]) {
766 if (asprintf(&s, "%s (%s)", boot_entry_title(entries + i), entries[i].id) < 0)
767 return -ENOMEM;
768
769 free_and_replace(entries[i].show_title, s);
770 }
771
772 return 0;
773 }
774
boot_config_find(const BootConfig * config,const char * id)775 static int boot_config_find(const BootConfig *config, const char *id) {
776 assert(config);
777
778 if (!id)
779 return -1;
780
781 for (size_t i = 0; i < config->n_entries; i++)
782 if (fnmatch(id, config->entries[i].id, FNM_CASEFOLD) == 0)
783 return i;
784
785 return -1;
786 }
787
boot_entries_select_default(const BootConfig * config)788 static int boot_entries_select_default(const BootConfig *config) {
789 int i;
790
791 assert(config);
792 assert(config->entries || config->n_entries == 0);
793
794 if (config->n_entries == 0) {
795 log_debug("Found no default boot entry :(");
796 return -1; /* -1 means "no default" */
797 }
798
799 if (config->entry_oneshot) {
800 i = boot_config_find(config, config->entry_oneshot);
801 if (i >= 0) {
802 log_debug("Found default: id \"%s\" is matched by LoaderEntryOneShot",
803 config->entries[i].id);
804 return i;
805 }
806 }
807
808 if (config->entry_default) {
809 i = boot_config_find(config, config->entry_default);
810 if (i >= 0) {
811 log_debug("Found default: id \"%s\" is matched by LoaderEntryDefault",
812 config->entries[i].id);
813 return i;
814 }
815 }
816
817 if (config->default_pattern) {
818 i = boot_config_find(config, config->default_pattern);
819 if (i >= 0) {
820 log_debug("Found default: id \"%s\" is matched by pattern \"%s\"",
821 config->entries[i].id, config->default_pattern);
822 return i;
823 }
824 }
825
826 log_debug("Found default: first entry \"%s\"", config->entries[0].id);
827 return 0;
828 }
829
boot_entries_select_selected(const BootConfig * config)830 static int boot_entries_select_selected(const BootConfig *config) {
831 assert(config);
832 assert(config->entries || config->n_entries == 0);
833
834 if (!config->entry_selected || config->n_entries == 0)
835 return -1;
836
837 return boot_config_find(config, config->entry_selected);
838 }
839
boot_load_efi_entry_pointers(BootConfig * config)840 static int boot_load_efi_entry_pointers(BootConfig *config) {
841 int r;
842
843 assert(config);
844
845 if (!is_efi_boot())
846 return 0;
847
848 /* Loads the three "pointers" to boot loader entries from their EFI variables */
849
850 r = efi_get_variable_string(EFI_LOADER_VARIABLE(LoaderEntryOneShot), &config->entry_oneshot);
851 if (r == -ENOMEM)
852 return log_oom();
853 if (r < 0 && !IN_SET(r, -ENOENT, -ENODATA))
854 log_warning_errno(r, "Failed to read EFI variable \"LoaderEntryOneShot\", ignoring: %m");
855
856 r = efi_get_variable_string(EFI_LOADER_VARIABLE(LoaderEntryDefault), &config->entry_default);
857 if (r == -ENOMEM)
858 return log_oom();
859 if (r < 0 && !IN_SET(r, -ENOENT, -ENODATA))
860 log_warning_errno(r, "Failed to read EFI variable \"LoaderEntryDefault\", ignoring: %m");
861
862 r = efi_get_variable_string(EFI_LOADER_VARIABLE(LoaderEntrySelected), &config->entry_selected);
863 if (r == -ENOMEM)
864 return log_oom();
865 if (r < 0 && !IN_SET(r, -ENOENT, -ENODATA))
866 log_warning_errno(r, "Failed to read EFI variable \"LoaderEntrySelected\", ignoring: %m");
867
868 return 1;
869 }
870
boot_config_select_special_entries(BootConfig * config)871 int boot_config_select_special_entries(BootConfig *config) {
872 int r;
873
874 assert(config);
875
876 r = boot_load_efi_entry_pointers(config);
877 if (r < 0)
878 return r;
879
880 config->default_entry = boot_entries_select_default(config);
881 config->selected_entry = boot_entries_select_selected(config);
882
883 return 0;
884 }
885
boot_config_finalize(BootConfig * config)886 int boot_config_finalize(BootConfig *config) {
887 int r;
888
889 typesafe_qsort(config->entries, config->n_entries, boot_entry_compare);
890
891 r = boot_entries_uniquify(config->entries, config->n_entries);
892 if (r < 0)
893 return log_error_errno(r, "Failed to uniquify boot entries: %m");
894
895 return 0;
896 }
897
boot_config_load(BootConfig * config,const char * esp_path,const char * xbootldr_path)898 int boot_config_load(
899 BootConfig *config,
900 const char *esp_path,
901 const char *xbootldr_path) {
902
903 const char *p;
904 int r;
905
906 assert(config);
907
908 if (esp_path) {
909 p = strjoina(esp_path, "/loader/loader.conf");
910 r = boot_loader_read_conf_path(config, p);
911 if (r < 0)
912 return r;
913
914 p = strjoina(esp_path, "/loader/entries");
915 r = boot_entries_find_type1(config, esp_path, p);
916 if (r < 0)
917 return r;
918
919 p = strjoina(esp_path, "/EFI/Linux/");
920 r = boot_entries_find_unified(config, esp_path, p);
921 if (r < 0)
922 return r;
923 }
924
925 if (xbootldr_path) {
926 p = strjoina(xbootldr_path, "/loader/entries");
927 r = boot_entries_find_type1(config, xbootldr_path, p);
928 if (r < 0)
929 return r;
930
931 p = strjoina(xbootldr_path, "/EFI/Linux/");
932 r = boot_entries_find_unified(config, xbootldr_path, p);
933 if (r < 0)
934 return r;
935 }
936
937 return boot_config_finalize(config);
938 }
939
boot_config_load_auto(BootConfig * config,const char * override_esp_path,const char * override_xbootldr_path)940 int boot_config_load_auto(
941 BootConfig *config,
942 const char *override_esp_path,
943 const char *override_xbootldr_path) {
944
945 _cleanup_free_ char *esp_where = NULL, *xbootldr_where = NULL;
946 dev_t esp_devid = 0, xbootldr_devid = 0;
947 int r;
948
949 assert(config);
950
951 /* This function is similar to boot_entries_load_config(), however we automatically search for the
952 * ESP and the XBOOTLDR partition unless it is explicitly specified. Also, if the user did not pass
953 * an ESP or XBOOTLDR path directly, let's see if /run/boot-loader-entries/ exists. If so, let's
954 * read data from there, as if it was an ESP (i.e. loading both entries and loader.conf data from
955 * it). This allows other boot loaders to pass boot loader entry information to our tools if they
956 * want to. */
957
958 if (!override_esp_path && !override_xbootldr_path) {
959 if (access("/run/boot-loader-entries/", F_OK) >= 0)
960 return boot_config_load(config, "/run/boot-loader-entries/", NULL);
961
962 if (errno != ENOENT)
963 return log_error_errno(errno,
964 "Failed to determine whether /run/boot-loader-entries/ exists: %m");
965 }
966
967 r = find_esp_and_warn(override_esp_path, /* unprivileged_mode= */ false, &esp_where, NULL, NULL, NULL, NULL, &esp_devid);
968 if (r < 0) /* we don't log about ENOKEY here, but propagate it, leaving it to the caller to log */
969 return r;
970
971 r = find_xbootldr_and_warn(override_xbootldr_path, /* unprivileged_mode= */ false, &xbootldr_where, NULL, &xbootldr_devid);
972 if (r < 0 && r != -ENOKEY)
973 return r; /* It's fine if the XBOOTLDR partition doesn't exist, hence we ignore ENOKEY here */
974
975 /* If both paths actually refer to the same inode, suppress the xbootldr path */
976 if (esp_where && xbootldr_where && devnum_set_and_equal(esp_devid, xbootldr_devid))
977 xbootldr_where = mfree(xbootldr_where);
978
979 return boot_config_load(config, esp_where, xbootldr_where);
980 }
981
boot_config_augment_from_loader(BootConfig * config,char ** found_by_loader,bool only_auto)982 int boot_config_augment_from_loader(
983 BootConfig *config,
984 char **found_by_loader,
985 bool only_auto) {
986
987 static const char *const title_table[] = {
988 /* Pretty names for a few well-known automatically discovered entries. */
989 "auto-osx", "macOS",
990 "auto-windows", "Windows Boot Manager",
991 "auto-efi-shell", "EFI Shell",
992 "auto-efi-default", "EFI Default Loader",
993 "auto-reboot-to-firmware-setup", "Reboot Into Firmware Interface",
994 NULL,
995 };
996
997 assert(config);
998
999 /* Let's add the entries discovered by the boot loader to the end of our list, unless they are
1000 * already included there. */
1001
1002 STRV_FOREACH(i, found_by_loader) {
1003 BootEntry *existing;
1004 _cleanup_free_ char *c = NULL, *t = NULL, *p = NULL;
1005
1006 existing = boot_config_find_entry(config, *i);
1007 if (existing) {
1008 existing->reported_by_loader = true;
1009 continue;
1010 }
1011
1012 if (only_auto && !startswith(*i, "auto-"))
1013 continue;
1014
1015 c = strdup(*i);
1016 if (!c)
1017 return log_oom();
1018
1019 STRV_FOREACH_PAIR(a, b, title_table)
1020 if (streq(*a, *i)) {
1021 t = strdup(*b);
1022 if (!t)
1023 return log_oom();
1024 break;
1025 }
1026
1027 p = strdup(EFIVAR_PATH(EFI_LOADER_VARIABLE(LoaderEntries)));
1028 if (!p)
1029 return log_oom();
1030
1031 if (!GREEDY_REALLOC0(config->entries, config->n_entries + 1))
1032 return log_oom();
1033
1034 config->entries[config->n_entries++] = (BootEntry) {
1035 .type = startswith(*i, "auto-") ? BOOT_ENTRY_LOADER_AUTO : BOOT_ENTRY_LOADER,
1036 .id = TAKE_PTR(c),
1037 .title = TAKE_PTR(t),
1038 .path = TAKE_PTR(p),
1039 .reported_by_loader = true,
1040 };
1041 }
1042
1043 return 0;
1044 }
1045
boot_entry_file_check(const char * root,const char * p)1046 static int boot_entry_file_check(const char *root, const char *p) {
1047 _cleanup_free_ char *path = NULL;
1048
1049 path = path_join(root, p);
1050 if (!path)
1051 return log_oom();
1052
1053 return RET_NERRNO(access(path, F_OK));
1054 }
1055
boot_config_find_entry(BootConfig * config,const char * id)1056 BootEntry* boot_config_find_entry(BootConfig *config, const char *id) {
1057 assert(config);
1058 assert(id);
1059
1060 for (size_t j = 0; j < config->n_entries; j++)
1061 if (streq_ptr(config->entries[j].id, id) ||
1062 streq_ptr(config->entries[j].id_old, id))
1063 return config->entries + j;
1064
1065 return NULL;
1066 }
1067
boot_entry_file_list(const char * field,const char * root,const char * p,int * ret_status)1068 static void boot_entry_file_list(const char *field, const char *root, const char *p, int *ret_status) {
1069 int status = boot_entry_file_check(root, p);
1070
1071 printf("%13s%s ", strempty(field), field ? ":" : " ");
1072 if (status < 0) {
1073 errno = -status;
1074 printf("%s%s%s (%m)\n", ansi_highlight_red(), p, ansi_normal());
1075 } else
1076 printf("%s\n", p);
1077
1078 if (*ret_status == 0 && status < 0)
1079 *ret_status = status;
1080 }
1081
show_boot_entry(const BootEntry * e,bool show_as_default,bool show_as_selected,bool show_reported)1082 int show_boot_entry(
1083 const BootEntry *e,
1084 bool show_as_default,
1085 bool show_as_selected,
1086 bool show_reported) {
1087
1088 int status = 0;
1089
1090 /* Returns 0 on success, negative on processing error, and positive if something is wrong with the
1091 boot entry itself. */
1092
1093 assert(e);
1094
1095 printf(" type: %s\n",
1096 boot_entry_type_to_string(e->type));
1097
1098 printf(" title: %s%s%s",
1099 ansi_highlight(), boot_entry_title(e), ansi_normal());
1100
1101 if (show_as_default)
1102 printf(" %s(default)%s",
1103 ansi_highlight_green(), ansi_normal());
1104
1105 if (show_as_selected)
1106 printf(" %s(selected)%s",
1107 ansi_highlight_magenta(), ansi_normal());
1108
1109 if (show_reported) {
1110 if (e->type == BOOT_ENTRY_LOADER)
1111 printf(" %s(reported/absent)%s",
1112 ansi_highlight_red(), ansi_normal());
1113 else if (!e->reported_by_loader && e->type != BOOT_ENTRY_LOADER_AUTO)
1114 printf(" %s(not reported/new)%s",
1115 ansi_highlight_green(), ansi_normal());
1116 }
1117
1118 putchar('\n');
1119
1120 if (e->id)
1121 printf(" id: %s\n", e->id);
1122 if (e->path) {
1123 _cleanup_free_ char *link = NULL;
1124
1125 /* Let's urlify the link to make it easy to view in an editor, but only if it is a text
1126 * file. Unified images are binary ELFs, and EFI variables are not pure text either. */
1127 if (e->type == BOOT_ENTRY_CONF)
1128 (void) terminal_urlify_path(e->path, NULL, &link);
1129
1130 printf(" source: %s\n", link ?: e->path);
1131 }
1132 if (e->sort_key)
1133 printf(" sort-key: %s\n", e->sort_key);
1134 if (e->version)
1135 printf(" version: %s\n", e->version);
1136 if (e->machine_id)
1137 printf(" machine-id: %s\n", e->machine_id);
1138 if (e->architecture)
1139 printf(" architecture: %s\n", e->architecture);
1140 if (e->kernel)
1141 boot_entry_file_list("linux", e->root, e->kernel, &status);
1142
1143 STRV_FOREACH(s, e->initrd)
1144 boot_entry_file_list(s == e->initrd ? "initrd" : NULL,
1145 e->root,
1146 *s,
1147 &status);
1148
1149 if (!strv_isempty(e->options)) {
1150 _cleanup_free_ char *t = NULL, *t2 = NULL;
1151 _cleanup_strv_free_ char **ts = NULL;
1152
1153 t = strv_join(e->options, " ");
1154 if (!t)
1155 return log_oom();
1156
1157 ts = strv_split_newlines(t);
1158 if (!ts)
1159 return log_oom();
1160
1161 t2 = strv_join(ts, "\n ");
1162 if (!t2)
1163 return log_oom();
1164
1165 printf(" options: %s\n", t2);
1166 }
1167
1168 if (e->device_tree)
1169 boot_entry_file_list("devicetree", e->root, e->device_tree, &status);
1170
1171 STRV_FOREACH(s, e->device_tree_overlay)
1172 boot_entry_file_list(s == e->device_tree_overlay ? "devicetree-overlay" : NULL,
1173 e->root,
1174 *s,
1175 &status);
1176
1177 return -status;
1178 }
1179
show_boot_entries(const BootConfig * config,JsonFormatFlags json_format)1180 int show_boot_entries(const BootConfig *config, JsonFormatFlags json_format) {
1181 int r;
1182
1183 if (!FLAGS_SET(json_format, JSON_FORMAT_OFF)) {
1184 for (size_t i = 0; i < config->n_entries; i++) {
1185 _cleanup_free_ char *opts = NULL;
1186 const BootEntry *e = config->entries + i;
1187 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
1188
1189 if (!strv_isempty(e->options)) {
1190 opts = strv_join(e->options, " ");
1191 if (!opts)
1192 return log_oom();
1193 }
1194
1195 r = json_build(&v, JSON_BUILD_OBJECT(
1196 JSON_BUILD_PAIR_CONDITION(e->id, "id", JSON_BUILD_STRING(e->id)),
1197 JSON_BUILD_PAIR_CONDITION(e->path, "path", JSON_BUILD_STRING(e->path)),
1198 JSON_BUILD_PAIR_CONDITION(e->root, "root", JSON_BUILD_STRING(e->root)),
1199 JSON_BUILD_PAIR_CONDITION(e->title, "title", JSON_BUILD_STRING(e->title)),
1200 JSON_BUILD_PAIR_CONDITION(boot_entry_title(e), "showTitle", JSON_BUILD_STRING(boot_entry_title(e))),
1201 JSON_BUILD_PAIR_CONDITION(e->sort_key, "sortKey", JSON_BUILD_STRING(e->sort_key)),
1202 JSON_BUILD_PAIR_CONDITION(e->version, "version", JSON_BUILD_STRING(e->version)),
1203 JSON_BUILD_PAIR_CONDITION(e->machine_id, "machineId", JSON_BUILD_STRING(e->machine_id)),
1204 JSON_BUILD_PAIR_CONDITION(e->architecture, "architecture", JSON_BUILD_STRING(e->architecture)),
1205 JSON_BUILD_PAIR_CONDITION(opts, "options", JSON_BUILD_STRING(opts)),
1206 JSON_BUILD_PAIR_CONDITION(e->kernel, "linux", JSON_BUILD_STRING(e->kernel)),
1207 JSON_BUILD_PAIR_CONDITION(e->efi, "efi", JSON_BUILD_STRING(e->efi)),
1208 JSON_BUILD_PAIR_CONDITION(!strv_isempty(e->initrd), "initrd", JSON_BUILD_STRV(e->initrd)),
1209 JSON_BUILD_PAIR_CONDITION(e->device_tree, "devicetree", JSON_BUILD_STRING(e->device_tree)),
1210 JSON_BUILD_PAIR_CONDITION(!strv_isempty(e->device_tree_overlay), "devicetreeOverlay", JSON_BUILD_STRV(e->device_tree_overlay))));
1211 if (r < 0)
1212 return log_oom();
1213
1214 json_variant_dump(v, json_format, stdout, NULL);
1215 }
1216
1217 } else {
1218 printf("Boot Loader Entries:\n");
1219
1220 for (size_t n = 0; n < config->n_entries; n++) {
1221 r = show_boot_entry(
1222 config->entries + n,
1223 /* show_as_default= */ n == (size_t) config->default_entry,
1224 /* show_as_selected= */ n == (size_t) config->selected_entry,
1225 /* show_discovered= */ true);
1226 if (r < 0)
1227 return r;
1228
1229 if (n+1 < config->n_entries)
1230 putchar('\n');
1231 }
1232 }
1233
1234 return 0;
1235 }
1236