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