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