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