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