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