]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/bootspec.c
Merge pull request #11827 from keszybz/pkgconfig-variables
[thirdparty/systemd.git] / src / shared / bootspec.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2
3 #include <stdio.h>
4 #include <linux/magic.h>
5
6 #include "sd-id128.h"
7
8 #include "alloc-util.h"
9 #include "blkid-util.h"
10 #include "bootspec.h"
11 #include "conf-files.h"
12 #include "def.h"
13 #include "device-nodes.h"
14 #include "efivars.h"
15 #include "env-util.h"
16 #include "fd-util.h"
17 #include "fileio.h"
18 #include "parse-util.h"
19 #include "path-util.h"
20 #include "stat-util.h"
21 #include "string-util.h"
22 #include "strv.h"
23 #include "virt.h"
24
25 static void boot_entry_free(BootEntry *entry) {
26 assert(entry);
27
28 free(entry->id);
29 free(entry->path);
30 free(entry->title);
31 free(entry->show_title);
32 free(entry->version);
33 free(entry->machine_id);
34 free(entry->architecture);
35 strv_free(entry->options);
36 free(entry->kernel);
37 free(entry->efi);
38 strv_free(entry->initrd);
39 free(entry->device_tree);
40 }
41
42 static int boot_entry_load(const char *path, BootEntry *entry) {
43 _cleanup_(boot_entry_free) BootEntry tmp = {};
44 _cleanup_fclose_ FILE *f = NULL;
45 unsigned line = 1;
46 char *b, *c;
47 int r;
48
49 assert(path);
50 assert(entry);
51
52 c = endswith_no_case(path, ".conf");
53 if (!c) {
54 log_error("Invalid loader entry filename: %s", path);
55 return -EINVAL;
56 }
57
58 b = basename(path);
59 tmp.id = strndup(b, c - b);
60 if (!tmp.id)
61 return log_oom();
62
63 tmp.path = strdup(path);
64 if (!tmp.path)
65 return log_oom();
66
67 f = fopen(path, "re");
68 if (!f)
69 return log_error_errno(errno, "Failed to open \"%s\": %m", path);
70
71 for (;;) {
72 _cleanup_free_ char *buf = NULL, *field = NULL;
73 const char *p;
74
75 r = read_line(f, LONG_LINE_MAX, &buf);
76 if (r == 0)
77 break;
78 if (r == -ENOBUFS)
79 return log_error_errno(r, "%s:%u: Line too long", path, line);
80 if (r < 0)
81 return log_error_errno(r, "%s:%u: Error while reading: %m", path, line);
82
83 line++;
84
85 if (IN_SET(*strstrip(buf), '#', '\0'))
86 continue;
87
88 p = buf;
89 r = extract_first_word(&p, &field, " \t", 0);
90 if (r < 0) {
91 log_error_errno(r, "Failed to parse config file %s line %u: %m", path, line);
92 continue;
93 }
94 if (r == 0) {
95 log_warning("%s:%u: Bad syntax", path, line);
96 continue;
97 }
98
99 if (streq(field, "title"))
100 r = free_and_strdup(&tmp.title, p);
101 else if (streq(field, "version"))
102 r = free_and_strdup(&tmp.version, p);
103 else if (streq(field, "machine-id"))
104 r = free_and_strdup(&tmp.machine_id, p);
105 else if (streq(field, "architecture"))
106 r = free_and_strdup(&tmp.architecture, p);
107 else if (streq(field, "options"))
108 r = strv_extend(&tmp.options, p);
109 else if (streq(field, "linux"))
110 r = free_and_strdup(&tmp.kernel, p);
111 else if (streq(field, "efi"))
112 r = free_and_strdup(&tmp.efi, p);
113 else if (streq(field, "initrd"))
114 r = strv_extend(&tmp.initrd, p);
115 else if (streq(field, "devicetree"))
116 r = free_and_strdup(&tmp.device_tree, p);
117 else {
118 log_notice("%s:%u: Unknown line \"%s\"", path, line, field);
119 continue;
120 }
121 if (r < 0)
122 return log_error_errno(r, "%s:%u: Error while reading: %m", path, line);
123 }
124
125 *entry = tmp;
126 tmp = (BootEntry) {};
127 return 0;
128 }
129
130 void boot_config_free(BootConfig *config) {
131 size_t i;
132
133 assert(config);
134
135 free(config->default_pattern);
136 free(config->timeout);
137 free(config->editor);
138 free(config->auto_entries);
139 free(config->auto_firmware);
140
141 free(config->entry_oneshot);
142 free(config->entry_default);
143
144 for (i = 0; i < config->n_entries; i++)
145 boot_entry_free(config->entries + i);
146 free(config->entries);
147 }
148
149 static int boot_loader_read_conf(const char *path, BootConfig *config) {
150 _cleanup_fclose_ FILE *f = NULL;
151 unsigned line = 1;
152 int r;
153
154 assert(path);
155 assert(config);
156
157 f = fopen(path, "re");
158 if (!f) {
159 if (errno == ENOENT)
160 return 0;
161
162 return log_error_errno(errno, "Failed to open \"%s\": %m", path);
163 }
164
165 for (;;) {
166 _cleanup_free_ char *buf = NULL, *field = NULL;
167 const char *p;
168
169 r = read_line(f, LONG_LINE_MAX, &buf);
170 if (r == 0)
171 break;
172 if (r == -ENOBUFS)
173 return log_error_errno(r, "%s:%u: Line too long", path, line);
174 if (r < 0)
175 return log_error_errno(r, "%s:%u: Error while reading: %m", path, line);
176
177 line++;
178
179 if (IN_SET(*strstrip(buf), '#', '\0'))
180 continue;
181
182 p = buf;
183 r = extract_first_word(&p, &field, " \t", 0);
184 if (r < 0) {
185 log_error_errno(r, "Failed to parse config file %s line %u: %m", path, line);
186 continue;
187 }
188 if (r == 0) {
189 log_warning("%s:%u: Bad syntax", path, line);
190 continue;
191 }
192
193 if (streq(field, "default"))
194 r = free_and_strdup(&config->default_pattern, p);
195 else if (streq(field, "timeout"))
196 r = free_and_strdup(&config->timeout, p);
197 else if (streq(field, "editor"))
198 r = free_and_strdup(&config->editor, p);
199 else if (streq(field, "auto-entries"))
200 r = free_and_strdup(&config->auto_entries, p);
201 else if (streq(field, "auto-firmware"))
202 r = free_and_strdup(&config->auto_firmware, p);
203 else if (streq(field, "console-mode"))
204 r = free_and_strdup(&config->console_mode, p);
205 else {
206 log_notice("%s:%u: Unknown line \"%s\"", path, line, field);
207 continue;
208 }
209 if (r < 0)
210 return log_error_errno(r, "%s:%u: Error while reading: %m", path, line);
211 }
212
213 return 1;
214 }
215
216 static int boot_entry_compare(const BootEntry *a, const BootEntry *b) {
217 return str_verscmp(a->id, b->id);
218 }
219
220 static int boot_entries_find(const char *dir, BootEntry **ret_entries, size_t *ret_n_entries) {
221 _cleanup_strv_free_ char **files = NULL;
222 char **f;
223 int r;
224 BootEntry *array = NULL;
225 size_t n_allocated = 0, n = 0;
226
227 assert(dir);
228 assert(ret_entries);
229 assert(ret_n_entries);
230
231 r = conf_files_list(&files, ".conf", NULL, 0, dir, NULL);
232 if (r < 0)
233 return log_error_errno(r, "Failed to list files in \"%s\": %m", dir);
234
235 STRV_FOREACH(f, files) {
236 if (!GREEDY_REALLOC0(array, n_allocated, n + 1))
237 return log_oom();
238
239 r = boot_entry_load(*f, array + n);
240 if (r < 0)
241 continue;
242
243 n++;
244 }
245
246 typesafe_qsort(array, n, boot_entry_compare);
247
248 *ret_entries = array;
249 *ret_n_entries = n;
250
251 return 0;
252 }
253
254 static bool find_nonunique(BootEntry *entries, size_t n_entries, bool *arr) {
255 size_t i, j;
256 bool non_unique = false;
257
258 assert(entries || n_entries == 0);
259 assert(arr || n_entries == 0);
260
261 for (i = 0; i < n_entries; i++)
262 arr[i] = false;
263
264 for (i = 0; i < n_entries; i++)
265 for (j = 0; j < n_entries; j++)
266 if (i != j && streq(boot_entry_title(entries + i),
267 boot_entry_title(entries + j)))
268 non_unique = arr[i] = arr[j] = true;
269
270 return non_unique;
271 }
272
273 static int boot_entries_uniquify(BootEntry *entries, size_t n_entries) {
274 char *s;
275 size_t i;
276 int r;
277 bool arr[n_entries];
278
279 assert(entries || n_entries == 0);
280
281 /* Find _all_ non-unique titles */
282 if (!find_nonunique(entries, n_entries, arr))
283 return 0;
284
285 /* Add version to non-unique titles */
286 for (i = 0; i < n_entries; i++)
287 if (arr[i] && entries[i].version) {
288 r = asprintf(&s, "%s (%s)", boot_entry_title(entries + i), entries[i].version);
289 if (r < 0)
290 return -ENOMEM;
291
292 free_and_replace(entries[i].show_title, s);
293 }
294
295 if (!find_nonunique(entries, n_entries, arr))
296 return 0;
297
298 /* Add machine-id to non-unique titles */
299 for (i = 0; i < n_entries; i++)
300 if (arr[i] && entries[i].machine_id) {
301 r = asprintf(&s, "%s (%s)", boot_entry_title(entries + i), entries[i].machine_id);
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 file name to non-unique titles */
312 for (i = 0; i < n_entries; i++)
313 if (arr[i]) {
314 r = asprintf(&s, "%s (%s)", boot_entry_title(entries + i), entries[i].id);
315 if (r < 0)
316 return -ENOMEM;
317
318 free_and_replace(entries[i].show_title, s);
319 }
320
321 return 0;
322 }
323
324 static int boot_entries_select_default(const BootConfig *config) {
325 int i;
326
327 assert(config);
328
329 if (config->entry_oneshot)
330 for (i = config->n_entries - 1; i >= 0; i--)
331 if (streq(config->entry_oneshot, config->entries[i].id)) {
332 log_debug("Found default: id \"%s\" is matched by LoaderEntryOneShot",
333 config->entries[i].id);
334 return i;
335 }
336
337 if (config->entry_default)
338 for (i = config->n_entries - 1; i >= 0; i--)
339 if (streq(config->entry_default, config->entries[i].id)) {
340 log_debug("Found default: id \"%s\" is matched by LoaderEntryDefault",
341 config->entries[i].id);
342 return i;
343 }
344
345 if (config->default_pattern)
346 for (i = config->n_entries - 1; i >= 0; i--)
347 if (fnmatch(config->default_pattern, config->entries[i].id, FNM_CASEFOLD) == 0) {
348 log_debug("Found default: id \"%s\" is matched by pattern \"%s\"",
349 config->entries[i].id, config->default_pattern);
350 return i;
351 }
352
353 if (config->n_entries > 0)
354 log_debug("Found default: last entry \"%s\"", config->entries[config->n_entries - 1].id);
355 else
356 log_debug("Found no default boot entry :(");
357
358 return config->n_entries - 1; /* -1 means "no default" */
359 }
360
361 int boot_entries_load_config(const char *esp_path, BootConfig *config) {
362 const char *p;
363 int r;
364
365 assert(esp_path);
366 assert(config);
367
368 p = strjoina(esp_path, "/loader/loader.conf");
369 r = boot_loader_read_conf(p, config);
370 if (r < 0)
371 return r;
372
373 p = strjoina(esp_path, "/loader/entries");
374 r = boot_entries_find(p, &config->entries, &config->n_entries);
375 if (r < 0)
376 return r;
377
378 r = boot_entries_uniquify(config->entries, config->n_entries);
379 if (r < 0)
380 return log_error_errno(r, "Failed to uniquify boot entries: %m");
381
382 if (is_efi_boot()) {
383 r = efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderEntryOneShot", &config->entry_oneshot);
384 if (r < 0 && r != -ENOENT)
385 return log_error_errno(r, "Failed to read EFI var \"LoaderEntryOneShot\": %m");
386
387 r = efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderEntryDefault", &config->entry_default);
388 if (r < 0 && r != -ENOENT)
389 return log_error_errno(r, "Failed to read EFI var \"LoaderEntryDefault\": %m");
390 }
391
392 config->default_entry = boot_entries_select_default(config);
393 return 0;
394 }
395
396 /********************************************************************************/
397
398 static int verify_esp(
399 const char *p,
400 bool searching,
401 bool unprivileged_mode,
402 uint32_t *ret_part,
403 uint64_t *ret_pstart,
404 uint64_t *ret_psize,
405 sd_id128_t *ret_uuid) {
406 #if HAVE_BLKID
407 _cleanup_(blkid_free_probep) blkid_probe b = NULL;
408 _cleanup_free_ char *node = NULL;
409 const char *v;
410 #endif
411 uint64_t pstart = 0, psize = 0;
412 struct stat st, st2;
413 const char *t2;
414 struct statfs sfs;
415 sd_id128_t uuid = SD_ID128_NULL;
416 uint32_t part = 0;
417 bool relax_checks;
418 int r;
419
420 assert(p);
421
422 relax_checks = getenv_bool("SYSTEMD_RELAX_ESP_CHECKS") > 0;
423
424 /* Non-root user can only check the status, so if an error occured in the following, it does not cause any
425 * issues. Let's also, silence the error messages. */
426
427 if (!relax_checks) {
428 if (statfs(p, &sfs) < 0) {
429 /* If we are searching for the mount point, don't generate a log message if we can't find the path */
430 if (errno == ENOENT && searching)
431 return -ENOENT;
432
433 return log_full_errno(unprivileged_mode && errno == EACCES ? LOG_DEBUG : LOG_ERR, errno,
434 "Failed to check file system type of \"%s\": %m", p);
435 }
436
437 if (!F_TYPE_EQUAL(sfs.f_type, MSDOS_SUPER_MAGIC)) {
438 if (searching)
439 return -EADDRNOTAVAIL;
440
441 log_error("File system \"%s\" is not a FAT EFI System Partition (ESP) file system.", p);
442 return -ENODEV;
443 }
444 }
445
446 if (stat(p, &st) < 0)
447 return log_full_errno(unprivileged_mode && errno == EACCES ? LOG_DEBUG : LOG_ERR, errno,
448 "Failed to determine block device node of \"%s\": %m", p);
449
450 if (major(st.st_dev) == 0) {
451 log_error("Block device node of %p is invalid.", p);
452 return -ENODEV;
453 }
454
455 t2 = strjoina(p, "/..");
456 r = stat(t2, &st2);
457 if (r < 0)
458 return log_full_errno(unprivileged_mode && errno == EACCES ? LOG_DEBUG : LOG_ERR, errno,
459 "Failed to determine block device node of parent of \"%s\": %m", p);
460
461 if (st.st_dev == st2.st_dev) {
462 log_error("Directory \"%s\" is not the root of the EFI System Partition (ESP) file system.", p);
463 return -ENODEV;
464 }
465
466 /* In a container we don't have access to block devices, skip this part of the verification, we trust the
467 * container manager set everything up correctly on its own. Also skip the following verification for non-root user. */
468 if (detect_container() > 0 || unprivileged_mode || relax_checks)
469 goto finish;
470
471 #if HAVE_BLKID
472 r = device_path_make_major_minor(S_IFBLK, st.st_dev, &node);
473 if (r < 0)
474 return log_error_errno(r, "Failed to format major/minor device path: %m");
475 errno = 0;
476 b = blkid_new_probe_from_filename(node);
477 if (!b)
478 return log_error_errno(errno ?: ENOMEM, "Failed to open file system \"%s\": %m", p);
479
480 blkid_probe_enable_superblocks(b, 1);
481 blkid_probe_set_superblocks_flags(b, BLKID_SUBLKS_TYPE);
482 blkid_probe_enable_partitions(b, 1);
483 blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS);
484
485 errno = 0;
486 r = blkid_do_safeprobe(b);
487 if (r == -2) {
488 log_error("File system \"%s\" is ambiguous.", p);
489 return -ENODEV;
490 } else if (r == 1) {
491 log_error("File system \"%s\" does not contain a label.", p);
492 return -ENODEV;
493 } else if (r != 0)
494 return log_error_errno(errno ?: EIO, "Failed to probe file system \"%s\": %m", p);
495
496 errno = 0;
497 r = blkid_probe_lookup_value(b, "TYPE", &v, NULL);
498 if (r != 0)
499 return log_error_errno(errno ?: EIO, "Failed to probe file system type \"%s\": %m", p);
500 if (!streq(v, "vfat")) {
501 log_error("File system \"%s\" is not FAT.", p);
502 return -ENODEV;
503 }
504
505 errno = 0;
506 r = blkid_probe_lookup_value(b, "PART_ENTRY_SCHEME", &v, NULL);
507 if (r != 0)
508 return log_error_errno(errno ?: EIO, "Failed to probe partition scheme \"%s\": %m", p);
509 if (!streq(v, "gpt")) {
510 log_error("File system \"%s\" is not on a GPT partition table.", p);
511 return -ENODEV;
512 }
513
514 errno = 0;
515 r = blkid_probe_lookup_value(b, "PART_ENTRY_TYPE", &v, NULL);
516 if (r != 0)
517 return log_error_errno(errno ?: EIO, "Failed to probe partition type UUID \"%s\": %m", p);
518 if (!streq(v, "c12a7328-f81f-11d2-ba4b-00a0c93ec93b")) {
519 log_error("File system \"%s\" has wrong type for an EFI System Partition (ESP).", p);
520 return -ENODEV;
521 }
522
523 errno = 0;
524 r = blkid_probe_lookup_value(b, "PART_ENTRY_UUID", &v, NULL);
525 if (r != 0)
526 return log_error_errno(errno ?: EIO, "Failed to probe partition entry UUID \"%s\": %m", p);
527 r = sd_id128_from_string(v, &uuid);
528 if (r < 0) {
529 log_error("Partition \"%s\" has invalid UUID \"%s\".", p, v);
530 return -EIO;
531 }
532
533 errno = 0;
534 r = blkid_probe_lookup_value(b, "PART_ENTRY_NUMBER", &v, NULL);
535 if (r != 0)
536 return log_error_errno(errno ?: EIO, "Failed to probe partition number \"%s\": m", p);
537 r = safe_atou32(v, &part);
538 if (r < 0)
539 return log_error_errno(r, "Failed to parse PART_ENTRY_NUMBER field.");
540
541 errno = 0;
542 r = blkid_probe_lookup_value(b, "PART_ENTRY_OFFSET", &v, NULL);
543 if (r != 0)
544 return log_error_errno(errno ?: EIO, "Failed to probe partition offset \"%s\": %m", p);
545 r = safe_atou64(v, &pstart);
546 if (r < 0)
547 return log_error_errno(r, "Failed to parse PART_ENTRY_OFFSET field.");
548
549 errno = 0;
550 r = blkid_probe_lookup_value(b, "PART_ENTRY_SIZE", &v, NULL);
551 if (r != 0)
552 return log_error_errno(errno ?: EIO, "Failed to probe partition size \"%s\": %m", p);
553 r = safe_atou64(v, &psize);
554 if (r < 0)
555 return log_error_errno(r, "Failed to parse PART_ENTRY_SIZE field.");
556 #endif
557
558 finish:
559 if (ret_part)
560 *ret_part = part;
561 if (ret_pstart)
562 *ret_pstart = pstart;
563 if (ret_psize)
564 *ret_psize = psize;
565 if (ret_uuid)
566 *ret_uuid = uuid;
567
568 return 0;
569 }
570
571 int find_esp_and_warn(
572 const char *path,
573 bool unprivileged_mode,
574 char **ret_path,
575 uint32_t *ret_part,
576 uint64_t *ret_pstart,
577 uint64_t *ret_psize,
578 sd_id128_t *ret_uuid) {
579
580 int r;
581
582 /* This logs about all errors except:
583 *
584 * -ENOKEY → when we can't find the partition
585 * -EACCESS → when unprivileged_mode is true, and we can't access something
586 */
587
588 if (path) {
589 r = verify_esp(path, false, unprivileged_mode, ret_part, ret_pstart, ret_psize, ret_uuid);
590 if (r < 0)
591 return r;
592
593 goto found;
594 }
595
596 path = getenv("SYSTEMD_ESP_PATH");
597 if (path) {
598 if (!path_is_valid(path) || !path_is_absolute(path))
599 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
600 "$SYSTEMD_ESP_PATH does not refer to absolute path, refusing to use it: %s",
601 path);
602
603 /* Note: when the user explicitly configured things with an env var we won't validate the mount
604 * point. After all we want this to be useful for testing. */
605 goto found;
606 }
607
608 FOREACH_STRING(path, "/efi", "/boot", "/boot/efi") {
609
610 r = verify_esp(path, true, unprivileged_mode, ret_part, ret_pstart, ret_psize, ret_uuid);
611 if (r >= 0)
612 goto found;
613 if (!IN_SET(r, -ENOENT, -EADDRNOTAVAIL)) /* This one is not it */
614 return r;
615 }
616
617 /* No logging here */
618 return -ENOKEY;
619
620 found:
621 if (ret_path) {
622 char *c;
623
624 c = strdup(path);
625 if (!c)
626 return log_oom();
627
628 *ret_path = c;
629 }
630
631 return 0;
632 }
633
634 int find_default_boot_entry(
635 const char *esp_path,
636 char **esp_where,
637 BootConfig *config,
638 const BootEntry **e) {
639
640 _cleanup_free_ char *where = NULL;
641 int r;
642
643 assert(config);
644 assert(e);
645
646 r = find_esp_and_warn(esp_path, false, &where, NULL, NULL, NULL, NULL);
647 if (r < 0)
648 return r;
649
650 r = boot_entries_load_config(where, config);
651 if (r < 0)
652 return log_error_errno(r, "Failed to load bootspec config from \"%s/loader\": %m", where);
653
654 if (config->default_entry < 0)
655 return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
656 "No entry suitable as default, refusing to guess.");
657
658 *e = &config->entries[config->default_entry];
659 log_debug("Found default boot entry in file \"%s\"", (*e)->path);
660
661 if (esp_where)
662 *esp_where = TAKE_PTR(where);
663
664 return 0;
665 }