]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/bootspec.c
bootspec: load entries from both the ESP and XBOOTLDR partitions
[thirdparty/systemd.git] / src / shared / bootspec.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2
3 #include <stdio.h>
4 #include <linux/magic.h>
5
6 #include "sd-id128.h"
7
8 #include "alloc-util.h"
9 #include "blkid-util.h"
10 #include "bootspec.h"
11 #include "conf-files.h"
12 #include "def.h"
13 #include "device-nodes.h"
14 #include "efivars.h"
15 #include "env-util.h"
16 #include "fd-util.h"
17 #include "fileio.h"
18 #include "parse-util.h"
19 #include "path-util.h"
20 #include "stat-util.h"
21 #include "string-util.h"
22 #include "strv.h"
23 #include "virt.h"
24
25 static void boot_entry_free(BootEntry *entry) {
26 assert(entry);
27
28 free(entry->id);
29 free(entry->path);
30 free(entry->root);
31 free(entry->title);
32 free(entry->show_title);
33 free(entry->version);
34 free(entry->machine_id);
35 free(entry->architecture);
36 strv_free(entry->options);
37 free(entry->kernel);
38 free(entry->efi);
39 strv_free(entry->initrd);
40 free(entry->device_tree);
41 }
42
43 static int boot_entry_load(
44 const char *root,
45 const char *path,
46 BootEntry *entry) {
47
48 _cleanup_(boot_entry_free) BootEntry tmp = {};
49 _cleanup_fclose_ FILE *f = NULL;
50 unsigned line = 1;
51 char *b, *c;
52 int r;
53
54 assert(root);
55 assert(path);
56 assert(entry);
57
58 c = endswith_no_case(path, ".conf");
59 if (!c)
60 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry filename: %s", path);
61
62 b = basename(path);
63 tmp.id = strndup(b, c - b);
64 if (!tmp.id)
65 return log_oom();
66
67 tmp.path = strdup(path);
68 if (!tmp.path)
69 return log_oom();
70
71 tmp.root = strdup(root);
72 if (!tmp.root)
73 return log_oom();
74
75 f = fopen(path, "re");
76 if (!f)
77 return log_error_errno(errno, "Failed to open \"%s\": %m", path);
78
79 for (;;) {
80 _cleanup_free_ char *buf = NULL, *field = NULL;
81 const char *p;
82
83 r = read_line(f, LONG_LINE_MAX, &buf);
84 if (r == 0)
85 break;
86 if (r == -ENOBUFS)
87 return log_error_errno(r, "%s:%u: Line too long", path, line);
88 if (r < 0)
89 return log_error_errno(r, "%s:%u: Error while reading: %m", path, line);
90
91 line++;
92
93 if (IN_SET(*strstrip(buf), '#', '\0'))
94 continue;
95
96 p = buf;
97 r = extract_first_word(&p, &field, " \t", 0);
98 if (r < 0) {
99 log_error_errno(r, "Failed to parse config file %s line %u: %m", path, line);
100 continue;
101 }
102 if (r == 0) {
103 log_warning("%s:%u: Bad syntax", path, line);
104 continue;
105 }
106
107 if (streq(field, "title"))
108 r = free_and_strdup(&tmp.title, p);
109 else if (streq(field, "version"))
110 r = free_and_strdup(&tmp.version, p);
111 else if (streq(field, "machine-id"))
112 r = free_and_strdup(&tmp.machine_id, p);
113 else if (streq(field, "architecture"))
114 r = free_and_strdup(&tmp.architecture, p);
115 else if (streq(field, "options"))
116 r = strv_extend(&tmp.options, p);
117 else if (streq(field, "linux"))
118 r = free_and_strdup(&tmp.kernel, p);
119 else if (streq(field, "efi"))
120 r = free_and_strdup(&tmp.efi, p);
121 else if (streq(field, "initrd"))
122 r = strv_extend(&tmp.initrd, p);
123 else if (streq(field, "devicetree"))
124 r = free_and_strdup(&tmp.device_tree, p);
125 else {
126 log_notice("%s:%u: Unknown line \"%s\", ignoring.", path, line, field);
127 continue;
128 }
129 if (r < 0)
130 return log_error_errno(r, "%s:%u: Error while reading: %m", path, line);
131 }
132
133 *entry = tmp;
134 tmp = (BootEntry) {};
135 return 0;
136 }
137
138 void boot_config_free(BootConfig *config) {
139 size_t i;
140
141 assert(config);
142
143 free(config->default_pattern);
144 free(config->timeout);
145 free(config->editor);
146 free(config->auto_entries);
147 free(config->auto_firmware);
148
149 free(config->entry_oneshot);
150 free(config->entry_default);
151
152 for (i = 0; i < config->n_entries; i++)
153 boot_entry_free(config->entries + i);
154 free(config->entries);
155 }
156
157 static int boot_loader_read_conf(const char *path, BootConfig *config) {
158 _cleanup_fclose_ FILE *f = NULL;
159 unsigned line = 1;
160 int r;
161
162 assert(path);
163 assert(config);
164
165 f = fopen(path, "re");
166 if (!f) {
167 if (errno == ENOENT)
168 return 0;
169
170 return log_error_errno(errno, "Failed to open \"%s\": %m", path);
171 }
172
173 for (;;) {
174 _cleanup_free_ char *buf = NULL, *field = NULL;
175 const char *p;
176
177 r = read_line(f, LONG_LINE_MAX, &buf);
178 if (r == 0)
179 break;
180 if (r == -ENOBUFS)
181 return log_error_errno(r, "%s:%u: Line too long", path, line);
182 if (r < 0)
183 return log_error_errno(r, "%s:%u: Error while reading: %m", path, line);
184
185 line++;
186
187 if (IN_SET(*strstrip(buf), '#', '\0'))
188 continue;
189
190 p = buf;
191 r = extract_first_word(&p, &field, " \t", 0);
192 if (r < 0) {
193 log_error_errno(r, "Failed to parse config file %s line %u: %m", path, line);
194 continue;
195 }
196 if (r == 0) {
197 log_warning("%s:%u: Bad syntax", path, line);
198 continue;
199 }
200
201 if (streq(field, "default"))
202 r = free_and_strdup(&config->default_pattern, p);
203 else if (streq(field, "timeout"))
204 r = free_and_strdup(&config->timeout, p);
205 else if (streq(field, "editor"))
206 r = free_and_strdup(&config->editor, p);
207 else if (streq(field, "auto-entries"))
208 r = free_and_strdup(&config->auto_entries, p);
209 else if (streq(field, "auto-firmware"))
210 r = free_and_strdup(&config->auto_firmware, p);
211 else if (streq(field, "console-mode"))
212 r = free_and_strdup(&config->console_mode, p);
213 else {
214 log_notice("%s:%u: Unknown line \"%s\", ignoring.", path, line, field);
215 continue;
216 }
217 if (r < 0)
218 return log_error_errno(r, "%s:%u: Error while reading: %m", path, line);
219 }
220
221 return 1;
222 }
223
224 static int boot_entry_compare(const BootEntry *a, const BootEntry *b) {
225 return str_verscmp(a->id, b->id);
226 }
227
228 static int boot_entries_find(
229 const char *root,
230 const char *dir,
231 BootEntry **entries,
232 size_t *n_entries) {
233
234 _cleanup_strv_free_ char **files = NULL;
235 size_t n_allocated = *n_entries;
236 bool added = false;
237 char **f;
238 int r;
239
240 assert(root);
241 assert(dir);
242 assert(entries);
243 assert(n_entries);
244
245 r = conf_files_list(&files, ".conf", NULL, 0, dir, NULL);
246 if (r < 0)
247 return log_error_errno(r, "Failed to list files in \"%s\": %m", dir);
248
249 STRV_FOREACH(f, files) {
250 if (!GREEDY_REALLOC0(*entries, n_allocated, *n_entries + 1))
251 return log_oom();
252
253 r = boot_entry_load(root, *f, *entries + *n_entries);
254 if (r < 0)
255 continue;
256
257 (*n_entries) ++;
258 added = true;
259 }
260
261 if (added)
262 typesafe_qsort(*entries, *n_entries, boot_entry_compare);
263
264 return 0;
265 }
266
267 static bool find_nonunique(BootEntry *entries, size_t n_entries, bool *arr) {
268 size_t i, j;
269 bool non_unique = false;
270
271 assert(entries || n_entries == 0);
272 assert(arr || n_entries == 0);
273
274 for (i = 0; i < n_entries; i++)
275 arr[i] = false;
276
277 for (i = 0; i < n_entries; i++)
278 for (j = 0; j < n_entries; j++)
279 if (i != j && streq(boot_entry_title(entries + i),
280 boot_entry_title(entries + j)))
281 non_unique = arr[i] = arr[j] = true;
282
283 return non_unique;
284 }
285
286 static int boot_entries_uniquify(BootEntry *entries, size_t n_entries) {
287 char *s;
288 size_t i;
289 int r;
290 bool arr[n_entries];
291
292 assert(entries || n_entries == 0);
293
294 /* Find _all_ non-unique titles */
295 if (!find_nonunique(entries, n_entries, arr))
296 return 0;
297
298 /* Add version to non-unique titles */
299 for (i = 0; i < n_entries; i++)
300 if (arr[i] && entries[i].version) {
301 r = asprintf(&s, "%s (%s)", boot_entry_title(entries + i), entries[i].version);
302 if (r < 0)
303 return -ENOMEM;
304
305 free_and_replace(entries[i].show_title, s);
306 }
307
308 if (!find_nonunique(entries, n_entries, arr))
309 return 0;
310
311 /* Add machine-id to non-unique titles */
312 for (i = 0; i < n_entries; i++)
313 if (arr[i] && entries[i].machine_id) {
314 r = asprintf(&s, "%s (%s)", boot_entry_title(entries + i), entries[i].machine_id);
315 if (r < 0)
316 return -ENOMEM;
317
318 free_and_replace(entries[i].show_title, s);
319 }
320
321 if (!find_nonunique(entries, n_entries, arr))
322 return 0;
323
324 /* Add file name to non-unique titles */
325 for (i = 0; i < n_entries; i++)
326 if (arr[i]) {
327 r = asprintf(&s, "%s (%s)", boot_entry_title(entries + i), entries[i].id);
328 if (r < 0)
329 return -ENOMEM;
330
331 free_and_replace(entries[i].show_title, s);
332 }
333
334 return 0;
335 }
336
337 static int boot_entries_select_default(const BootConfig *config) {
338 int i;
339
340 assert(config);
341
342 if (config->entry_oneshot)
343 for (i = config->n_entries - 1; i >= 0; i--)
344 if (streq(config->entry_oneshot, config->entries[i].id)) {
345 log_debug("Found default: id \"%s\" is matched by LoaderEntryOneShot",
346 config->entries[i].id);
347 return i;
348 }
349
350 if (config->entry_default)
351 for (i = config->n_entries - 1; i >= 0; i--)
352 if (streq(config->entry_default, config->entries[i].id)) {
353 log_debug("Found default: id \"%s\" is matched by LoaderEntryDefault",
354 config->entries[i].id);
355 return i;
356 }
357
358 if (config->default_pattern)
359 for (i = config->n_entries - 1; i >= 0; i--)
360 if (fnmatch(config->default_pattern, config->entries[i].id, FNM_CASEFOLD) == 0) {
361 log_debug("Found default: id \"%s\" is matched by pattern \"%s\"",
362 config->entries[i].id, config->default_pattern);
363 return i;
364 }
365
366 if (config->n_entries > 0)
367 log_debug("Found default: last entry \"%s\"", config->entries[config->n_entries - 1].id);
368 else
369 log_debug("Found no default boot entry :(");
370
371 return config->n_entries - 1; /* -1 means "no default" */
372 }
373
374 int boot_entries_load_config(
375 const char *esp_path,
376 const char *xbootldr_path,
377 BootConfig *config) {
378
379 const char *p;
380 int r;
381
382 assert(config);
383
384 if (esp_path) {
385 p = strjoina(esp_path, "/loader/loader.conf");
386 r = boot_loader_read_conf(p, config);
387 if (r < 0)
388 return r;
389
390 p = strjoina(esp_path, "/loader/entries");
391 r = boot_entries_find(esp_path, p, &config->entries, &config->n_entries);
392 if (r < 0)
393 return r;
394 }
395
396 if (xbootldr_path) {
397 p = strjoina(xbootldr_path, "/loader/entries");
398 r = boot_entries_find(xbootldr_path, p, &config->entries, &config->n_entries);
399 if (r < 0)
400 return r;
401 }
402
403 r = boot_entries_uniquify(config->entries, config->n_entries);
404 if (r < 0)
405 return log_error_errno(r, "Failed to uniquify boot entries: %m");
406
407 if (is_efi_boot()) {
408 r = efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderEntryOneShot", &config->entry_oneshot);
409 if (r < 0 && r != -ENOENT)
410 return log_error_errno(r, "Failed to read EFI var \"LoaderEntryOneShot\": %m");
411
412 r = efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderEntryDefault", &config->entry_default);
413 if (r < 0 && r != -ENOENT)
414 return log_error_errno(r, "Failed to read EFI var \"LoaderEntryDefault\": %m");
415 }
416
417 config->default_entry = boot_entries_select_default(config);
418 return 0;
419 }
420
421 /********************************************************************************/
422
423 static int verify_esp(
424 const char *p,
425 bool searching,
426 bool unprivileged_mode,
427 uint32_t *ret_part,
428 uint64_t *ret_pstart,
429 uint64_t *ret_psize,
430 sd_id128_t *ret_uuid) {
431 #if HAVE_BLKID
432 _cleanup_(blkid_free_probep) blkid_probe b = NULL;
433 _cleanup_free_ char *node = NULL;
434 const char *v;
435 #endif
436 uint64_t pstart = 0, psize = 0;
437 struct stat st, st2;
438 const char *t2;
439 struct statfs sfs;
440 sd_id128_t uuid = SD_ID128_NULL;
441 uint32_t part = 0;
442 bool relax_checks;
443 int r;
444
445 assert(p);
446
447 relax_checks = getenv_bool("SYSTEMD_RELAX_ESP_CHECKS") > 0;
448
449 /* Non-root user can only check the status, so if an error occured in the following, it does not cause any
450 * issues. Let's also, silence the error messages. */
451
452 if (!relax_checks) {
453 if (statfs(p, &sfs) < 0) {
454 /* If we are searching for the mount point, don't generate a log message if we can't find the path */
455 if (errno == ENOENT && searching)
456 return -ENOENT;
457
458 return log_full_errno(unprivileged_mode && errno == EACCES ? LOG_DEBUG : LOG_ERR, errno,
459 "Failed to check file system type of \"%s\": %m", p);
460 }
461
462 if (!F_TYPE_EQUAL(sfs.f_type, MSDOS_SUPER_MAGIC)) {
463 if (searching)
464 return -EADDRNOTAVAIL;
465
466 log_error("File system \"%s\" is not a FAT EFI System Partition (ESP) file system.", p);
467 return -ENODEV;
468 }
469 }
470
471 if (stat(p, &st) < 0)
472 return log_full_errno(unprivileged_mode && errno == EACCES ? LOG_DEBUG : LOG_ERR, errno,
473 "Failed to determine block device node of \"%s\": %m", p);
474
475 if (major(st.st_dev) == 0) {
476 log_error("Block device node of %p is invalid.", p);
477 return -ENODEV;
478 }
479
480 t2 = strjoina(p, "/..");
481 r = stat(t2, &st2);
482 if (r < 0)
483 return log_full_errno(unprivileged_mode && errno == EACCES ? LOG_DEBUG : LOG_ERR, errno,
484 "Failed to determine block device node of parent of \"%s\": %m", p);
485
486 if (st.st_dev == st2.st_dev) {
487 log_error("Directory \"%s\" is not the root of the EFI System Partition (ESP) file system.", p);
488 return -ENODEV;
489 }
490
491 /* In a container we don't have access to block devices, skip this part of the verification, we trust the
492 * container manager set everything up correctly on its own. Also skip the following verification for non-root user. */
493 if (detect_container() > 0 || unprivileged_mode || relax_checks)
494 goto finish;
495
496 #if HAVE_BLKID
497 r = device_path_make_major_minor(S_IFBLK, st.st_dev, &node);
498 if (r < 0)
499 return log_error_errno(r, "Failed to format major/minor device path: %m");
500 errno = 0;
501 b = blkid_new_probe_from_filename(node);
502 if (!b)
503 return log_error_errno(errno ?: ENOMEM, "Failed to open file system \"%s\": %m", p);
504
505 blkid_probe_enable_superblocks(b, 1);
506 blkid_probe_set_superblocks_flags(b, BLKID_SUBLKS_TYPE);
507 blkid_probe_enable_partitions(b, 1);
508 blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS);
509
510 errno = 0;
511 r = blkid_do_safeprobe(b);
512 if (r == -2) {
513 log_error("File system \"%s\" is ambiguous.", p);
514 return -ENODEV;
515 } else if (r == 1) {
516 log_error("File system \"%s\" does not contain a label.", p);
517 return -ENODEV;
518 } else if (r != 0)
519 return log_error_errno(errno ?: EIO, "Failed to probe file system \"%s\": %m", p);
520
521 errno = 0;
522 r = blkid_probe_lookup_value(b, "TYPE", &v, NULL);
523 if (r != 0)
524 return log_error_errno(errno ?: EIO, "Failed to probe file system type \"%s\": %m", p);
525 if (!streq(v, "vfat")) {
526 log_error("File system \"%s\" is not FAT.", p);
527 return -ENODEV;
528 }
529
530 errno = 0;
531 r = blkid_probe_lookup_value(b, "PART_ENTRY_SCHEME", &v, NULL);
532 if (r != 0)
533 return log_error_errno(errno ?: EIO, "Failed to probe partition scheme \"%s\": %m", p);
534 if (!streq(v, "gpt")) {
535 log_error("File system \"%s\" is not on a GPT partition table.", p);
536 return -ENODEV;
537 }
538
539 errno = 0;
540 r = blkid_probe_lookup_value(b, "PART_ENTRY_TYPE", &v, NULL);
541 if (r != 0)
542 return log_error_errno(errno ?: EIO, "Failed to probe partition type UUID \"%s\": %m", p);
543 if (!streq(v, "c12a7328-f81f-11d2-ba4b-00a0c93ec93b")) {
544 log_error("File system \"%s\" has wrong type for an EFI System Partition (ESP).", p);
545 return -ENODEV;
546 }
547
548 errno = 0;
549 r = blkid_probe_lookup_value(b, "PART_ENTRY_UUID", &v, NULL);
550 if (r != 0)
551 return log_error_errno(errno ?: EIO, "Failed to probe partition entry UUID \"%s\": %m", p);
552 r = sd_id128_from_string(v, &uuid);
553 if (r < 0) {
554 log_error("Partition \"%s\" has invalid UUID \"%s\".", p, v);
555 return -EIO;
556 }
557
558 errno = 0;
559 r = blkid_probe_lookup_value(b, "PART_ENTRY_NUMBER", &v, NULL);
560 if (r != 0)
561 return log_error_errno(errno ?: EIO, "Failed to probe partition number \"%s\": m", p);
562 r = safe_atou32(v, &part);
563 if (r < 0)
564 return log_error_errno(r, "Failed to parse PART_ENTRY_NUMBER field.");
565
566 errno = 0;
567 r = blkid_probe_lookup_value(b, "PART_ENTRY_OFFSET", &v, NULL);
568 if (r != 0)
569 return log_error_errno(errno ?: EIO, "Failed to probe partition offset \"%s\": %m", p);
570 r = safe_atou64(v, &pstart);
571 if (r < 0)
572 return log_error_errno(r, "Failed to parse PART_ENTRY_OFFSET field.");
573
574 errno = 0;
575 r = blkid_probe_lookup_value(b, "PART_ENTRY_SIZE", &v, NULL);
576 if (r != 0)
577 return log_error_errno(errno ?: EIO, "Failed to probe partition size \"%s\": %m", p);
578 r = safe_atou64(v, &psize);
579 if (r < 0)
580 return log_error_errno(r, "Failed to parse PART_ENTRY_SIZE field.");
581 #endif
582
583 finish:
584 if (ret_part)
585 *ret_part = part;
586 if (ret_pstart)
587 *ret_pstart = pstart;
588 if (ret_psize)
589 *ret_psize = psize;
590 if (ret_uuid)
591 *ret_uuid = uuid;
592
593 return 0;
594 }
595
596 int find_esp_and_warn(
597 const char *path,
598 bool unprivileged_mode,
599 char **ret_path,
600 uint32_t *ret_part,
601 uint64_t *ret_pstart,
602 uint64_t *ret_psize,
603 sd_id128_t *ret_uuid) {
604
605 int r;
606
607 /* This logs about all errors except:
608 *
609 * -ENOKEY → when we can't find the partition
610 * -EACCESS → when unprivileged_mode is true, and we can't access something
611 */
612
613 if (path) {
614 r = verify_esp(path, false, unprivileged_mode, ret_part, ret_pstart, ret_psize, ret_uuid);
615 if (r < 0)
616 return r;
617
618 goto found;
619 }
620
621 path = getenv("SYSTEMD_ESP_PATH");
622 if (path) {
623 if (!path_is_valid(path) || !path_is_absolute(path))
624 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
625 "$SYSTEMD_ESP_PATH does not refer to absolute path, refusing to use it: %s",
626 path);
627
628 /* Note: when the user explicitly configured things with an env var we won't validate the mount
629 * point. After all we want this to be useful for testing. */
630 goto found;
631 }
632
633 FOREACH_STRING(path, "/efi", "/boot", "/boot/efi") {
634
635 r = verify_esp(path, true, unprivileged_mode, ret_part, ret_pstart, ret_psize, ret_uuid);
636 if (r >= 0)
637 goto found;
638 if (!IN_SET(r, -ENOENT, -EADDRNOTAVAIL)) /* This one is not it */
639 return r;
640 }
641
642 /* No logging here */
643 return -ENOKEY;
644
645 found:
646 if (ret_path) {
647 char *c;
648
649 c = strdup(path);
650 if (!c)
651 return log_oom();
652
653 *ret_path = c;
654 }
655
656 return 0;
657 }
658
659 static int verify_xbootldr(
660 const char *p,
661 bool searching,
662 bool unprivileged_mode,
663 sd_id128_t *ret_uuid) {
664 #if HAVE_BLKID
665 _cleanup_(blkid_free_probep) blkid_probe b = NULL;
666 _cleanup_free_ char *node = NULL;
667 const char *v;
668 #endif
669 struct stat st, st2;
670 const char *t2;
671 sd_id128_t uuid = SD_ID128_NULL;
672 bool relax_checks;
673 int r;
674
675 assert(p);
676
677 relax_checks = getenv_bool("SYSTEMD_RELAX_XBOOTLDR_CHECKS") > 0;
678
679 if (stat(p, &st) < 0)
680 return log_full_errno((searching && errno == ENOENT) ||
681 (unprivileged_mode && errno == EACCES) ? LOG_DEBUG : LOG_ERR, errno,
682 "Failed to determine block device node of \"%s\": %m", p);
683
684 if (major(st.st_dev) == 0)
685 return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
686 SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
687 "Block device node of \"%s\" is invalid.", p);
688
689 t2 = strjoina(p, "/..");
690 r = stat(t2, &st2);
691 if (r < 0)
692 return log_full_errno(unprivileged_mode && errno == EACCES ? LOG_DEBUG : LOG_ERR, errno,
693 "Failed to determine block device node of parent of \"%s\": %m", p);
694
695 if (st.st_dev == st2.st_dev)
696 return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
697 SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
698 "Directory \"%s\" is not the root of the XBOOTLDR file system.", p);
699
700 /* In a container we don't have access to block devices, skip this part of the verification, we trust
701 * the container manager set everything up correctly on its own. Also skip the following verification
702 * for non-root user. */
703 if (detect_container() > 0 || unprivileged_mode || relax_checks)
704 goto finish;
705
706 #if HAVE_BLKID
707 r = device_path_make_major_minor(S_IFBLK, st.st_dev, &node);
708 if (r < 0)
709 return log_error_errno(r, "Failed to format major/minor device path: %m");
710 errno = 0;
711 b = blkid_new_probe_from_filename(node);
712 if (!b)
713 return log_error_errno(errno ?: SYNTHETIC_ERRNO(ENOMEM), "Failed to open file system \"%s\": %m", p);
714
715 blkid_probe_enable_partitions(b, 1);
716 blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS);
717
718 errno = 0;
719 r = blkid_do_safeprobe(b);
720 if (r == -2)
721 return log_error_errno(SYNTHETIC_ERRNO(ENODEV), "File system \"%s\" is ambiguous.", p);
722 else if (r == 1)
723 return log_error_errno(SYNTHETIC_ERRNO(ENODEV), "File system \"%s\" does not contain a label.", p);
724 else if (r != 0)
725 return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe file system \"%s\": %m", p);
726
727 errno = 0;
728 r = blkid_probe_lookup_value(b, "PART_ENTRY_SCHEME", &v, NULL);
729 if (r != 0)
730 return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition scheme \"%s\": %m", p);
731 if (streq(v, "gpt")) {
732
733 errno = 0;
734 r = blkid_probe_lookup_value(b, "PART_ENTRY_TYPE", &v, NULL);
735 if (r != 0)
736 return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition type UUID \"%s\": %m", p);
737 if (!streq(v, "bc13c2ff-59e6-4262-a352-b275fd6f7172"))
738 return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
739 searching ? SYNTHETIC_ERRNO(EADDRNOTAVAIL) : SYNTHETIC_ERRNO(ENODEV),
740 "File system \"%s\" has wrong type for extended boot loader partition.", p);
741
742 errno = 0;
743 r = blkid_probe_lookup_value(b, "PART_ENTRY_UUID", &v, NULL);
744 if (r != 0)
745 return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition entry UUID \"%s\": %m", p);
746 r = sd_id128_from_string(v, &uuid);
747 if (r < 0)
748 return log_error_errno(r, "Partition \"%s\" has invalid UUID \"%s\".", p, v);
749
750 } else if (streq(v, "dos")) {
751
752 errno = 0;
753 r = blkid_probe_lookup_value(b, "PART_ENTRY_TYPE", &v, NULL);
754 if (r != 0)
755 return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition type UUID \"%s\": %m", p);
756 if (!streq(v, "0xea"))
757 return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
758 searching ? SYNTHETIC_ERRNO(EADDRNOTAVAIL) : SYNTHETIC_ERRNO(ENODEV),
759 "File system \"%s\" has wrong type for extended boot loader partition.", p);
760
761 } else
762 return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
763 searching ? SYNTHETIC_ERRNO(EADDRNOTAVAIL) : SYNTHETIC_ERRNO(ENODEV),
764 "File system \"%s\" is not on a GPT or DOS partition table.", p);
765
766 #endif
767
768 finish:
769 if (ret_uuid)
770 *ret_uuid = uuid;
771
772 return 0;
773 }
774
775 int find_xbootldr_and_warn(
776 const char *path,
777 bool unprivileged_mode,
778 char **ret_path,
779 sd_id128_t *ret_uuid) {
780
781 int r;
782
783 /* Similar to find_esp_and_warn(), but finds the XBOOTLDR partition. Returns the same errors. */
784
785 if (path) {
786 r = verify_xbootldr(path, false, unprivileged_mode, ret_uuid);
787 if (r < 0)
788 return r;
789
790 goto found;
791 }
792
793 path = getenv("SYSTEMD_XBOOTLDR_PATH");
794 if (path) {
795 if (!path_is_valid(path) || !path_is_absolute(path))
796 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
797 "$SYSTEMD_XBOOTLDR_PATH does not refer to absolute path, refusing to use it: %s",
798 path);
799
800 goto found;
801 }
802
803 r = verify_xbootldr("/boot", true, unprivileged_mode, ret_uuid);
804 if (r >= 0) {
805 path = "/boot";
806 goto found;
807 }
808 if (!IN_SET(r, -ENOENT, -EADDRNOTAVAIL)) /* This one is not it */
809 return r;
810
811 return -ENOKEY;
812
813 found:
814 if (ret_path) {
815 char *c;
816
817 c = strdup(path);
818 if (!c)
819 return log_oom();
820
821 *ret_path = c;
822 }
823
824 return 0;
825 }
826
827 int find_default_boot_entry(
828 const char *esp_path,
829 const char *xbootldr_path,
830 BootConfig *config,
831 const BootEntry **e) {
832
833 _cleanup_free_ char *esp_where = NULL, *xbootldr_where = NULL;
834 int r;
835
836 assert(config);
837 assert(e);
838
839 r = find_esp_and_warn(esp_path, false, &esp_where, NULL, NULL, NULL, NULL);
840 if (r < 0)
841 return r;
842
843 r = find_xbootldr_and_warn(xbootldr_path, false, &xbootldr_where, NULL);
844 if (r < 0 && r != -ENOKEY)
845 return r;
846
847 r = boot_entries_load_config(esp_where, xbootldr_where, config);
848 if (r < 0)
849 return log_error_errno(r, "Failed to load boot loader entries: %m");
850
851 if (config->default_entry < 0)
852 return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
853 "No boot loader entry suitable as default, refusing to guess.");
854
855 *e = &config->entries[config->default_entry];
856 log_debug("Found default boot loader entry in file \"%s\"", (*e)->path);
857
858 return 0;
859 }