]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/boot/bootctl-install.c
tree-wide: check if return value of lseek() and friends is negative
[thirdparty/systemd.git] / src / boot / bootctl-install.c
CommitLineData
baafb202
LP
1/* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3#include "bootctl.h"
4#include "bootctl-install.h"
5#include "bootctl-random-seed.h"
6#include "bootctl-util.h"
f461a28d 7#include "chase.h"
baafb202 8#include "copy.h"
74e795ee
LP
9#include "dirent-util.h"
10#include "efi-api.h"
baafb202
LP
11#include "env-file.h"
12#include "fd-util.h"
13#include "fileio.h"
14#include "fs-util.h"
15#include "glyph-util.h"
74e795ee 16#include "id128-util.h"
baafb202
LP
17#include "os-util.h"
18#include "path-util.h"
74e795ee 19#include "rm-rf.h"
baafb202
LP
20#include "stat-util.h"
21#include "sync-util.h"
22#include "tmpfile-util.h"
23#include "umask-util.h"
24#include "utf8.h"
baafb202
LP
25
26static int load_etc_machine_id(void) {
27 int r;
28
29 r = sd_id128_get_machine(&arg_machine_id);
d66ad6ff
ZJS
30 if (ERRNO_IS_NEG_MACHINE_ID_UNSET(r)) /* Not set or empty */
31 return 0;
32 if (r < 0)
baafb202
LP
33 return log_error_errno(r, "Failed to get machine-id: %m");
34
35 log_debug("Loaded machine ID %s from /etc/machine-id.", SD_ID128_TO_STRING(arg_machine_id));
36 return 0;
37}
38
39static int load_etc_machine_info(void) {
40 /* systemd v250 added support to store the kernel-install layout setting and the machine ID to use
41 * for setting up the ESP in /etc/machine-info. The newer /etc/kernel/entry-token file, as well as
42 * the $layout field in /etc/kernel/install.conf are better replacements for this though, hence this
43 * has been deprecated and is only returned for compatibility. */
77db9ef2 44 _cleanup_free_ char *p = NULL, *s = NULL, *layout = NULL;
baafb202
LP
45 int r;
46
77db9ef2
DDM
47 p = path_join(arg_root, "etc/machine-info");
48 if (!p)
49 return log_oom();
50
51 r = parse_env_file(NULL, p,
baafb202
LP
52 "KERNEL_INSTALL_LAYOUT", &layout,
53 "KERNEL_INSTALL_MACHINE_ID", &s);
54 if (r == -ENOENT)
55 return 0;
56 if (r < 0)
57 return log_error_errno(r, "Failed to parse /etc/machine-info: %m");
58
59 if (!isempty(s)) {
60 if (!arg_quiet)
61 log_notice("Read $KERNEL_INSTALL_MACHINE_ID from /etc/machine-info. "
62 "Please move it to /etc/kernel/entry-token.");
63
64 r = sd_id128_from_string(s, &arg_machine_id);
65 if (r < 0)
66 return log_error_errno(r, "Failed to parse KERNEL_INSTALL_MACHINE_ID=%s in /etc/machine-info: %m", s);
67
2d5ccb08 68 log_debug("Loaded KERNEL_INSTALL_MACHINE_ID=%s from /etc/machine-info.",
baafb202
LP
69 SD_ID128_TO_STRING(arg_machine_id));
70 }
71
72 if (!isempty(layout)) {
73 if (!arg_quiet)
74 log_notice("Read $KERNEL_INSTALL_LAYOUT from /etc/machine-info. "
75 "Please move it to the layout= setting of /etc/kernel/install.conf.");
76
77 log_debug("KERNEL_INSTALL_LAYOUT=%s is specified in /etc/machine-info.", layout);
78 free_and_replace(arg_install_layout, layout);
79 }
80
81 return 0;
82}
83
84static int load_etc_kernel_install_conf(void) {
2e76ca79 85 _cleanup_free_ char *layout = NULL, *p = NULL;
baafb202
LP
86 int r;
87
77db9ef2 88 p = path_join(arg_root, etc_kernel(), "install.conf");
2e76ca79
LN
89 if (!p)
90 return log_oom();
91
92 r = parse_env_file(NULL, p, "layout", &layout);
baafb202
LP
93 if (r == -ENOENT)
94 return 0;
95 if (r < 0)
2e76ca79 96 return log_error_errno(r, "Failed to parse %s: %m", p);
baafb202
LP
97
98 if (!isempty(layout)) {
2d5ccb08 99 log_debug("layout=%s is specified in %s.", layout, p);
baafb202
LP
100 free_and_replace(arg_install_layout, layout);
101 }
102
103 return 0;
104}
105
baafb202
LP
106static bool use_boot_loader_spec_type1(void) {
107 /* If the layout is not specified, or if it is set explicitly to "bls" we assume Boot Loader
108 * Specification Type #1 is the chosen format for our boot loader entries */
109 return !arg_install_layout || streq(arg_install_layout, "bls");
110}
111
112static int settle_make_entry_directory(void) {
113 int r;
114
115 r = load_etc_machine_id();
116 if (r < 0)
117 return r;
118
119 r = load_etc_machine_info();
120 if (r < 0)
121 return r;
122
123 r = load_etc_kernel_install_conf();
124 if (r < 0)
125 return r;
126
127 r = settle_entry_token();
128 if (r < 0)
129 return r;
130
131 bool layout_type1 = use_boot_loader_spec_type1();
132 if (arg_make_entry_directory < 0) { /* Automatic mode */
133 if (layout_type1) {
9e43296f 134 if (arg_entry_token_type == BOOT_ENTRY_TOKEN_MACHINE_ID) {
baafb202
LP
135 r = path_is_temporary_fs("/etc/machine-id");
136 if (r < 0)
137 return log_debug_errno(r, "Couldn't determine whether /etc/machine-id is on a temporary file system: %m");
138
139 arg_make_entry_directory = r == 0;
140 } else
141 arg_make_entry_directory = true;
142 } else
143 arg_make_entry_directory = false;
144 }
145
146 if (arg_make_entry_directory > 0 && !layout_type1)
147 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
148 "KERNEL_INSTALL_LAYOUT=%s is configured, but Boot Loader Specification Type #1 entry directory creation was requested.",
149 arg_install_layout);
150
151 return 0;
152}
153
154static int compare_product(const char *a, const char *b) {
155 size_t x, y;
156
157 assert(a);
158 assert(b);
159
160 x = strcspn(a, " ");
161 y = strcspn(b, " ");
162 if (x != y)
163 return x < y ? -1 : x > y ? 1 : 0;
164
165 return strncmp(a, b, x);
166}
167
168static int compare_version(const char *a, const char *b) {
169 assert(a);
170 assert(b);
171
172 a += strcspn(a, " ");
173 a += strspn(a, " ");
174 b += strcspn(b, " ");
175 b += strspn(b, " ");
176
177 return strverscmp_improved(a, b);
178}
179
180static int version_check(int fd_from, const char *from, int fd_to, const char *to) {
181 _cleanup_free_ char *a = NULL, *b = NULL;
182 int r;
183
184 assert(fd_from >= 0);
185 assert(from);
186 assert(fd_to >= 0);
187 assert(to);
188
189 r = get_file_version(fd_from, &a);
9b9f6aa4
ZJS
190 if (r == -ESRCH)
191 return log_notice_errno(r, "Source file \"%s\" does not carry version information!", from);
baafb202
LP
192 if (r < 0)
193 return r;
baafb202
LP
194
195 r = get_file_version(fd_to, &b);
9b9f6aa4
ZJS
196 if (r == -ESRCH)
197 return log_notice_errno(r, "Skipping \"%s\", it's owned by another boot loader (no version info found).",
198 to);
baafb202
LP
199 if (r < 0)
200 return r;
9b9f6aa4
ZJS
201 if (compare_product(a, b) != 0)
202 return log_notice_errno(SYNTHETIC_ERRNO(ESRCH),
203 "Skipping \"%s\", it's owned by another boot loader.", to);
baafb202
LP
204
205 r = compare_version(a, b);
206 log_debug("Comparing versions: \"%s\" %s \"%s", a, comparison_operator(r), b);
207 if (r < 0)
208 return log_warning_errno(SYNTHETIC_ERRNO(ESTALE),
9b9f6aa4 209 "Skipping \"%s\", newer boot loader version in place already.", to);
baafb202
LP
210 if (r == 0)
211 return log_info_errno(SYNTHETIC_ERRNO(ESTALE),
9b9f6aa4 212 "Skipping \"%s\", same boot loader version in place already.", to);
baafb202
LP
213
214 return 0;
215}
216
217static int copy_file_with_version_check(const char *from, const char *to, bool force) {
254d1313 218 _cleanup_close_ int fd_from = -EBADF, fd_to = -EBADF;
baafb202
LP
219 _cleanup_free_ char *t = NULL;
220 int r;
221
222 fd_from = open(from, O_RDONLY|O_CLOEXEC|O_NOCTTY);
223 if (fd_from < 0)
224 return log_error_errno(errno, "Failed to open \"%s\" for reading: %m", from);
225
226 if (!force) {
227 fd_to = open(to, O_RDONLY|O_CLOEXEC|O_NOCTTY);
228 if (fd_to < 0) {
229 if (errno != ENOENT)
230 return log_error_errno(errno, "Failed to open \"%s\" for reading: %m", to);
231 } else {
232 r = version_check(fd_from, from, fd_to, to);
233 if (r < 0)
234 return r;
235
86cbbc6d 236 if (lseek(fd_from, 0, SEEK_SET) < 0)
baafb202
LP
237 return log_error_errno(errno, "Failed to seek in \"%s\": %m", from);
238
239 fd_to = safe_close(fd_to);
240 }
241 }
242
243 r = tempfn_random(to, NULL, &t);
244 if (r < 0)
245 return log_oom();
246
247 WITH_UMASK(0000) {
248 fd_to = open(t, O_WRONLY|O_CREAT|O_CLOEXEC|O_EXCL|O_NOFOLLOW, 0644);
249 if (fd_to < 0)
250 return log_error_errno(errno, "Failed to open \"%s\" for writing: %m", t);
251 }
252
253 r = copy_bytes(fd_from, fd_to, UINT64_MAX, COPY_REFLINK);
254 if (r < 0) {
255 (void) unlink(t);
256 return log_error_errno(r, "Failed to copy data from \"%s\" to \"%s\": %m", from, t);
257 }
258
259 (void) copy_times(fd_from, fd_to, 0);
260
261 r = fsync_full(fd_to);
262 if (r < 0) {
39eb3ffa 263 (void) unlink(t);
baafb202
LP
264 return log_error_errno(r, "Failed to copy data from \"%s\" to \"%s\": %m", from, t);
265 }
266
39eb3ffa
DDM
267 r = RET_NERRNO(renameat(AT_FDCWD, t, AT_FDCWD, to));
268 if (r < 0) {
269 (void) unlink(t);
270 return log_error_errno(r, "Failed to rename \"%s\" to \"%s\": %m", t, to);
baafb202
LP
271 }
272
273 log_info("Copied \"%s\" to \"%s\".", from, to);
274
275 return 0;
276}
277
278static int mkdir_one(const char *prefix, const char *suffix) {
279 _cleanup_free_ char *p = NULL;
280
281 p = path_join(prefix, suffix);
282 if (mkdir(p, 0700) < 0) {
283 if (errno != EEXIST)
284 return log_error_errno(errno, "Failed to create \"%s\": %m", p);
285 } else
286 log_info("Created \"%s\".", p);
287
288 return 0;
289}
290
291static const char *const esp_subdirs[] = {
292 /* The directories to place in the ESP */
293 "EFI",
294 "EFI/systemd",
295 "EFI/BOOT",
296 "loader",
297 NULL
298};
299
300static const char *const dollar_boot_subdirs[] = {
301 /* The directories to place in the XBOOTLDR partition or the ESP, depending what exists */
302 "loader",
303 "loader/entries", /* Type #1 entries */
304 "EFI",
305 "EFI/Linux", /* Type #2 entries */
306 NULL
307};
308
309static int create_subdirs(const char *root, const char * const *subdirs) {
310 int r;
311
312 STRV_FOREACH(i, subdirs) {
313 r = mkdir_one(root, *i);
314 if (r < 0)
315 return r;
316 }
317
318 return 0;
319}
320
321
322static int copy_one_file(const char *esp_path, const char *name, bool force) {
323 char *root = IN_SET(arg_install_source, ARG_INSTALL_SOURCE_AUTO, ARG_INSTALL_SOURCE_IMAGE) ? arg_root : NULL;
324 _cleanup_free_ char *source_path = NULL, *dest_path = NULL, *p = NULL, *q = NULL;
325 const char *e;
326 char *dest_name, *s;
327 int r, ret;
328
329 dest_name = strdupa_safe(name);
330 s = endswith_no_case(dest_name, ".signed");
331 if (s)
332 *s = 0;
333
334 p = path_join(BOOTLIBDIR, name);
335 if (!p)
336 return log_oom();
337
f461a28d 338 r = chase(p, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &source_path, NULL);
baafb202
LP
339 /* If we had a root directory to try, we didn't find it and we are in auto mode, retry on the host */
340 if (r == -ENOENT && root && arg_install_source == ARG_INSTALL_SOURCE_AUTO)
f461a28d 341 r = chase(p, NULL, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &source_path, NULL);
baafb202
LP
342 if (r < 0)
343 return log_error_errno(r,
344 "Failed to resolve path %s%s%s: %m",
345 p,
346 root ? " under directory " : "",
347 strempty(root));
348
349 q = path_join("/EFI/systemd/", dest_name);
350 if (!q)
351 return log_oom();
352
f461a28d 353 r = chase(q, esp_path, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_NONEXISTENT, &dest_path, NULL);
baafb202
LP
354 if (r < 0)
355 return log_error_errno(r, "Failed to resolve path %s under directory %s: %m", q, esp_path);
356
357 /* Note that if this fails we do the second copy anyway, but return this error code,
358 * so we stash it away in a separate variable. */
359 ret = copy_file_with_version_check(source_path, dest_path, force);
360
361 e = startswith(dest_name, "systemd-boot");
362 if (e) {
363 _cleanup_free_ char *default_dest_path = NULL;
364 char *v;
365
366 /* Create the EFI default boot loader name (specified for removable devices) */
367 v = strjoina("/EFI/BOOT/BOOT", e);
368 ascii_strupper(strrchr(v, '/') + 1);
369
f461a28d 370 r = chase(v, esp_path, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_NONEXISTENT, &default_dest_path, NULL);
baafb202
LP
371 if (r < 0)
372 return log_error_errno(r, "Failed to resolve path %s under directory %s: %m", v, esp_path);
373
374 r = copy_file_with_version_check(source_path, default_dest_path, force);
375 if (r < 0 && ret == 0)
376 ret = r;
377 }
378
379 return ret;
380}
381
382static int install_binaries(const char *esp_path, const char *arch, bool force) {
383 char *root = IN_SET(arg_install_source, ARG_INSTALL_SOURCE_AUTO, ARG_INSTALL_SOURCE_IMAGE) ? arg_root : NULL;
384 _cleanup_closedir_ DIR *d = NULL;
385 _cleanup_free_ char *path = NULL;
386 int r;
387
f461a28d 388 r = chase_and_opendir(BOOTLIBDIR, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &path, &d);
baafb202
LP
389 /* If we had a root directory to try, we didn't find it and we are in auto mode, retry on the host */
390 if (r == -ENOENT && root && arg_install_source == ARG_INSTALL_SOURCE_AUTO)
f461a28d 391 r = chase_and_opendir(BOOTLIBDIR, NULL, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &path, &d);
05f70527
DDM
392 if (r == -ENOENT && arg_graceful) {
393 log_debug("Source directory does not exist, ignoring.");
394 return 0;
395 }
baafb202
LP
396 if (r < 0)
397 return log_error_errno(r, "Failed to open boot loader directory %s%s: %m", strempty(root), BOOTLIBDIR);
398
399 const char *suffix = strjoina(arch, ".efi");
400 const char *suffix_signed = strjoina(arch, ".efi.signed");
401
402 FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read \"%s\": %m", path)) {
403 int k;
404
405 if (!endswith_no_case(de->d_name, suffix) && !endswith_no_case(de->d_name, suffix_signed))
406 continue;
407
408 /* skip the .efi file, if there's a .signed version of it */
409 if (endswith_no_case(de->d_name, ".efi")) {
410 _cleanup_free_ const char *s = strjoin(de->d_name, ".signed");
411 if (!s)
412 return log_oom();
413 if (faccessat(dirfd(d), s, F_OK, 0) >= 0)
414 continue;
415 }
416
417 k = copy_one_file(esp_path, de->d_name, force);
418 /* Don't propagate an error code if no update necessary, installed version already equal or
419 * newer version, or other boot loader in place. */
9b9f6aa4 420 if (arg_graceful && IN_SET(k, -ESTALE, -ESRCH))
baafb202 421 continue;
9ef648cc 422 RET_GATHER(r, k);
baafb202
LP
423 }
424
425 return r;
426}
427
428static int install_loader_config(const char *esp_path) {
429 _cleanup_(unlink_and_freep) char *t = NULL;
430 _cleanup_fclose_ FILE *f = NULL;
23e208e7 431 _cleanup_free_ char *p = NULL;
baafb202
LP
432 int r;
433
434 assert(arg_make_entry_directory >= 0);
435
23e208e7
LP
436 p = path_join(esp_path, "/loader/loader.conf");
437 if (!p)
438 return log_oom();
baafb202
LP
439 if (access(p, F_OK) >= 0) /* Silently skip creation if the file already exists (early check) */
440 return 0;
441
442 r = fopen_tmpfile_linkable(p, O_WRONLY|O_CLOEXEC, &t, &f);
443 if (r < 0)
444 return log_error_errno(r, "Failed to open \"%s\" for writing: %m", p);
445
446 fprintf(f, "#timeout 3\n"
447 "#console-mode keep\n");
448
449 if (arg_make_entry_directory) {
450 assert(arg_entry_token);
451 fprintf(f, "default %s-*\n", arg_entry_token);
452 }
453
8dfd099b 454 r = flink_tmpfile(f, t, p, LINK_TMPFILE_SYNC);
baafb202
LP
455 if (r == -EEXIST)
456 return 0; /* Silently skip creation if the file exists now (recheck) */
457 if (r < 0)
458 return log_error_errno(r, "Failed to move \"%s\" into place: %m", p);
459
460 t = mfree(t);
461 return 1;
462}
463
464static int install_loader_specification(const char *root) {
465 _cleanup_(unlink_and_freep) char *t = NULL;
466 _cleanup_fclose_ FILE *f = NULL;
467 _cleanup_free_ char *p = NULL;
468 int r;
469
470 p = path_join(root, "/loader/entries.srel");
471 if (!p)
472 return log_oom();
473
474 if (access(p, F_OK) >= 0) /* Silently skip creation if the file already exists (early check) */
475 return 0;
476
477 r = fopen_tmpfile_linkable(p, O_WRONLY|O_CLOEXEC, &t, &f);
478 if (r < 0)
479 return log_error_errno(r, "Failed to open \"%s\" for writing: %m", p);
480
481 fprintf(f, "type1\n");
482
8dfd099b 483 r = flink_tmpfile(f, t, p, LINK_TMPFILE_SYNC);
baafb202
LP
484 if (r == -EEXIST)
485 return 0; /* Silently skip creation if the file exists now (recheck) */
486 if (r < 0)
487 return log_error_errno(r, "Failed to move \"%s\" into place: %m", p);
488
489 t = mfree(t);
490 return 1;
491}
492
493static int install_entry_directory(const char *root) {
494 assert(root);
495 assert(arg_make_entry_directory >= 0);
496
497 if (!arg_make_entry_directory)
498 return 0;
499
500 assert(arg_entry_token);
501 return mkdir_one(root, arg_entry_token);
502}
503
504static int install_entry_token(void) {
2e76ca79 505 _cleanup_free_ char* p = NULL;
baafb202
LP
506 int r;
507
508 assert(arg_make_entry_directory >= 0);
509 assert(arg_entry_token);
510
511 /* Let's save the used entry token in /etc/kernel/entry-token if we used it to create the entry
512 * directory, or if anything else but the machine ID */
513
9e43296f 514 if (!arg_make_entry_directory && arg_entry_token_type == BOOT_ENTRY_TOKEN_MACHINE_ID)
baafb202
LP
515 return 0;
516
77db9ef2 517 p = path_join(arg_root, etc_kernel(), "entry-token");
2e76ca79
LN
518 if (!p)
519 return log_oom();
520
521 r = write_string_file(p, arg_entry_token, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_MKDIR_0755);
baafb202 522 if (r < 0)
3b42ffe5 523 return log_error_errno(r, "Failed to write entry token '%s' to %s: %m", arg_entry_token, p);
baafb202
LP
524
525 return 0;
526}
527
528static bool same_entry(uint16_t id, sd_id128_t uuid, const char *path) {
529 _cleanup_free_ char *opath = NULL;
530 sd_id128_t ouuid;
531 int r;
532
533 r = efi_get_boot_option(id, NULL, &ouuid, &opath, NULL);
534 if (r < 0)
535 return false;
536 if (!sd_id128_equal(uuid, ouuid))
537 return false;
538
539 /* Some motherboards convert the path to uppercase under certain circumstances
540 * (e.g. after booting into the Boot Menu in the ASUS ROG STRIX B350-F GAMING),
541 * so use case-insensitive checking */
542 if (!strcaseeq_ptr(path, opath))
543 return false;
544
545 return true;
546}
547
548static int find_slot(sd_id128_t uuid, const char *path, uint16_t *id) {
549 _cleanup_free_ uint16_t *options = NULL;
550
551 int n = efi_get_boot_options(&options);
552 if (n < 0)
553 return n;
554
555 /* find already existing systemd-boot entry */
556 for (int i = 0; i < n; i++)
557 if (same_entry(options[i], uuid, path)) {
558 *id = options[i];
559 return 1;
560 }
561
562 /* find free slot in the sorted BootXXXX variable list */
563 for (int i = 0; i < n; i++)
564 if (i != options[i]) {
565 *id = i;
566 return 0;
567 }
568
569 /* use the next one */
570 if (n == 0xffff)
571 return -ENOSPC;
572 *id = n;
573 return 0;
574}
575
576static int insert_into_order(uint16_t slot, bool first) {
577 _cleanup_free_ uint16_t *order = NULL;
578 uint16_t *t;
579 int n;
580
581 n = efi_get_boot_order(&order);
582 if (n <= 0)
583 /* no entry, add us */
584 return efi_set_boot_order(&slot, 1);
585
586 /* are we the first and only one? */
587 if (n == 1 && order[0] == slot)
588 return 0;
589
590 /* are we already in the boot order? */
591 for (int i = 0; i < n; i++) {
592 if (order[i] != slot)
593 continue;
594
595 /* we do not require to be the first one, all is fine */
596 if (!first)
597 return 0;
598
599 /* move us to the first slot */
600 memmove(order + 1, order, i * sizeof(uint16_t));
601 order[0] = slot;
602 return efi_set_boot_order(order, n);
603 }
604
605 /* extend array */
606 t = reallocarray(order, n + 1, sizeof(uint16_t));
607 if (!t)
608 return -ENOMEM;
609 order = t;
610
611 /* add us to the top or end of the list */
612 if (first) {
613 memmove(order + 1, order, n * sizeof(uint16_t));
614 order[0] = slot;
615 } else
616 order[n] = slot;
617
618 return efi_set_boot_order(order, n + 1);
619}
620
621static int remove_from_order(uint16_t slot) {
622 _cleanup_free_ uint16_t *order = NULL;
623 int n;
624
625 n = efi_get_boot_order(&order);
626 if (n <= 0)
627 return n;
628
629 for (int i = 0; i < n; i++) {
630 if (order[i] != slot)
631 continue;
632
633 if (i + 1 < n)
634 memmove(order + i, order + i+1, (n - i) * sizeof(uint16_t));
635 return efi_set_boot_order(order, n - 1);
636 }
637
638 return 0;
639}
640
641static const char *pick_efi_boot_option_description(void) {
642 return arg_efi_boot_option_description ?: "Linux Boot Manager";
643}
644
645static int install_variables(
646 const char *esp_path,
647 uint32_t part,
648 uint64_t pstart,
649 uint64_t psize,
650 sd_id128_t uuid,
651 const char *path,
06d104d5
KF
652 bool first,
653 bool graceful) {
baafb202
LP
654
655 uint16_t slot;
656 int r;
657
658 if (arg_root) {
659 log_info("Acting on %s, skipping EFI variable setup.",
660 arg_image ? "image" : "root directory");
661 return 0;
662 }
663
664 if (!is_efi_boot()) {
665 log_warning("Not booted with EFI, skipping EFI variable setup.");
666 return 0;
667 }
668
f461a28d 669 r = chase_and_access(path, esp_path, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, F_OK, NULL);
baafb202
LP
670 if (r == -ENOENT)
671 return 0;
672 if (r < 0)
673 return log_error_errno(r, "Cannot access \"%s/%s\": %m", esp_path, path);
674
675 r = find_slot(uuid, path, &slot);
06d104d5
KF
676 if (r < 0) {
677 int level = graceful ? arg_quiet ? LOG_DEBUG : LOG_INFO : LOG_ERR;
678 const char *skip = graceful ? ", skipping" : "";
679
680 log_full_errno(level, r,
681 r == -ENOENT ?
682 "Failed to access EFI variables%s. Is the \"efivarfs\" filesystem mounted?" :
683 "Failed to determine current boot order%s: %m", skip);
684
685 return graceful ? 0 : r;
686 }
baafb202
LP
687
688 if (first || r == 0) {
689 r = efi_add_boot_option(slot, pick_efi_boot_option_description(),
690 part, pstart, psize,
691 uuid, path);
06d104d5
KF
692 if (r < 0) {
693 int level = graceful ? arg_quiet ? LOG_DEBUG : LOG_INFO : LOG_ERR;
694 const char *skip = graceful ? ", skipping" : "";
695
696 log_full_errno(level, r, "Failed to create EFI Boot variable entry%s: %m", skip);
697
698 return graceful ? 0 : r;
699 }
baafb202
LP
700
701 log_info("Created EFI boot entry \"%s\".", pick_efi_boot_option_description());
702 }
703
704 return insert_into_order(slot, first);
705}
706
707static int are_we_installed(const char *esp_path) {
708 int r;
709
710 /* Tests whether systemd-boot is installed. It's not obvious what to use as check here: we could
711 * check EFI variables, we could check what binary /EFI/BOOT/BOOT*.EFI points to, or whether the
712 * loader entries directory exists. Here we opted to check whether /EFI/systemd/ is non-empty, which
713 * should be a suitable and very minimal check for a number of reasons:
714 *
715 * → The check is architecture independent (i.e. we check if any systemd-boot loader is installed,
716 * not a specific one.)
717 *
718 * → It doesn't assume we are the only boot loader (i.e doesn't check if we own the main
719 * /EFI/BOOT/BOOT*.EFI fallback binary.
720 *
721 * → It specifically checks for systemd-boot, not for other boot loaders (which a check for
722 * /boot/loader/entries would do). */
723
724 _cleanup_free_ char *p = path_join(esp_path, "/EFI/systemd/");
725 if (!p)
726 return log_oom();
727
728 log_debug("Checking whether %s contains any files%s", p, special_glyph(SPECIAL_GLYPH_ELLIPSIS));
729 r = dir_is_empty(p, /* ignore_hidden_or_backup= */ false);
730 if (r < 0 && r != -ENOENT)
731 return log_error_errno(r, "Failed to check whether %s contains any files: %m", p);
732
733 return r == 0;
734}
735
736int verb_install(int argc, char *argv[], void *userdata) {
737 sd_id128_t uuid = SD_ID128_NULL;
738 uint64_t pstart = 0, psize = 0;
739 uint32_t part = 0;
740 bool install, graceful;
741 int r;
742
743 /* Invoked for both "update" and "install" */
744
745 install = streq(argv[0], "install");
746 graceful = !install && arg_graceful; /* support graceful mode for updates */
747
748 r = acquire_esp(/* unprivileged_mode= */ false, graceful, &part, &pstart, &psize, &uuid, NULL);
749 if (graceful && r == -ENOKEY)
750 return 0; /* If --graceful is specified and we can't find an ESP, handle this cleanly */
751 if (r < 0)
752 return r;
753
754 if (!install) {
755 /* If we are updating, don't do anything if sd-boot wasn't actually installed. */
756 r = are_we_installed(arg_esp_path);
757 if (r < 0)
758 return r;
759 if (r == 0) {
760 log_debug("Skipping update because sd-boot is not installed in the ESP.");
761 return 0;
762 }
763 }
764
765 r = acquire_xbootldr(/* unprivileged_mode= */ false, NULL, NULL);
766 if (r < 0)
767 return r;
768
769 r = settle_make_entry_directory();
770 if (r < 0)
771 return r;
772
773 const char *arch = arg_arch_all ? "" : get_efi_arch();
774
775 WITH_UMASK(0002) {
776 if (install) {
777 /* Don't create any of these directories when we are just updating. When we update
778 * we'll drop-in our files (unless there are newer ones already), but we won't create
779 * the directories for them in the first place. */
780 r = create_subdirs(arg_esp_path, esp_subdirs);
781 if (r < 0)
782 return r;
783
784 r = create_subdirs(arg_dollar_boot_path(), dollar_boot_subdirs);
785 if (r < 0)
786 return r;
787 }
788
789 r = install_binaries(arg_esp_path, arch, install);
790 if (r < 0)
791 return r;
792
793 if (install) {
794 r = install_loader_config(arg_esp_path);
795 if (r < 0)
796 return r;
797
798 r = install_entry_directory(arg_dollar_boot_path());
799 if (r < 0)
800 return r;
801
802 r = install_entry_token();
803 if (r < 0)
804 return r;
805
806 r = install_random_seed(arg_esp_path);
807 if (r < 0)
808 return r;
809 }
810
811 r = install_loader_specification(arg_dollar_boot_path());
812 if (r < 0)
813 return r;
814 }
815
816 (void) sync_everything();
817
818 if (!arg_touch_variables)
819 return 0;
820
821 if (arg_arch_all) {
822 log_info("Not changing EFI variables with --all-architectures.");
823 return 0;
824 }
825
826 char *path = strjoina("/EFI/systemd/systemd-boot", arch, ".efi");
06d104d5 827 return install_variables(arg_esp_path, part, pstart, psize, uuid, path, install, graceful);
baafb202
LP
828}
829
830static int remove_boot_efi(const char *esp_path) {
831 _cleanup_closedir_ DIR *d = NULL;
832 _cleanup_free_ char *p = NULL;
833 int r, c = 0;
834
f461a28d 835 r = chase_and_opendir("/EFI/BOOT", esp_path, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &p, &d);
baafb202
LP
836 if (r == -ENOENT)
837 return 0;
838 if (r < 0)
839 return log_error_errno(r, "Failed to open directory \"%s/EFI/BOOT\": %m", esp_path);
840
841 FOREACH_DIRENT(de, d, break) {
254d1313 842 _cleanup_close_ int fd = -EBADF;
baafb202
LP
843 _cleanup_free_ char *v = NULL;
844
845 if (!endswith_no_case(de->d_name, ".efi"))
846 continue;
847
848 if (!startswith_no_case(de->d_name, "boot"))
849 continue;
850
851 fd = openat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC);
852 if (fd < 0)
853 return log_error_errno(errno, "Failed to open \"%s/%s\" for reading: %m", p, de->d_name);
854
855 r = get_file_version(fd, &v);
9b9f6aa4
ZJS
856 if (r == -ESRCH)
857 continue; /* No version information */
baafb202
LP
858 if (r < 0)
859 return r;
9b9f6aa4 860 if (startswith(v, "systemd-boot ")) {
baafb202
LP
861 r = unlinkat(dirfd(d), de->d_name, 0);
862 if (r < 0)
863 return log_error_errno(errno, "Failed to remove \"%s/%s\": %m", p, de->d_name);
864
865 log_info("Removed \"%s/%s\".", p, de->d_name);
866 }
867
868 c++;
869 }
870
871 return c;
872}
873
874static int rmdir_one(const char *prefix, const char *suffix) {
875 const char *p;
876
877 p = prefix_roota(prefix, suffix);
878 if (rmdir(p) < 0) {
879 bool ignore = IN_SET(errno, ENOENT, ENOTEMPTY);
880
881 log_full_errno(ignore ? LOG_DEBUG : LOG_ERR, errno,
882 "Failed to remove directory \"%s\": %m", p);
883 if (!ignore)
884 return -errno;
885 } else
886 log_info("Removed \"%s\".", p);
887
888 return 0;
889}
890
891static int remove_subdirs(const char *root, const char *const *subdirs) {
892 int r, q;
893
894 /* We use recursion here to destroy the directories in reverse order. Which should be safe given how
895 * short the array is. */
896
897 if (!subdirs[0]) /* A the end of the list */
898 return 0;
899
900 r = remove_subdirs(root, subdirs + 1);
901 q = rmdir_one(root, subdirs[0]);
902
903 return r < 0 ? r : q;
904}
905
906static int remove_entry_directory(const char *root) {
907 assert(root);
908 assert(arg_make_entry_directory >= 0);
909
910 if (!arg_make_entry_directory || !arg_entry_token)
911 return 0;
912
913 return rmdir_one(root, arg_entry_token);
914}
915
916static int remove_binaries(const char *esp_path) {
917 const char *p;
918 int r, q;
919
920 p = prefix_roota(esp_path, "/EFI/systemd");
921 r = rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL);
922
923 q = remove_boot_efi(esp_path);
924 if (q < 0 && r == 0)
925 r = q;
926
927 return r;
928}
929
930static int remove_file(const char *root, const char *file) {
931 const char *p;
932
933 assert(root);
934 assert(file);
935
936 p = prefix_roota(root, file);
937 if (unlink(p) < 0) {
938 log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR, errno,
939 "Failed to unlink file \"%s\": %m", p);
940
941 return errno == ENOENT ? 0 : -errno;
942 }
943
944 log_info("Removed \"%s\".", p);
945 return 1;
946}
947
948static int remove_variables(sd_id128_t uuid, const char *path, bool in_order) {
949 uint16_t slot;
950 int r;
951
952 if (arg_root || !is_efi_boot())
953 return 0;
954
955 r = find_slot(uuid, path, &slot);
956 if (r != 1)
957 return 0;
958
959 r = efi_remove_boot_option(slot);
960 if (r < 0)
961 return r;
962
963 if (in_order)
964 return remove_from_order(slot);
965
966 return 0;
967}
968
969static int remove_loader_variables(void) {
970 int r = 0;
971
972 /* Remove all persistent loader variables we define */
973
974 FOREACH_STRING(var,
976904bf 975 EFI_LOADER_VARIABLE(LoaderConfigConsoleMode),
baafb202
LP
976 EFI_LOADER_VARIABLE(LoaderConfigTimeout),
977 EFI_LOADER_VARIABLE(LoaderConfigTimeoutOneShot),
978 EFI_LOADER_VARIABLE(LoaderEntryDefault),
976904bf 979 EFI_LOADER_VARIABLE(LoaderEntryLastBooted),
baafb202
LP
980 EFI_LOADER_VARIABLE(LoaderEntryOneShot),
981 EFI_LOADER_VARIABLE(LoaderSystemToken)){
982
983 int q;
984
985 q = efi_set_variable(var, NULL, 0);
986 if (q == -ENOENT)
987 continue;
988 if (q < 0) {
989 log_warning_errno(q, "Failed to remove EFI variable %s: %m", var);
990 if (r >= 0)
991 r = q;
992 } else
993 log_info("Removed EFI variable %s.", var);
994 }
995
996 return r;
997}
998
999int verb_remove(int argc, char *argv[], void *userdata) {
1000 sd_id128_t uuid = SD_ID128_NULL;
1001 int r, q;
1002
1003 r = acquire_esp(/* unprivileged_mode= */ false, /* graceful= */ false, NULL, NULL, NULL, &uuid, NULL);
1004 if (r < 0)
1005 return r;
1006
1007 r = acquire_xbootldr(/* unprivileged_mode= */ false, NULL, NULL);
1008 if (r < 0)
1009 return r;
1010
1011 r = settle_make_entry_directory();
1012 if (r < 0)
1013 return r;
1014
1015 r = remove_binaries(arg_esp_path);
1016
1017 q = remove_file(arg_esp_path, "/loader/loader.conf");
1018 if (q < 0 && r >= 0)
1019 r = q;
1020
1021 q = remove_file(arg_esp_path, "/loader/random-seed");
1022 if (q < 0 && r >= 0)
1023 r = q;
1024
1025 q = remove_file(arg_esp_path, "/loader/entries.srel");
1026 if (q < 0 && r >= 0)
1027 r = q;
1028
1029 q = remove_subdirs(arg_esp_path, esp_subdirs);
1030 if (q < 0 && r >= 0)
1031 r = q;
1032
1033 q = remove_subdirs(arg_esp_path, dollar_boot_subdirs);
1034 if (q < 0 && r >= 0)
1035 r = q;
1036
1037 q = remove_entry_directory(arg_esp_path);
1038 if (q < 0 && r >= 0)
1039 r = q;
1040
1041 if (arg_xbootldr_path) {
1042 /* Remove a subset of these also from the XBOOTLDR partition if it exists */
1043
1044 q = remove_file(arg_xbootldr_path, "/loader/entries.srel");
1045 if (q < 0 && r >= 0)
1046 r = q;
1047
1048 q = remove_subdirs(arg_xbootldr_path, dollar_boot_subdirs);
1049 if (q < 0 && r >= 0)
1050 r = q;
1051
1052 q = remove_entry_directory(arg_xbootldr_path);
1053 if (q < 0 && r >= 0)
1054 r = q;
1055 }
1056
1057 (void) sync_everything();
1058
1059 if (!arg_touch_variables)
1060 return r;
1061
1062 if (arg_arch_all) {
1063 log_info("Not changing EFI variables with --all-architectures.");
1064 return r;
1065 }
1066
1067 char *path = strjoina("/EFI/systemd/systemd-boot", get_efi_arch(), ".efi");
1068 q = remove_variables(uuid, path, true);
1069 if (q < 0 && r >= 0)
1070 r = q;
1071
1072 q = remove_loader_variables();
1073 if (q < 0 && r >= 0)
1074 r = q;
1075
1076 return r;
1077}
1078
1079int verb_is_installed(int argc, char *argv[], void *userdata) {
1080 int r;
1081
1082 r = acquire_esp(/* privileged_mode= */ false,
1083 /* graceful= */ arg_graceful,
1084 NULL, NULL, NULL, NULL, NULL);
1085 if (r < 0)
1086 return r;
1087
1088 r = are_we_installed(arg_esp_path);
1089 if (r < 0)
1090 return r;
1091
1092 if (r > 0) {
1093 if (!arg_quiet)
1094 puts("yes");
1095 return EXIT_SUCCESS;
1096 } else {
1097 if (!arg_quiet)
1098 puts("no");
1099 return EXIT_FAILURE;
1100 }
1101}