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