]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/shared/bootspec.c
boot: Deduplicate old-style loader entries.
[thirdparty/systemd.git] / src / shared / bootspec.c
CommitLineData
58f21e63 1/* SPDX-License-Identifier: LGPL-2.1+ */
7e87c7d9
ZJS
2
3#include <stdio.h>
af918182 4#include <linux/magic.h>
ca78ad1d 5#include <unistd.h>
7e87c7d9 6
cedb9eec 7#include "sd-device.h"
dccca82b
LP
8#include "sd-id128.h"
9
7e87c7d9 10#include "alloc-util.h"
af918182 11#include "blkid-util.h"
7e87c7d9
ZJS
12#include "bootspec.h"
13#include "conf-files.h"
14#include "def.h"
c67f84b0 15#include "device-nodes.h"
5e146a75 16#include "dirent-util.h"
7e87c7d9 17#include "efivars.h"
0bb2f0f1 18#include "efi-loader.h"
5e146a75 19#include "env-file.h"
8cbb7d87 20#include "env-util.h"
7e87c7d9
ZJS
21#include "fd-util.h"
22#include "fileio.h"
af918182 23#include "parse-util.h"
cc7a0bfa 24#include "path-util.h"
5e146a75 25#include "pe-header.h"
760877e9 26#include "sort-util.h"
af918182 27#include "stat-util.h"
93f14ce2 28#include "string-table.h"
7e87c7d9
ZJS
29#include "string-util.h"
30#include "strv.h"
5e146a75 31#include "unaligned.h"
ca78ad1d 32#include "util.h"
af918182 33#include "virt.h"
7e87c7d9 34
0de2e1fd 35static void boot_entry_free(BootEntry *entry) {
4fe2ba0e 36 assert(entry);
7e87c7d9 37
12580bc3 38 free(entry->id);
15b82eec 39 free(entry->id_old);
2d3bfb69 40 free(entry->path);
43b736a8 41 free(entry->root);
7e87c7d9 42 free(entry->title);
64f05708 43 free(entry->show_title);
7e87c7d9
ZJS
44 free(entry->version);
45 free(entry->machine_id);
46 free(entry->architecture);
47 strv_free(entry->options);
48 free(entry->kernel);
49 free(entry->efi);
50 strv_free(entry->initrd);
51 free(entry->device_tree);
52}
53
43b736a8
LP
54static int boot_entry_load(
55 const char *root,
56 const char *path,
57 BootEntry *entry) {
58
93f14ce2
LP
59 _cleanup_(boot_entry_free) BootEntry tmp = {
60 .type = BOOT_ENTRY_CONF,
61 };
62
7e87c7d9
ZJS
63 _cleanup_fclose_ FILE *f = NULL;
64 unsigned line = 1;
263195c6 65 char *b, *c;
7e87c7d9
ZJS
66 int r;
67
43b736a8 68 assert(root);
4fe2ba0e
LP
69 assert(path);
70 assert(entry);
71
263195c6 72 c = endswith_no_case(path, ".conf");
46bba8a5 73 if (!c)
eed7210a 74 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry file suffix: %s", path);
7e87c7d9 75
263195c6 76 b = basename(path);
ae474efc 77 tmp.id = strdup(b);
15b82eec
SM
78 tmp.id_old = strndup(b, c - b);
79 if (!tmp.id || !tmp.id_old)
7e87c7d9
ZJS
80 return log_oom();
81
eed7210a 82 if (!efi_loader_entry_name_valid(tmp.id))
ae474efc 83 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry: %s", tmp.id);
eed7210a 84
2d3bfb69
ZJS
85 tmp.path = strdup(path);
86 if (!tmp.path)
87 return log_oom();
88
43b736a8
LP
89 tmp.root = strdup(root);
90 if (!tmp.root)
91 return log_oom();
92
263195c6
YW
93 f = fopen(path, "re");
94 if (!f)
95 return log_error_errno(errno, "Failed to open \"%s\": %m", path);
96
7e87c7d9 97 for (;;) {
f99fdc3e
YW
98 _cleanup_free_ char *buf = NULL, *field = NULL;
99 const char *p;
7e87c7d9
ZJS
100
101 r = read_line(f, LONG_LINE_MAX, &buf);
102 if (r == 0)
103 break;
104 if (r == -ENOBUFS)
105 return log_error_errno(r, "%s:%u: Line too long", path, line);
106 if (r < 0)
107 return log_error_errno(r, "%s:%u: Error while reading: %m", path, line);
108
109 line++;
110
111 if (IN_SET(*strstrip(buf), '#', '\0'))
112 continue;
113
f99fdc3e
YW
114 p = buf;
115 r = extract_first_word(&p, &field, " \t", 0);
116 if (r < 0) {
117 log_error_errno(r, "Failed to parse config file %s line %u: %m", path, line);
118 continue;
119 }
120 if (r == 0) {
7e87c7d9
ZJS
121 log_warning("%s:%u: Bad syntax", path, line);
122 continue;
123 }
7e87c7d9 124
f99fdc3e 125 if (streq(field, "title"))
7e87c7d9 126 r = free_and_strdup(&tmp.title, p);
f99fdc3e 127 else if (streq(field, "version"))
7e87c7d9 128 r = free_and_strdup(&tmp.version, p);
f99fdc3e 129 else if (streq(field, "machine-id"))
7e87c7d9 130 r = free_and_strdup(&tmp.machine_id, p);
f99fdc3e 131 else if (streq(field, "architecture"))
7e87c7d9 132 r = free_and_strdup(&tmp.architecture, p);
f99fdc3e 133 else if (streq(field, "options"))
7e87c7d9 134 r = strv_extend(&tmp.options, p);
f99fdc3e 135 else if (streq(field, "linux"))
7e87c7d9 136 r = free_and_strdup(&tmp.kernel, p);
f99fdc3e 137 else if (streq(field, "efi"))
7e87c7d9 138 r = free_and_strdup(&tmp.efi, p);
f99fdc3e 139 else if (streq(field, "initrd"))
7e87c7d9 140 r = strv_extend(&tmp.initrd, p);
f99fdc3e 141 else if (streq(field, "devicetree"))
7e87c7d9
ZJS
142 r = free_and_strdup(&tmp.device_tree, p);
143 else {
feb41f1f 144 log_notice("%s:%u: Unknown line \"%s\", ignoring.", path, line, field);
7e87c7d9
ZJS
145 continue;
146 }
147 if (r < 0)
148 return log_error_errno(r, "%s:%u: Error while reading: %m", path, line);
149 }
150
151 *entry = tmp;
152 tmp = (BootEntry) {};
153 return 0;
154}
155
156void boot_config_free(BootConfig *config) {
da6053d0 157 size_t i;
7e87c7d9 158
4fe2ba0e
LP
159 assert(config);
160
7e87c7d9
ZJS
161 free(config->default_pattern);
162 free(config->timeout);
163 free(config->editor);
c1d4e298
JJ
164 free(config->auto_entries);
165 free(config->auto_firmware);
72263375 166 free(config->console_mode);
7e87c7d9
ZJS
167
168 free(config->entry_oneshot);
169 free(config->entry_default);
170
171 for (i = 0; i < config->n_entries; i++)
172 boot_entry_free(config->entries + i);
173 free(config->entries);
174}
175
0de2e1fd 176static int boot_loader_read_conf(const char *path, BootConfig *config) {
7e87c7d9
ZJS
177 _cleanup_fclose_ FILE *f = NULL;
178 unsigned line = 1;
179 int r;
180
4fe2ba0e
LP
181 assert(path);
182 assert(config);
183
7e87c7d9 184 f = fopen(path, "re");
f91ed3dc
LP
185 if (!f) {
186 if (errno == ENOENT)
187 return 0;
188
7e87c7d9 189 return log_error_errno(errno, "Failed to open \"%s\": %m", path);
f91ed3dc 190 }
7e87c7d9
ZJS
191
192 for (;;) {
f99fdc3e
YW
193 _cleanup_free_ char *buf = NULL, *field = NULL;
194 const char *p;
7e87c7d9
ZJS
195
196 r = read_line(f, LONG_LINE_MAX, &buf);
197 if (r == 0)
198 break;
199 if (r == -ENOBUFS)
200 return log_error_errno(r, "%s:%u: Line too long", path, line);
201 if (r < 0)
202 return log_error_errno(r, "%s:%u: Error while reading: %m", path, line);
203
204 line++;
205
206 if (IN_SET(*strstrip(buf), '#', '\0'))
207 continue;
208
f99fdc3e
YW
209 p = buf;
210 r = extract_first_word(&p, &field, " \t", 0);
211 if (r < 0) {
212 log_error_errno(r, "Failed to parse config file %s line %u: %m", path, line);
213 continue;
214 }
215 if (r == 0) {
7e87c7d9
ZJS
216 log_warning("%s:%u: Bad syntax", path, line);
217 continue;
218 }
7e87c7d9 219
f99fdc3e 220 if (streq(field, "default"))
7e87c7d9 221 r = free_and_strdup(&config->default_pattern, p);
f99fdc3e 222 else if (streq(field, "timeout"))
7e87c7d9 223 r = free_and_strdup(&config->timeout, p);
f99fdc3e 224 else if (streq(field, "editor"))
7e87c7d9 225 r = free_and_strdup(&config->editor, p);
790f84eb 226 else if (streq(field, "auto-entries"))
c1d4e298 227 r = free_and_strdup(&config->auto_entries, p);
790f84eb 228 else if (streq(field, "auto-firmware"))
c1d4e298 229 r = free_and_strdup(&config->auto_firmware, p);
790f84eb 230 else if (streq(field, "console-mode"))
d37b0737 231 r = free_and_strdup(&config->console_mode, p);
7e87c7d9 232 else {
feb41f1f 233 log_notice("%s:%u: Unknown line \"%s\", ignoring.", path, line, field);
7e87c7d9
ZJS
234 continue;
235 }
236 if (r < 0)
237 return log_error_errno(r, "%s:%u: Error while reading: %m", path, line);
238 }
239
f91ed3dc 240 return 1;
7e87c7d9
ZJS
241}
242
93bab288 243static int boot_entry_compare(const BootEntry *a, const BootEntry *b) {
12580bc3 244 return str_verscmp(a->id, b->id);
7e87c7d9
ZJS
245}
246
43b736a8
LP
247static int boot_entries_find(
248 const char *root,
249 const char *dir,
a2f8664e
LP
250 BootEntry **entries,
251 size_t *n_entries) {
43b736a8 252
7e87c7d9 253 _cleanup_strv_free_ char **files = NULL;
a2f8664e 254 size_t n_allocated = *n_entries;
7e87c7d9
ZJS
255 char **f;
256 int r;
7e87c7d9 257
43b736a8 258 assert(root);
4fe2ba0e 259 assert(dir);
a2f8664e
LP
260 assert(entries);
261 assert(n_entries);
4fe2ba0e 262
36b12282 263 r = conf_files_list(&files, ".conf", NULL, 0, dir);
7e87c7d9
ZJS
264 if (r < 0)
265 return log_error_errno(r, "Failed to list files in \"%s\": %m", dir);
266
267 STRV_FOREACH(f, files) {
a2f8664e 268 if (!GREEDY_REALLOC0(*entries, n_allocated, *n_entries + 1))
7e87c7d9
ZJS
269 return log_oom();
270
a2f8664e 271 r = boot_entry_load(root, *f, *entries + *n_entries);
7e87c7d9
ZJS
272 if (r < 0)
273 continue;
274
a2f8664e 275 (*n_entries) ++;
7e87c7d9
ZJS
276 }
277
7e87c7d9
ZJS
278 return 0;
279}
280
5e146a75
LP
281static int boot_entry_load_unified(
282 const char *root,
283 const char *path,
284 const char *osrelease,
285 const char *cmdline,
286 BootEntry *ret) {
287
15b82eec 288 _cleanup_free_ char *os_pretty_name = NULL, *os_id = NULL, *version_id = NULL, *build_id = NULL;
93f14ce2
LP
289 _cleanup_(boot_entry_free) BootEntry tmp = {
290 .type = BOOT_ENTRY_UNIFIED,
291 };
5e146a75
LP
292 _cleanup_fclose_ FILE *f = NULL;
293 const char *k;
ae474efc 294 char *b;
5e146a75
LP
295 int r;
296
297 assert(root);
298 assert(path);
299 assert(osrelease);
300
301 k = path_startswith(path, root);
302 if (!k)
303 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Path is not below root: %s", path);
304
673a1e6f 305 f = fmemopen_unlocked((void*) osrelease, strlen(osrelease), "r");
5e146a75
LP
306 if (!f)
307 return log_error_errno(errno, "Failed to open os-release buffer: %m");
308
15b82eec
SM
309 r = parse_env_file(f, "os-release",
310 "PRETTY_NAME", &os_pretty_name,
311 "ID", &os_id,
312 "VERSION_ID", &version_id,
313 "BUILD_ID", &build_id);
5e146a75
LP
314 if (r < 0)
315 return log_error_errno(r, "Failed to parse os-release data from unified kernel image %s: %m", path);
316
15b82eec 317 if (!os_pretty_name || !os_id || !(version_id || build_id))
5e146a75
LP
318 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Missing fields in os-release data from unified kernel image %s, refusing.", path);
319
ae474efc
SM
320 b = basename(path);
321 tmp.id = strdup(b);
15b82eec
SM
322 tmp.id_old = strjoin(os_id, "-", version_id ?: build_id);
323 if (!tmp.id || !tmp.id_old)
5e146a75
LP
324 return log_oom();
325
eed7210a
LP
326 if (!efi_loader_entry_name_valid(tmp.id))
327 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry: %s", tmp.id);
328
5e146a75
LP
329 tmp.path = strdup(path);
330 if (!tmp.path)
331 return log_oom();
332
333 tmp.root = strdup(root);
334 if (!tmp.root)
335 return log_oom();
336
337 tmp.kernel = strdup(skip_leading_chars(k, "/"));
338 if (!tmp.kernel)
339 return log_oom();
340
341 tmp.options = strv_new(skip_leading_chars(cmdline, WHITESPACE));
342 if (!tmp.options)
343 return log_oom();
344
345 delete_trailing_chars(tmp.options[0], WHITESPACE);
346
347 tmp.title = TAKE_PTR(os_pretty_name);
348
349 *ret = tmp;
350 tmp = (BootEntry) {};
351 return 0;
352}
353
354/* Maximum PE section we are willing to load (Note that sections we are not interested in may be larger, but
355 * the ones we do care about and we are willing to load into memory have this size limit.) */
356#define PE_SECTION_SIZE_MAX (4U*1024U*1024U)
357
358static int find_sections(
359 int fd,
360 char **ret_osrelease,
361 char **ret_cmdline) {
362
363 _cleanup_free_ struct PeSectionHeader *sections = NULL;
364 _cleanup_free_ char *osrelease = NULL, *cmdline = NULL;
365 size_t i, n_sections;
366 struct DosFileHeader dos;
367 struct PeHeader pe;
368 uint64_t start;
369 ssize_t n;
370
371 n = pread(fd, &dos, sizeof(dos), 0);
372 if (n < 0)
373 return log_error_errno(errno, "Failed read DOS header: %m");
374 if (n != sizeof(dos))
375 return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short read while reading DOS header, refusing.");
376
377 if (dos.Magic[0] != 'M' || dos.Magic[1] != 'Z')
378 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "DOS executable magic missing, refusing.");
379
380 start = unaligned_read_le32(&dos.ExeHeader);
381 n = pread(fd, &pe, sizeof(pe), start);
382 if (n < 0)
383 return log_error_errno(errno, "Failed to read PE header: %m");
384 if (n != sizeof(pe))
385 return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short read while reading PE header, refusing.");
386
387 if (pe.Magic[0] != 'P' || pe.Magic[1] != 'E' || pe.Magic[2] != 0 || pe.Magic[3] != 0)
388 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "PE executable magic missing, refusing.");
389
390 n_sections = unaligned_read_le16(&pe.FileHeader.NumberOfSections);
391 if (n_sections > 96)
392 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "PE header has too many sections, refusing.");
393
394 sections = new(struct PeSectionHeader, n_sections);
395 if (!sections)
396 return log_oom();
397
398 n = pread(fd, sections,
399 n_sections * sizeof(struct PeSectionHeader),
400 start + sizeof(pe) + unaligned_read_le16(&pe.FileHeader.SizeOfOptionalHeader));
401 if (n < 0)
402 return log_error_errno(errno, "Failed to read section data: %m");
403 if ((size_t) n != n_sections * sizeof(struct PeSectionHeader))
404 return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short read while reading sections, refusing.");
405
406 for (i = 0; i < n_sections; i++) {
407 _cleanup_free_ char *k = NULL;
408 uint32_t offset, size;
409 char **b;
410
411 if (strneq((char*) sections[i].Name, ".osrel", sizeof(sections[i].Name)))
412 b = &osrelease;
413 else if (strneq((char*) sections[i].Name, ".cmdline", sizeof(sections[i].Name)))
414 b = &cmdline;
415 else
416 continue;
417
418 if (*b)
419 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Duplicate section %s, refusing.", sections[i].Name);
420
421 offset = unaligned_read_le32(&sections[i].PointerToRawData);
422 size = unaligned_read_le32(&sections[i].VirtualSize);
423
424 if (size > PE_SECTION_SIZE_MAX)
425 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Section %s too large, refusing.", sections[i].Name);
426
427 k = new(char, size+1);
428 if (!k)
429 return log_oom();
430
431 n = pread(fd, k, size, offset);
432 if (n < 0)
433 return log_error_errno(errno, "Failed to read section payload: %m");
a75fcef8 434 if ((size_t) n != size)
5e146a75
LP
435 return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short read while reading section payload, refusing:");
436
437 /* Allow one trailing NUL byte, but nothing more. */
438 if (size > 0 && memchr(k, 0, size - 1))
439 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Section contains embedded NUL byte: %m");
440
441 k[size] = 0;
442 *b = TAKE_PTR(k);
443 }
444
445 if (!osrelease)
446 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Image lacks .osrel section, refusing.");
447
448 if (ret_osrelease)
449 *ret_osrelease = TAKE_PTR(osrelease);
450 if (ret_cmdline)
451 *ret_cmdline = TAKE_PTR(cmdline);
452
453 return 0;
454}
455
456static int boot_entries_find_unified(
457 const char *root,
458 const char *dir,
459 BootEntry **entries,
460 size_t *n_entries) {
461
462 _cleanup_(closedirp) DIR *d = NULL;
463 size_t n_allocated = *n_entries;
5e146a75
LP
464 struct dirent *de;
465 int r;
466
467 assert(root);
468 assert(dir);
469 assert(entries);
470 assert(n_entries);
471
472 d = opendir(dir);
473 if (!d) {
474 if (errno == ENOENT)
475 return 0;
476
477 return log_error_errno(errno, "Failed to open %s: %m", dir);
478 }
479
480 FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read %s: %m", dir)) {
481 _cleanup_free_ char *j = NULL, *osrelease = NULL, *cmdline = NULL;
482 _cleanup_close_ int fd = -1;
483
28e68bb2 484 dirent_ensure_type(d, de);
5e146a75
LP
485 if (!dirent_is_file(de))
486 continue;
487
488 if (!endswith_no_case(de->d_name, ".efi"))
489 continue;
490
491 if (!GREEDY_REALLOC0(*entries, n_allocated, *n_entries + 1))
492 return log_oom();
493
494 fd = openat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC|O_NONBLOCK);
495 if (fd < 0) {
496 log_warning_errno(errno, "Failed to open %s/%s, ignoring: %m", dir, de->d_name);
497 continue;
498 }
499
500 r = fd_verify_regular(fd);
501 if (r < 0) {
dba33c4a 502 log_warning_errno(r, "File %s/%s is not regular, ignoring: %m", dir, de->d_name);
5e146a75
LP
503 continue;
504 }
505
506 r = find_sections(fd, &osrelease, &cmdline);
507 if (r < 0)
508 continue;
509
510 j = path_join(dir, de->d_name);
511 if (!j)
512 return log_oom();
513
514 r = boot_entry_load_unified(root, j, osrelease, cmdline, *entries + *n_entries);
515 if (r < 0)
516 continue;
517
518 (*n_entries) ++;
5e146a75
LP
519 }
520
5e146a75
LP
521 return 0;
522}
523
64f05708 524static bool find_nonunique(BootEntry *entries, size_t n_entries, bool *arr) {
da6053d0 525 size_t i, j;
64f05708
ZJS
526 bool non_unique = false;
527
4fe2ba0e
LP
528 assert(entries || n_entries == 0);
529 assert(arr || n_entries == 0);
530
64f05708
ZJS
531 for (i = 0; i < n_entries; i++)
532 arr[i] = false;
533
534 for (i = 0; i < n_entries; i++)
535 for (j = 0; j < n_entries; j++)
536 if (i != j && streq(boot_entry_title(entries + i),
537 boot_entry_title(entries + j)))
538 non_unique = arr[i] = arr[j] = true;
539
540 return non_unique;
541}
542
543static int boot_entries_uniquify(BootEntry *entries, size_t n_entries) {
544 char *s;
da6053d0 545 size_t i;
64f05708
ZJS
546 int r;
547 bool arr[n_entries];
548
4fe2ba0e
LP
549 assert(entries || n_entries == 0);
550
64f05708
ZJS
551 /* Find _all_ non-unique titles */
552 if (!find_nonunique(entries, n_entries, arr))
553 return 0;
554
555 /* Add version to non-unique titles */
556 for (i = 0; i < n_entries; i++)
557 if (arr[i] && entries[i].version) {
558 r = asprintf(&s, "%s (%s)", boot_entry_title(entries + i), entries[i].version);
559 if (r < 0)
560 return -ENOMEM;
561
562 free_and_replace(entries[i].show_title, s);
563 }
564
565 if (!find_nonunique(entries, n_entries, arr))
566 return 0;
567
568 /* Add machine-id to non-unique titles */
569 for (i = 0; i < n_entries; i++)
570 if (arr[i] && entries[i].machine_id) {
571 r = asprintf(&s, "%s (%s)", boot_entry_title(entries + i), entries[i].machine_id);
572 if (r < 0)
573 return -ENOMEM;
574
575 free_and_replace(entries[i].show_title, s);
576 }
577
578 if (!find_nonunique(entries, n_entries, arr))
579 return 0;
580
581 /* Add file name to non-unique titles */
582 for (i = 0; i < n_entries; i++)
583 if (arr[i]) {
12580bc3 584 r = asprintf(&s, "%s (%s)", boot_entry_title(entries + i), entries[i].id);
64f05708
ZJS
585 if (r < 0)
586 return -ENOMEM;
587
588 free_and_replace(entries[i].show_title, s);
589 }
590
591 return 0;
592}
593
ad1afd60 594static int boot_entries_select_default(const BootConfig *config) {
7e87c7d9
ZJS
595 int i;
596
4fe2ba0e 597 assert(config);
388d2993
ZJS
598 assert(config->entries || config->n_entries == 0);
599
600 if (config->n_entries == 0) {
601 log_debug("Found no default boot entry :(");
602 return -1; /* -1 means "no default" */
603 }
4fe2ba0e 604
7e87c7d9
ZJS
605 if (config->entry_oneshot)
606 for (i = config->n_entries - 1; i >= 0; i--)
12580bc3
LP
607 if (streq(config->entry_oneshot, config->entries[i].id)) {
608 log_debug("Found default: id \"%s\" is matched by LoaderEntryOneShot",
609 config->entries[i].id);
7e87c7d9
ZJS
610 return i;
611 }
612
613 if (config->entry_default)
614 for (i = config->n_entries - 1; i >= 0; i--)
12580bc3
LP
615 if (streq(config->entry_default, config->entries[i].id)) {
616 log_debug("Found default: id \"%s\" is matched by LoaderEntryDefault",
617 config->entries[i].id);
7e87c7d9
ZJS
618 return i;
619 }
620
621 if (config->default_pattern)
622 for (i = config->n_entries - 1; i >= 0; i--)
12580bc3
LP
623 if (fnmatch(config->default_pattern, config->entries[i].id, FNM_CASEFOLD) == 0) {
624 log_debug("Found default: id \"%s\" is matched by pattern \"%s\"",
625 config->entries[i].id, config->default_pattern);
7e87c7d9
ZJS
626 return i;
627 }
628
388d2993
ZJS
629 log_debug("Found default: last entry \"%s\"", config->entries[config->n_entries - 1].id);
630 return config->n_entries - 1;
7e87c7d9
ZJS
631}
632
a2f8664e
LP
633int boot_entries_load_config(
634 const char *esp_path,
635 const char *xbootldr_path,
636 BootConfig *config) {
637
7e87c7d9
ZJS
638 const char *p;
639 int r;
640
4fe2ba0e
LP
641 assert(config);
642
a2f8664e
LP
643 if (esp_path) {
644 p = strjoina(esp_path, "/loader/loader.conf");
645 r = boot_loader_read_conf(p, config);
646 if (r < 0)
647 return r;
7e87c7d9 648
a2f8664e
LP
649 p = strjoina(esp_path, "/loader/entries");
650 r = boot_entries_find(esp_path, p, &config->entries, &config->n_entries);
651 if (r < 0)
652 return r;
5e146a75
LP
653
654 p = strjoina(esp_path, "/EFI/Linux/");
655 r = boot_entries_find_unified(esp_path, p, &config->entries, &config->n_entries);
656 if (r < 0)
657 return r;
a2f8664e
LP
658 }
659
660 if (xbootldr_path) {
661 p = strjoina(xbootldr_path, "/loader/entries");
662 r = boot_entries_find(xbootldr_path, p, &config->entries, &config->n_entries);
663 if (r < 0)
664 return r;
5e146a75
LP
665
666 p = strjoina(xbootldr_path, "/EFI/Linux/");
667 r = boot_entries_find_unified(xbootldr_path, p, &config->entries, &config->n_entries);
668 if (r < 0)
669 return r;
a2f8664e 670 }
7e87c7d9 671
dd2bf34c
LP
672 typesafe_qsort(config->entries, config->n_entries, boot_entry_compare);
673
64f05708
ZJS
674 r = boot_entries_uniquify(config->entries, config->n_entries);
675 if (r < 0)
676 return log_error_errno(r, "Failed to uniquify boot entries: %m");
677
9c4a6c13
LP
678 if (is_efi_boot()) {
679 r = efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderEntryOneShot", &config->entry_oneshot);
bd29f9de
ZJS
680 if (r < 0 && !IN_SET(r, -ENOENT, -ENODATA)) {
681 log_warning_errno(r, "Failed to read EFI variable \"LoaderEntryOneShot\": %m");
682 if (r == -ENOMEM)
683 return r;
684 }
9c4a6c13
LP
685
686 r = efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderEntryDefault", &config->entry_default);
bd29f9de
ZJS
687 if (r < 0 && !IN_SET(r, -ENOENT, -ENODATA)) {
688 log_warning_errno(r, "Failed to read EFI variable \"LoaderEntryDefault\": %m");
689 if (r == -ENOMEM)
690 return r;
691 }
9c4a6c13 692 }
7e87c7d9
ZJS
693
694 config->default_entry = boot_entries_select_default(config);
695 return 0;
696}
af918182 697
eea4ce1e
LP
698int boot_entries_load_config_auto(
699 const char *override_esp_path,
700 const char *override_xbootldr_path,
701 BootConfig *config) {
702
703 _cleanup_free_ char *esp_where = NULL, *xbootldr_where = NULL;
704 int r;
705
706 assert(config);
707
708 /* This function is similar to boot_entries_load_config(), however we automatically search for the
709 * ESP and the XBOOTLDR partition unless it is explicitly specified. Also, if the user did not pass
710 * an ESP or XBOOTLDR path directly, let's see if /run/boot-loader-entries/ exists. If so, let's
711 * read data from there, as if it was an ESP (i.e. loading both entries and loader.conf data from
712 * it). This allows other boot loaders to pass boot loader entry information to our tools if they
713 * want to. */
714
715 if (!override_esp_path && !override_xbootldr_path) {
f40999f8
ZJS
716 if (access("/run/boot-loader-entries/", F_OK) >= 0)
717 return boot_entries_load_config("/run/boot-loader-entries/", NULL, config);
eea4ce1e 718
f40999f8
ZJS
719 if (errno != ENOENT)
720 return log_error_errno(errno,
721 "Failed to determine whether /run/boot-loader-entries/ exists: %m");
eea4ce1e
LP
722 }
723
724 r = find_esp_and_warn(override_esp_path, false, &esp_where, NULL, NULL, NULL, NULL);
cc5957dc 725 if (r < 0) /* we don't log about ENOKEY here, but propagate it, leaving it to the caller to log */
eea4ce1e
LP
726 return r;
727
728 r = find_xbootldr_and_warn(override_xbootldr_path, false, &xbootldr_where, NULL);
729 if (r < 0 && r != -ENOKEY)
730 return r; /* It's fine if the XBOOTLDR partition doesn't exist, hence we ignore ENOKEY here */
731
f40999f8 732 return boot_entries_load_config(esp_where, xbootldr_where, config);
eea4ce1e
LP
733}
734
528fcf8d 735#if ENABLE_EFI
93f14ce2 736int boot_entries_augment_from_loader(BootConfig *config, bool only_auto) {
93f14ce2
LP
737 static const char * const title_table[] = {
738 /* Pretty names for a few well-known automatically discovered entries. */
739 "auto-osx", "macOS",
740 "auto-windows", "Windows Boot Manager",
741 "auto-efi-shell", "EFI Shell",
742 "auto-efi-default", "EFI Default Loader",
743 "auto-reboot-to-firmware-setup", "Reboot Into Firmware Interface",
744 };
745
18268197 746 _cleanup_strv_free_ char **found_by_loader = NULL;
93f14ce2
LP
747 size_t n_allocated;
748 char **i;
749 int r;
750
751 assert(config);
752
753 /* Let's add the entries discovered by the boot loader to the end of our list, unless they are
754 * already included there. */
755
756 r = efi_loader_get_entries(&found_by_loader);
757 if (IN_SET(r, -ENOENT, -EOPNOTSUPP))
758 return log_debug_errno(r, "Boot loader reported no entries.");
759 if (r < 0)
760 return log_error_errno(r, "Failed to determine entries reported by boot loader: %m");
761
762 n_allocated = config->n_entries;
763
764 STRV_FOREACH(i, found_by_loader) {
ce4c4f81 765 _cleanup_free_ char *c = NULL, *t = NULL, *p = NULL;
93f14ce2
LP
766 char **a, **b;
767
768 if (boot_config_has_entry(config, *i))
769 continue;
770
771 if (only_auto && !startswith(*i, "auto-"))
772 continue;
773
774 c = strdup(*i);
775 if (!c)
776 return log_oom();
777
778 STRV_FOREACH_PAIR(a, b, (char**) title_table)
779 if (streq(*a, *i)) {
780 t = strdup(*b);
781 if (!t)
782 return log_oom();
783 break;
784 }
785
ce4c4f81
ZJS
786 p = efi_variable_path(EFI_VENDOR_LOADER, "LoaderEntries");
787 if (!p)
788 return log_oom();
789
93f14ce2
LP
790 if (!GREEDY_REALLOC0(config->entries, n_allocated, config->n_entries + 1))
791 return log_oom();
792
793 config->entries[config->n_entries++] = (BootEntry) {
794 .type = BOOT_ENTRY_LOADER,
795 .id = TAKE_PTR(c),
796 .title = TAKE_PTR(t),
ce4c4f81 797 .path = TAKE_PTR(p),
93f14ce2
LP
798 };
799 }
800
801 return 0;
802}
528fcf8d 803#endif
93f14ce2 804
af918182
ZJS
805/********************************************************************************/
806
575d4370
LP
807static int verify_esp_blkid(
808 dev_t devid,
5caa3167 809 bool searching,
af918182
ZJS
810 uint32_t *ret_part,
811 uint64_t *ret_pstart,
812 uint64_t *ret_psize,
813 sd_id128_t *ret_uuid) {
575d4370
LP
814
815 sd_id128_t uuid = SD_ID128_NULL;
816 uint64_t pstart = 0, psize = 0;
817 uint32_t part = 0;
818
4e066f7f 819#if HAVE_BLKID
8e766630 820 _cleanup_(blkid_free_probep) blkid_probe b = NULL;
54b22b26 821 _cleanup_free_ char *node = NULL;
4e066f7f 822 const char *v;
af918182
ZJS
823 int r;
824
575d4370 825 r = device_path_make_major_minor(S_IFBLK, devid, &node);
54b22b26
LP
826 if (r < 0)
827 return log_error_errno(r, "Failed to format major/minor device path: %m");
575d4370 828
af918182 829 errno = 0;
54b22b26 830 b = blkid_new_probe_from_filename(node);
af918182 831 if (!b)
575d4370 832 return log_error_errno(errno ?: SYNTHETIC_ERRNO(ENOMEM), "Failed to open file system \"%s\": %m", node);
af918182
ZJS
833
834 blkid_probe_enable_superblocks(b, 1);
835 blkid_probe_set_superblocks_flags(b, BLKID_SUBLKS_TYPE);
836 blkid_probe_enable_partitions(b, 1);
837 blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS);
838
839 errno = 0;
840 r = blkid_do_safeprobe(b);
a7afbd60 841 if (r == -2)
575d4370 842 return log_error_errno(SYNTHETIC_ERRNO(ENODEV), "File system \"%s\" is ambiguous.", node);
a7afbd60 843 else if (r == 1)
575d4370 844 return log_error_errno(SYNTHETIC_ERRNO(ENODEV), "File system \"%s\" does not contain a label.", node);
a7afbd60 845 else if (r != 0)
575d4370 846 return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe file system \"%s\": %m", node);
af918182
ZJS
847
848 errno = 0;
849 r = blkid_probe_lookup_value(b, "TYPE", &v, NULL);
850 if (r != 0)
575d4370 851 return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe file system type of \"%s\": %m", node);
a7afbd60
LP
852 if (!streq(v, "vfat"))
853 return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
854 SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
575d4370 855 "File system \"%s\" is not FAT.", node);
af918182
ZJS
856
857 errno = 0;
858 r = blkid_probe_lookup_value(b, "PART_ENTRY_SCHEME", &v, NULL);
859 if (r != 0)
575d4370 860 return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition scheme of \"%s\": %m", node);
a7afbd60
LP
861 if (!streq(v, "gpt"))
862 return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
863 SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
575d4370 864 "File system \"%s\" is not on a GPT partition table.", node);
af918182
ZJS
865
866 errno = 0;
867 r = blkid_probe_lookup_value(b, "PART_ENTRY_TYPE", &v, NULL);
868 if (r != 0)
575d4370 869 return log_error_errno(errno ?: EIO, "Failed to probe partition type UUID of \"%s\": %m", node);
a7afbd60
LP
870 if (!streq(v, "c12a7328-f81f-11d2-ba4b-00a0c93ec93b"))
871 return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
872 SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
575d4370 873 "File system \"%s\" has wrong type for an EFI System Partition (ESP).", node);
af918182
ZJS
874
875 errno = 0;
876 r = blkid_probe_lookup_value(b, "PART_ENTRY_UUID", &v, NULL);
877 if (r != 0)
575d4370 878 return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition entry UUID of \"%s\": %m", node);
af918182 879 r = sd_id128_from_string(v, &uuid);
a7afbd60 880 if (r < 0)
575d4370 881 return log_error_errno(r, "Partition \"%s\" has invalid UUID \"%s\".", node, v);
af918182
ZJS
882
883 errno = 0;
884 r = blkid_probe_lookup_value(b, "PART_ENTRY_NUMBER", &v, NULL);
885 if (r != 0)
28b77ab2 886 return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition number of \"%s\": %m", node);
af918182
ZJS
887 r = safe_atou32(v, &part);
888 if (r < 0)
889 return log_error_errno(r, "Failed to parse PART_ENTRY_NUMBER field.");
890
891 errno = 0;
892 r = blkid_probe_lookup_value(b, "PART_ENTRY_OFFSET", &v, NULL);
893 if (r != 0)
575d4370 894 return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition offset of \"%s\": %m", node);
af918182
ZJS
895 r = safe_atou64(v, &pstart);
896 if (r < 0)
897 return log_error_errno(r, "Failed to parse PART_ENTRY_OFFSET field.");
898
899 errno = 0;
900 r = blkid_probe_lookup_value(b, "PART_ENTRY_SIZE", &v, NULL);
901 if (r != 0)
575d4370 902 return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition size of \"%s\": %m", node);
af918182
ZJS
903 r = safe_atou64(v, &psize);
904 if (r < 0)
905 return log_error_errno(r, "Failed to parse PART_ENTRY_SIZE field.");
4e066f7f 906#endif
af918182 907
af918182
ZJS
908 if (ret_part)
909 *ret_part = part;
910 if (ret_pstart)
911 *ret_pstart = pstart;
912 if (ret_psize)
913 *ret_psize = psize;
914 if (ret_uuid)
915 *ret_uuid = uuid;
916
917 return 0;
918}
919
cedb9eec
LP
920static int verify_esp_udev(
921 dev_t devid,
922 bool searching,
923 uint32_t *ret_part,
924 uint64_t *ret_pstart,
925 uint64_t *ret_psize,
926 sd_id128_t *ret_uuid) {
927
928 _cleanup_(sd_device_unrefp) sd_device *d = NULL;
929 _cleanup_free_ char *node = NULL;
930 sd_id128_t uuid = SD_ID128_NULL;
931 uint64_t pstart = 0, psize = 0;
932 uint32_t part = 0;
933 const char *v;
934 int r;
935
936 r = device_path_make_major_minor(S_IFBLK, devid, &node);
937 if (r < 0)
938 return log_error_errno(r, "Failed to format major/minor device path: %m");
939
940 r = sd_device_new_from_devnum(&d, 'b', devid);
941 if (r < 0)
942 return log_error_errno(r, "Failed to get device from device number: %m");
943
944 r = sd_device_get_property_value(d, "ID_FS_TYPE", &v);
945 if (r < 0)
946 return log_error_errno(r, "Failed to get device property: %m");
947 if (!streq(v, "vfat"))
948 return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
949 SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
950 "File system \"%s\" is not FAT.", node );
951
952 r = sd_device_get_property_value(d, "ID_PART_ENTRY_SCHEME", &v);
953 if (r < 0)
954 return log_error_errno(r, "Failed to get device property: %m");
955 if (!streq(v, "gpt"))
956 return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
957 SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
958 "File system \"%s\" is not on a GPT partition table.", node);
959
960 r = sd_device_get_property_value(d, "ID_PART_ENTRY_TYPE", &v);
961 if (r < 0)
962 return log_error_errno(r, "Failed to get device property: %m");
963 if (!streq(v, "c12a7328-f81f-11d2-ba4b-00a0c93ec93b"))
964 return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
965 SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
966 "File system \"%s\" has wrong type for an EFI System Partition (ESP).", node);
967
968 r = sd_device_get_property_value(d, "ID_PART_ENTRY_UUID", &v);
969 if (r < 0)
970 return log_error_errno(r, "Failed to get device property: %m");
971 r = sd_id128_from_string(v, &uuid);
972 if (r < 0)
973 return log_error_errno(r, "Partition \"%s\" has invalid UUID \"%s\".", node, v);
974
975 r = sd_device_get_property_value(d, "ID_PART_ENTRY_NUMBER", &v);
976 if (r < 0)
977 return log_error_errno(r, "Failed to get device property: %m");
978 r = safe_atou32(v, &part);
979 if (r < 0)
980 return log_error_errno(r, "Failed to parse PART_ENTRY_NUMBER field.");
981
982 r = sd_device_get_property_value(d, "ID_PART_ENTRY_OFFSET", &v);
983 if (r < 0)
984 return log_error_errno(r, "Failed to get device property: %m");
985 r = safe_atou64(v, &pstart);
986 if (r < 0)
987 return log_error_errno(r, "Failed to parse PART_ENTRY_OFFSET field.");
988
989 r = sd_device_get_property_value(d, "ID_PART_ENTRY_SIZE", &v);
990 if (r < 0)
991 return log_error_errno(r, "Failed to get device property: %m");
992 r = safe_atou64(v, &psize);
993 if (r < 0)
994 return log_error_errno(r, "Failed to parse PART_ENTRY_SIZE field.");
995
996 if (ret_part)
997 *ret_part = part;
998 if (ret_pstart)
999 *ret_pstart = pstart;
1000 if (ret_psize)
1001 *ret_psize = psize;
1002 if (ret_uuid)
1003 *ret_uuid = uuid;
1004
1005 return 0;
1006}
1007
18ae9ef1
LP
1008static int verify_fsroot_dir(
1009 const char *path,
1010 bool searching,
1011 bool unprivileged_mode,
1012 dev_t *ret_dev) {
1013
1014 struct stat st, st2;
8312d694 1015 const char *t2, *trigger;
18ae9ef1
LP
1016 int r;
1017
1018 assert(path);
1019 assert(ret_dev);
1020
8312d694
LP
1021 /* So, the ESP and XBOOTLDR partition are commonly located on an autofs mount. stat() on the
1022 * directory won't trigger it, if it is not mounted yet. Let's hence explicitly trigger it here,
1023 * before stat()ing */
1024 trigger = strjoina(path, "/trigger"); /* Filename doesn't matter... */
1025 (void) access(trigger, F_OK);
1026
18ae9ef1
LP
1027 if (stat(path, &st) < 0)
1028 return log_full_errno((searching && errno == ENOENT) ||
1029 (unprivileged_mode && errno == EACCES) ? LOG_DEBUG : LOG_ERR, errno,
1030 "Failed to determine block device node of \"%s\": %m", path);
1031
1032 if (major(st.st_dev) == 0)
1033 return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
1034 SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
1035 "Block device node of \"%s\" is invalid.", path);
1036
1037 t2 = strjoina(path, "/..");
1038 if (stat(t2, &st2) < 0) {
1039 if (errno != EACCES)
1040 r = -errno;
1041 else {
1042 _cleanup_free_ char *parent = NULL;
1043
1044 /* If going via ".." didn't work due to EACCESS, then let's determine the parent path
1045 * directly instead. It's not as good, due to symlinks and such, but we can't do
1046 * anything better here. */
1047
1048 parent = dirname_malloc(path);
1049 if (!parent)
1050 return log_oom();
1051
1052 if (stat(parent, &st2) < 0)
1053 r = -errno;
1054 else
1055 r = 0;
1056 }
1057
1058 if (r < 0)
1059 return log_full_errno(unprivileged_mode && r == -EACCES ? LOG_DEBUG : LOG_ERR, r,
1060 "Failed to determine block device node of parent of \"%s\": %m", path);
1061 }
1062
1063 if (st.st_dev == st2.st_dev)
1064 return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
1065 SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
1066 "Directory \"%s\" is not the root of the file system.", path);
1067
1068 if (ret_dev)
1069 *ret_dev = st.st_dev;
1070
1071 return 0;
1072}
1073
575d4370
LP
1074static int verify_esp(
1075 const char *p,
1076 bool searching,
1077 bool unprivileged_mode,
1078 uint32_t *ret_part,
1079 uint64_t *ret_pstart,
1080 uint64_t *ret_psize,
1081 sd_id128_t *ret_uuid) {
1082
575d4370 1083 bool relax_checks;
18ae9ef1 1084 dev_t devid;
575d4370
LP
1085 int r;
1086
1087 assert(p);
1088
1089 /* This logs about all errors, except:
1090 *
1091 * -ENOENT → if 'searching' is set, and the dir doesn't exist
1092 * -EADDRNOTAVAIL → if 'searching' is set, and the dir doesn't look like an ESP
5238e957 1093 * -EACESS → if 'unprivileged_mode' is set, and we have trouble accessing the thing
575d4370
LP
1094 */
1095
1096 relax_checks = getenv_bool("SYSTEMD_RELAX_ESP_CHECKS") > 0;
1097
5238e957 1098 /* Non-root user can only check the status, so if an error occurred in the following, it does not cause any
575d4370
LP
1099 * issues. Let's also, silence the error messages. */
1100
1101 if (!relax_checks) {
18ae9ef1
LP
1102 struct statfs sfs;
1103
575d4370
LP
1104 if (statfs(p, &sfs) < 0)
1105 /* If we are searching for the mount point, don't generate a log message if we can't find the path */
1106 return log_full_errno((searching && errno == ENOENT) ||
1107 (unprivileged_mode && errno == EACCES) ? LOG_DEBUG : LOG_ERR, errno,
1108 "Failed to check file system type of \"%s\": %m", p);
1109
1110 if (!F_TYPE_EQUAL(sfs.f_type, MSDOS_SUPER_MAGIC))
1111 return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
1112 SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
1113 "File system \"%s\" is not a FAT EFI System Partition (ESP) file system.", p);
1114 }
1115
18ae9ef1 1116 r = verify_fsroot_dir(p, searching, unprivileged_mode, &devid);
575d4370 1117 if (r < 0)
18ae9ef1 1118 return r;
575d4370
LP
1119
1120 /* In a container we don't have access to block devices, skip this part of the verification, we trust
cedb9eec
LP
1121 * the container manager set everything up correctly on its own. */
1122 if (detect_container() > 0 || relax_checks)
575d4370
LP
1123 goto finish;
1124
cedb9eec
LP
1125 /* If we are unprivileged we ask udev for the metadata about the partition. If we are privileged we
1126 * use blkid instead. Why? Because this code is called from 'bootctl' which is pretty much an
1127 * emergency recovery tool that should also work when udev isn't up (i.e. from the emergency shell),
1128 * however blkid can't work if we have no privileges to access block devices directly, which is why
1129 * we use udev in that case. */
1130 if (unprivileged_mode)
18ae9ef1 1131 return verify_esp_udev(devid, searching, ret_part, ret_pstart, ret_psize, ret_uuid);
cedb9eec 1132 else
18ae9ef1 1133 return verify_esp_blkid(devid, searching, ret_part, ret_pstart, ret_psize, ret_uuid);
575d4370
LP
1134
1135finish:
1136 if (ret_part)
1137 *ret_part = 0;
1138 if (ret_pstart)
1139 *ret_pstart = 0;
1140 if (ret_psize)
1141 *ret_psize = 0;
1142 if (ret_uuid)
1143 *ret_uuid = SD_ID128_NULL;
1144
1145 return 0;
1146}
1147
5caa3167
LP
1148int find_esp_and_warn(
1149 const char *path,
1150 bool unprivileged_mode,
1151 char **ret_path,
1152 uint32_t *ret_part,
1153 uint64_t *ret_pstart,
1154 uint64_t *ret_psize,
1155 sd_id128_t *ret_uuid) {
af918182 1156
af918182
ZJS
1157 int r;
1158
5caa3167
LP
1159 /* This logs about all errors except:
1160 *
1161 * -ENOKEY → when we can't find the partition
1162 * -EACCESS → when unprivileged_mode is true, and we can't access something
1163 */
af918182 1164
5caa3167
LP
1165 if (path) {
1166 r = verify_esp(path, false, unprivileged_mode, ret_part, ret_pstart, ret_psize, ret_uuid);
af918182
ZJS
1167 if (r < 0)
1168 return r;
1169
5caa3167
LP
1170 goto found;
1171 }
1172
cc7a0bfa
LP
1173 path = getenv("SYSTEMD_ESP_PATH");
1174 if (path) {
baaa35ad
ZJS
1175 if (!path_is_valid(path) || !path_is_absolute(path))
1176 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
1177 "$SYSTEMD_ESP_PATH does not refer to absolute path, refusing to use it: %s",
1178 path);
cc7a0bfa
LP
1179
1180 /* Note: when the user explicitly configured things with an env var we won't validate the mount
1181 * point. After all we want this to be useful for testing. */
1182 goto found;
1183 }
1184
5caa3167
LP
1185 FOREACH_STRING(path, "/efi", "/boot", "/boot/efi") {
1186
1187 r = verify_esp(path, true, unprivileged_mode, ret_part, ret_pstart, ret_psize, ret_uuid);
1188 if (r >= 0)
1189 goto found;
1190 if (!IN_SET(r, -ENOENT, -EADDRNOTAVAIL)) /* This one is not it */
1191 return r;
1192 }
1193
1194 /* No logging here */
1195 return -ENOKEY;
1196
15cb6c98
LP
1197found:
1198 if (ret_path) {
1199 char *c;
1200
1201 c = strdup(path);
1202 if (!c)
1203 return log_oom();
1204
1205 *ret_path = c;
1206 }
1207
1208 return 0;
1209}
1210
ad95aa44
LP
1211static int verify_xbootldr_blkid(
1212 dev_t devid,
15cb6c98 1213 bool searching,
15cb6c98 1214 sd_id128_t *ret_uuid) {
ad95aa44
LP
1215
1216 sd_id128_t uuid = SD_ID128_NULL;
1217
15cb6c98
LP
1218#if HAVE_BLKID
1219 _cleanup_(blkid_free_probep) blkid_probe b = NULL;
1220 _cleanup_free_ char *node = NULL;
1221 const char *v;
15cb6c98
LP
1222 int r;
1223
ad95aa44 1224 r = device_path_make_major_minor(S_IFBLK, devid, &node);
15cb6c98
LP
1225 if (r < 0)
1226 return log_error_errno(r, "Failed to format major/minor device path: %m");
1227 errno = 0;
1228 b = blkid_new_probe_from_filename(node);
1229 if (!b)
ad95aa44 1230 return log_error_errno(errno ?: SYNTHETIC_ERRNO(ENOMEM), "Failed to open file system \"%s\": %m", node);
15cb6c98
LP
1231
1232 blkid_probe_enable_partitions(b, 1);
1233 blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS);
1234
1235 errno = 0;
1236 r = blkid_do_safeprobe(b);
1237 if (r == -2)
ad95aa44 1238 return log_error_errno(SYNTHETIC_ERRNO(ENODEV), "File system \"%s\" is ambiguous.", node);
15cb6c98 1239 else if (r == 1)
ad95aa44 1240 return log_error_errno(SYNTHETIC_ERRNO(ENODEV), "File system \"%s\" does not contain a label.", node);
15cb6c98 1241 else if (r != 0)
ad95aa44 1242 return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe file system \"%s\": %m", node);
15cb6c98
LP
1243
1244 errno = 0;
1245 r = blkid_probe_lookup_value(b, "PART_ENTRY_SCHEME", &v, NULL);
1246 if (r != 0)
ad95aa44 1247 return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition scheme of \"%s\": %m", node);
15cb6c98
LP
1248 if (streq(v, "gpt")) {
1249
1250 errno = 0;
1251 r = blkid_probe_lookup_value(b, "PART_ENTRY_TYPE", &v, NULL);
1252 if (r != 0)
ad95aa44 1253 return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition type UUID of \"%s\": %m", node);
15cb6c98
LP
1254 if (!streq(v, "bc13c2ff-59e6-4262-a352-b275fd6f7172"))
1255 return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
1256 searching ? SYNTHETIC_ERRNO(EADDRNOTAVAIL) : SYNTHETIC_ERRNO(ENODEV),
ad95aa44 1257 "File system \"%s\" has wrong type for extended boot loader partition.", node);
15cb6c98
LP
1258
1259 errno = 0;
1260 r = blkid_probe_lookup_value(b, "PART_ENTRY_UUID", &v, NULL);
1261 if (r != 0)
ad95aa44 1262 return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition entry UUID of \"%s\": %m", node);
15cb6c98
LP
1263 r = sd_id128_from_string(v, &uuid);
1264 if (r < 0)
ad95aa44 1265 return log_error_errno(r, "Partition \"%s\" has invalid UUID \"%s\".", node, v);
15cb6c98
LP
1266
1267 } else if (streq(v, "dos")) {
1268
1269 errno = 0;
1270 r = blkid_probe_lookup_value(b, "PART_ENTRY_TYPE", &v, NULL);
1271 if (r != 0)
ad95aa44 1272 return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition type UUID of \"%s\": %m", node);
15cb6c98
LP
1273 if (!streq(v, "0xea"))
1274 return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
1275 searching ? SYNTHETIC_ERRNO(EADDRNOTAVAIL) : SYNTHETIC_ERRNO(ENODEV),
ad95aa44 1276 "File system \"%s\" has wrong type for extended boot loader partition.", node);
15cb6c98
LP
1277
1278 } else
1279 return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
1280 searching ? SYNTHETIC_ERRNO(EADDRNOTAVAIL) : SYNTHETIC_ERRNO(ENODEV),
ad95aa44 1281 "File system \"%s\" is not on a GPT or DOS partition table.", node);
15cb6c98
LP
1282#endif
1283
15cb6c98
LP
1284 if (ret_uuid)
1285 *ret_uuid = uuid;
1286
1287 return 0;
1288}
1289
85d02102
LP
1290static int verify_xbootldr_udev(
1291 dev_t devid,
1292 bool searching,
1293 sd_id128_t *ret_uuid) {
1294
1295 _cleanup_(sd_device_unrefp) sd_device *d = NULL;
1296 _cleanup_free_ char *node = NULL;
1297 sd_id128_t uuid = SD_ID128_NULL;
1298 const char *v;
1299 int r;
1300
1301 r = device_path_make_major_minor(S_IFBLK, devid, &node);
1302 if (r < 0)
1303 return log_error_errno(r, "Failed to format major/minor device path: %m");
1304
1305 r = sd_device_new_from_devnum(&d, 'b', devid);
1306 if (r < 0)
1307 return log_error_errno(r, "Failed to get device from device number: %m");
1308
1309 r = sd_device_get_property_value(d, "ID_PART_ENTRY_SCHEME", &v);
1310 if (r < 0)
1311 return log_error_errno(r, "Failed to get device property: %m");
1312
1313 if (streq(v, "gpt")) {
1314
1315 r = sd_device_get_property_value(d, "ID_PART_ENTRY_TYPE", &v);
1316 if (r < 0)
1317 return log_error_errno(r, "Failed to get device property: %m");
1318 if (!streq(v, "bc13c2ff-59e6-4262-a352-b275fd6f7172"))
1319 return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
1320 searching ? SYNTHETIC_ERRNO(EADDRNOTAVAIL) : SYNTHETIC_ERRNO(ENODEV),
1321 "File system \"%s\" has wrong type for extended boot loader partition.", node);
1322
1323 r = sd_device_get_property_value(d, "ID_PART_ENTRY_UUID", &v);
1324 if (r < 0)
1325 return log_error_errno(r, "Failed to get device property: %m");
1326 r = sd_id128_from_string(v, &uuid);
1327 if (r < 0)
1328 return log_error_errno(r, "Partition \"%s\" has invalid UUID \"%s\".", node, v);
1329
1330 } else if (streq(v, "dos")) {
1331
1332 r = sd_device_get_property_value(d, "ID_PART_ENTRY_TYPE", &v);
1333 if (r < 0)
1334 return log_error_errno(r, "Failed to get device property: %m");
1335 if (!streq(v, "0xea"))
1336 return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
1337 searching ? SYNTHETIC_ERRNO(EADDRNOTAVAIL) : SYNTHETIC_ERRNO(ENODEV),
1338 "File system \"%s\" has wrong type for extended boot loader partition.", node);
1339 } else
1340 return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
1341 searching ? SYNTHETIC_ERRNO(EADDRNOTAVAIL) : SYNTHETIC_ERRNO(ENODEV),
1342 "File system \"%s\" is not on a GPT or DOS partition table.", node);
1343
1344 if (ret_uuid)
1345 *ret_uuid = uuid;
1346
1347 return 0;
1348}
1349
ad95aa44
LP
1350static int verify_xbootldr(
1351 const char *p,
1352 bool searching,
1353 bool unprivileged_mode,
1354 sd_id128_t *ret_uuid) {
1355
ad95aa44 1356 bool relax_checks;
4a4994b6 1357 dev_t devid;
ad95aa44
LP
1358 int r;
1359
1360 assert(p);
1361
1362 relax_checks = getenv_bool("SYSTEMD_RELAX_XBOOTLDR_CHECKS") > 0;
1363
4a4994b6 1364 r = verify_fsroot_dir(p, searching, unprivileged_mode, &devid);
ad95aa44 1365 if (r < 0)
4a4994b6 1366 return r;
ad95aa44 1367
85d02102 1368 if (detect_container() > 0 || relax_checks)
ad95aa44
LP
1369 goto finish;
1370
85d02102 1371 if (unprivileged_mode)
4a4994b6 1372 return verify_xbootldr_udev(devid, searching, ret_uuid);
85d02102 1373 else
4a4994b6 1374 return verify_xbootldr_blkid(devid, searching, ret_uuid);
ad95aa44
LP
1375
1376finish:
1377 if (ret_uuid)
1378 *ret_uuid = SD_ID128_NULL;
1379
1380 return 0;
1381}
1382
15cb6c98
LP
1383int find_xbootldr_and_warn(
1384 const char *path,
1385 bool unprivileged_mode,
1386 char **ret_path,
1387 sd_id128_t *ret_uuid) {
1388
1389 int r;
1390
1391 /* Similar to find_esp_and_warn(), but finds the XBOOTLDR partition. Returns the same errors. */
1392
1393 if (path) {
1394 r = verify_xbootldr(path, false, unprivileged_mode, ret_uuid);
1395 if (r < 0)
1396 return r;
1397
1398 goto found;
1399 }
1400
1401 path = getenv("SYSTEMD_XBOOTLDR_PATH");
1402 if (path) {
1403 if (!path_is_valid(path) || !path_is_absolute(path))
1404 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
1405 "$SYSTEMD_XBOOTLDR_PATH does not refer to absolute path, refusing to use it: %s",
1406 path);
1407
1408 goto found;
1409 }
1410
1411 r = verify_xbootldr("/boot", true, unprivileged_mode, ret_uuid);
1412 if (r >= 0) {
1413 path = "/boot";
1414 goto found;
1415 }
1416 if (!IN_SET(r, -ENOENT, -EADDRNOTAVAIL)) /* This one is not it */
1417 return r;
1418
1419 return -ENOKEY;
1420
5caa3167
LP
1421found:
1422 if (ret_path) {
1423 char *c;
1424
1425 c = strdup(path);
1426 if (!c)
af918182
ZJS
1427 return log_oom();
1428
5caa3167 1429 *ret_path = c;
af918182
ZJS
1430 }
1431
5caa3167 1432 return 0;
af918182 1433}