]>
Commit | Line | Data |
---|---|---|
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 | 25 | static void boot_entry_free(BootEntry *entry) { |
4fe2ba0e | 26 | assert(entry); |
7e87c7d9 | 27 | |
12580bc3 | 28 | free(entry->id); |
2d3bfb69 | 29 | free(entry->path); |
7e87c7d9 | 30 | free(entry->title); |
64f05708 | 31 | free(entry->show_title); |
7e87c7d9 ZJS |
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 | ||
0de2e1fd | 42 | static int boot_entry_load(const char *path, BootEntry *entry) { |
263195c6 | 43 | _cleanup_(boot_entry_free) BootEntry tmp = {}; |
7e87c7d9 ZJS |
44 | _cleanup_fclose_ FILE *f = NULL; |
45 | unsigned line = 1; | |
263195c6 | 46 | char *b, *c; |
7e87c7d9 ZJS |
47 | int r; |
48 | ||
4fe2ba0e LP |
49 | assert(path); |
50 | assert(entry); | |
51 | ||
263195c6 YW |
52 | c = endswith_no_case(path, ".conf"); |
53 | if (!c) { | |
54 | log_error("Invalid loader entry filename: %s", path); | |
55 | return -EINVAL; | |
56 | } | |
7e87c7d9 | 57 | |
263195c6 | 58 | b = basename(path); |
12580bc3 LP |
59 | tmp.id = strndup(b, c - b); |
60 | if (!tmp.id) | |
7e87c7d9 ZJS |
61 | return log_oom(); |
62 | ||
2d3bfb69 ZJS |
63 | tmp.path = strdup(path); |
64 | if (!tmp.path) | |
65 | return log_oom(); | |
66 | ||
263195c6 YW |
67 | f = fopen(path, "re"); |
68 | if (!f) | |
69 | return log_error_errno(errno, "Failed to open \"%s\": %m", path); | |
70 | ||
7e87c7d9 | 71 | for (;;) { |
f99fdc3e YW |
72 | _cleanup_free_ char *buf = NULL, *field = NULL; |
73 | const char *p; | |
7e87c7d9 ZJS |
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 | ||
f99fdc3e YW |
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) { | |
7e87c7d9 ZJS |
95 | log_warning("%s:%u: Bad syntax", path, line); |
96 | continue; | |
97 | } | |
7e87c7d9 | 98 | |
f99fdc3e | 99 | if (streq(field, "title")) |
7e87c7d9 | 100 | r = free_and_strdup(&tmp.title, p); |
f99fdc3e | 101 | else if (streq(field, "version")) |
7e87c7d9 | 102 | r = free_and_strdup(&tmp.version, p); |
f99fdc3e | 103 | else if (streq(field, "machine-id")) |
7e87c7d9 | 104 | r = free_and_strdup(&tmp.machine_id, p); |
f99fdc3e | 105 | else if (streq(field, "architecture")) |
7e87c7d9 | 106 | r = free_and_strdup(&tmp.architecture, p); |
f99fdc3e | 107 | else if (streq(field, "options")) |
7e87c7d9 | 108 | r = strv_extend(&tmp.options, p); |
f99fdc3e | 109 | else if (streq(field, "linux")) |
7e87c7d9 | 110 | r = free_and_strdup(&tmp.kernel, p); |
f99fdc3e | 111 | else if (streq(field, "efi")) |
7e87c7d9 | 112 | r = free_and_strdup(&tmp.efi, p); |
f99fdc3e | 113 | else if (streq(field, "initrd")) |
7e87c7d9 | 114 | r = strv_extend(&tmp.initrd, p); |
f99fdc3e | 115 | else if (streq(field, "devicetree")) |
7e87c7d9 ZJS |
116 | r = free_and_strdup(&tmp.device_tree, p); |
117 | else { | |
f99fdc3e | 118 | log_notice("%s:%u: Unknown line \"%s\"", path, line, field); |
7e87c7d9 ZJS |
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) { | |
da6053d0 | 131 | size_t i; |
7e87c7d9 | 132 | |
4fe2ba0e LP |
133 | assert(config); |
134 | ||
7e87c7d9 ZJS |
135 | free(config->default_pattern); |
136 | free(config->timeout); | |
137 | free(config->editor); | |
c1d4e298 JJ |
138 | free(config->auto_entries); |
139 | free(config->auto_firmware); | |
7e87c7d9 ZJS |
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 | ||
0de2e1fd | 149 | static int boot_loader_read_conf(const char *path, BootConfig *config) { |
7e87c7d9 ZJS |
150 | _cleanup_fclose_ FILE *f = NULL; |
151 | unsigned line = 1; | |
152 | int r; | |
153 | ||
4fe2ba0e LP |
154 | assert(path); |
155 | assert(config); | |
156 | ||
7e87c7d9 | 157 | f = fopen(path, "re"); |
f91ed3dc LP |
158 | if (!f) { |
159 | if (errno == ENOENT) | |
160 | return 0; | |
161 | ||
7e87c7d9 | 162 | return log_error_errno(errno, "Failed to open \"%s\": %m", path); |
f91ed3dc | 163 | } |
7e87c7d9 ZJS |
164 | |
165 | for (;;) { | |
f99fdc3e YW |
166 | _cleanup_free_ char *buf = NULL, *field = NULL; |
167 | const char *p; | |
7e87c7d9 ZJS |
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 | ||
f99fdc3e YW |
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) { | |
7e87c7d9 ZJS |
189 | log_warning("%s:%u: Bad syntax", path, line); |
190 | continue; | |
191 | } | |
7e87c7d9 | 192 | |
f99fdc3e | 193 | if (streq(field, "default")) |
7e87c7d9 | 194 | r = free_and_strdup(&config->default_pattern, p); |
f99fdc3e | 195 | else if (streq(field, "timeout")) |
7e87c7d9 | 196 | r = free_and_strdup(&config->timeout, p); |
f99fdc3e | 197 | else if (streq(field, "editor")) |
7e87c7d9 | 198 | r = free_and_strdup(&config->editor, p); |
790f84eb | 199 | else if (streq(field, "auto-entries")) |
c1d4e298 | 200 | r = free_and_strdup(&config->auto_entries, p); |
790f84eb | 201 | else if (streq(field, "auto-firmware")) |
c1d4e298 | 202 | r = free_and_strdup(&config->auto_firmware, p); |
790f84eb | 203 | else if (streq(field, "console-mode")) |
d37b0737 | 204 | r = free_and_strdup(&config->console_mode, p); |
7e87c7d9 | 205 | else { |
f99fdc3e | 206 | log_notice("%s:%u: Unknown line \"%s\"", path, line, field); |
7e87c7d9 ZJS |
207 | continue; |
208 | } | |
209 | if (r < 0) | |
210 | return log_error_errno(r, "%s:%u: Error while reading: %m", path, line); | |
211 | } | |
212 | ||
f91ed3dc | 213 | return 1; |
7e87c7d9 ZJS |
214 | } |
215 | ||
93bab288 | 216 | static int boot_entry_compare(const BootEntry *a, const BootEntry *b) { |
12580bc3 | 217 | return str_verscmp(a->id, b->id); |
7e87c7d9 ZJS |
218 | } |
219 | ||
0de2e1fd | 220 | static int boot_entries_find(const char *dir, BootEntry **ret_entries, size_t *ret_n_entries) { |
7e87c7d9 ZJS |
221 | _cleanup_strv_free_ char **files = NULL; |
222 | char **f; | |
223 | int r; | |
7e87c7d9 ZJS |
224 | BootEntry *array = NULL; |
225 | size_t n_allocated = 0, n = 0; | |
226 | ||
4fe2ba0e LP |
227 | assert(dir); |
228 | assert(ret_entries); | |
229 | assert(ret_n_entries); | |
230 | ||
7e87c7d9 ZJS |
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 | ||
93bab288 | 246 | typesafe_qsort(array, n, boot_entry_compare); |
7e87c7d9 | 247 | |
4fe2ba0e LP |
248 | *ret_entries = array; |
249 | *ret_n_entries = n; | |
250 | ||
7e87c7d9 ZJS |
251 | return 0; |
252 | } | |
253 | ||
64f05708 | 254 | static bool find_nonunique(BootEntry *entries, size_t n_entries, bool *arr) { |
da6053d0 | 255 | size_t i, j; |
64f05708 ZJS |
256 | bool non_unique = false; |
257 | ||
4fe2ba0e LP |
258 | assert(entries || n_entries == 0); |
259 | assert(arr || n_entries == 0); | |
260 | ||
64f05708 ZJS |
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; | |
da6053d0 | 275 | size_t i; |
64f05708 ZJS |
276 | int r; |
277 | bool arr[n_entries]; | |
278 | ||
4fe2ba0e LP |
279 | assert(entries || n_entries == 0); |
280 | ||
64f05708 ZJS |
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]) { | |
12580bc3 | 314 | r = asprintf(&s, "%s (%s)", boot_entry_title(entries + i), entries[i].id); |
64f05708 ZJS |
315 | if (r < 0) |
316 | return -ENOMEM; | |
317 | ||
318 | free_and_replace(entries[i].show_title, s); | |
319 | } | |
320 | ||
321 | return 0; | |
322 | } | |
323 | ||
ad1afd60 | 324 | static int boot_entries_select_default(const BootConfig *config) { |
7e87c7d9 ZJS |
325 | int i; |
326 | ||
4fe2ba0e LP |
327 | assert(config); |
328 | ||
7e87c7d9 ZJS |
329 | if (config->entry_oneshot) |
330 | for (i = config->n_entries - 1; i >= 0; i--) | |
12580bc3 LP |
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); | |
7e87c7d9 ZJS |
334 | return i; |
335 | } | |
336 | ||
337 | if (config->entry_default) | |
338 | for (i = config->n_entries - 1; i >= 0; i--) | |
12580bc3 LP |
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); | |
7e87c7d9 ZJS |
342 | return i; |
343 | } | |
344 | ||
345 | if (config->default_pattern) | |
346 | for (i = config->n_entries - 1; i >= 0; i--) | |
12580bc3 LP |
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); | |
7e87c7d9 ZJS |
350 | return i; |
351 | } | |
352 | ||
353 | if (config->n_entries > 0) | |
12580bc3 | 354 | log_debug("Found default: last entry \"%s\"", config->entries[config->n_entries - 1].id); |
7e87c7d9 ZJS |
355 | else |
356 | log_debug("Found no default boot entry :("); | |
4fe2ba0e | 357 | |
7e87c7d9 ZJS |
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 | ||
4fe2ba0e LP |
365 | assert(esp_path); |
366 | assert(config); | |
367 | ||
7e87c7d9 ZJS |
368 | p = strjoina(esp_path, "/loader/loader.conf"); |
369 | r = boot_loader_read_conf(p, config); | |
370 | if (r < 0) | |
21f7a622 | 371 | return r; |
7e87c7d9 ZJS |
372 | |
373 | p = strjoina(esp_path, "/loader/entries"); | |
374 | r = boot_entries_find(p, &config->entries, &config->n_entries); | |
375 | if (r < 0) | |
21f7a622 | 376 | return r; |
7e87c7d9 | 377 | |
64f05708 ZJS |
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 | ||
9c4a6c13 LP |
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 | } | |
7e87c7d9 ZJS |
391 | |
392 | config->default_entry = boot_entries_select_default(config); | |
393 | return 0; | |
394 | } | |
af918182 ZJS |
395 | |
396 | /********************************************************************************/ | |
397 | ||
398 | static int verify_esp( | |
af918182 | 399 | const char *p, |
5caa3167 LP |
400 | bool searching, |
401 | bool unprivileged_mode, | |
af918182 ZJS |
402 | uint32_t *ret_part, |
403 | uint64_t *ret_pstart, | |
404 | uint64_t *ret_psize, | |
405 | sd_id128_t *ret_uuid) { | |
4e066f7f | 406 | #if HAVE_BLKID |
8e766630 | 407 | _cleanup_(blkid_free_probep) blkid_probe b = NULL; |
54b22b26 | 408 | _cleanup_free_ char *node = NULL; |
4e066f7f YW |
409 | const char *v; |
410 | #endif | |
af918182 ZJS |
411 | uint64_t pstart = 0, psize = 0; |
412 | struct stat st, st2; | |
4e066f7f | 413 | const char *t2; |
af918182 ZJS |
414 | struct statfs sfs; |
415 | sd_id128_t uuid = SD_ID128_NULL; | |
416 | uint32_t part = 0; | |
8cbb7d87 | 417 | bool relax_checks; |
af918182 ZJS |
418 | int r; |
419 | ||
420 | assert(p); | |
421 | ||
8cbb7d87 LP |
422 | relax_checks = getenv_bool("SYSTEMD_RELAX_ESP_CHECKS") > 0; |
423 | ||
5caa3167 LP |
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. */ | |
af918182 | 426 | |
8cbb7d87 LP |
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; | |
af918182 | 432 | |
8cbb7d87 LP |
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 | } | |
af918182 | 436 | |
8cbb7d87 LP |
437 | if (!F_TYPE_EQUAL(sfs.f_type, MSDOS_SUPER_MAGIC)) { |
438 | if (searching) | |
439 | return -EADDRNOTAVAIL; | |
af918182 | 440 | |
8cbb7d87 LP |
441 | log_error("File system \"%s\" is not a FAT EFI System Partition (ESP) file system.", p); |
442 | return -ENODEV; | |
443 | } | |
af918182 ZJS |
444 | } |
445 | ||
446 | if (stat(p, &st) < 0) | |
5caa3167 | 447 | return log_full_errno(unprivileged_mode && errno == EACCES ? LOG_DEBUG : LOG_ERR, errno, |
af918182 ZJS |
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) | |
5caa3167 | 458 | return log_full_errno(unprivileged_mode && errno == EACCES ? LOG_DEBUG : LOG_ERR, errno, |
af918182 ZJS |
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. */ | |
8cbb7d87 | 468 | if (detect_container() > 0 || unprivileged_mode || relax_checks) |
af918182 ZJS |
469 | goto finish; |
470 | ||
4e066f7f | 471 | #if HAVE_BLKID |
54b22b26 LP |
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"); | |
af918182 | 475 | errno = 0; |
54b22b26 | 476 | b = blkid_new_probe_from_filename(node); |
af918182 ZJS |
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."); | |
4e066f7f | 556 | #endif |
af918182 ZJS |
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 | ||
5caa3167 LP |
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) { | |
af918182 | 579 | |
af918182 ZJS |
580 | int r; |
581 | ||
5caa3167 LP |
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 | */ | |
af918182 | 587 | |
5caa3167 LP |
588 | if (path) { |
589 | r = verify_esp(path, false, unprivileged_mode, ret_part, ret_pstart, ret_psize, ret_uuid); | |
af918182 ZJS |
590 | if (r < 0) |
591 | return r; | |
592 | ||
5caa3167 LP |
593 | goto found; |
594 | } | |
595 | ||
cc7a0bfa LP |
596 | path = getenv("SYSTEMD_ESP_PATH"); |
597 | if (path) { | |
baaa35ad ZJS |
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); | |
cc7a0bfa LP |
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 | ||
5caa3167 LP |
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) | |
af918182 ZJS |
626 | return log_oom(); |
627 | ||
5caa3167 | 628 | *ret_path = c; |
af918182 ZJS |
629 | } |
630 | ||
5caa3167 | 631 | return 0; |
af918182 | 632 | } |
1b20d889 ZJS |
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 | ||
baaa35ad ZJS |
654 | if (config->default_entry < 0) |
655 | return log_error_errno(SYNTHETIC_ERRNO(ENOENT), | |
656 | "No entry suitable as default, refusing to guess."); | |
1b20d889 ZJS |
657 | |
658 | *e = &config->entries[config->default_entry]; | |
2d3bfb69 | 659 | log_debug("Found default boot entry in file \"%s\"", (*e)->path); |
1b20d889 ZJS |
660 | |
661 | if (esp_where) | |
662 | *esp_where = TAKE_PTR(where); | |
663 | ||
664 | return 0; | |
665 | } |