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(&sections[i].PointerToRawData);
609                 size = unaligned_read_le32(&sections[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