]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/shared/bootspec.c
bootspec: add comment explaining verify_esp() return codes
[thirdparty/systemd.git] / src / shared / bootspec.c
CommitLineData
58f21e63 1/* SPDX-License-Identifier: LGPL-2.1+ */
7e87c7d9
ZJS
2
3#include <stdio.h>
af918182 4#include <linux/magic.h>
7e87c7d9 5
dccca82b
LP
6#include "sd-id128.h"
7
7e87c7d9 8#include "alloc-util.h"
af918182 9#include "blkid-util.h"
7e87c7d9
ZJS
10#include "bootspec.h"
11#include "conf-files.h"
12#include "def.h"
c67f84b0 13#include "device-nodes.h"
7e87c7d9 14#include "efivars.h"
8cbb7d87 15#include "env-util.h"
7e87c7d9
ZJS
16#include "fd-util.h"
17#include "fileio.h"
af918182 18#include "parse-util.h"
cc7a0bfa 19#include "path-util.h"
af918182 20#include "stat-util.h"
7e87c7d9
ZJS
21#include "string-util.h"
22#include "strv.h"
af918182 23#include "virt.h"
7e87c7d9 24
0de2e1fd 25static void boot_entry_free(BootEntry *entry) {
4fe2ba0e 26 assert(entry);
7e87c7d9 27
12580bc3 28 free(entry->id);
2d3bfb69 29 free(entry->path);
43b736a8 30 free(entry->root);
7e87c7d9 31 free(entry->title);
64f05708 32 free(entry->show_title);
7e87c7d9
ZJS
33 free(entry->version);
34 free(entry->machine_id);
35 free(entry->architecture);
36 strv_free(entry->options);
37 free(entry->kernel);
38 free(entry->efi);
39 strv_free(entry->initrd);
40 free(entry->device_tree);
41}
42
43b736a8
LP
43static int boot_entry_load(
44 const char *root,
45 const char *path,
46 BootEntry *entry) {
47
263195c6 48 _cleanup_(boot_entry_free) BootEntry tmp = {};
7e87c7d9
ZJS
49 _cleanup_fclose_ FILE *f = NULL;
50 unsigned line = 1;
263195c6 51 char *b, *c;
7e87c7d9
ZJS
52 int r;
53
43b736a8 54 assert(root);
4fe2ba0e
LP
55 assert(path);
56 assert(entry);
57
263195c6 58 c = endswith_no_case(path, ".conf");
46bba8a5
LP
59 if (!c)
60 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry filename: %s", path);
7e87c7d9 61
263195c6 62 b = basename(path);
12580bc3
LP
63 tmp.id = strndup(b, c - b);
64 if (!tmp.id)
7e87c7d9
ZJS
65 return log_oom();
66
2d3bfb69
ZJS
67 tmp.path = strdup(path);
68 if (!tmp.path)
69 return log_oom();
70
43b736a8
LP
71 tmp.root = strdup(root);
72 if (!tmp.root)
73 return log_oom();
74
263195c6
YW
75 f = fopen(path, "re");
76 if (!f)
77 return log_error_errno(errno, "Failed to open \"%s\": %m", path);
78
7e87c7d9 79 for (;;) {
f99fdc3e
YW
80 _cleanup_free_ char *buf = NULL, *field = NULL;
81 const char *p;
7e87c7d9
ZJS
82
83 r = read_line(f, LONG_LINE_MAX, &buf);
84 if (r == 0)
85 break;
86 if (r == -ENOBUFS)
87 return log_error_errno(r, "%s:%u: Line too long", path, line);
88 if (r < 0)
89 return log_error_errno(r, "%s:%u: Error while reading: %m", path, line);
90
91 line++;
92
93 if (IN_SET(*strstrip(buf), '#', '\0'))
94 continue;
95
f99fdc3e
YW
96 p = buf;
97 r = extract_first_word(&p, &field, " \t", 0);
98 if (r < 0) {
99 log_error_errno(r, "Failed to parse config file %s line %u: %m", path, line);
100 continue;
101 }
102 if (r == 0) {
7e87c7d9
ZJS
103 log_warning("%s:%u: Bad syntax", path, line);
104 continue;
105 }
7e87c7d9 106
f99fdc3e 107 if (streq(field, "title"))
7e87c7d9 108 r = free_and_strdup(&tmp.title, p);
f99fdc3e 109 else if (streq(field, "version"))
7e87c7d9 110 r = free_and_strdup(&tmp.version, p);
f99fdc3e 111 else if (streq(field, "machine-id"))
7e87c7d9 112 r = free_and_strdup(&tmp.machine_id, p);
f99fdc3e 113 else if (streq(field, "architecture"))
7e87c7d9 114 r = free_and_strdup(&tmp.architecture, p);
f99fdc3e 115 else if (streq(field, "options"))
7e87c7d9 116 r = strv_extend(&tmp.options, p);
f99fdc3e 117 else if (streq(field, "linux"))
7e87c7d9 118 r = free_and_strdup(&tmp.kernel, p);
f99fdc3e 119 else if (streq(field, "efi"))
7e87c7d9 120 r = free_and_strdup(&tmp.efi, p);
f99fdc3e 121 else if (streq(field, "initrd"))
7e87c7d9 122 r = strv_extend(&tmp.initrd, p);
f99fdc3e 123 else if (streq(field, "devicetree"))
7e87c7d9
ZJS
124 r = free_and_strdup(&tmp.device_tree, p);
125 else {
feb41f1f 126 log_notice("%s:%u: Unknown line \"%s\", ignoring.", path, line, field);
7e87c7d9
ZJS
127 continue;
128 }
129 if (r < 0)
130 return log_error_errno(r, "%s:%u: Error while reading: %m", path, line);
131 }
132
133 *entry = tmp;
134 tmp = (BootEntry) {};
135 return 0;
136}
137
138void boot_config_free(BootConfig *config) {
da6053d0 139 size_t i;
7e87c7d9 140
4fe2ba0e
LP
141 assert(config);
142
7e87c7d9
ZJS
143 free(config->default_pattern);
144 free(config->timeout);
145 free(config->editor);
c1d4e298
JJ
146 free(config->auto_entries);
147 free(config->auto_firmware);
7e87c7d9
ZJS
148
149 free(config->entry_oneshot);
150 free(config->entry_default);
151
152 for (i = 0; i < config->n_entries; i++)
153 boot_entry_free(config->entries + i);
154 free(config->entries);
155}
156
0de2e1fd 157static int boot_loader_read_conf(const char *path, BootConfig *config) {
7e87c7d9
ZJS
158 _cleanup_fclose_ FILE *f = NULL;
159 unsigned line = 1;
160 int r;
161
4fe2ba0e
LP
162 assert(path);
163 assert(config);
164
7e87c7d9 165 f = fopen(path, "re");
f91ed3dc
LP
166 if (!f) {
167 if (errno == ENOENT)
168 return 0;
169
7e87c7d9 170 return log_error_errno(errno, "Failed to open \"%s\": %m", path);
f91ed3dc 171 }
7e87c7d9
ZJS
172
173 for (;;) {
f99fdc3e
YW
174 _cleanup_free_ char *buf = NULL, *field = NULL;
175 const char *p;
7e87c7d9
ZJS
176
177 r = read_line(f, LONG_LINE_MAX, &buf);
178 if (r == 0)
179 break;
180 if (r == -ENOBUFS)
181 return log_error_errno(r, "%s:%u: Line too long", path, line);
182 if (r < 0)
183 return log_error_errno(r, "%s:%u: Error while reading: %m", path, line);
184
185 line++;
186
187 if (IN_SET(*strstrip(buf), '#', '\0'))
188 continue;
189
f99fdc3e
YW
190 p = buf;
191 r = extract_first_word(&p, &field, " \t", 0);
192 if (r < 0) {
193 log_error_errno(r, "Failed to parse config file %s line %u: %m", path, line);
194 continue;
195 }
196 if (r == 0) {
7e87c7d9
ZJS
197 log_warning("%s:%u: Bad syntax", path, line);
198 continue;
199 }
7e87c7d9 200
f99fdc3e 201 if (streq(field, "default"))
7e87c7d9 202 r = free_and_strdup(&config->default_pattern, p);
f99fdc3e 203 else if (streq(field, "timeout"))
7e87c7d9 204 r = free_and_strdup(&config->timeout, p);
f99fdc3e 205 else if (streq(field, "editor"))
7e87c7d9 206 r = free_and_strdup(&config->editor, p);
790f84eb 207 else if (streq(field, "auto-entries"))
c1d4e298 208 r = free_and_strdup(&config->auto_entries, p);
790f84eb 209 else if (streq(field, "auto-firmware"))
c1d4e298 210 r = free_and_strdup(&config->auto_firmware, p);
790f84eb 211 else if (streq(field, "console-mode"))
d37b0737 212 r = free_and_strdup(&config->console_mode, p);
7e87c7d9 213 else {
feb41f1f 214 log_notice("%s:%u: Unknown line \"%s\", ignoring.", path, line, field);
7e87c7d9
ZJS
215 continue;
216 }
217 if (r < 0)
218 return log_error_errno(r, "%s:%u: Error while reading: %m", path, line);
219 }
220
f91ed3dc 221 return 1;
7e87c7d9
ZJS
222}
223
93bab288 224static int boot_entry_compare(const BootEntry *a, const BootEntry *b) {
12580bc3 225 return str_verscmp(a->id, b->id);
7e87c7d9
ZJS
226}
227
43b736a8
LP
228static int boot_entries_find(
229 const char *root,
230 const char *dir,
a2f8664e
LP
231 BootEntry **entries,
232 size_t *n_entries) {
43b736a8 233
7e87c7d9 234 _cleanup_strv_free_ char **files = NULL;
a2f8664e
LP
235 size_t n_allocated = *n_entries;
236 bool added = false;
7e87c7d9
ZJS
237 char **f;
238 int r;
7e87c7d9 239
43b736a8 240 assert(root);
4fe2ba0e 241 assert(dir);
a2f8664e
LP
242 assert(entries);
243 assert(n_entries);
4fe2ba0e 244
7e87c7d9
ZJS
245 r = conf_files_list(&files, ".conf", NULL, 0, dir, NULL);
246 if (r < 0)
247 return log_error_errno(r, "Failed to list files in \"%s\": %m", dir);
248
249 STRV_FOREACH(f, files) {
a2f8664e 250 if (!GREEDY_REALLOC0(*entries, n_allocated, *n_entries + 1))
7e87c7d9
ZJS
251 return log_oom();
252
a2f8664e 253 r = boot_entry_load(root, *f, *entries + *n_entries);
7e87c7d9
ZJS
254 if (r < 0)
255 continue;
256
a2f8664e
LP
257 (*n_entries) ++;
258 added = true;
7e87c7d9
ZJS
259 }
260
a2f8664e
LP
261 if (added)
262 typesafe_qsort(*entries, *n_entries, boot_entry_compare);
4fe2ba0e 263
7e87c7d9
ZJS
264 return 0;
265}
266
64f05708 267static bool find_nonunique(BootEntry *entries, size_t n_entries, bool *arr) {
da6053d0 268 size_t i, j;
64f05708
ZJS
269 bool non_unique = false;
270
4fe2ba0e
LP
271 assert(entries || n_entries == 0);
272 assert(arr || n_entries == 0);
273
64f05708
ZJS
274 for (i = 0; i < n_entries; i++)
275 arr[i] = false;
276
277 for (i = 0; i < n_entries; i++)
278 for (j = 0; j < n_entries; j++)
279 if (i != j && streq(boot_entry_title(entries + i),
280 boot_entry_title(entries + j)))
281 non_unique = arr[i] = arr[j] = true;
282
283 return non_unique;
284}
285
286static int boot_entries_uniquify(BootEntry *entries, size_t n_entries) {
287 char *s;
da6053d0 288 size_t i;
64f05708
ZJS
289 int r;
290 bool arr[n_entries];
291
4fe2ba0e
LP
292 assert(entries || n_entries == 0);
293
64f05708
ZJS
294 /* Find _all_ non-unique titles */
295 if (!find_nonunique(entries, n_entries, arr))
296 return 0;
297
298 /* Add version to non-unique titles */
299 for (i = 0; i < n_entries; i++)
300 if (arr[i] && entries[i].version) {
301 r = asprintf(&s, "%s (%s)", boot_entry_title(entries + i), entries[i].version);
302 if (r < 0)
303 return -ENOMEM;
304
305 free_and_replace(entries[i].show_title, s);
306 }
307
308 if (!find_nonunique(entries, n_entries, arr))
309 return 0;
310
311 /* Add machine-id to non-unique titles */
312 for (i = 0; i < n_entries; i++)
313 if (arr[i] && entries[i].machine_id) {
314 r = asprintf(&s, "%s (%s)", boot_entry_title(entries + i), entries[i].machine_id);
315 if (r < 0)
316 return -ENOMEM;
317
318 free_and_replace(entries[i].show_title, s);
319 }
320
321 if (!find_nonunique(entries, n_entries, arr))
322 return 0;
323
324 /* Add file name to non-unique titles */
325 for (i = 0; i < n_entries; i++)
326 if (arr[i]) {
12580bc3 327 r = asprintf(&s, "%s (%s)", boot_entry_title(entries + i), entries[i].id);
64f05708
ZJS
328 if (r < 0)
329 return -ENOMEM;
330
331 free_and_replace(entries[i].show_title, s);
332 }
333
334 return 0;
335}
336
ad1afd60 337static int boot_entries_select_default(const BootConfig *config) {
7e87c7d9
ZJS
338 int i;
339
4fe2ba0e
LP
340 assert(config);
341
7e87c7d9
ZJS
342 if (config->entry_oneshot)
343 for (i = config->n_entries - 1; i >= 0; i--)
12580bc3
LP
344 if (streq(config->entry_oneshot, config->entries[i].id)) {
345 log_debug("Found default: id \"%s\" is matched by LoaderEntryOneShot",
346 config->entries[i].id);
7e87c7d9
ZJS
347 return i;
348 }
349
350 if (config->entry_default)
351 for (i = config->n_entries - 1; i >= 0; i--)
12580bc3
LP
352 if (streq(config->entry_default, config->entries[i].id)) {
353 log_debug("Found default: id \"%s\" is matched by LoaderEntryDefault",
354 config->entries[i].id);
7e87c7d9
ZJS
355 return i;
356 }
357
358 if (config->default_pattern)
359 for (i = config->n_entries - 1; i >= 0; i--)
12580bc3
LP
360 if (fnmatch(config->default_pattern, config->entries[i].id, FNM_CASEFOLD) == 0) {
361 log_debug("Found default: id \"%s\" is matched by pattern \"%s\"",
362 config->entries[i].id, config->default_pattern);
7e87c7d9
ZJS
363 return i;
364 }
365
366 if (config->n_entries > 0)
12580bc3 367 log_debug("Found default: last entry \"%s\"", config->entries[config->n_entries - 1].id);
7e87c7d9
ZJS
368 else
369 log_debug("Found no default boot entry :(");
4fe2ba0e 370
7e87c7d9
ZJS
371 return config->n_entries - 1; /* -1 means "no default" */
372}
373
a2f8664e
LP
374int boot_entries_load_config(
375 const char *esp_path,
376 const char *xbootldr_path,
377 BootConfig *config) {
378
7e87c7d9
ZJS
379 const char *p;
380 int r;
381
4fe2ba0e
LP
382 assert(config);
383
a2f8664e
LP
384 if (esp_path) {
385 p = strjoina(esp_path, "/loader/loader.conf");
386 r = boot_loader_read_conf(p, config);
387 if (r < 0)
388 return r;
7e87c7d9 389
a2f8664e
LP
390 p = strjoina(esp_path, "/loader/entries");
391 r = boot_entries_find(esp_path, p, &config->entries, &config->n_entries);
392 if (r < 0)
393 return r;
394 }
395
396 if (xbootldr_path) {
397 p = strjoina(xbootldr_path, "/loader/entries");
398 r = boot_entries_find(xbootldr_path, p, &config->entries, &config->n_entries);
399 if (r < 0)
400 return r;
401 }
7e87c7d9 402
64f05708
ZJS
403 r = boot_entries_uniquify(config->entries, config->n_entries);
404 if (r < 0)
405 return log_error_errno(r, "Failed to uniquify boot entries: %m");
406
9c4a6c13
LP
407 if (is_efi_boot()) {
408 r = efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderEntryOneShot", &config->entry_oneshot);
409 if (r < 0 && r != -ENOENT)
410 return log_error_errno(r, "Failed to read EFI var \"LoaderEntryOneShot\": %m");
411
412 r = efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderEntryDefault", &config->entry_default);
413 if (r < 0 && r != -ENOENT)
414 return log_error_errno(r, "Failed to read EFI var \"LoaderEntryDefault\": %m");
415 }
7e87c7d9
ZJS
416
417 config->default_entry = boot_entries_select_default(config);
418 return 0;
419}
af918182
ZJS
420
421/********************************************************************************/
422
423static int verify_esp(
af918182 424 const char *p,
5caa3167
LP
425 bool searching,
426 bool unprivileged_mode,
af918182
ZJS
427 uint32_t *ret_part,
428 uint64_t *ret_pstart,
429 uint64_t *ret_psize,
430 sd_id128_t *ret_uuid) {
4e066f7f 431#if HAVE_BLKID
8e766630 432 _cleanup_(blkid_free_probep) blkid_probe b = NULL;
54b22b26 433 _cleanup_free_ char *node = NULL;
4e066f7f
YW
434 const char *v;
435#endif
af918182
ZJS
436 uint64_t pstart = 0, psize = 0;
437 struct stat st, st2;
4e066f7f 438 const char *t2;
af918182
ZJS
439 struct statfs sfs;
440 sd_id128_t uuid = SD_ID128_NULL;
441 uint32_t part = 0;
8cbb7d87 442 bool relax_checks;
af918182
ZJS
443 int r;
444
445 assert(p);
446
23953a3e
LP
447 /* This logs about all errors, except:
448 *
449 * -ENOENT → if 'searching' is set, and the dir doesn't exist
450 * -EADDRNOTAVAIL → if 'searching' is set, and the dir doesn't look like an ESP
451 * -EACESS → if 'unprivileged_mode' is set, and we have trouble acessing the thing
452 */
453
8cbb7d87
LP
454 relax_checks = getenv_bool("SYSTEMD_RELAX_ESP_CHECKS") > 0;
455
5caa3167
LP
456 /* Non-root user can only check the status, so if an error occured in the following, it does not cause any
457 * issues. Let's also, silence the error messages. */
af918182 458
8cbb7d87 459 if (!relax_checks) {
a7afbd60 460 if (statfs(p, &sfs) < 0)
8cbb7d87 461 /* If we are searching for the mount point, don't generate a log message if we can't find the path */
a7afbd60
LP
462 return log_full_errno((searching && errno == ENOENT) ||
463 (unprivileged_mode && errno == EACCES) ? LOG_DEBUG : LOG_ERR, errno,
8cbb7d87 464 "Failed to check file system type of \"%s\": %m", p);
af918182 465
a7afbd60
LP
466 if (!F_TYPE_EQUAL(sfs.f_type, MSDOS_SUPER_MAGIC))
467 return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
468 SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
469 "File system \"%s\" is not a FAT EFI System Partition (ESP) file system.", p);
af918182
ZJS
470 }
471
472 if (stat(p, &st) < 0)
a7afbd60
LP
473 return log_full_errno((searching && errno == ENOENT) ||
474 (unprivileged_mode && errno == EACCES) ? LOG_DEBUG : LOG_ERR, errno,
af918182
ZJS
475 "Failed to determine block device node of \"%s\": %m", p);
476
a7afbd60
LP
477 if (major(st.st_dev) == 0)
478 return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
479 SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
480 "Block device node of \"%s\" is invalid.", p);
af918182
ZJS
481
482 t2 = strjoina(p, "/..");
483 r = stat(t2, &st2);
484 if (r < 0)
5caa3167 485 return log_full_errno(unprivileged_mode && errno == EACCES ? LOG_DEBUG : LOG_ERR, errno,
af918182
ZJS
486 "Failed to determine block device node of parent of \"%s\": %m", p);
487
a7afbd60
LP
488 if (st.st_dev == st2.st_dev)
489 return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
490 SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
491 "Directory \"%s\" is not the root of the EFI System Partition (ESP) file system.", p);
af918182
ZJS
492
493 /* In a container we don't have access to block devices, skip this part of the verification, we trust the
494 * container manager set everything up correctly on its own. Also skip the following verification for non-root user. */
8cbb7d87 495 if (detect_container() > 0 || unprivileged_mode || relax_checks)
af918182
ZJS
496 goto finish;
497
4e066f7f 498#if HAVE_BLKID
54b22b26
LP
499 r = device_path_make_major_minor(S_IFBLK, st.st_dev, &node);
500 if (r < 0)
501 return log_error_errno(r, "Failed to format major/minor device path: %m");
af918182 502 errno = 0;
54b22b26 503 b = blkid_new_probe_from_filename(node);
af918182 504 if (!b)
a7afbd60 505 return log_error_errno(errno ?: SYNTHETIC_ERRNO(ENOMEM), "Failed to open file system \"%s\": %m", p);
af918182
ZJS
506
507 blkid_probe_enable_superblocks(b, 1);
508 blkid_probe_set_superblocks_flags(b, BLKID_SUBLKS_TYPE);
509 blkid_probe_enable_partitions(b, 1);
510 blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS);
511
512 errno = 0;
513 r = blkid_do_safeprobe(b);
a7afbd60
LP
514 if (r == -2)
515 return log_error_errno(SYNTHETIC_ERRNO(ENODEV), "File system \"%s\" is ambiguous.", p);
516 else if (r == 1)
517 return log_error_errno(SYNTHETIC_ERRNO(ENODEV), "File system \"%s\" does not contain a label.", p);
518 else if (r != 0)
519 return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe file system \"%s\": %m", p);
af918182
ZJS
520
521 errno = 0;
522 r = blkid_probe_lookup_value(b, "TYPE", &v, NULL);
523 if (r != 0)
a7afbd60
LP
524 return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe file system type \"%s\": %m", p);
525 if (!streq(v, "vfat"))
526 return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
527 SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
528 "File system \"%s\" is not FAT.", p);
af918182
ZJS
529
530 errno = 0;
531 r = blkid_probe_lookup_value(b, "PART_ENTRY_SCHEME", &v, NULL);
532 if (r != 0)
a7afbd60
LP
533 return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition scheme \"%s\": %m", p);
534 if (!streq(v, "gpt"))
535 return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
536 SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
537 "File system \"%s\" is not on a GPT partition table.", p);
af918182
ZJS
538
539 errno = 0;
540 r = blkid_probe_lookup_value(b, "PART_ENTRY_TYPE", &v, NULL);
541 if (r != 0)
542 return log_error_errno(errno ?: EIO, "Failed to probe partition type UUID \"%s\": %m", p);
a7afbd60
LP
543 if (!streq(v, "c12a7328-f81f-11d2-ba4b-00a0c93ec93b"))
544 return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
545 SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
546 "File system \"%s\" has wrong type for an EFI System Partition (ESP).", p);
af918182
ZJS
547
548 errno = 0;
549 r = blkid_probe_lookup_value(b, "PART_ENTRY_UUID", &v, NULL);
550 if (r != 0)
a7afbd60 551 return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition entry UUID \"%s\": %m", p);
af918182 552 r = sd_id128_from_string(v, &uuid);
a7afbd60
LP
553 if (r < 0)
554 return log_error_errno(r, "Partition \"%s\" has invalid UUID \"%s\".", p, v);
af918182
ZJS
555
556 errno = 0;
557 r = blkid_probe_lookup_value(b, "PART_ENTRY_NUMBER", &v, NULL);
558 if (r != 0)
a7afbd60 559 return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition number \"%s\": m", p);
af918182
ZJS
560 r = safe_atou32(v, &part);
561 if (r < 0)
562 return log_error_errno(r, "Failed to parse PART_ENTRY_NUMBER field.");
563
564 errno = 0;
565 r = blkid_probe_lookup_value(b, "PART_ENTRY_OFFSET", &v, NULL);
566 if (r != 0)
a7afbd60 567 return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition offset \"%s\": %m", p);
af918182
ZJS
568 r = safe_atou64(v, &pstart);
569 if (r < 0)
570 return log_error_errno(r, "Failed to parse PART_ENTRY_OFFSET field.");
571
572 errno = 0;
573 r = blkid_probe_lookup_value(b, "PART_ENTRY_SIZE", &v, NULL);
574 if (r != 0)
a7afbd60 575 return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition size \"%s\": %m", p);
af918182
ZJS
576 r = safe_atou64(v, &psize);
577 if (r < 0)
578 return log_error_errno(r, "Failed to parse PART_ENTRY_SIZE field.");
4e066f7f 579#endif
af918182
ZJS
580
581finish:
582 if (ret_part)
583 *ret_part = part;
584 if (ret_pstart)
585 *ret_pstart = pstart;
586 if (ret_psize)
587 *ret_psize = psize;
588 if (ret_uuid)
589 *ret_uuid = uuid;
590
591 return 0;
592}
593
5caa3167
LP
594int find_esp_and_warn(
595 const char *path,
596 bool unprivileged_mode,
597 char **ret_path,
598 uint32_t *ret_part,
599 uint64_t *ret_pstart,
600 uint64_t *ret_psize,
601 sd_id128_t *ret_uuid) {
af918182 602
af918182
ZJS
603 int r;
604
5caa3167
LP
605 /* This logs about all errors except:
606 *
607 * -ENOKEY → when we can't find the partition
608 * -EACCESS → when unprivileged_mode is true, and we can't access something
609 */
af918182 610
5caa3167
LP
611 if (path) {
612 r = verify_esp(path, false, unprivileged_mode, ret_part, ret_pstart, ret_psize, ret_uuid);
af918182
ZJS
613 if (r < 0)
614 return r;
615
5caa3167
LP
616 goto found;
617 }
618
cc7a0bfa
LP
619 path = getenv("SYSTEMD_ESP_PATH");
620 if (path) {
baaa35ad
ZJS
621 if (!path_is_valid(path) || !path_is_absolute(path))
622 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
623 "$SYSTEMD_ESP_PATH does not refer to absolute path, refusing to use it: %s",
624 path);
cc7a0bfa
LP
625
626 /* Note: when the user explicitly configured things with an env var we won't validate the mount
627 * point. After all we want this to be useful for testing. */
628 goto found;
629 }
630
5caa3167
LP
631 FOREACH_STRING(path, "/efi", "/boot", "/boot/efi") {
632
633 r = verify_esp(path, true, unprivileged_mode, ret_part, ret_pstart, ret_psize, ret_uuid);
634 if (r >= 0)
635 goto found;
636 if (!IN_SET(r, -ENOENT, -EADDRNOTAVAIL)) /* This one is not it */
637 return r;
638 }
639
640 /* No logging here */
641 return -ENOKEY;
642
15cb6c98
LP
643found:
644 if (ret_path) {
645 char *c;
646
647 c = strdup(path);
648 if (!c)
649 return log_oom();
650
651 *ret_path = c;
652 }
653
654 return 0;
655}
656
657static int verify_xbootldr(
658 const char *p,
659 bool searching,
660 bool unprivileged_mode,
661 sd_id128_t *ret_uuid) {
662#if HAVE_BLKID
663 _cleanup_(blkid_free_probep) blkid_probe b = NULL;
664 _cleanup_free_ char *node = NULL;
665 const char *v;
666#endif
667 struct stat st, st2;
668 const char *t2;
669 sd_id128_t uuid = SD_ID128_NULL;
670 bool relax_checks;
671 int r;
672
673 assert(p);
674
675 relax_checks = getenv_bool("SYSTEMD_RELAX_XBOOTLDR_CHECKS") > 0;
676
677 if (stat(p, &st) < 0)
678 return log_full_errno((searching && errno == ENOENT) ||
679 (unprivileged_mode && errno == EACCES) ? LOG_DEBUG : LOG_ERR, errno,
680 "Failed to determine block device node of \"%s\": %m", p);
681
682 if (major(st.st_dev) == 0)
683 return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
684 SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
685 "Block device node of \"%s\" is invalid.", p);
686
687 t2 = strjoina(p, "/..");
688 r = stat(t2, &st2);
689 if (r < 0)
690 return log_full_errno(unprivileged_mode && errno == EACCES ? LOG_DEBUG : LOG_ERR, errno,
691 "Failed to determine block device node of parent of \"%s\": %m", p);
692
693 if (st.st_dev == st2.st_dev)
694 return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
695 SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
696 "Directory \"%s\" is not the root of the XBOOTLDR file system.", p);
697
698 /* In a container we don't have access to block devices, skip this part of the verification, we trust
699 * the container manager set everything up correctly on its own. Also skip the following verification
700 * for non-root user. */
701 if (detect_container() > 0 || unprivileged_mode || relax_checks)
702 goto finish;
703
704#if HAVE_BLKID
705 r = device_path_make_major_minor(S_IFBLK, st.st_dev, &node);
706 if (r < 0)
707 return log_error_errno(r, "Failed to format major/minor device path: %m");
708 errno = 0;
709 b = blkid_new_probe_from_filename(node);
710 if (!b)
711 return log_error_errno(errno ?: SYNTHETIC_ERRNO(ENOMEM), "Failed to open file system \"%s\": %m", p);
712
713 blkid_probe_enable_partitions(b, 1);
714 blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS);
715
716 errno = 0;
717 r = blkid_do_safeprobe(b);
718 if (r == -2)
719 return log_error_errno(SYNTHETIC_ERRNO(ENODEV), "File system \"%s\" is ambiguous.", p);
720 else if (r == 1)
721 return log_error_errno(SYNTHETIC_ERRNO(ENODEV), "File system \"%s\" does not contain a label.", p);
722 else if (r != 0)
723 return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe file system \"%s\": %m", p);
724
725 errno = 0;
726 r = blkid_probe_lookup_value(b, "PART_ENTRY_SCHEME", &v, NULL);
727 if (r != 0)
728 return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition scheme \"%s\": %m", p);
729 if (streq(v, "gpt")) {
730
731 errno = 0;
732 r = blkid_probe_lookup_value(b, "PART_ENTRY_TYPE", &v, NULL);
733 if (r != 0)
734 return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition type UUID \"%s\": %m", p);
735 if (!streq(v, "bc13c2ff-59e6-4262-a352-b275fd6f7172"))
736 return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
737 searching ? SYNTHETIC_ERRNO(EADDRNOTAVAIL) : SYNTHETIC_ERRNO(ENODEV),
738 "File system \"%s\" has wrong type for extended boot loader partition.", p);
739
740 errno = 0;
741 r = blkid_probe_lookup_value(b, "PART_ENTRY_UUID", &v, NULL);
742 if (r != 0)
743 return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition entry UUID \"%s\": %m", p);
744 r = sd_id128_from_string(v, &uuid);
745 if (r < 0)
746 return log_error_errno(r, "Partition \"%s\" has invalid UUID \"%s\".", p, v);
747
748 } else if (streq(v, "dos")) {
749
750 errno = 0;
751 r = blkid_probe_lookup_value(b, "PART_ENTRY_TYPE", &v, NULL);
752 if (r != 0)
753 return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition type UUID \"%s\": %m", p);
754 if (!streq(v, "0xea"))
755 return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
756 searching ? SYNTHETIC_ERRNO(EADDRNOTAVAIL) : SYNTHETIC_ERRNO(ENODEV),
757 "File system \"%s\" has wrong type for extended boot loader partition.", p);
758
759 } else
760 return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
761 searching ? SYNTHETIC_ERRNO(EADDRNOTAVAIL) : SYNTHETIC_ERRNO(ENODEV),
762 "File system \"%s\" is not on a GPT or DOS partition table.", p);
763
764#endif
765
766finish:
767 if (ret_uuid)
768 *ret_uuid = uuid;
769
770 return 0;
771}
772
773int find_xbootldr_and_warn(
774 const char *path,
775 bool unprivileged_mode,
776 char **ret_path,
777 sd_id128_t *ret_uuid) {
778
779 int r;
780
781 /* Similar to find_esp_and_warn(), but finds the XBOOTLDR partition. Returns the same errors. */
782
783 if (path) {
784 r = verify_xbootldr(path, false, unprivileged_mode, ret_uuid);
785 if (r < 0)
786 return r;
787
788 goto found;
789 }
790
791 path = getenv("SYSTEMD_XBOOTLDR_PATH");
792 if (path) {
793 if (!path_is_valid(path) || !path_is_absolute(path))
794 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
795 "$SYSTEMD_XBOOTLDR_PATH does not refer to absolute path, refusing to use it: %s",
796 path);
797
798 goto found;
799 }
800
801 r = verify_xbootldr("/boot", true, unprivileged_mode, ret_uuid);
802 if (r >= 0) {
803 path = "/boot";
804 goto found;
805 }
806 if (!IN_SET(r, -ENOENT, -EADDRNOTAVAIL)) /* This one is not it */
807 return r;
808
809 return -ENOKEY;
810
5caa3167
LP
811found:
812 if (ret_path) {
813 char *c;
814
815 c = strdup(path);
816 if (!c)
af918182
ZJS
817 return log_oom();
818
5caa3167 819 *ret_path = c;
af918182
ZJS
820 }
821
5caa3167 822 return 0;
af918182 823}
1b20d889
ZJS
824
825int find_default_boot_entry(
826 const char *esp_path,
a2f8664e 827 const char *xbootldr_path,
1b20d889
ZJS
828 BootConfig *config,
829 const BootEntry **e) {
830
a2f8664e 831 _cleanup_free_ char *esp_where = NULL, *xbootldr_where = NULL;
1b20d889
ZJS
832 int r;
833
834 assert(config);
835 assert(e);
836
a2f8664e 837 r = find_esp_and_warn(esp_path, false, &esp_where, NULL, NULL, NULL, NULL);
1b20d889
ZJS
838 if (r < 0)
839 return r;
840
a2f8664e
LP
841 r = find_xbootldr_and_warn(xbootldr_path, false, &xbootldr_where, NULL);
842 if (r < 0 && r != -ENOKEY)
843 return r;
844
845 r = boot_entries_load_config(esp_where, xbootldr_where, config);
1b20d889 846 if (r < 0)
a2f8664e 847 return log_error_errno(r, "Failed to load boot loader entries: %m");
1b20d889 848
baaa35ad
ZJS
849 if (config->default_entry < 0)
850 return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
a2f8664e 851 "No boot loader entry suitable as default, refusing to guess.");
1b20d889
ZJS
852
853 *e = &config->entries[config->default_entry];
a2f8664e 854 log_debug("Found default boot loader entry in file \"%s\"", (*e)->path);
1b20d889 855
1b20d889
ZJS
856 return 0;
857}