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