]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
7b4d7cc0 | 2 | |
0974a682 | 3 | #include <ctype.h> |
3f6fd1ba | 4 | #include <errno.h> |
3f6fd1ba LP |
5 | #include <getopt.h> |
6 | #include <limits.h> | |
5fa6c13c | 7 | #include <linux/magic.h> |
0974a682 | 8 | #include <stdbool.h> |
3f6fd1ba | 9 | #include <stdlib.h> |
3f6fd1ba | 10 | #include <sys/mman.h> |
3f6fd1ba | 11 | #include <unistd.h> |
7b4d7cc0 | 12 | |
dccca82b LP |
13 | #include "sd-id128.h" |
14 | ||
b5efdb8a | 15 | #include "alloc-util.h" |
3f6fd1ba | 16 | #include "blkid-util.h" |
7e87c7d9 | 17 | #include "bootspec.h" |
d6b4d1c7 | 18 | #include "build.h" |
80a2381d | 19 | #include "chase-symlinks.h" |
175d308c | 20 | #include "copy.h" |
7176f06c | 21 | #include "devnum-util.h" |
e41256dc | 22 | #include "dirent-util.h" |
80a2381d | 23 | #include "dissect-image.h" |
7be4b236 | 24 | #include "efi-api.h" |
0bb2f0f1 | 25 | #include "efi-loader.h" |
0974a682 | 26 | #include "efivars.h" |
47fb161e | 27 | #include "env-file.h" |
e44c3229 | 28 | #include "env-util.h" |
9dae4c8a | 29 | #include "escape.h" |
3ffd4af2 | 30 | #include "fd-util.h" |
0d39fa9c | 31 | #include "fileio.h" |
e94830c0 | 32 | #include "find-esp.h" |
175d308c | 33 | #include "fs-util.h" |
d8e32c47 | 34 | #include "glyph-util.h" |
608f8ec9 | 35 | #include "main-func.h" |
a4a55e9a | 36 | #include "mkdir.h" |
80a2381d | 37 | #include "mount-util.h" |
f337f903 | 38 | #include "os-util.h" |
57db6f18 | 39 | #include "pager.h" |
6a3fff75 | 40 | #include "parse-argument.h" |
2f2c539c | 41 | #include "parse-util.h" |
294bf0c3 | 42 | #include "pretty-print.h" |
e44c3229 | 43 | #include "random-util.h" |
c6878637 | 44 | #include "rm-rf.h" |
175d308c | 45 | #include "stat-util.h" |
341890de | 46 | #include "stdio-util.h" |
bb682057 | 47 | #include "string-table.h" |
07630cea | 48 | #include "string-util.h" |
2f2c539c | 49 | #include "strv.h" |
bf819d3a | 50 | #include "sync-util.h" |
7e87c7d9 | 51 | #include "terminal-util.h" |
e4de7287 | 52 | #include "tmpfile-util.h" |
47fb161e | 53 | #include "tmpfile-util-label.h" |
0ea911d1 | 54 | #include "tpm2-util.h" |
2f2c539c | 55 | #include "umask-util.h" |
d88c96ff | 56 | #include "utf8.h" |
2f2c539c LP |
57 | #include "verbs.h" |
58 | #include "virt.h" | |
7b4d7cc0 | 59 | |
d9bdb29b RH |
60 | /* EFI_BOOT_OPTION_DESCRIPTION_MAX sets the maximum length for the boot option description |
61 | * stored in NVRAM. The UEFI spec does not specify a minimum or maximum length for this | |
62 | * string, but we limit the length to something reasonable to prevent from the firmware | |
63 | * having to deal with a potentially too long string. */ | |
64 | #define EFI_BOOT_OPTION_DESCRIPTION_MAX ((size_t) 255) | |
65 | ||
fbf45d22 LP |
66 | static char *arg_esp_path = NULL; |
67 | static char *arg_xbootldr_path = NULL; | |
68 | static bool arg_print_esp_path = false; | |
69 | static bool arg_print_dollar_boot_path = false; | |
25579a43 | 70 | static bool arg_touch_variables = true; |
0221d68a | 71 | static PagerFlags arg_pager_flags = 0; |
351de38e | 72 | static bool arg_graceful = false; |
14e6e444 | 73 | static bool arg_quiet = false; |
f337f903 | 74 | static int arg_make_entry_directory = false; /* tri-state: < 0 for automatic logic */ |
47fb161e ZJS |
75 | static sd_id128_t arg_machine_id = SD_ID128_NULL; |
76 | static char *arg_install_layout = NULL; | |
f337f903 LP |
77 | static enum { |
78 | ARG_ENTRY_TOKEN_MACHINE_ID, | |
79 | ARG_ENTRY_TOKEN_OS_IMAGE_ID, | |
80 | ARG_ENTRY_TOKEN_OS_ID, | |
81 | ARG_ENTRY_TOKEN_LITERAL, | |
82 | ARG_ENTRY_TOKEN_AUTO, | |
83 | } arg_entry_token_type = ARG_ENTRY_TOKEN_AUTO; | |
84 | static char *arg_entry_token = NULL; | |
0d1506d4 | 85 | static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF; |
6e916539 | 86 | static bool arg_arch_all = false; |
80a2381d LB |
87 | static char *arg_root = NULL; |
88 | static char *arg_image = NULL; | |
02d06ba1 LB |
89 | static enum { |
90 | ARG_INSTALL_SOURCE_IMAGE, | |
91 | ARG_INSTALL_SOURCE_HOST, | |
92 | ARG_INSTALL_SOURCE_AUTO, | |
93 | } arg_install_source = ARG_INSTALL_SOURCE_AUTO; | |
d9bdb29b | 94 | static char *arg_efi_boot_option_description = NULL; |
25579a43 | 95 | |
fbf45d22 LP |
96 | STATIC_DESTRUCTOR_REGISTER(arg_esp_path, freep); |
97 | STATIC_DESTRUCTOR_REGISTER(arg_xbootldr_path, freep); | |
47fb161e | 98 | STATIC_DESTRUCTOR_REGISTER(arg_install_layout, freep); |
f337f903 | 99 | STATIC_DESTRUCTOR_REGISTER(arg_entry_token, freep); |
80a2381d LB |
100 | STATIC_DESTRUCTOR_REGISTER(arg_root, freep); |
101 | STATIC_DESTRUCTOR_REGISTER(arg_image, freep); | |
d9bdb29b | 102 | STATIC_DESTRUCTOR_REGISTER(arg_efi_boot_option_description, freep); |
fbf45d22 LP |
103 | |
104 | static const char *arg_dollar_boot_path(void) { | |
105 | /* $BOOT shall be the XBOOTLDR partition if it exists, and otherwise the ESP */ | |
106 | return arg_xbootldr_path ?: arg_esp_path; | |
107 | } | |
608f8ec9 | 108 | |
d9bdb29b RH |
109 | static const char *pick_efi_boot_option_description(void) { |
110 | return arg_efi_boot_option_description ?: "Linux Boot Manager"; | |
111 | } | |
112 | ||
5caa3167 LP |
113 | static int acquire_esp( |
114 | bool unprivileged_mode, | |
e5a8b4b5 | 115 | bool graceful, |
5caa3167 LP |
116 | uint32_t *ret_part, |
117 | uint64_t *ret_pstart, | |
118 | uint64_t *ret_psize, | |
f63b5ad9 LP |
119 | sd_id128_t *ret_uuid, |
120 | dev_t *ret_devid) { | |
5caa3167 LP |
121 | |
122 | char *np; | |
2f2c539c LP |
123 | int r; |
124 | ||
fbf45d22 LP |
125 | /* Find the ESP, and log about errors. Note that find_esp_and_warn() will log in all error cases on |
126 | * its own, except for ENOKEY (which is good, we want to show our own message in that case, | |
127 | * suggesting use of --esp-path=) and EACCESS (only when we request unprivileged mode; in this case | |
128 | * we simply eat up the error here, so that --list and --status work too, without noise about | |
129 | * this). */ | |
5caa3167 | 130 | |
80a2381d | 131 | r = find_esp_and_warn(arg_root, arg_esp_path, unprivileged_mode, &np, ret_part, ret_pstart, ret_psize, ret_uuid, ret_devid); |
e5a8b4b5 LP |
132 | if (r == -ENOKEY) { |
133 | if (graceful) | |
14e6e444 ZJS |
134 | return log_full_errno(arg_quiet ? LOG_DEBUG : LOG_INFO, r, |
135 | "Couldn't find EFI system partition, skipping."); | |
e5a8b4b5 | 136 | |
af918182 | 137 | return log_error_errno(r, |
5caa3167 | 138 | "Couldn't find EFI system partition. It is recommended to mount it to /boot or /efi.\n" |
fbf45d22 | 139 | "Alternatively, use --esp-path= to specify path to mount point."); |
e5a8b4b5 | 140 | } |
5caa3167 LP |
141 | if (r < 0) |
142 | return r; | |
143 | ||
fbf45d22 LP |
144 | free_and_replace(arg_esp_path, np); |
145 | log_debug("Using EFI System Partition at %s.", arg_esp_path); | |
146 | ||
957b2423 | 147 | return 0; |
fbf45d22 | 148 | } |
0974a682 | 149 | |
f63b5ad9 LP |
150 | static int acquire_xbootldr( |
151 | bool unprivileged_mode, | |
152 | sd_id128_t *ret_uuid, | |
153 | dev_t *ret_devid) { | |
154 | ||
fbf45d22 LP |
155 | char *np; |
156 | int r; | |
5caa3167 | 157 | |
80a2381d | 158 | r = find_xbootldr_and_warn(arg_root, arg_xbootldr_path, unprivileged_mode, &np, ret_uuid, ret_devid); |
fbf45d22 LP |
159 | if (r == -ENOKEY) { |
160 | log_debug_errno(r, "Didn't find an XBOOTLDR partition, using the ESP as $BOOT."); | |
f63b5ad9 LP |
161 | arg_xbootldr_path = mfree(arg_xbootldr_path); |
162 | ||
fbf45d22 LP |
163 | if (ret_uuid) |
164 | *ret_uuid = SD_ID128_NULL; | |
f63b5ad9 LP |
165 | if (ret_devid) |
166 | *ret_devid = 0; | |
fbf45d22 LP |
167 | return 0; |
168 | } | |
169 | if (r < 0) | |
170 | return r; | |
171 | ||
172 | free_and_replace(arg_xbootldr_path, np); | |
173 | log_debug("Using XBOOTLDR partition at %s as $BOOT.", arg_xbootldr_path); | |
174 | ||
175 | return 1; | |
7b4d7cc0 KS |
176 | } |
177 | ||
b72676e7 LP |
178 | static int load_etc_machine_id(void) { |
179 | int r; | |
180 | ||
181 | r = sd_id128_get_machine(&arg_machine_id); | |
182 | if (IN_SET(r, -ENOENT, -ENOMEDIUM)) /* Not set or empty */ | |
183 | return 0; | |
184 | if (r < 0) | |
185 | return log_error_errno(r, "Failed to get machine-id: %m"); | |
186 | ||
187 | log_debug("Loaded machine ID %s from /etc/machine-id.", SD_ID128_TO_STRING(arg_machine_id)); | |
188 | return 0; | |
189 | } | |
190 | ||
191 | static int load_etc_machine_info(void) { | |
ea29abec LP |
192 | /* systemd v250 added support to store the kernel-install layout setting and the machine ID to use |
193 | * for setting up the ESP in /etc/machine-info. The newer /etc/kernel/entry-token file, as well as | |
194 | * the $layout field in /etc/kernel/install.conf are better replacements for this though, hence this | |
195 | * has been deprecated and is only returned for compatibility. */ | |
47fb161e | 196 | _cleanup_free_ char *s = NULL, *layout = NULL; |
6a3fff75 | 197 | int r; |
198 | ||
47fb161e ZJS |
199 | r = parse_env_file(NULL, "/etc/machine-info", |
200 | "KERNEL_INSTALL_LAYOUT", &layout, | |
201 | "KERNEL_INSTALL_MACHINE_ID", &s); | |
b72676e7 LP |
202 | if (r == -ENOENT) |
203 | return 0; | |
204 | if (r < 0) | |
47fb161e ZJS |
205 | return log_error_errno(r, "Failed to parse /etc/machine-info: %m"); |
206 | ||
b72676e7 | 207 | if (!isempty(s)) { |
14e6e444 ZJS |
208 | if (!arg_quiet) |
209 | log_notice("Read $KERNEL_INSTALL_MACHINE_ID from /etc/machine-info. " | |
210 | "Please move it to /etc/kernel/entry-token."); | |
ea29abec | 211 | |
47fb161e ZJS |
212 | r = sd_id128_from_string(s, &arg_machine_id); |
213 | if (r < 0) | |
214 | return log_error_errno(r, "Failed to parse KERNEL_INSTALL_MACHINE_ID=%s in /etc/machine-info: %m", s); | |
215 | ||
b72676e7 LP |
216 | log_debug("Loaded KERNEL_INSTALL_MACHINE_ID=%s from KERNEL_INSTALL_MACHINE_ID in /etc/machine-info.", |
217 | SD_ID128_TO_STRING(arg_machine_id)); | |
47fb161e | 218 | } |
47fb161e ZJS |
219 | |
220 | if (!isempty(layout)) { | |
14e6e444 ZJS |
221 | if (!arg_quiet) |
222 | log_notice("Read $KERNEL_INSTALL_LAYOUT from /etc/machine-info. " | |
223 | "Please move it to the layout= setting of /etc/kernel/install.conf."); | |
ea29abec | 224 | |
47fb161e | 225 | log_debug("KERNEL_INSTALL_LAYOUT=%s is specified in /etc/machine-info.", layout); |
b72676e7 LP |
226 | free_and_replace(arg_install_layout, layout); |
227 | } | |
228 | ||
229 | return 0; | |
230 | } | |
231 | ||
232 | static int load_etc_kernel_install_conf(void) { | |
233 | _cleanup_free_ char *layout = NULL; | |
234 | int r; | |
235 | ||
236 | r = parse_env_file(NULL, "/etc/kernel/install.conf", | |
237 | "layout", &layout); | |
238 | if (r == -ENOENT) | |
239 | return 0; | |
240 | if (r < 0) | |
241 | return log_error_errno(r, "Failed to parse /etc/kernel/install.conf: %m"); | |
242 | ||
243 | if (!isempty(layout)) { | |
244 | log_debug("layout=%s is specified in /etc/machine-info.", layout); | |
245 | free_and_replace(arg_install_layout, layout); | |
47fb161e | 246 | } |
6a3fff75 | 247 | |
47fb161e ZJS |
248 | return 0; |
249 | } | |
250 | ||
f337f903 | 251 | static int settle_entry_token(void) { |
47fb161e ZJS |
252 | int r; |
253 | ||
f337f903 | 254 | switch (arg_entry_token_type) { |
6a3fff75 | 255 | |
f337f903 LP |
256 | case ARG_ENTRY_TOKEN_AUTO: { |
257 | _cleanup_free_ char *buf = NULL; | |
258 | r = read_one_line_file("/etc/kernel/entry-token", &buf); | |
259 | if (r < 0 && r != -ENOENT) | |
260 | return log_error_errno(r, "Failed to read /etc/kernel/entry-token: %m"); | |
261 | ||
262 | if (!isempty(buf)) { | |
263 | free_and_replace(arg_entry_token, buf); | |
264 | arg_entry_token_type = ARG_ENTRY_TOKEN_LITERAL; | |
265 | } else if (sd_id128_is_null(arg_machine_id)) { | |
266 | _cleanup_free_ char *id = NULL, *image_id = NULL; | |
267 | ||
268 | r = parse_os_release(NULL, | |
269 | "IMAGE_ID", &image_id, | |
270 | "ID", &id); | |
271 | if (r < 0) | |
272 | return log_error_errno(r, "Failed to load /etc/os-release: %m"); | |
273 | ||
274 | if (!isempty(image_id)) { | |
275 | free_and_replace(arg_entry_token, image_id); | |
276 | arg_entry_token_type = ARG_ENTRY_TOKEN_OS_IMAGE_ID; | |
277 | } else if (!isempty(id)) { | |
278 | free_and_replace(arg_entry_token, id); | |
279 | arg_entry_token_type = ARG_ENTRY_TOKEN_OS_ID; | |
280 | } else | |
281 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No machine ID set, and /etc/os-release carries no ID=/IMAGE_ID= fields."); | |
282 | } else { | |
283 | r = free_and_strdup_warn(&arg_entry_token, SD_ID128_TO_STRING(arg_machine_id)); | |
47fb161e | 284 | if (r < 0) |
f337f903 LP |
285 | return r; |
286 | ||
287 | arg_entry_token_type = ARG_ENTRY_TOKEN_MACHINE_ID; | |
47fb161e | 288 | } |
f337f903 LP |
289 | |
290 | break; | |
47fb161e ZJS |
291 | } |
292 | ||
f337f903 LP |
293 | case ARG_ENTRY_TOKEN_MACHINE_ID: |
294 | if (sd_id128_is_null(arg_machine_id)) | |
295 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No machine ID set."); | |
296 | ||
297 | r = free_and_strdup_warn(&arg_entry_token, SD_ID128_TO_STRING(arg_machine_id)); | |
298 | if (r < 0) | |
299 | return r; | |
300 | ||
301 | break; | |
302 | ||
303 | case ARG_ENTRY_TOKEN_OS_IMAGE_ID: { | |
304 | _cleanup_free_ char *buf = NULL; | |
305 | ||
306 | r = parse_os_release(NULL, "IMAGE_ID", &buf); | |
307 | if (r < 0) | |
308 | return log_error_errno(r, "Failed to load /etc/os-release: %m"); | |
309 | ||
310 | if (isempty(buf)) | |
311 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "IMAGE_ID= field not set in /etc/os-release."); | |
17e2e807 | 312 | |
f337f903 LP |
313 | free_and_replace(arg_entry_token, buf); |
314 | break; | |
315 | } | |
316 | ||
317 | case ARG_ENTRY_TOKEN_OS_ID: { | |
318 | _cleanup_free_ char *buf = NULL; | |
319 | ||
320 | r = parse_os_release(NULL, "ID", &buf); | |
321 | if (r < 0) | |
322 | return log_error_errno(r, "Failed to load /etc/os-release: %m"); | |
323 | ||
324 | if (isempty(buf)) | |
325 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "ID= field not set in /etc/os-release."); | |
326 | ||
327 | free_and_replace(arg_entry_token, buf); | |
328 | break; | |
329 | } | |
330 | ||
331 | case ARG_ENTRY_TOKEN_LITERAL: | |
332 | assert(!isempty(arg_entry_token)); /* already filled in by command line parser */ | |
333 | break; | |
334 | } | |
335 | ||
d9bdb29b | 336 | if (isempty(arg_entry_token) || !(utf8_is_valid(arg_entry_token) && string_is_safe(arg_entry_token))) |
f337f903 LP |
337 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected entry token not valid: %s", arg_entry_token); |
338 | ||
339 | log_debug("Using entry token: %s", arg_entry_token); | |
340 | return 0; | |
341 | } | |
342 | ||
343 | static bool use_boot_loader_spec_type1(void) { | |
344 | /* If the layout is not specified, or if it is set explicitly to "bls" we assume Boot Loader | |
345 | * Specification Type #1 is the chosen format for our boot loader entries */ | |
346 | return !arg_install_layout || streq(arg_install_layout, "bls"); | |
347 | } | |
348 | ||
349 | static int settle_make_entry_directory(void) { | |
350 | int r; | |
351 | ||
b72676e7 LP |
352 | r = load_etc_machine_id(); |
353 | if (r < 0) | |
354 | return r; | |
355 | ||
356 | r = load_etc_machine_info(); | |
357 | if (r < 0) | |
358 | return r; | |
359 | ||
360 | r = load_etc_kernel_install_conf(); | |
f337f903 LP |
361 | if (r < 0) |
362 | return r; | |
363 | ||
364 | r = settle_entry_token(); | |
365 | if (r < 0) | |
366 | return r; | |
367 | ||
368 | bool layout_type1 = use_boot_loader_spec_type1(); | |
369 | if (arg_make_entry_directory < 0) { /* Automatic mode */ | |
370 | if (layout_type1) { | |
371 | if (arg_entry_token == ARG_ENTRY_TOKEN_MACHINE_ID) { | |
372 | r = path_is_temporary_fs("/etc/machine-id"); | |
373 | if (r < 0) | |
374 | return log_debug_errno(r, "Couldn't determine whether /etc/machine-id is on a temporary file system: %m"); | |
375 | ||
376 | arg_make_entry_directory = r == 0; | |
377 | } else | |
378 | arg_make_entry_directory = true; | |
379 | } else | |
380 | arg_make_entry_directory = false; | |
381 | } | |
382 | ||
383 | if (arg_make_entry_directory > 0 && !layout_type1) | |
47fb161e | 384 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), |
f337f903 | 385 | "KERNEL_INSTALL_LAYOUT=%s is configured, but Boot Loader Specification Type #1 entry directory creation was requested.", |
47fb161e ZJS |
386 | arg_install_layout); |
387 | ||
388 | return 0; | |
6a3fff75 | 389 | } |
390 | ||
e7dd673d | 391 | /* search for "#### LoaderInfo: systemd-boot 218 ####" string inside the binary */ |
d3226d77 | 392 | static int get_file_version(int fd, char **v) { |
0974a682 KS |
393 | struct stat st; |
394 | char *buf; | |
395 | const char *s, *e; | |
396 | char *x = NULL; | |
c53aafb7 | 397 | int r; |
7b4d7cc0 | 398 | |
d3226d77 | 399 | assert(fd >= 0); |
0974a682 | 400 | assert(v); |
7b4d7cc0 | 401 | |
d3226d77 | 402 | if (fstat(fd, &st) < 0) |
db6d9fae | 403 | return log_error_errno(errno, "Failed to stat EFI binary: %m"); |
7b4d7cc0 | 404 | |
c4ba5b51 LP |
405 | r = stat_verify_regular(&st); |
406 | if (r < 0) | |
d90f2add | 407 | return log_error_errno(r, "EFI binary is not a regular file: %m"); |
c4ba5b51 | 408 | |
1a823cde | 409 | if (st.st_size < 27 || file_offset_beyond_memory_size(st.st_size)) { |
db6d9fae | 410 | *v = NULL; |
0974a682 | 411 | return 0; |
db6d9fae | 412 | } |
eb9da376 | 413 | |
d3226d77 | 414 | buf = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); |
0974a682 | 415 | if (buf == MAP_FAILED) |
db6d9fae | 416 | return log_error_errno(errno, "Failed to memory map EFI binary: %m"); |
7b4d7cc0 | 417 | |
d8782cc5 | 418 | s = mempmem_safe(buf, st.st_size - 8, "#### LoaderInfo: ", 17); |
0974a682 KS |
419 | if (!s) |
420 | goto finish; | |
7b4d7cc0 | 421 | |
e8b08edc | 422 | e = memmem_safe(s, st.st_size - (s - buf), " ####", 5); |
0974a682 | 423 | if (!e || e - s < 3) { |
78d5d4ed | 424 | r = log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Malformed version string."); |
0974a682 KS |
425 | goto finish; |
426 | } | |
7b4d7cc0 | 427 | |
0974a682 KS |
428 | x = strndup(s, e - s); |
429 | if (!x) { | |
d3226d77 | 430 | r = log_oom(); |
0974a682 KS |
431 | goto finish; |
432 | } | |
433 | r = 1; | |
7b4d7cc0 | 434 | |
0974a682 | 435 | finish: |
db6d9fae | 436 | (void) munmap(buf, st.st_size); |
0974a682 KS |
437 | *v = x; |
438 | return r; | |
439 | } | |
7b4d7cc0 | 440 | |
6e916539 JJ |
441 | static const char *get_efi_arch(void) { |
442 | /* Detect EFI firmware architecture of the running system. On mixed mode systems, it could be 32bit | |
443 | * while the kernel is running in 64bit. */ | |
444 | ||
445 | #ifdef __x86_64__ | |
446 | _cleanup_free_ char *platform_size = NULL; | |
447 | int r; | |
448 | ||
449 | r = read_one_line_file("/sys/firmware/efi/fw_platform_size", &platform_size); | |
450 | if (r == -ENOENT) | |
451 | return EFI_MACHINE_TYPE_NAME; | |
452 | if (r < 0) { | |
c0f86d66 | 453 | log_warning_errno(r, "Error reading EFI firmware word size, assuming '%i': %m", __WORDSIZE); |
6e916539 JJ |
454 | return EFI_MACHINE_TYPE_NAME; |
455 | } | |
456 | ||
457 | if (streq(platform_size, "64")) | |
458 | return EFI_MACHINE_TYPE_NAME; | |
459 | if (streq(platform_size, "32")) | |
460 | return "ia32"; | |
461 | ||
462 | log_warning( | |
c0f86d66 | 463 | "Unknown EFI firmware word size '%s', using default word size '%i' instead.", |
6e916539 JJ |
464 | platform_size, |
465 | __WORDSIZE); | |
466 | #endif | |
467 | ||
468 | return EFI_MACHINE_TYPE_NAME; | |
469 | } | |
470 | ||
84be9b63 LP |
471 | static int enumerate_binaries( |
472 | const char *esp_path, | |
473 | const char *path, | |
474 | const char *prefix, | |
475 | char **previous, | |
476 | bool *is_first) { | |
477 | ||
d3226d77 | 478 | _cleanup_closedir_ DIR *d = NULL; |
3730dc5d | 479 | _cleanup_free_ char *p = NULL; |
fbf45d22 | 480 | int c = 0, r; |
fbf45d22 LP |
481 | |
482 | assert(esp_path); | |
483 | assert(path); | |
84be9b63 LP |
484 | assert(previous); |
485 | assert(is_first); | |
0974a682 | 486 | |
b353d5ee | 487 | r = chase_symlinks_and_opendir(path, esp_path, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &p, &d); |
3730dc5d LP |
488 | if (r == -ENOENT) |
489 | return 0; | |
490 | if (r < 0) | |
491 | return log_error_errno(r, "Failed to read \"%s/%s\": %m", esp_path, path); | |
0974a682 | 492 | |
e41256dc | 493 | FOREACH_DIRENT(de, d, break) { |
d3226d77 | 494 | _cleanup_free_ char *v = NULL; |
fbf45d22 | 495 | _cleanup_close_ int fd = -1; |
0974a682 | 496 | |
d3226d77 | 497 | if (!endswith_no_case(de->d_name, ".efi")) |
0974a682 KS |
498 | continue; |
499 | ||
d3226d77 | 500 | if (prefix && !startswith_no_case(de->d_name, prefix)) |
0974a682 KS |
501 | continue; |
502 | ||
d3226d77 ZJS |
503 | fd = openat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC); |
504 | if (fd < 0) | |
505 | return log_error_errno(errno, "Failed to open \"%s/%s\" for reading: %m", p, de->d_name); | |
0974a682 | 506 | |
d3226d77 | 507 | r = get_file_version(fd, &v); |
0974a682 | 508 | if (r < 0) |
d3226d77 | 509 | return r; |
84be9b63 LP |
510 | |
511 | if (*previous) { /* let's output the previous entry now, since now we know that there will be one more, and can draw the tree glyph properly */ | |
512 | printf(" %s %s%s\n", | |
513 | *is_first ? "File:" : " ", | |
514 | special_glyph(SPECIAL_GLYPH_TREE_BRANCH), *previous); | |
515 | *is_first = false; | |
516 | *previous = mfree(*previous); | |
517 | } | |
518 | ||
519 | /* Do not output this entry immediately, but store what should be printed in a state | |
520 | * variable, because we only will know the tree glyph to print (branch or final edge) once we | |
521 | * read one more entry */ | |
0974a682 | 522 | if (r > 0) |
84be9b63 | 523 | r = asprintf(previous, "/%s/%s (%s%s%s)", path, de->d_name, ansi_highlight(), v, ansi_normal()); |
0974a682 | 524 | else |
84be9b63 LP |
525 | r = asprintf(previous, "/%s/%s", path, de->d_name); |
526 | if (r < 0) | |
527 | return log_oom(); | |
fbf45d22 | 528 | |
0974a682 | 529 | c++; |
0974a682 KS |
530 | } |
531 | ||
d3226d77 | 532 | return c; |
7b4d7cc0 KS |
533 | } |
534 | ||
0974a682 | 535 | static int status_binaries(const char *esp_path, sd_id128_t partition) { |
84be9b63 LP |
536 | _cleanup_free_ char *last = NULL; |
537 | bool is_first = true; | |
538 | int r, k; | |
0974a682 | 539 | |
cb180369 | 540 | printf("%sAvailable Boot Loaders on ESP:%s\n", ansi_underline(), ansi_normal()); |
0974a682 | 541 | |
cd2d4c7f YW |
542 | if (!esp_path) { |
543 | printf(" ESP: Cannot find or access mount point of ESP.\n\n"); | |
544 | return -ENOENT; | |
545 | } | |
546 | ||
547 | printf(" ESP: %s", esp_path); | |
548 | if (!sd_id128_is_null(partition)) | |
bd44566c | 549 | printf(" (/dev/disk/by-partuuid/" SD_ID128_UUID_FORMAT_STR ")", SD_ID128_FORMAT_VAL(partition)); |
cd2d4c7f | 550 | printf("\n"); |
0974a682 | 551 | |
84be9b63 LP |
552 | r = enumerate_binaries(esp_path, "EFI/systemd", NULL, &last, &is_first); |
553 | if (r < 0) { | |
554 | printf("\n"); | |
555 | return r; | |
556 | } | |
557 | ||
558 | k = enumerate_binaries(esp_path, "EFI/BOOT", "boot", &last, &is_first); | |
559 | if (k < 0) { | |
560 | printf("\n"); | |
561 | return k; | |
562 | } | |
563 | ||
564 | if (last) /* let's output the last entry now, since now we know that there will be no more, and can draw the tree glyph properly */ | |
565 | printf(" %s %s%s\n", | |
566 | is_first ? "File:" : " ", | |
567 | special_glyph(SPECIAL_GLYPH_TREE_RIGHT), last); | |
0974a682 | 568 | |
14e6e444 | 569 | if (r == 0 && !arg_quiet) |
84be9b63 LP |
570 | log_info("systemd-boot not installed in ESP."); |
571 | if (k == 0 && !arg_quiet) | |
48184e43 | 572 | log_info("No default/fallback boot loader installed in ESP."); |
0974a682 | 573 | |
882b3bd6 | 574 | printf("\n"); |
84be9b63 | 575 | return 0; |
0974a682 KS |
576 | } |
577 | ||
51470e1e | 578 | static int print_efi_option(uint16_t id, int *n_printed, bool in_order) { |
7cb0f263 TA |
579 | _cleanup_free_ char *title = NULL; |
580 | _cleanup_free_ char *path = NULL; | |
0974a682 KS |
581 | sd_id128_t partition; |
582 | bool active; | |
c53aafb7 | 583 | int r; |
0974a682 | 584 | |
51470e1e LP |
585 | assert(n_printed); |
586 | ||
0974a682 | 587 | r = efi_get_boot_option(id, &title, &partition, &path, &active); |
af1bed8e LP |
588 | if (r == -ENOENT) { |
589 | log_debug_errno(r, "Boot option 0x%04X referenced but missing, ignoring: %m", id); | |
590 | return 0; | |
591 | } | |
0974a682 | 592 | if (r < 0) |
af1bed8e | 593 | return log_error_errno(r, "Failed to read boot option 0x%04X: %m", id); |
7b4d7cc0 | 594 | |
0974a682 | 595 | /* print only configured entries with partition information */ |
51470e1e | 596 | if (!path || sd_id128_is_null(partition)) { |
a7dcb75c | 597 | log_debug("Ignoring boot entry 0x%04X without partition information.", id); |
0974a682 | 598 | return 0; |
51470e1e | 599 | } |
0974a682 KS |
600 | |
601 | efi_tilt_backslashes(path); | |
602 | ||
51470e1e LP |
603 | if (*n_printed == 0) /* Print section title before first entry */ |
604 | printf("%sBoot Loaders Listed in EFI Variables:%s\n", ansi_underline(), ansi_normal()); | |
605 | ||
ba857253 | 606 | printf(" Title: %s%s%s\n", ansi_highlight(), strna(title), ansi_normal()); |
0974a682 KS |
607 | printf(" ID: 0x%04X\n", id); |
608 | printf(" Status: %sactive%s\n", active ? "" : "in", in_order ? ", boot-order" : ""); | |
bd44566c ZJS |
609 | printf(" Partition: /dev/disk/by-partuuid/" SD_ID128_UUID_FORMAT_STR "\n", |
610 | SD_ID128_FORMAT_VAL(partition)); | |
9a6f746f | 611 | printf(" File: %s%s\n", special_glyph(SPECIAL_GLYPH_TREE_RIGHT), path); |
0974a682 KS |
612 | printf("\n"); |
613 | ||
51470e1e LP |
614 | (*n_printed)++; |
615 | return 1; | |
0974a682 KS |
616 | } |
617 | ||
618 | static int status_variables(void) { | |
d3226d77 | 619 | _cleanup_free_ uint16_t *options = NULL, *order = NULL; |
51470e1e | 620 | int n_options, n_order, n_printed = 0; |
0974a682 | 621 | |
0974a682 | 622 | n_options = efi_get_boot_options(&options); |
d3226d77 | 623 | if (n_options == -ENOENT) |
f939cff7 LP |
624 | return log_error_errno(n_options, |
625 | "Failed to access EFI variables, efivarfs" | |
d3226d77 | 626 | " needs to be available at /sys/firmware/efi/efivars/."); |
f939cff7 | 627 | if (n_options < 0) |
d3226d77 | 628 | return log_error_errno(n_options, "Failed to read EFI boot entries: %m"); |
0974a682 | 629 | |
0974a682 | 630 | n_order = efi_get_boot_order(&order); |
d3226d77 | 631 | if (n_order == -ENOENT) |
0974a682 | 632 | n_order = 0; |
d3226d77 | 633 | else if (n_order < 0) |
7709ef3a | 634 | return log_error_errno(n_order, "Failed to read EFI boot order: %m"); |
0974a682 KS |
635 | |
636 | /* print entries in BootOrder first */ | |
c67bd42b | 637 | for (int i = 0; i < n_order; i++) |
51470e1e | 638 | (void) print_efi_option(order[i], &n_printed, /* in_order= */ true); |
0974a682 KS |
639 | |
640 | /* print remaining entries */ | |
c67bd42b ZJS |
641 | for (int i = 0; i < n_options; i++) { |
642 | for (int j = 0; j < n_order; j++) | |
d3226d77 | 643 | if (options[i] == order[j]) |
a908cf0a | 644 | goto next_option; |
0974a682 | 645 | |
51470e1e | 646 | (void) print_efi_option(options[i], &n_printed, /* in_order= */ false); |
a908cf0a MM |
647 | |
648 | next_option: | |
649 | continue; | |
0974a682 KS |
650 | } |
651 | ||
51470e1e LP |
652 | if (n_printed == 0) |
653 | printf("No boot loaders listed in EFI Variables.\n\n"); | |
654 | ||
d3226d77 | 655 | return 0; |
0974a682 KS |
656 | } |
657 | ||
8214758b ZJS |
658 | static int boot_config_load_and_select( |
659 | BootConfig *config, | |
660 | const char *esp_path, | |
661 | dev_t esp_devid, | |
662 | const char *xbootldr_path, | |
663 | dev_t xbootldr_devid) { | |
664 | ||
8214758b ZJS |
665 | int r; |
666 | ||
667 | /* If XBOOTLDR and ESP actually refer to the same block device, suppress XBOOTLDR, since it would | |
668 | * find the same entries twice. */ | |
7176f06c | 669 | bool same = esp_path && xbootldr_path && devnum_set_and_equal(esp_devid, xbootldr_devid); |
8214758b ZJS |
670 | |
671 | r = boot_config_load(config, esp_path, same ? NULL : xbootldr_path); | |
672 | if (r < 0) | |
673 | return r; | |
674 | ||
80a2381d LB |
675 | if (!arg_root) { |
676 | _cleanup_strv_free_ char **efi_entries = NULL; | |
8214758b | 677 | |
80a2381d LB |
678 | r = efi_loader_get_entries(&efi_entries); |
679 | if (r == -ENOENT || ERRNO_IS_NOT_SUPPORTED(r)) | |
680 | log_debug_errno(r, "Boot loader reported no entries."); | |
681 | else if (r < 0) | |
682 | log_warning_errno(r, "Failed to determine entries reported by boot loader, ignoring: %m"); | |
683 | else | |
684 | (void) boot_config_augment_from_loader(config, efi_entries, /* only_auto= */ false); | |
685 | } | |
686 | ||
687 | return boot_config_select_special_entries(config, /* skip_efivars= */ !!arg_root); | |
8214758b ZJS |
688 | } |
689 | ||
81fed855 | 690 | static int status_entries( |
8214758b | 691 | const BootConfig *config, |
81fed855 LP |
692 | const char *esp_path, |
693 | sd_id128_t esp_partition_uuid, | |
694 | const char *xbootldr_path, | |
695 | sd_id128_t xbootldr_partition_uuid) { | |
696 | ||
81fed855 LP |
697 | sd_id128_t dollar_boot_partition_uuid; |
698 | const char *dollar_boot_path; | |
a099e035 | 699 | int r; |
7e87c7d9 | 700 | |
8214758b | 701 | assert(config); |
81fed855 LP |
702 | assert(esp_path || xbootldr_path); |
703 | ||
704 | if (xbootldr_path) { | |
705 | dollar_boot_path = xbootldr_path; | |
706 | dollar_boot_partition_uuid = xbootldr_partition_uuid; | |
707 | } else { | |
708 | dollar_boot_path = esp_path; | |
709 | dollar_boot_partition_uuid = esp_partition_uuid; | |
710 | } | |
711 | ||
cb180369 LP |
712 | printf("%sBoot Loader Entries:%s\n" |
713 | " $BOOT: %s", ansi_underline(), ansi_normal(), dollar_boot_path); | |
81fed855 | 714 | if (!sd_id128_is_null(dollar_boot_partition_uuid)) |
bd44566c ZJS |
715 | printf(" (/dev/disk/by-partuuid/" SD_ID128_UUID_FORMAT_STR ")", |
716 | SD_ID128_FORMAT_VAL(dollar_boot_partition_uuid)); | |
81fed855 LP |
717 | printf("\n\n"); |
718 | ||
8214758b ZJS |
719 | if (config->default_entry < 0) |
720 | printf("%zu entries, no entry could be determined as default.\n", config->n_entries); | |
7e87c7d9 | 721 | else { |
cb180369 | 722 | printf("%sDefault Boot Loader Entry:%s\n", ansi_underline(), ansi_normal()); |
a099e035 | 723 | |
432ce537 | 724 | r = show_boot_entry( |
8214758b | 725 | boot_config_default_entry(config), |
bb682057 LP |
726 | /* show_as_default= */ false, |
727 | /* show_as_selected= */ false, | |
728 | /* show_discovered= */ false); | |
d3eb6072 ZJS |
729 | if (r > 0) |
730 | /* < 0 is already logged by the function itself, let's just emit an extra warning if | |
731 | the default entry is broken */ | |
732 | printf("\nWARNING: default boot entry is broken\n"); | |
7e87c7d9 ZJS |
733 | } |
734 | ||
735 | return 0; | |
736 | } | |
737 | ||
0974a682 KS |
738 | static int compare_product(const char *a, const char *b) { |
739 | size_t x, y; | |
740 | ||
741 | assert(a); | |
742 | assert(b); | |
743 | ||
744 | x = strcspn(a, " "); | |
745 | y = strcspn(b, " "); | |
746 | if (x != y) | |
747 | return x < y ? -1 : x > y ? 1 : 0; | |
748 | ||
749 | return strncmp(a, b, x); | |
750 | } | |
751 | ||
752 | static int compare_version(const char *a, const char *b) { | |
753 | assert(a); | |
754 | assert(b); | |
755 | ||
756 | a += strcspn(a, " "); | |
757 | a += strspn(a, " "); | |
758 | b += strcspn(b, " "); | |
759 | b += strspn(b, " "); | |
760 | ||
8087644a | 761 | return strverscmp_improved(a, b); |
0974a682 KS |
762 | } |
763 | ||
175d308c | 764 | static int version_check(int fd_from, const char *from, int fd_to, const char *to) { |
d3226d77 | 765 | _cleanup_free_ char *a = NULL, *b = NULL; |
0974a682 KS |
766 | int r; |
767 | ||
175d308c | 768 | assert(fd_from >= 0); |
0974a682 | 769 | assert(from); |
175d308c | 770 | assert(fd_to >= 0); |
0974a682 KS |
771 | assert(to); |
772 | ||
175d308c | 773 | r = get_file_version(fd_from, &a); |
0974a682 | 774 | if (r < 0) |
d3226d77 | 775 | return r; |
baaa35ad | 776 | if (r == 0) |
e5a8b4b5 | 777 | return log_notice_errno(SYNTHETIC_ERRNO(EREMOTE), |
baaa35ad ZJS |
778 | "Source file \"%s\" does not carry version information!", |
779 | from); | |
0974a682 | 780 | |
175d308c | 781 | r = get_file_version(fd_to, &b); |
0974a682 | 782 | if (r < 0) |
d3226d77 | 783 | return r; |
baaa35ad | 784 | if (r == 0 || compare_product(a, b) != 0) |
e5a8b4b5 | 785 | return log_notice_errno(SYNTHETIC_ERRNO(EREMOTE), |
baaa35ad ZJS |
786 | "Skipping \"%s\", since it's owned by another boot loader.", |
787 | to); | |
0974a682 | 788 | |
e5a8b4b5 | 789 | r = compare_version(a, b); |
892fcb89 | 790 | log_debug("Comparing versions: \"%s\" %s \"%s", a, comparison_operator(r), b); |
e5a8b4b5 | 791 | if (r < 0) |
892fcb89 ZJS |
792 | return log_warning_errno(SYNTHETIC_ERRNO(ESTALE), |
793 | "Skipping \"%s\", since newer boot loader version in place already.", to); | |
794 | if (r == 0) | |
795 | return log_info_errno(SYNTHETIC_ERRNO(ESTALE), | |
796 | "Skipping \"%s\", since same boot loader version in place already.", to); | |
0974a682 | 797 | |
d3226d77 | 798 | return 0; |
0974a682 KS |
799 | } |
800 | ||
175d308c LP |
801 | static int copy_file_with_version_check(const char *from, const char *to, bool force) { |
802 | _cleanup_close_ int fd_from = -1, fd_to = -1; | |
803 | _cleanup_free_ char *t = NULL; | |
0974a682 | 804 | int r; |
0974a682 | 805 | |
175d308c LP |
806 | fd_from = open(from, O_RDONLY|O_CLOEXEC|O_NOCTTY); |
807 | if (fd_from < 0) | |
d3226d77 | 808 | return log_error_errno(errno, "Failed to open \"%s\" for reading: %m", from); |
0974a682 KS |
809 | |
810 | if (!force) { | |
175d308c LP |
811 | fd_to = open(to, O_RDONLY|O_CLOEXEC|O_NOCTTY); |
812 | if (fd_to < 0) { | |
6b8664cb | 813 | if (errno != ENOENT) |
175d308c LP |
814 | return log_error_errno(errno, "Failed to open \"%s\" for reading: %m", to); |
815 | } else { | |
816 | r = version_check(fd_from, from, fd_to, to); | |
817 | if (r < 0) | |
818 | return r; | |
819 | ||
820 | if (lseek(fd_from, 0, SEEK_SET) == (off_t) -1) | |
6719ca72 | 821 | return log_error_errno(errno, "Failed to seek in \"%s\": %m", from); |
175d308c LP |
822 | |
823 | fd_to = safe_close(fd_to); | |
0974a682 | 824 | } |
175d308c | 825 | } |
d3226d77 | 826 | |
175d308c LP |
827 | r = tempfn_random(to, NULL, &t); |
828 | if (r < 0) | |
829 | return log_oom(); | |
0974a682 | 830 | |
175d308c LP |
831 | RUN_WITH_UMASK(0000) { |
832 | fd_to = open(t, O_WRONLY|O_CREAT|O_CLOEXEC|O_EXCL|O_NOFOLLOW, 0644); | |
833 | if (fd_to < 0) | |
834 | return log_error_errno(errno, "Failed to open \"%s\" for writing: %m", t); | |
0974a682 KS |
835 | } |
836 | ||
f5fbe71d | 837 | r = copy_bytes(fd_from, fd_to, UINT64_MAX, COPY_REFLINK); |
0974a682 | 838 | if (r < 0) { |
0675e94a AJ |
839 | (void) unlink(t); |
840 | return log_error_errno(r, "Failed to copy data from \"%s\" to \"%s\": %m", from, t); | |
0974a682 KS |
841 | } |
842 | ||
adc6f43b | 843 | (void) copy_times(fd_from, fd_to, 0); |
0974a682 | 844 | |
bf819d3a LP |
845 | r = fsync_full(fd_to); |
846 | if (r < 0) { | |
0675e94a | 847 | (void) unlink_noerrno(t); |
bf819d3a | 848 | return log_error_errno(r, "Failed to copy data from \"%s\" to \"%s\": %m", from, t); |
0675e94a AJ |
849 | } |
850 | ||
b1c05b98 | 851 | if (renameat(AT_FDCWD, t, AT_FDCWD, to) < 0) { |
175d308c LP |
852 | (void) unlink_noerrno(t); |
853 | return log_error_errno(errno, "Failed to rename \"%s\" to \"%s\": %m", t, to); | |
0974a682 KS |
854 | } |
855 | ||
d3226d77 | 856 | log_info("Copied \"%s\" to \"%s\".", from, to); |
0974a682 | 857 | |
175d308c | 858 | return 0; |
0974a682 KS |
859 | } |
860 | ||
0974a682 | 861 | static int mkdir_one(const char *prefix, const char *suffix) { |
e2600fd5 | 862 | _cleanup_free_ char *p = NULL; |
0974a682 | 863 | |
e2600fd5 | 864 | p = path_join(prefix, suffix); |
0974a682 | 865 | if (mkdir(p, 0700) < 0) { |
d3226d77 ZJS |
866 | if (errno != EEXIST) |
867 | return log_error_errno(errno, "Failed to create \"%s\": %m", p); | |
0974a682 | 868 | } else |
d3226d77 | 869 | log_info("Created \"%s\".", p); |
7b4d7cc0 | 870 | |
0974a682 KS |
871 | return 0; |
872 | } | |
873 | ||
fbf45d22 | 874 | static const char *const esp_subdirs[] = { |
e44c3229 | 875 | /* The directories to place in the ESP */ |
d3226d77 ZJS |
876 | "EFI", |
877 | "EFI/systemd", | |
00f69504 | 878 | "EFI/BOOT", |
d3226d77 | 879 | "loader", |
9ee051b9 | 880 | NULL |
d3226d77 ZJS |
881 | }; |
882 | ||
e44c3229 LP |
883 | static const char *const dollar_boot_subdirs[] = { |
884 | /* The directories to place in the XBOOTLDR partition or the ESP, depending what exists */ | |
885 | "loader", | |
886 | "loader/entries", /* Type #1 entries */ | |
887 | "EFI", | |
888 | "EFI/Linux", /* Type #2 entries */ | |
889 | NULL | |
890 | }; | |
891 | ||
892 | static int create_subdirs(const char *root, const char * const *subdirs) { | |
0974a682 KS |
893 | int r; |
894 | ||
e44c3229 LP |
895 | STRV_FOREACH(i, subdirs) { |
896 | r = mkdir_one(root, *i); | |
d3226d77 ZJS |
897 | if (r < 0) |
898 | return r; | |
899 | } | |
7b4d7cc0 | 900 | |
7b4d7cc0 | 901 | return 0; |
7b4d7cc0 KS |
902 | } |
903 | ||
0974a682 | 904 | static int copy_one_file(const char *esp_path, const char *name, bool force) { |
02d06ba1 | 905 | char *root = IN_SET(arg_install_source, ARG_INSTALL_SOURCE_AUTO, ARG_INSTALL_SOURCE_IMAGE) ? arg_root : NULL; |
80a2381d | 906 | _cleanup_free_ char *source_path = NULL, *dest_path = NULL, *p = NULL, *q = NULL; |
5509f912 | 907 | const char *e; |
80a2381d LB |
908 | char *dest_name, *s; |
909 | int r, ret; | |
0974a682 | 910 | |
12caf727 ДГ |
911 | dest_name = strdupa_safe(name); |
912 | s = endswith_no_case(dest_name, ".signed"); | |
913 | if (s) | |
914 | *s = 0; | |
915 | ||
80a2381d LB |
916 | p = path_join(BOOTLIBDIR, name); |
917 | if (!p) | |
918 | return log_oom(); | |
919 | ||
b353d5ee | 920 | r = chase_symlinks(p, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &source_path, NULL); |
02d06ba1 LB |
921 | /* If we had a root directory to try, we didn't find it and we are in auto mode, retry on the host */ |
922 | if (r == -ENOENT && root && arg_install_source == ARG_INSTALL_SOURCE_AUTO) | |
b353d5ee | 923 | r = chase_symlinks(p, NULL, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &source_path, NULL); |
80a2381d LB |
924 | if (r < 0) |
925 | return log_error_errno(r, | |
926 | "Failed to resolve path %s%s%s: %m", | |
927 | p, | |
02d06ba1 | 928 | root ? " under directory " : "", |
05abe850 | 929 | strempty(root)); |
80a2381d LB |
930 | |
931 | q = path_join("/EFI/systemd/", dest_name); | |
932 | if (!q) | |
933 | return log_oom(); | |
934 | ||
b353d5ee | 935 | r = chase_symlinks(q, esp_path, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_NONEXISTENT, &dest_path, NULL); |
80a2381d LB |
936 | if (r < 0) |
937 | return log_error_errno(r, "Failed to resolve path %s under directory %s: %m", q, esp_path); | |
938 | ||
939 | /* Note that if this fails we do the second copy anyway, but return this error code, | |
940 | * so we stash it away in a separate variable. */ | |
941 | ret = copy_file_with_version_check(source_path, dest_path, force); | |
0974a682 | 942 | |
12caf727 | 943 | e = startswith(dest_name, "systemd-boot"); |
5509f912 | 944 | if (e) { |
80a2381d | 945 | _cleanup_free_ char *default_dest_path = NULL; |
d3226d77 | 946 | char *v; |
0974a682 KS |
947 | |
948 | /* Create the EFI default boot loader name (specified for removable devices) */ | |
80a2381d | 949 | v = strjoina("/EFI/BOOT/BOOT", e); |
846b8fc3 | 950 | ascii_strupper(strrchr(v, '/') + 1); |
0974a682 | 951 | |
b353d5ee | 952 | r = chase_symlinks(v, esp_path, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_NONEXISTENT, &default_dest_path, NULL); |
80a2381d LB |
953 | if (r < 0) |
954 | return log_error_errno(r, "Failed to resolve path %s under directory %s: %m", v, esp_path); | |
955 | ||
956 | r = copy_file_with_version_check(source_path, default_dest_path, force); | |
957 | if (r < 0 && ret == 0) | |
958 | ret = r; | |
0974a682 KS |
959 | } |
960 | ||
80a2381d | 961 | return ret; |
7b4d7cc0 KS |
962 | } |
963 | ||
6e916539 | 964 | static int install_binaries(const char *esp_path, const char *arch, bool force) { |
02d06ba1 | 965 | char *root = IN_SET(arg_install_source, ARG_INSTALL_SOURCE_AUTO, ARG_INSTALL_SOURCE_IMAGE) ? arg_root : NULL; |
d3226d77 | 966 | _cleanup_closedir_ DIR *d = NULL; |
80a2381d LB |
967 | _cleanup_free_ char *path = NULL; |
968 | int r; | |
0974a682 | 969 | |
b353d5ee | 970 | r = chase_symlinks_and_opendir(BOOTLIBDIR, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &path, &d); |
02d06ba1 LB |
971 | /* If we had a root directory to try, we didn't find it and we are in auto mode, retry on the host */ |
972 | if (r == -ENOENT && root && arg_install_source == ARG_INSTALL_SOURCE_AUTO) | |
b353d5ee | 973 | r = chase_symlinks_and_opendir(BOOTLIBDIR, NULL, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &path, &d); |
80a2381d | 974 | if (r < 0) |
05abe850 | 975 | return log_error_errno(r, "Failed to open boot loader directory %s%s: %m", strempty(root), BOOTLIBDIR); |
0974a682 | 976 | |
6e916539 JJ |
977 | const char *suffix = strjoina(arch, ".efi"); |
978 | const char *suffix_signed = strjoina(arch, ".efi.signed"); | |
979 | ||
80a2381d | 980 | FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read \"%s\": %m", path)) { |
0974a682 KS |
981 | int k; |
982 | ||
6e916539 | 983 | if (!endswith_no_case(de->d_name, suffix) && !endswith_no_case(de->d_name, suffix_signed)) |
0974a682 KS |
984 | continue; |
985 | ||
12caf727 ДГ |
986 | /* skip the .efi file, if there's a .signed version of it */ |
987 | if (endswith_no_case(de->d_name, ".efi")) { | |
d16da79e | 988 | _cleanup_free_ const char *s = strjoin(de->d_name, ".signed"); |
12caf727 ДГ |
989 | if (!s) |
990 | return log_oom(); | |
d16da79e | 991 | if (faccessat(dirfd(d), s, F_OK, 0) >= 0) |
12caf727 ДГ |
992 | continue; |
993 | } | |
994 | ||
0974a682 | 995 | k = copy_one_file(esp_path, de->d_name, force); |
e5a8b4b5 LP |
996 | /* Don't propagate an error code if no update necessary, installed version already equal or |
997 | * newer version, or other boot loader in place. */ | |
998 | if (arg_graceful && IN_SET(k, -ESTALE, -EREMOTE)) | |
999 | continue; | |
0974a682 KS |
1000 | if (k < 0 && r == 0) |
1001 | r = k; | |
1002 | } | |
1003 | ||
0974a682 | 1004 | return r; |
7b4d7cc0 KS |
1005 | } |
1006 | ||
b461576d | 1007 | static bool same_entry(uint16_t id, sd_id128_t uuid, const char *path) { |
d3226d77 | 1008 | _cleanup_free_ char *opath = NULL; |
0974a682 | 1009 | sd_id128_t ouuid; |
d3226d77 | 1010 | int r; |
7b4d7cc0 | 1011 | |
d3226d77 ZJS |
1012 | r = efi_get_boot_option(id, NULL, &ouuid, &opath, NULL); |
1013 | if (r < 0) | |
0974a682 KS |
1014 | return false; |
1015 | if (!sd_id128_equal(uuid, ouuid)) | |
d3226d77 | 1016 | return false; |
5c2e5957 | 1017 | |
1018 | /* Some motherboards convert the path to uppercase under certain circumstances | |
1019 | * (e.g. after booting into the Boot Menu in the ASUS ROG STRIX B350-F GAMING), | |
1020 | * so use case-insensitive checking */ | |
1021 | if (!strcaseeq_ptr(path, opath)) | |
d3226d77 | 1022 | return false; |
0974a682 | 1023 | |
d3226d77 | 1024 | return true; |
0974a682 KS |
1025 | } |
1026 | ||
1027 | static int find_slot(sd_id128_t uuid, const char *path, uint16_t *id) { | |
d3226d77 | 1028 | _cleanup_free_ uint16_t *options = NULL; |
0974a682 | 1029 | |
da60e5b5 | 1030 | int n = efi_get_boot_options(&options); |
d3226d77 ZJS |
1031 | if (n < 0) |
1032 | return n; | |
0974a682 | 1033 | |
e7dd673d | 1034 | /* find already existing systemd-boot entry */ |
da60e5b5 | 1035 | for (int i = 0; i < n; i++) |
0974a682 | 1036 | if (same_entry(options[i], uuid, path)) { |
d3226d77 ZJS |
1037 | *id = options[i]; |
1038 | return 1; | |
0974a682 KS |
1039 | } |
1040 | ||
1041 | /* find free slot in the sorted BootXXXX variable list */ | |
da60e5b5 | 1042 | for (int i = 0; i < n; i++) |
0974a682 | 1043 | if (i != options[i]) { |
d3226d77 | 1044 | *id = i; |
26d54e12 | 1045 | return 0; |
0974a682 KS |
1046 | } |
1047 | ||
1048 | /* use the next one */ | |
da60e5b5 | 1049 | if (n == 0xffff) |
0974a682 | 1050 | return -ENOSPC; |
da60e5b5 | 1051 | *id = n; |
d3226d77 | 1052 | return 0; |
0974a682 KS |
1053 | } |
1054 | ||
1055 | static int insert_into_order(uint16_t slot, bool first) { | |
d3226d77 ZJS |
1056 | _cleanup_free_ uint16_t *order = NULL; |
1057 | uint16_t *t; | |
c67bd42b | 1058 | int n; |
0974a682 | 1059 | |
d3226d77 ZJS |
1060 | n = efi_get_boot_order(&order); |
1061 | if (n <= 0) | |
0974a682 | 1062 | /* no entry, add us */ |
d3226d77 | 1063 | return efi_set_boot_order(&slot, 1); |
0974a682 KS |
1064 | |
1065 | /* are we the first and only one? */ | |
d3226d77 ZJS |
1066 | if (n == 1 && order[0] == slot) |
1067 | return 0; | |
0974a682 KS |
1068 | |
1069 | /* are we already in the boot order? */ | |
c67bd42b | 1070 | for (int i = 0; i < n; i++) { |
0974a682 KS |
1071 | if (order[i] != slot) |
1072 | continue; | |
1073 | ||
1074 | /* we do not require to be the first one, all is fine */ | |
1075 | if (!first) | |
d3226d77 | 1076 | return 0; |
0974a682 KS |
1077 | |
1078 | /* move us to the first slot */ | |
d3226d77 | 1079 | memmove(order + 1, order, i * sizeof(uint16_t)); |
0974a682 | 1080 | order[0] = slot; |
d3226d77 | 1081 | return efi_set_boot_order(order, n); |
0974a682 KS |
1082 | } |
1083 | ||
1084 | /* extend array */ | |
a7798cd8 | 1085 | t = reallocarray(order, n + 1, sizeof(uint16_t)); |
d3226d77 ZJS |
1086 | if (!t) |
1087 | return -ENOMEM; | |
1088 | order = t; | |
0974a682 KS |
1089 | |
1090 | /* add us to the top or end of the list */ | |
1091 | if (first) { | |
d3226d77 | 1092 | memmove(order + 1, order, n * sizeof(uint16_t)); |
0974a682 KS |
1093 | order[0] = slot; |
1094 | } else | |
d3226d77 | 1095 | order[n] = slot; |
0974a682 | 1096 | |
d3226d77 | 1097 | return efi_set_boot_order(order, n + 1); |
0974a682 KS |
1098 | } |
1099 | ||
1100 | static int remove_from_order(uint16_t slot) { | |
7cb0f263 | 1101 | _cleanup_free_ uint16_t *order = NULL; |
c67bd42b | 1102 | int n; |
0974a682 | 1103 | |
d3226d77 ZJS |
1104 | n = efi_get_boot_order(&order); |
1105 | if (n <= 0) | |
1106 | return n; | |
0974a682 | 1107 | |
c67bd42b | 1108 | for (int i = 0; i < n; i++) { |
0974a682 KS |
1109 | if (order[i] != slot) |
1110 | continue; | |
1111 | ||
d3226d77 ZJS |
1112 | if (i + 1 < n) |
1113 | memmove(order + i, order + i+1, (n - i) * sizeof(uint16_t)); | |
1114 | return efi_set_boot_order(order, n - 1); | |
0974a682 KS |
1115 | } |
1116 | ||
d3226d77 | 1117 | return 0; |
0974a682 KS |
1118 | } |
1119 | ||
3730dc5d LP |
1120 | static int install_variables( |
1121 | const char *esp_path, | |
1122 | uint32_t part, | |
1123 | uint64_t pstart, | |
1124 | uint64_t psize, | |
1125 | sd_id128_t uuid, | |
1126 | const char *path, | |
1127 | bool first) { | |
1128 | ||
0974a682 KS |
1129 | uint16_t slot; |
1130 | int r; | |
1131 | ||
80a2381d LB |
1132 | if (arg_root) { |
1133 | log_info("Acting on %s, skipping EFI variable setup.", | |
1134 | arg_image ? "image" : "root directory"); | |
1135 | return 0; | |
1136 | } | |
1137 | ||
0974a682 | 1138 | if (!is_efi_boot()) { |
d3226d77 | 1139 | log_warning("Not booted with EFI, skipping EFI variable setup."); |
0974a682 KS |
1140 | return 0; |
1141 | } | |
1142 | ||
b353d5ee | 1143 | r = chase_symlinks_and_access(path, esp_path, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, F_OK, NULL, NULL); |
3730dc5d LP |
1144 | if (r == -ENOENT) |
1145 | return 0; | |
1146 | if (r < 0) | |
1147 | return log_error_errno(r, "Cannot access \"%s/%s\": %m", esp_path, path); | |
7b4d7cc0 | 1148 | |
0974a682 | 1149 | r = find_slot(uuid, path, &slot); |
d3226d77 ZJS |
1150 | if (r < 0) |
1151 | return log_error_errno(r, | |
1152 | r == -ENOENT ? | |
1153 | "Failed to access EFI variables. Is the \"efivarfs\" filesystem mounted?" : | |
1154 | "Failed to determine current boot order: %m"); | |
7b4d7cc0 | 1155 | |
181ccb43 | 1156 | if (first || r == 0) { |
d9bdb29b | 1157 | r = efi_add_boot_option(slot, pick_efi_boot_option_description(), |
0974a682 KS |
1158 | part, pstart, psize, |
1159 | uuid, path); | |
d3226d77 ZJS |
1160 | if (r < 0) |
1161 | return log_error_errno(r, "Failed to create EFI Boot variable entry: %m"); | |
0974a682 | 1162 | |
d9bdb29b | 1163 | log_info("Created EFI boot entry \"%s\".", pick_efi_boot_option_description()); |
d3226d77 | 1164 | } |
0974a682 | 1165 | |
d3226d77 | 1166 | return insert_into_order(slot, first); |
0974a682 KS |
1167 | } |
1168 | ||
1169 | static int remove_boot_efi(const char *esp_path) { | |
d3226d77 | 1170 | _cleanup_closedir_ DIR *d = NULL; |
3730dc5d | 1171 | _cleanup_free_ char *p = NULL; |
d3226d77 | 1172 | int r, c = 0; |
0974a682 | 1173 | |
b353d5ee | 1174 | r = chase_symlinks_and_opendir("/EFI/BOOT", esp_path, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &p, &d); |
3730dc5d LP |
1175 | if (r == -ENOENT) |
1176 | return 0; | |
1177 | if (r < 0) | |
1178 | return log_error_errno(r, "Failed to open directory \"%s/EFI/BOOT\": %m", esp_path); | |
0974a682 | 1179 | |
e41256dc | 1180 | FOREACH_DIRENT(de, d, break) { |
d3226d77 ZJS |
1181 | _cleanup_close_ int fd = -1; |
1182 | _cleanup_free_ char *v = NULL; | |
0974a682 | 1183 | |
d3226d77 | 1184 | if (!endswith_no_case(de->d_name, ".efi")) |
0974a682 KS |
1185 | continue; |
1186 | ||
b7536c45 | 1187 | if (!startswith_no_case(de->d_name, "boot")) |
0974a682 KS |
1188 | continue; |
1189 | ||
d3226d77 | 1190 | fd = openat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC); |
dd114e11 | 1191 | if (fd < 0) |
d3226d77 | 1192 | return log_error_errno(errno, "Failed to open \"%s/%s\" for reading: %m", p, de->d_name); |
0974a682 | 1193 | |
d3226d77 | 1194 | r = get_file_version(fd, &v); |
0974a682 | 1195 | if (r < 0) |
d3226d77 ZJS |
1196 | return r; |
1197 | if (r > 0 && startswith(v, "systemd-boot ")) { | |
1198 | r = unlinkat(dirfd(d), de->d_name, 0); | |
1199 | if (r < 0) | |
1200 | return log_error_errno(errno, "Failed to remove \"%s/%s\": %m", p, de->d_name); | |
1201 | ||
a592ab6a | 1202 | log_info("Removed \"%s/%s\".", p, de->d_name); |
0974a682 KS |
1203 | } |
1204 | ||
1205 | c++; | |
0974a682 KS |
1206 | } |
1207 | ||
d3226d77 | 1208 | return c; |
0974a682 KS |
1209 | } |
1210 | ||
1211 | static int rmdir_one(const char *prefix, const char *suffix) { | |
270384b2 | 1212 | const char *p; |
7b4d7cc0 | 1213 | |
270384b2 | 1214 | p = prefix_roota(prefix, suffix); |
0974a682 | 1215 | if (rmdir(p) < 0) { |
fbf45d22 LP |
1216 | bool ignore = IN_SET(errno, ENOENT, ENOTEMPTY); |
1217 | ||
1218 | log_full_errno(ignore ? LOG_DEBUG : LOG_ERR, errno, | |
1219 | "Failed to remove directory \"%s\": %m", p); | |
1220 | if (!ignore) | |
1221 | return -errno; | |
7b4d7cc0 | 1222 | } else |
d3226d77 | 1223 | log_info("Removed \"%s\".", p); |
7b4d7cc0 | 1224 | |
0974a682 | 1225 | return 0; |
7b4d7cc0 KS |
1226 | } |
1227 | ||
e44c3229 LP |
1228 | static int remove_subdirs(const char *root, const char *const *subdirs) { |
1229 | int r, q; | |
fbf45d22 | 1230 | |
e44c3229 LP |
1231 | /* We use recursion here to destroy the directories in reverse order. Which should be safe given how |
1232 | * short the array is. */ | |
fbf45d22 | 1233 | |
e44c3229 LP |
1234 | if (!subdirs[0]) /* A the end of the list */ |
1235 | return 0; | |
fbf45d22 | 1236 | |
e44c3229 LP |
1237 | r = remove_subdirs(root, subdirs + 1); |
1238 | q = rmdir_one(root, subdirs[0]); | |
1239 | ||
1240 | return r < 0 ? r : q; | |
1241 | } | |
1242 | ||
f337f903 | 1243 | static int remove_entry_directory(const char *root) { |
6a3fff75 | 1244 | assert(root); |
f337f903 | 1245 | assert(arg_make_entry_directory >= 0); |
6a3fff75 | 1246 | |
f337f903 | 1247 | if (!arg_make_entry_directory || !arg_entry_token) |
6a3fff75 | 1248 | return 0; |
1249 | ||
f337f903 | 1250 | return rmdir_one(root, arg_entry_token); |
6a3fff75 | 1251 | } |
1252 | ||
0974a682 | 1253 | static int remove_binaries(const char *esp_path) { |
270384b2 | 1254 | const char *p; |
0974a682 KS |
1255 | int r, q; |
1256 | ||
270384b2 | 1257 | p = prefix_roota(esp_path, "/EFI/systemd"); |
c6878637 | 1258 | r = rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL); |
0974a682 KS |
1259 | |
1260 | q = remove_boot_efi(esp_path); | |
1261 | if (q < 0 && r == 0) | |
1262 | r = q; | |
1263 | ||
0974a682 KS |
1264 | return r; |
1265 | } | |
1266 | ||
e44c3229 | 1267 | static int remove_file(const char *root, const char *file) { |
fbf45d22 LP |
1268 | const char *p; |
1269 | ||
e44c3229 LP |
1270 | assert(root); |
1271 | assert(file); | |
fbf45d22 | 1272 | |
e44c3229 | 1273 | p = prefix_roota(root, file); |
fbf45d22 | 1274 | if (unlink(p) < 0) { |
e44c3229 LP |
1275 | log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR, errno, |
1276 | "Failed to unlink file \"%s\": %m", p); | |
fbf45d22 | 1277 | |
e44c3229 LP |
1278 | return errno == ENOENT ? 0 : -errno; |
1279 | } | |
fbf45d22 | 1280 | |
e44c3229 LP |
1281 | log_info("Removed \"%s\".", p); |
1282 | return 1; | |
fbf45d22 LP |
1283 | } |
1284 | ||
0974a682 KS |
1285 | static int remove_variables(sd_id128_t uuid, const char *path, bool in_order) { |
1286 | uint16_t slot; | |
1287 | int r; | |
1288 | ||
80a2381d | 1289 | if (arg_root || !is_efi_boot()) |
0974a682 KS |
1290 | return 0; |
1291 | ||
1292 | r = find_slot(uuid, path, &slot); | |
1293 | if (r != 1) | |
1294 | return 0; | |
1295 | ||
1296 | r = efi_remove_boot_option(slot); | |
1297 | if (r < 0) | |
1298 | return r; | |
1299 | ||
1300 | if (in_order) | |
d3226d77 | 1301 | return remove_from_order(slot); |
f939cff7 LP |
1302 | |
1303 | return 0; | |
0974a682 KS |
1304 | } |
1305 | ||
e44c3229 | 1306 | static int remove_loader_variables(void) { |
e44c3229 LP |
1307 | int r = 0; |
1308 | ||
1309 | /* Remove all persistent loader variables we define */ | |
1310 | ||
5980d463 | 1311 | FOREACH_STRING(var, |
e6f055cb ZJS |
1312 | EFI_LOADER_VARIABLE(LoaderConfigTimeout), |
1313 | EFI_LOADER_VARIABLE(LoaderConfigTimeoutOneShot), | |
1314 | EFI_LOADER_VARIABLE(LoaderEntryDefault), | |
1315 | EFI_LOADER_VARIABLE(LoaderEntryOneShot), | |
1316 | EFI_LOADER_VARIABLE(LoaderSystemToken)){ | |
e44c3229 LP |
1317 | |
1318 | int q; | |
1319 | ||
5980d463 | 1320 | q = efi_set_variable(var, NULL, 0); |
e44c3229 LP |
1321 | if (q == -ENOENT) |
1322 | continue; | |
1323 | if (q < 0) { | |
5980d463 | 1324 | log_warning_errno(q, "Failed to remove EFI variable %s: %m", var); |
e44c3229 LP |
1325 | if (r >= 0) |
1326 | r = q; | |
1327 | } else | |
5980d463 | 1328 | log_info("Removed EFI variable %s.", var); |
e44c3229 LP |
1329 | } |
1330 | ||
1331 | return r; | |
1332 | } | |
1333 | ||
31e57550 | 1334 | static int install_loader_config(const char *esp_path) { |
f5b84de2 LP |
1335 | _cleanup_(unlink_and_freep) char *t = NULL; |
1336 | _cleanup_fclose_ FILE *f = NULL; | |
d5ff6d6d | 1337 | const char *p; |
b46c3e49 | 1338 | int r; |
0974a682 | 1339 | |
f337f903 | 1340 | assert(arg_make_entry_directory >= 0); |
6a3fff75 | 1341 | |
270384b2 | 1342 | p = prefix_roota(esp_path, "/loader/loader.conf"); |
f5b84de2 LP |
1343 | if (access(p, F_OK) >= 0) /* Silently skip creation if the file already exists (early check) */ |
1344 | return 0; | |
1345 | ||
a5b30e15 LP |
1346 | r = fopen_tmpfile_linkable(p, O_WRONLY|O_CLOEXEC, &t, &f); |
1347 | if (r < 0) | |
1348 | return log_error_errno(r, "Failed to open \"%s\" for writing: %m", p); | |
0974a682 | 1349 | |
a36b411e | 1350 | fprintf(f, "#timeout 3\n" |
31e57550 | 1351 | "#console-mode keep\n"); |
85b55869 | 1352 | |
f337f903 LP |
1353 | if (arg_make_entry_directory) { |
1354 | assert(arg_entry_token); | |
1355 | fprintf(f, "default %s-*\n", arg_entry_token); | |
6a3fff75 | 1356 | } |
0974a682 | 1357 | |
a5b30e15 | 1358 | r = flink_tmpfile(f, t, p); |
f5b84de2 LP |
1359 | if (r == -EEXIST) |
1360 | return 0; /* Silently skip creation if the file exists now (recheck) */ | |
1361 | if (r < 0) | |
1362 | return log_error_errno(r, "Failed to move \"%s\" into place: %m", p); | |
1363 | ||
1364 | t = mfree(t); | |
f5b84de2 | 1365 | return 1; |
0974a682 KS |
1366 | } |
1367 | ||
80889bd9 LP |
1368 | static int install_loader_specification(const char *root) { |
1369 | _cleanup_(unlink_and_freep) char *t = NULL; | |
1370 | _cleanup_fclose_ FILE *f = NULL; | |
1371 | _cleanup_free_ char *p = NULL; | |
1372 | int r; | |
1373 | ||
1374 | p = path_join(root, "/loader/entries.srel"); | |
1375 | if (!p) | |
1376 | return log_oom(); | |
1377 | ||
1378 | if (access(p, F_OK) >= 0) /* Silently skip creation if the file already exists (early check) */ | |
1379 | return 0; | |
1380 | ||
1381 | r = fopen_tmpfile_linkable(p, O_WRONLY|O_CLOEXEC, &t, &f); | |
1382 | if (r < 0) | |
1383 | return log_error_errno(r, "Failed to open \"%s\" for writing: %m", p); | |
1384 | ||
1385 | fprintf(f, "type1\n"); | |
1386 | ||
1387 | r = flink_tmpfile(f, t, p); | |
1388 | if (r == -EEXIST) | |
1389 | return 0; /* Silently skip creation if the file exists now (recheck) */ | |
1390 | if (r < 0) | |
1391 | return log_error_errno(r, "Failed to move \"%s\" into place: %m", p); | |
1392 | ||
1393 | t = mfree(t); | |
1394 | return 1; | |
1395 | } | |
1396 | ||
f337f903 | 1397 | static int install_entry_directory(const char *root) { |
6a3fff75 | 1398 | assert(root); |
f337f903 | 1399 | assert(arg_make_entry_directory >= 0); |
6a3fff75 | 1400 | |
f337f903 | 1401 | if (!arg_make_entry_directory) |
6a3fff75 | 1402 | return 0; |
1403 | ||
f337f903 LP |
1404 | assert(arg_entry_token); |
1405 | return mkdir_one(root, arg_entry_token); | |
47fb161e ZJS |
1406 | } |
1407 | ||
f337f903 | 1408 | static int install_entry_token(void) { |
47fb161e ZJS |
1409 | int r; |
1410 | ||
f337f903 LP |
1411 | assert(arg_make_entry_directory >= 0); |
1412 | assert(arg_entry_token); | |
47fb161e | 1413 | |
f337f903 LP |
1414 | /* Let's save the used entry token in /etc/kernel/entry-token if we used it to create the entry |
1415 | * directory, or if anything else but the machine ID */ | |
47fb161e | 1416 | |
f337f903 LP |
1417 | if (!arg_make_entry_directory && arg_entry_token_type == ARG_ENTRY_TOKEN_MACHINE_ID) |
1418 | return 0; | |
47fb161e | 1419 | |
f337f903 | 1420 | r = write_string_file("/etc/kernel/entry-token", arg_entry_token, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_MKDIR_0755); |
6a3fff75 | 1421 | if (r < 0) |
f337f903 | 1422 | return log_error_errno(r, "Failed to write entry token '%s' to /etc/kernel/entry-token", arg_entry_token); |
47fb161e | 1423 | |
47fb161e | 1424 | return 0; |
6a3fff75 | 1425 | } |
1426 | ||
2f2c539c | 1427 | static int help(int argc, char *argv[], void *userdata) { |
37ec0fdd LP |
1428 | _cleanup_free_ char *link = NULL; |
1429 | int r; | |
1430 | ||
1431 | r = terminal_urlify_man("bootctl", "1", &link); | |
1432 | if (r < 0) | |
1433 | return log_oom(); | |
2f2c539c | 1434 | |
64a7fcc5 LP |
1435 | printf("%1$s [OPTIONS...] COMMAND ...\n" |
1436 | "\n%5$sControl EFI firmware boot settings and manage boot loader.%6$s\n" | |
1437 | "\n%3$sGeneric EFI Firmware/Boot Loader Commands:%4$s\n" | |
1438 | " status Show status of installed boot loader and EFI variables\n" | |
002914e6 LP |
1439 | " reboot-to-firmware [BOOL]\n" |
1440 | " Query or set reboot-to-firmware EFI flag\n" | |
64a7fcc5 LP |
1441 | " systemd-efi-options [STRING]\n" |
1442 | " Query or set system options string in EFI variable\n" | |
1443 | "\n%3$sBoot Loader Specification Commands:%4$s\n" | |
2536752d ZJS |
1444 | " list List boot loader entries\n" |
1445 | " set-default ID Set default boot loader entry\n" | |
1446 | " set-oneshot ID Set default boot loader entry, for next boot only\n" | |
39ddc32a JJ |
1447 | " set-timeout SECONDS Set the menu timeout\n" |
1448 | " set-timeout-oneshot SECONDS\n" | |
1449 | " Set the menu timeout for the next boot only\n" | |
64a7fcc5 LP |
1450 | "\n%3$ssystemd-boot Commands:%4$s\n" |
1451 | " install Install systemd-boot to the ESP and EFI variables\n" | |
1452 | " update Update systemd-boot in the ESP and EFI variables\n" | |
1453 | " remove Remove systemd-boot from the ESP and EFI variables\n" | |
1454 | " is-installed Test whether systemd-boot is installed in the ESP\n" | |
1455 | " random-seed Initialize random seed in ESP and EFI variables\n" | |
1456 | "\n%3$sOptions:%4$s\n" | |
e1fac8a6 ZJS |
1457 | " -h --help Show this help\n" |
1458 | " --version Print version\n" | |
1459 | " --esp-path=PATH Path to the EFI System Partition (ESP)\n" | |
1460 | " --boot-path=PATH Path to the $BOOT partition\n" | |
80a2381d LB |
1461 | " --root=PATH Operate on an alternate filesystem root\n" |
1462 | " --image=PATH Operate on disk image as filesystem root\n" | |
02d06ba1 LB |
1463 | " --install-source=auto|image|host\n" |
1464 | " Where to pick files when using --root=/--image=\n" | |
e1fac8a6 ZJS |
1465 | " -p --print-esp-path Print path to the EFI System Partition\n" |
1466 | " -x --print-boot-path Print path to the $BOOT partition\n" | |
1467 | " --no-variables Don't touch EFI variables\n" | |
1468 | " --no-pager Do not pipe output into a pager\n" | |
351de38e LP |
1469 | " --graceful Don't fail when the ESP cannot be found or EFI\n" |
1470 | " variables cannot be written\n" | |
14e6e444 | 1471 | " -q --quiet Suppress output\n" |
f337f903 LP |
1472 | " --make-entry-directory=yes|no|auto\n" |
1473 | " Create $BOOT/ENTRY-TOKEN/ directory\n" | |
1474 | " --entry-token=machine-id|os-id|os-image-id|auto|literal:…\n" | |
1475 | " Entry token to use for this installation\n" | |
0d1506d4 LP |
1476 | " --json=pretty|short|off\n" |
1477 | " Generate JSON output\n" | |
6e916539 JJ |
1478 | " --all-architectures\n" |
1479 | " Install all supported EFI architectures\n" | |
d9bdb29b RH |
1480 | " --efi-boot-option-description=DESCRIPTION\n" |
1481 | " Description of the entry in the boot option list\n" | |
bc556335 DDM |
1482 | "\nSee the %2$s for details.\n", |
1483 | program_invocation_short_name, | |
1484 | link, | |
1485 | ansi_underline(), | |
1486 | ansi_normal(), | |
1487 | ansi_highlight(), | |
1488 | ansi_normal()); | |
0974a682 KS |
1489 | |
1490 | return 0; | |
1491 | } | |
1492 | ||
0974a682 KS |
1493 | static int parse_argv(int argc, char *argv[]) { |
1494 | enum { | |
fbf45d22 LP |
1495 | ARG_ESP_PATH = 0x100, |
1496 | ARG_BOOT_PATH, | |
80a2381d LB |
1497 | ARG_ROOT, |
1498 | ARG_IMAGE, | |
02d06ba1 | 1499 | ARG_INSTALL_SOURCE, |
0974a682 KS |
1500 | ARG_VERSION, |
1501 | ARG_NO_VARIABLES, | |
57db6f18 | 1502 | ARG_NO_PAGER, |
351de38e | 1503 | ARG_GRACEFUL, |
f337f903 LP |
1504 | ARG_MAKE_ENTRY_DIRECTORY, |
1505 | ARG_ENTRY_TOKEN, | |
0d1506d4 | 1506 | ARG_JSON, |
6e916539 | 1507 | ARG_ARCH_ALL, |
d9bdb29b | 1508 | ARG_EFI_BOOT_OPTION_DESCRIPTION, |
7b4d7cc0 KS |
1509 | }; |
1510 | ||
0974a682 | 1511 | static const struct option options[] = { |
d9bdb29b RH |
1512 | { "help", no_argument, NULL, 'h' }, |
1513 | { "version", no_argument, NULL, ARG_VERSION }, | |
1514 | { "esp-path", required_argument, NULL, ARG_ESP_PATH }, | |
1515 | { "path", required_argument, NULL, ARG_ESP_PATH }, /* Compatibility alias */ | |
1516 | { "boot-path", required_argument, NULL, ARG_BOOT_PATH }, | |
1517 | { "root", required_argument, NULL, ARG_ROOT }, | |
1518 | { "image", required_argument, NULL, ARG_IMAGE }, | |
1519 | { "install-source", required_argument, NULL, ARG_INSTALL_SOURCE }, | |
1520 | { "print-esp-path", no_argument, NULL, 'p' }, | |
1521 | { "print-path", no_argument, NULL, 'p' }, /* Compatibility alias */ | |
1522 | { "print-boot-path", no_argument, NULL, 'x' }, | |
1523 | { "no-variables", no_argument, NULL, ARG_NO_VARIABLES }, | |
1524 | { "no-pager", no_argument, NULL, ARG_NO_PAGER }, | |
1525 | { "graceful", no_argument, NULL, ARG_GRACEFUL }, | |
1526 | { "quiet", no_argument, NULL, 'q' }, | |
1527 | { "make-entry-directory", required_argument, NULL, ARG_MAKE_ENTRY_DIRECTORY }, | |
1528 | { "make-machine-id-directory", required_argument, NULL, ARG_MAKE_ENTRY_DIRECTORY }, /* Compatibility alias */ | |
1529 | { "entry-token", required_argument, NULL, ARG_ENTRY_TOKEN }, | |
1530 | { "json", required_argument, NULL, ARG_JSON }, | |
1531 | { "all-architectures", no_argument, NULL, ARG_ARCH_ALL }, | |
1532 | { "efi-boot-option-description", required_argument, NULL, ARG_EFI_BOOT_OPTION_DESCRIPTION }, | |
a36b411e | 1533 | {} |
0974a682 KS |
1534 | }; |
1535 | ||
2f2c539c | 1536 | int c, r; |
6a3fff75 | 1537 | bool b; |
7b4d7cc0 KS |
1538 | |
1539 | assert(argc >= 0); | |
1540 | assert(argv); | |
1541 | ||
fba4e945 | 1542 | while ((c = getopt_long(argc, argv, "hpx", options, NULL)) >= 0) |
0974a682 | 1543 | switch (c) { |
7b4d7cc0 | 1544 | |
0974a682 | 1545 | case 'h': |
2f2c539c | 1546 | help(0, NULL, NULL); |
7b4d7cc0 | 1547 | return 0; |
7b4d7cc0 | 1548 | |
0974a682 | 1549 | case ARG_VERSION: |
3f6fd1ba | 1550 | return version(); |
7b4d7cc0 | 1551 | |
fbf45d22 LP |
1552 | case ARG_ESP_PATH: |
1553 | r = free_and_strdup(&arg_esp_path, optarg); | |
1554 | if (r < 0) | |
1555 | return log_oom(); | |
1556 | break; | |
1557 | ||
1558 | case ARG_BOOT_PATH: | |
1559 | r = free_and_strdup(&arg_xbootldr_path, optarg); | |
2f2c539c LP |
1560 | if (r < 0) |
1561 | return log_oom(); | |
0974a682 KS |
1562 | break; |
1563 | ||
80a2381d LB |
1564 | case ARG_ROOT: |
1565 | r = parse_path_argument(optarg, /* suppress_root= */ true, &arg_root); | |
1566 | if (r < 0) | |
1567 | return r; | |
1568 | break; | |
1569 | ||
1570 | case ARG_IMAGE: | |
1571 | r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image); | |
1572 | if (r < 0) | |
1573 | return r; | |
1574 | break; | |
1575 | ||
02d06ba1 LB |
1576 | case ARG_INSTALL_SOURCE: |
1577 | if (streq(optarg, "auto")) | |
1578 | arg_install_source = ARG_INSTALL_SOURCE_AUTO; | |
1579 | else if (streq(optarg, "image")) | |
1580 | arg_install_source = ARG_INSTALL_SOURCE_IMAGE; | |
1581 | else if (streq(optarg, "host")) | |
1582 | arg_install_source = ARG_INSTALL_SOURCE_HOST; | |
1583 | else | |
1584 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), | |
1585 | "Unexpected parameter for --install-source=: %s", optarg); | |
1586 | ||
1587 | break; | |
1588 | ||
30b50477 | 1589 | case 'p': |
aa467bca ZJS |
1590 | if (arg_print_dollar_boot_path) |
1591 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), | |
1592 | "--print-boot-path/-x cannot be combined with --print-esp-path/-p"); | |
fbf45d22 LP |
1593 | arg_print_esp_path = true; |
1594 | break; | |
1595 | ||
fba4e945 | 1596 | case 'x': |
aa467bca ZJS |
1597 | if (arg_print_esp_path) |
1598 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), | |
1599 | "--print-boot-path/-x cannot be combined with --print-esp-path/-p"); | |
fbf45d22 | 1600 | arg_print_dollar_boot_path = true; |
30b50477 ZJS |
1601 | break; |
1602 | ||
0974a682 KS |
1603 | case ARG_NO_VARIABLES: |
1604 | arg_touch_variables = false; | |
1605 | break; | |
1606 | ||
57db6f18 | 1607 | case ARG_NO_PAGER: |
0221d68a | 1608 | arg_pager_flags |= PAGER_DISABLE; |
57db6f18 LP |
1609 | break; |
1610 | ||
351de38e LP |
1611 | case ARG_GRACEFUL: |
1612 | arg_graceful = true; | |
1613 | break; | |
1614 | ||
14e6e444 ZJS |
1615 | case 'q': |
1616 | arg_quiet = true; | |
1617 | break; | |
1618 | ||
f337f903 LP |
1619 | case ARG_ENTRY_TOKEN: { |
1620 | const char *e; | |
1621 | ||
1622 | if (streq(optarg, "machine-id")) { | |
1623 | arg_entry_token_type = ARG_ENTRY_TOKEN_MACHINE_ID; | |
1624 | arg_entry_token = mfree(arg_entry_token); | |
1625 | } else if (streq(optarg, "os-image-id")) { | |
1626 | arg_entry_token_type = ARG_ENTRY_TOKEN_OS_IMAGE_ID; | |
1627 | arg_entry_token = mfree(arg_entry_token); | |
1628 | } else if (streq(optarg, "os-id")) { | |
1629 | arg_entry_token_type = ARG_ENTRY_TOKEN_OS_ID; | |
1630 | arg_entry_token = mfree(arg_entry_token); | |
1631 | } else if ((e = startswith(optarg, "literal:"))) { | |
1632 | arg_entry_token_type = ARG_ENTRY_TOKEN_LITERAL; | |
1633 | ||
1634 | r = free_and_strdup_warn(&arg_entry_token, e); | |
1635 | if (r < 0) | |
1636 | return r; | |
1637 | } else | |
1638 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), | |
1639 | "Unexpected parameter for --entry-token=: %s", optarg); | |
1640 | ||
1641 | break; | |
1642 | } | |
1643 | ||
1644 | case ARG_MAKE_ENTRY_DIRECTORY: | |
1ff493d5 | 1645 | if (streq(optarg, "auto")) /* retained for backwards compatibility */ |
f337f903 | 1646 | arg_make_entry_directory = -1; /* yes if machine-id is permanent */ |
6a3fff75 | 1647 | else { |
f337f903 | 1648 | r = parse_boolean_argument("--make-entry-directory=", optarg, &b); |
6a3fff75 | 1649 | if (r < 0) |
1650 | return r; | |
f337f903 LP |
1651 | |
1652 | arg_make_entry_directory = b; | |
6a3fff75 | 1653 | } |
1654 | break; | |
1655 | ||
0d1506d4 LP |
1656 | case ARG_JSON: |
1657 | r = parse_json_argument(optarg, &arg_json_format_flags); | |
1658 | if (r <= 0) | |
1659 | return r; | |
6e916539 | 1660 | break; |
0d1506d4 | 1661 | |
6e916539 JJ |
1662 | case ARG_ARCH_ALL: |
1663 | arg_arch_all = true; | |
0d1506d4 LP |
1664 | break; |
1665 | ||
d9bdb29b RH |
1666 | case ARG_EFI_BOOT_OPTION_DESCRIPTION: |
1667 | if (isempty(optarg) || !(string_is_safe(optarg) && utf8_is_valid(optarg))) { | |
1668 | _cleanup_free_ char *escaped = NULL; | |
1669 | ||
1670 | escaped = cescape(optarg); | |
1671 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), | |
1672 | "Invalid --efi-boot-option-description=: %s", strna(escaped)); | |
1673 | } | |
1674 | if (strlen(optarg) > EFI_BOOT_OPTION_DESCRIPTION_MAX) | |
1675 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), | |
1676 | "--efi-boot-option-description= too long: %zu > %zu", strlen(optarg), EFI_BOOT_OPTION_DESCRIPTION_MAX); | |
1677 | r = free_and_strdup_warn(&arg_efi_boot_option_description, optarg); | |
1678 | if (r < 0) | |
1679 | return r; | |
1680 | break; | |
1681 | ||
0974a682 KS |
1682 | case '?': |
1683 | return -EINVAL; | |
1684 | ||
1685 | default: | |
04499a70 | 1686 | assert_not_reached(); |
7b4d7cc0 | 1687 | } |
7b4d7cc0 | 1688 | |
80a2381d LB |
1689 | if ((arg_root || arg_image) && argv[optind] && !STR_IN_SET(argv[optind], "status", "list", |
1690 | "install", "update", "remove", "is-installed", "random-seed")) | |
1691 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), | |
1692 | "Options --root= and --image= are not supported with verb %s.", | |
1693 | argv[optind]); | |
1694 | ||
1695 | if (arg_root && arg_image) | |
1696 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Please specify either --root= or --image=, the combination of both is not supported."); | |
1697 | ||
02d06ba1 LB |
1698 | if (arg_install_source != ARG_INSTALL_SOURCE_AUTO && !arg_root && !arg_image) |
1699 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--install-from-host is only supported with --root= or --image=."); | |
1700 | ||
0974a682 KS |
1701 | return 1; |
1702 | } | |
7b4d7cc0 | 1703 | |
e6f055cb | 1704 | static void read_efi_var(const char *variable, char **ret) { |
551710cf ZJS |
1705 | int r; |
1706 | ||
e6f055cb | 1707 | r = efi_get_variable_string(variable, ret); |
551710cf | 1708 | if (r < 0 && r != -ENOENT) |
e6f055cb | 1709 | log_warning_errno(r, "Failed to read EFI variable %s: %m", variable); |
551710cf ZJS |
1710 | } |
1711 | ||
607ebf2b | 1712 | static void print_yes_no_line(bool first, bool good, const char *name) { |
ae54abe7 | 1713 | printf("%s%s %s\n", |
607ebf2b | 1714 | first ? " Features: " : " ", |
ae54abe7 | 1715 | COLOR_MARK_BOOL(good), |
607ebf2b ZJS |
1716 | name); |
1717 | } | |
1718 | ||
957b2423 | 1719 | static int are_we_installed(const char *esp_path) { |
d9f048b5 ZJS |
1720 | int r; |
1721 | ||
d9f048b5 ZJS |
1722 | /* Tests whether systemd-boot is installed. It's not obvious what to use as check here: we could |
1723 | * check EFI variables, we could check what binary /EFI/BOOT/BOOT*.EFI points to, or whether the | |
1724 | * loader entries directory exists. Here we opted to check whether /EFI/systemd/ is non-empty, which | |
1725 | * should be a suitable and very minimal check for a number of reasons: | |
1726 | * | |
1727 | * → The check is architecture independent (i.e. we check if any systemd-boot loader is installed, | |
1728 | * not a specific one.) | |
1729 | * | |
1730 | * → It doesn't assume we are the only boot loader (i.e doesn't check if we own the main | |
1731 | * /EFI/BOOT/BOOT*.EFI fallback binary. | |
1732 | * | |
1733 | * → It specifically checks for systemd-boot, not for other boot loaders (which a check for | |
1734 | * /boot/loader/entries would do). */ | |
1735 | ||
957b2423 | 1736 | _cleanup_free_ char *p = path_join(esp_path, "/EFI/systemd/"); |
d9f048b5 ZJS |
1737 | if (!p) |
1738 | return log_oom(); | |
1739 | ||
28e5e1e9 | 1740 | log_debug("Checking whether %s contains any files%s", p, special_glyph(SPECIAL_GLYPH_ELLIPSIS)); |
db55bbf2 | 1741 | r = dir_is_empty(p, /* ignore_hidden_or_backup= */ false); |
d9f048b5 ZJS |
1742 | if (r < 0 && r != -ENOENT) |
1743 | return log_error_errno(r, "Failed to check whether %s contains any files: %m", p); | |
1744 | ||
1745 | return r == 0; | |
1746 | } | |
1747 | ||
2f2c539c | 1748 | static int verb_status(int argc, char *argv[], void *userdata) { |
fbf45d22 | 1749 | sd_id128_t esp_uuid = SD_ID128_NULL, xbootldr_uuid = SD_ID128_NULL; |
f63b5ad9 | 1750 | dev_t esp_devid = 0, xbootldr_devid = 0; |
46fb255b | 1751 | int r, k; |
0974a682 | 1752 | |
f63b5ad9 | 1753 | r = acquire_esp(/* unprivileged_mode= */ geteuid() != 0, /* graceful= */ false, NULL, NULL, NULL, &esp_uuid, &esp_devid); |
fbf45d22 | 1754 | if (arg_print_esp_path) { |
5caa3167 LP |
1755 | if (r == -EACCES) /* If we couldn't acquire the ESP path, log about access errors (which is the only |
1756 | * error the find_esp_and_warn() won't log on its own) */ | |
fbf45d22 | 1757 | return log_error_errno(r, "Failed to determine ESP location: %m"); |
30b50477 ZJS |
1758 | if (r < 0) |
1759 | return r; | |
1760 | ||
fbf45d22 LP |
1761 | puts(arg_esp_path); |
1762 | } | |
1763 | ||
f63b5ad9 | 1764 | r = acquire_xbootldr(/* unprivileged_mode= */ geteuid() != 0, &xbootldr_uuid, &xbootldr_devid); |
fbf45d22 LP |
1765 | if (arg_print_dollar_boot_path) { |
1766 | if (r == -EACCES) | |
8214758b | 1767 | return log_error_errno(r, "Failed to determine XBOOTLDR partition: %m"); |
fbf45d22 LP |
1768 | if (r < 0) |
1769 | return r; | |
1770 | ||
f8a2b09a YW |
1771 | const char *path = arg_dollar_boot_path(); |
1772 | if (!path) | |
1773 | return log_error_errno(SYNTHETIC_ERRNO(EACCES), "Failed to determine XBOOTLDR location: %m"); | |
1774 | ||
1775 | puts(path); | |
30b50477 | 1776 | } |
551710cf | 1777 | |
fbf45d22 LP |
1778 | if (arg_print_esp_path || arg_print_dollar_boot_path) |
1779 | return 0; | |
1780 | ||
957b2423 ZJS |
1781 | r = 0; /* If we couldn't determine the path, then don't consider that a problem from here on, just |
1782 | * show what we can show */ | |
5caa3167 | 1783 | |
384c2c32 | 1784 | pager_open(arg_pager_flags); |
57db6f18 | 1785 | |
80a2381d | 1786 | if (!arg_root && is_efi_boot()) { |
80641a81 LP |
1787 | static const struct { |
1788 | uint64_t flag; | |
1789 | const char *name; | |
6977ea99 | 1790 | } loader_flags[] = { |
22c5ff51 LP |
1791 | { EFI_LOADER_FEATURE_BOOT_COUNTING, "Boot counting" }, |
1792 | { EFI_LOADER_FEATURE_CONFIG_TIMEOUT, "Menu timeout control" }, | |
1793 | { EFI_LOADER_FEATURE_CONFIG_TIMEOUT_ONE_SHOT, "One-shot menu timeout control" }, | |
1794 | { EFI_LOADER_FEATURE_ENTRY_DEFAULT, "Default entry control" }, | |
1795 | { EFI_LOADER_FEATURE_ENTRY_ONESHOT, "One-shot entry control" }, | |
1796 | { EFI_LOADER_FEATURE_XBOOTLDR, "Support for XBOOTLDR partition" }, | |
1797 | { EFI_LOADER_FEATURE_RANDOM_SEED, "Support for passing random seed to OS" }, | |
2553a548 | 1798 | { EFI_LOADER_FEATURE_LOAD_DRIVER, "Load drop-in drivers" }, |
a67f22c4 LP |
1799 | { EFI_LOADER_FEATURE_SORT_KEY, "Support Type #1 sort-key field" }, |
1800 | { EFI_LOADER_FEATURE_SAVED_ENTRY, "Support @saved pseudo-entry" }, | |
1801 | { EFI_LOADER_FEATURE_DEVICETREE, "Support Type #1 devicetree field" }, | |
80641a81 | 1802 | }; |
6977ea99 LP |
1803 | static const struct { |
1804 | uint64_t flag; | |
1805 | const char *name; | |
1806 | } stub_flags[] = { | |
1807 | { EFI_STUB_FEATURE_REPORT_BOOT_PARTITION, "Stub sets ESP information" }, | |
1808 | { EFI_STUB_FEATURE_PICK_UP_CREDENTIALS, "Picks up credentials from boot partition" }, | |
1809 | { EFI_STUB_FEATURE_PICK_UP_SYSEXTS, "Picks up system extension images from boot partition" }, | |
1810 | { EFI_STUB_FEATURE_THREE_PCRS, "Measures kernel+command line+sysexts" }, | |
1811 | }; | |
81375b9b | 1812 | _cleanup_free_ char *fw_type = NULL, *fw_info = NULL, *loader = NULL, *loader_path = NULL, *stub = NULL; |
25579a43 | 1813 | sd_id128_t loader_part_uuid = SD_ID128_NULL; |
6977ea99 | 1814 | uint64_t loader_features = 0, stub_features = 0; |
0ea911d1 | 1815 | Tpm2Support s; |
e6f055cb | 1816 | int have; |
0974a682 | 1817 | |
e6f055cb ZJS |
1818 | read_efi_var(EFI_LOADER_VARIABLE(LoaderFirmwareType), &fw_type); |
1819 | read_efi_var(EFI_LOADER_VARIABLE(LoaderFirmwareInfo), &fw_info); | |
1820 | read_efi_var(EFI_LOADER_VARIABLE(LoaderInfo), &loader); | |
1821 | read_efi_var(EFI_LOADER_VARIABLE(StubInfo), &stub); | |
1822 | read_efi_var(EFI_LOADER_VARIABLE(LoaderImageIdentifier), &loader_path); | |
80641a81 | 1823 | (void) efi_loader_get_features(&loader_features); |
6977ea99 | 1824 | (void) efi_stub_get_features(&stub_features); |
551710cf | 1825 | |
2f2c539c LP |
1826 | if (loader_path) |
1827 | efi_tilt_backslashes(loader_path); | |
1828 | ||
46fb255b ZJS |
1829 | k = efi_loader_get_device_part_uuid(&loader_part_uuid); |
1830 | if (k < 0 && k != -ENOENT) | |
1831 | r = log_warning_errno(k, "Failed to read EFI variable LoaderDevicePartUUID: %m"); | |
2f2c539c | 1832 | |
c4964512 | 1833 | SecureBootMode secure = efi_get_secure_boot_mode(); |
cb180369 | 1834 | printf("%sSystem:%s\n", ansi_underline(), ansi_normal()); |
6e916539 JJ |
1835 | printf(" Firmware: %s%s (%s)%s\n", ansi_highlight(), strna(fw_type), strna(fw_info), ansi_normal()); |
1836 | printf(" Firmware Arch: %s\n", get_efi_arch()); | |
1837 | printf(" Secure Boot: %sd (%s)\n", | |
c4964512 JJ |
1838 | enable_disable(IN_SET(secure, SECURE_BOOT_USER, SECURE_BOOT_DEPLOYED)), |
1839 | secure_boot_mode_to_string(secure)); | |
0ea911d1 LP |
1840 | |
1841 | s = tpm2_support(); | |
6e916539 | 1842 | printf(" TPM2 Support: %s%s%s\n", |
0ea911d1 LP |
1843 | FLAGS_SET(s, TPM2_SUPPORT_FIRMWARE|TPM2_SUPPORT_DRIVER) ? ansi_highlight_green() : |
1844 | (s & (TPM2_SUPPORT_FIRMWARE|TPM2_SUPPORT_DRIVER)) != 0 ? ansi_highlight_red() : ansi_highlight_yellow(), | |
1845 | FLAGS_SET(s, TPM2_SUPPORT_FIRMWARE|TPM2_SUPPORT_DRIVER) ? "yes" : | |
1846 | (s & TPM2_SUPPORT_FIRMWARE) ? "firmware only, driver unavailable" : | |
1847 | (s & TPM2_SUPPORT_DRIVER) ? "driver only, firmware unavailable" : "no", | |
1848 | ansi_normal()); | |
8a96369e | 1849 | |
c1b9708c LP |
1850 | k = efi_get_reboot_to_firmware(); |
1851 | if (k > 0) | |
6e916539 | 1852 | printf(" Boot into FW: %sactive%s\n", ansi_highlight_yellow(), ansi_normal()); |
c1b9708c | 1853 | else if (k == 0) |
6e916539 | 1854 | printf(" Boot into FW: supported\n"); |
c1b9708c | 1855 | else if (k == -EOPNOTSUPP) |
6e916539 | 1856 | printf(" Boot into FW: not supported\n"); |
8a96369e | 1857 | else { |
c1b9708c | 1858 | errno = -k; |
6e916539 | 1859 | printf(" Boot into FW: %sfailed%s (%m)\n", ansi_highlight_red(), ansi_normal()); |
8a96369e | 1860 | } |
2f2c539c LP |
1861 | printf("\n"); |
1862 | ||
cb180369 | 1863 | printf("%sCurrent Boot Loader:%s\n", ansi_underline(), ansi_normal()); |
ba857253 | 1864 | printf(" Product: %s%s%s\n", ansi_highlight(), strna(loader), ansi_normal()); |
80641a81 | 1865 | |
6977ea99 LP |
1866 | for (size_t i = 0; i < ELEMENTSOF(loader_flags); i++) |
1867 | print_yes_no_line(i == 0, FLAGS_SET(loader_features, loader_flags[i].flag), loader_flags[i].name); | |
80641a81 | 1868 | |
607ebf2b ZJS |
1869 | sd_id128_t bootloader_esp_uuid; |
1870 | bool have_bootloader_esp_uuid = efi_loader_get_device_part_uuid(&bootloader_esp_uuid) >= 0; | |
80641a81 | 1871 | |
250db1bf | 1872 | print_yes_no_line(false, have_bootloader_esp_uuid, "Boot loader sets ESP information"); |
22e54dd6 ZJS |
1873 | if (have_bootloader_esp_uuid && !sd_id128_is_null(esp_uuid) && |
1874 | !sd_id128_equal(esp_uuid, bootloader_esp_uuid)) | |
1875 | printf("WARNING: The boot loader reports a different ESP UUID than detected ("SD_ID128_UUID_FORMAT_STR" vs. "SD_ID128_UUID_FORMAT_STR")!\n", | |
1876 | SD_ID128_FORMAT_VAL(bootloader_esp_uuid), | |
1877 | SD_ID128_FORMAT_VAL(esp_uuid)); | |
80641a81 | 1878 | |
6977ea99 | 1879 | if (stub) { |
81375b9b | 1880 | printf(" Stub: %s\n", stub); |
6977ea99 LP |
1881 | for (size_t i = 0; i < ELEMENTSOF(stub_flags); i++) |
1882 | print_yes_no_line(i == 0, FLAGS_SET(stub_features, stub_flags[i].flag), stub_flags[i].name); | |
1883 | } | |
e28973ee | 1884 | if (!sd_id128_is_null(loader_part_uuid)) |
bd44566c | 1885 | printf(" ESP: /dev/disk/by-partuuid/" SD_ID128_UUID_FORMAT_STR "\n", |
2f2c539c LP |
1886 | SD_ID128_FORMAT_VAL(loader_part_uuid)); |
1887 | else | |
cd2d4c7f | 1888 | printf(" ESP: n/a\n"); |
9a6f746f | 1889 | printf(" File: %s%s\n", special_glyph(SPECIAL_GLYPH_TREE_RIGHT), strna(loader_path)); |
2f2c539c | 1890 | printf("\n"); |
d6e9a347 | 1891 | |
cb180369 | 1892 | printf("%sRandom Seed:%s\n", ansi_underline(), ansi_normal()); |
e6f055cb ZJS |
1893 | have = access(EFIVAR_PATH(EFI_LOADER_VARIABLE(LoaderSystemToken)), F_OK) >= 0; |
1894 | printf(" System Token: %s\n", have ? "set" : "not set"); | |
d6e9a347 LP |
1895 | |
1896 | if (arg_esp_path) { | |
1897 | _cleanup_free_ char *p = NULL; | |
1898 | ||
1899 | p = path_join(arg_esp_path, "/loader/random-seed"); | |
1900 | if (!p) | |
1901 | return log_oom(); | |
1902 | ||
e6f055cb ZJS |
1903 | have = access(p, F_OK) >= 0; |
1904 | printf(" Exists: %s\n", yes_no(have)); | |
d6e9a347 LP |
1905 | } |
1906 | ||
1907 | printf("\n"); | |
2f2c539c | 1908 | } else |
cb180369 LP |
1909 | printf("%sSystem:%s\n" |
1910 | "Not booted with EFI\n\n", | |
1911 | ansi_underline(), ansi_normal()); | |
7b4d7cc0 | 1912 | |
fbf45d22 LP |
1913 | if (arg_esp_path) { |
1914 | k = status_binaries(arg_esp_path, esp_uuid); | |
ecec2a5d LP |
1915 | if (k < 0) |
1916 | r = k; | |
1917 | } | |
2f2c539c | 1918 | |
80a2381d | 1919 | if (!arg_root && is_efi_boot()) { |
46fb255b ZJS |
1920 | k = status_variables(); |
1921 | if (k < 0) | |
1922 | r = k; | |
cd2d4c7f | 1923 | } |
0974a682 | 1924 | |
fbf45d22 | 1925 | if (arg_esp_path || arg_xbootldr_path) { |
8214758b ZJS |
1926 | _cleanup_(boot_config_free) BootConfig config = BOOT_CONFIG_NULL; |
1927 | ||
1928 | k = boot_config_load_and_select(&config, | |
1929 | arg_esp_path, esp_devid, | |
1930 | arg_xbootldr_path, xbootldr_devid); | |
ecec2a5d LP |
1931 | if (k < 0) |
1932 | r = k; | |
8214758b ZJS |
1933 | else { |
1934 | k = status_entries(&config, | |
1935 | arg_esp_path, esp_uuid, | |
1936 | arg_xbootldr_path, xbootldr_uuid); | |
1937 | if (k < 0) | |
1938 | r = k; | |
1939 | } | |
ecec2a5d | 1940 | } |
7e87c7d9 | 1941 | |
46fb255b | 1942 | return r; |
2f2c539c | 1943 | } |
7b4d7cc0 | 1944 | |
7e87c7d9 | 1945 | static int verb_list(int argc, char *argv[], void *userdata) { |
f7a7a5e2 | 1946 | _cleanup_(boot_config_free) BootConfig config = BOOT_CONFIG_NULL; |
f63b5ad9 | 1947 | dev_t esp_devid = 0, xbootldr_devid = 0; |
5caa3167 | 1948 | int r; |
7e87c7d9 | 1949 | |
8214758b ZJS |
1950 | /* If we lack privileges we invoke find_esp_and_warn() in "unprivileged mode" here, which does two |
1951 | * things: turn off logging about access errors and turn off potentially privileged device probing. | |
1952 | * Here we're interested in the latter but not the former, hence request the mode, and log about | |
1953 | * EACCES. */ | |
7e87c7d9 | 1954 | |
f63b5ad9 | 1955 | r = acquire_esp(/* unprivileged_mode= */ geteuid() != 0, /* graceful= */ false, NULL, NULL, NULL, NULL, &esp_devid); |
5caa3167 | 1956 | if (r == -EACCES) /* We really need the ESP path for this call, hence also log about access errors */ |
8214758b | 1957 | return log_error_errno(r, "Failed to determine ESP location: %m"); |
7e87c7d9 ZJS |
1958 | if (r < 0) |
1959 | return r; | |
1960 | ||
f63b5ad9 | 1961 | r = acquire_xbootldr(/* unprivileged_mode= */ geteuid() != 0, NULL, &xbootldr_devid); |
fbf45d22 LP |
1962 | if (r == -EACCES) |
1963 | return log_error_errno(r, "Failed to determine XBOOTLDR partition: %m"); | |
1964 | if (r < 0) | |
1965 | return r; | |
1966 | ||
8214758b | 1967 | r = boot_config_load_and_select(&config, arg_esp_path, esp_devid, arg_xbootldr_path, xbootldr_devid); |
f7a7a5e2 LP |
1968 | if (r < 0) |
1969 | return r; | |
1970 | ||
432ce537 | 1971 | if (config.n_entries == 0 && FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF)) { |
20a28174 | 1972 | log_info("No boot loader entries found."); |
432ce537 | 1973 | return 0; |
cd2d4c7f | 1974 | } |
0974a682 | 1975 | |
432ce537 ZJS |
1976 | pager_open(arg_pager_flags); |
1977 | return show_boot_entries(&config, arg_json_format_flags); | |
2f2c539c | 1978 | } |
7b4d7cc0 | 1979 | |
e44c3229 LP |
1980 | static int install_random_seed(const char *esp) { |
1981 | _cleanup_(unlink_and_freep) char *tmp = NULL; | |
3daeef08 | 1982 | uint8_t buffer[RANDOM_EFI_SEED_SIZE]; |
e44c3229 LP |
1983 | _cleanup_free_ char *path = NULL; |
1984 | _cleanup_close_ int fd = -1; | |
0be72218 | 1985 | size_t token_size; |
e44c3229 LP |
1986 | ssize_t n; |
1987 | int r; | |
1988 | ||
1989 | assert(esp); | |
1990 | ||
1991 | path = path_join(esp, "/loader/random-seed"); | |
1992 | if (!path) | |
1993 | return log_oom(); | |
1994 | ||
0be72218 | 1995 | r = crypto_random_bytes(buffer, sizeof(buffer)); |
e44c3229 | 1996 | if (r < 0) |
a4d20801 | 1997 | return log_error_errno(r, "Failed to acquire random seed: %m"); |
e44c3229 | 1998 | |
a4a55e9a LP |
1999 | /* Normally create_subdirs() should already have created everything we need, but in case "bootctl |
2000 | * random-seed" is called we want to just create the minimum we need for it, and not the full | |
2001 | * list. */ | |
2002 | r = mkdir_parents(path, 0755); | |
2003 | if (r < 0) | |
2004 | return log_error_errno(r, "Failed to create parent directory for %s: %m", path); | |
2005 | ||
e44c3229 LP |
2006 | r = tempfn_random(path, "bootctl", &tmp); |
2007 | if (r < 0) | |
2008 | return log_oom(); | |
2009 | ||
2010 | fd = open(tmp, O_CREAT|O_EXCL|O_NOFOLLOW|O_NOCTTY|O_WRONLY|O_CLOEXEC, 0600); | |
2011 | if (fd < 0) { | |
2012 | tmp = mfree(tmp); | |
2013 | return log_error_errno(fd, "Failed to open random seed file for writing: %m"); | |
2014 | } | |
2015 | ||
0be72218 | 2016 | n = write(fd, buffer, sizeof(buffer)); |
e44c3229 LP |
2017 | if (n < 0) |
2018 | return log_error_errno(errno, "Failed to write random seed file: %m"); | |
0be72218 | 2019 | if ((size_t) n != sizeof(buffer)) |
e44c3229 LP |
2020 | return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short write while writing random seed file."); |
2021 | ||
2022 | if (rename(tmp, path) < 0) | |
2023 | return log_error_errno(r, "Failed to move random seed file into place: %m"); | |
2024 | ||
2025 | tmp = mfree(tmp); | |
2026 | ||
0be72218 | 2027 | log_info("Random seed file %s successfully written (%zu bytes).", path, sizeof(buffer)); |
e44c3229 LP |
2028 | |
2029 | if (!arg_touch_variables) | |
2030 | return 0; | |
2031 | ||
2032 | if (!is_efi_boot()) { | |
2033 | log_notice("Not booted with EFI, skipping EFI variable setup."); | |
2034 | return 0; | |
2035 | } | |
2036 | ||
80a2381d LB |
2037 | if (arg_root) { |
2038 | log_warning("Acting on %s, skipping EFI variable setup.", | |
2039 | arg_image ? "image" : "root directory"); | |
2040 | return 0; | |
2041 | } | |
2042 | ||
e44c3229 LP |
2043 | r = getenv_bool("SYSTEMD_WRITE_SYSTEM_TOKEN"); |
2044 | if (r < 0) { | |
2045 | if (r != -ENXIO) | |
2046 | log_warning_errno(r, "Failed to parse $SYSTEMD_WRITE_SYSTEM_TOKEN, ignoring."); | |
2047 | ||
2048 | if (detect_vm() > 0) { | |
2049 | /* Let's not write a system token if we detect we are running in a VM | |
2050 | * environment. Why? Our default security model for the random seed uses the system | |
2051 | * token as a mechanism to ensure we are not vulnerable to golden master sloppiness | |
2052 | * issues, i.e. that people initialize the random seed file, then copy the image to | |
2053 | * many systems and end up with the same random seed in each that is assumed to be | |
2054 | * valid but in reality is the same for all machines. By storing a system token in | |
2055 | * the EFI variable space we can make sure that even though the random seeds on disk | |
2056 | * are all the same they will be different on each system under the assumption that | |
2057 | * the EFI variable space is maintained separate from the random seed storage. That | |
162392b7 | 2058 | * is generally the case on physical systems, as the ESP is stored on persistent |
e44c3229 LP |
2059 | * storage, and the EFI variables in NVRAM. However in virtualized environments this |
2060 | * is generally not true: the EFI variable set is typically stored along with the | |
2061 | * disk image itself. For example, using the OVMF EFI firmware the EFI variables are | |
2062 | * stored in a file in the ESP itself. */ | |
2063 | ||
2064 | log_notice("Not installing system token, since we are running in a virtualized environment."); | |
2065 | return 0; | |
2066 | } | |
2067 | } else if (r == 0) { | |
2068 | log_notice("Not writing system token, because $SYSTEMD_WRITE_SYSTEM_TOKEN is set to false."); | |
2069 | return 0; | |
2070 | } | |
2071 | ||
e6f055cb | 2072 | r = efi_get_variable(EFI_LOADER_VARIABLE(LoaderSystemToken), NULL, NULL, &token_size); |
ad0b610b LP |
2073 | if (r == -ENODATA) |
2074 | log_debug_errno(r, "LoaderSystemToken EFI variable is invalid (too short?), replacing."); | |
2075 | else if (r < 0) { | |
e44c3229 LP |
2076 | if (r != -ENOENT) |
2077 | return log_error_errno(r, "Failed to test system token validity: %m"); | |
2078 | } else { | |
0be72218 | 2079 | if (token_size >= sizeof(buffer)) { |
e44c3229 LP |
2080 | /* Let's avoid writes if we can, and initialize this only once. */ |
2081 | log_debug("System token already written, not updating."); | |
2082 | return 0; | |
2083 | } | |
2084 | ||
0be72218 | 2085 | log_debug("Existing system token size (%zu) does not match our expectations (%zu), replacing.", token_size, sizeof(buffer)); |
e44c3229 LP |
2086 | } |
2087 | ||
0be72218 | 2088 | r = crypto_random_bytes(buffer, sizeof(buffer)); |
e44c3229 LP |
2089 | if (r < 0) |
2090 | return log_error_errno(r, "Failed to acquire random seed: %m"); | |
2091 | ||
2092 | /* Let's write this variable with an umask in effect, so that unprivileged users can't see the token | |
2093 | * and possibly get identification information or too much insight into the kernel's entropy pool | |
2094 | * state. */ | |
2095 | RUN_WITH_UMASK(0077) { | |
0be72218 | 2096 | r = efi_set_variable(EFI_LOADER_VARIABLE(LoaderSystemToken), buffer, sizeof(buffer)); |
351de38e LP |
2097 | if (r < 0) { |
2098 | if (!arg_graceful) | |
2099 | return log_error_errno(r, "Failed to write 'LoaderSystemToken' EFI variable: %m"); | |
2100 | ||
2101 | if (r == -EINVAL) | |
2102 | log_warning_errno(r, "Unable to write 'LoaderSystemToken' EFI variable (firmware problem?), ignoring: %m"); | |
2103 | else | |
2104 | log_warning_errno(r, "Unable to write 'LoaderSystemToken' EFI variable, ignoring: %m"); | |
2105 | } else | |
0be72218 | 2106 | log_info("Successfully initialized system token in EFI variable with %zu bytes.", sizeof(buffer)); |
e44c3229 LP |
2107 | } |
2108 | ||
e44c3229 LP |
2109 | return 0; |
2110 | } | |
2111 | ||
fbf45d22 LP |
2112 | static int sync_everything(void) { |
2113 | int ret = 0, k; | |
e0e8d177 | 2114 | |
fbf45d22 LP |
2115 | if (arg_esp_path) { |
2116 | k = syncfs_path(AT_FDCWD, arg_esp_path); | |
2117 | if (k < 0) | |
2118 | ret = log_error_errno(k, "Failed to synchronize the ESP '%s': %m", arg_esp_path); | |
2119 | } | |
e0e8d177 | 2120 | |
fbf45d22 LP |
2121 | if (arg_xbootldr_path) { |
2122 | k = syncfs_path(AT_FDCWD, arg_xbootldr_path); | |
2123 | if (k < 0) | |
2124 | ret = log_error_errno(k, "Failed to synchronize $BOOT '%s': %m", arg_xbootldr_path); | |
2125 | } | |
e0e8d177 | 2126 | |
fbf45d22 | 2127 | return ret; |
e0e8d177 LP |
2128 | } |
2129 | ||
2f2c539c | 2130 | static int verb_install(int argc, char *argv[], void *userdata) { |
2f2c539c LP |
2131 | sd_id128_t uuid = SD_ID128_NULL; |
2132 | uint64_t pstart = 0, psize = 0; | |
2133 | uint32_t part = 0; | |
e5a8b4b5 | 2134 | bool install, graceful; |
2f2c539c LP |
2135 | int r; |
2136 | ||
8d3e0d60 LP |
2137 | /* Invoked for both "update" and "install" */ |
2138 | ||
e5a8b4b5 LP |
2139 | install = streq(argv[0], "install"); |
2140 | graceful = !install && arg_graceful; /* support graceful mode for updates */ | |
2141 | ||
f63b5ad9 | 2142 | r = acquire_esp(/* unprivileged_mode= */ false, graceful, &part, &pstart, &psize, &uuid, NULL); |
e5a8b4b5 LP |
2143 | if (graceful && r == -ENOKEY) |
2144 | return 0; /* If --graceful is specified and we can't find an ESP, handle this cleanly */ | |
2f2c539c LP |
2145 | if (r < 0) |
2146 | return r; | |
2147 | ||
49927ad8 ZJS |
2148 | if (!install) { |
2149 | /* If we are updating, don't do anything if sd-boot wasn't actually installed. */ | |
957b2423 | 2150 | r = are_we_installed(arg_esp_path); |
49927ad8 ZJS |
2151 | if (r < 0) |
2152 | return r; | |
2153 | if (r == 0) { | |
2154 | log_debug("Skipping update because sd-boot is not installed in the ESP."); | |
2155 | return 0; | |
2156 | } | |
2157 | } | |
2158 | ||
f63b5ad9 | 2159 | r = acquire_xbootldr(/* unprivileged_mode= */ false, NULL, NULL); |
fbf45d22 LP |
2160 | if (r < 0) |
2161 | return r; | |
2162 | ||
f337f903 | 2163 | r = settle_make_entry_directory(); |
47fb161e ZJS |
2164 | if (r < 0) |
2165 | return r; | |
6a3fff75 | 2166 | |
6e916539 JJ |
2167 | const char *arch = arg_arch_all ? "" : get_efi_arch(); |
2168 | ||
2f2c539c | 2169 | RUN_WITH_UMASK(0002) { |
fbf45d22 LP |
2170 | if (install) { |
2171 | /* Don't create any of these directories when we are just updating. When we update | |
2172 | * we'll drop-in our files (unless there are newer ones already), but we won't create | |
2173 | * the directories for them in the first place. */ | |
e44c3229 LP |
2174 | r = create_subdirs(arg_esp_path, esp_subdirs); |
2175 | if (r < 0) | |
2176 | return r; | |
2177 | ||
2178 | r = create_subdirs(arg_dollar_boot_path(), dollar_boot_subdirs); | |
fbf45d22 LP |
2179 | if (r < 0) |
2180 | return r; | |
2181 | } | |
2182 | ||
6e916539 | 2183 | r = install_binaries(arg_esp_path, arch, install); |
0974a682 | 2184 | if (r < 0) |
d3226d77 | 2185 | return r; |
0974a682 | 2186 | |
2f2c539c | 2187 | if (install) { |
31e57550 | 2188 | r = install_loader_config(arg_esp_path); |
e44c3229 LP |
2189 | if (r < 0) |
2190 | return r; | |
2191 | ||
f337f903 | 2192 | r = install_entry_directory(arg_dollar_boot_path()); |
6a3fff75 | 2193 | if (r < 0) |
2194 | return r; | |
2195 | ||
f337f903 | 2196 | r = install_entry_token(); |
47fb161e ZJS |
2197 | if (r < 0) |
2198 | return r; | |
2199 | ||
e44c3229 | 2200 | r = install_random_seed(arg_esp_path); |
d3226d77 ZJS |
2201 | if (r < 0) |
2202 | return r; | |
2203 | } | |
80889bd9 LP |
2204 | |
2205 | r = install_loader_specification(arg_dollar_boot_path()); | |
2206 | if (r < 0) | |
2207 | return r; | |
2f2c539c | 2208 | } |
0974a682 | 2209 | |
fbf45d22 | 2210 | (void) sync_everything(); |
e0e8d177 | 2211 | |
6e916539 JJ |
2212 | if (!arg_touch_variables) |
2213 | return 0; | |
7b4d7cc0 | 2214 | |
6e916539 JJ |
2215 | if (arg_arch_all) { |
2216 | log_info("Not changing EFI variables with --all-architectures."); | |
2217 | return 0; | |
2218 | } | |
2219 | ||
2220 | char *path = strjoina("/EFI/systemd/systemd-boot", arch, ".efi"); | |
2221 | return install_variables(arg_esp_path, part, pstart, psize, uuid, path, install); | |
2f2c539c | 2222 | } |
0974a682 | 2223 | |
2f2c539c | 2224 | static int verb_remove(int argc, char *argv[], void *userdata) { |
31e57550 | 2225 | sd_id128_t uuid = SD_ID128_NULL; |
fbf45d22 | 2226 | int r, q; |
0974a682 | 2227 | |
f63b5ad9 | 2228 | r = acquire_esp(/* unprivileged_mode= */ false, /* graceful= */ false, NULL, NULL, NULL, &uuid, NULL); |
2f2c539c LP |
2229 | if (r < 0) |
2230 | return r; | |
2231 | ||
f63b5ad9 | 2232 | r = acquire_xbootldr(/* unprivileged_mode= */ false, NULL, NULL); |
fbf45d22 LP |
2233 | if (r < 0) |
2234 | return r; | |
2f2c539c | 2235 | |
f337f903 | 2236 | r = settle_make_entry_directory(); |
47fb161e ZJS |
2237 | if (r < 0) |
2238 | return r; | |
6a3fff75 | 2239 | |
fbf45d22 | 2240 | r = remove_binaries(arg_esp_path); |
e0e8d177 | 2241 | |
e44c3229 | 2242 | q = remove_file(arg_esp_path, "/loader/loader.conf"); |
fbf45d22 LP |
2243 | if (q < 0 && r >= 0) |
2244 | r = q; | |
2245 | ||
e44c3229 | 2246 | q = remove_file(arg_esp_path, "/loader/random-seed"); |
fbf45d22 LP |
2247 | if (q < 0 && r >= 0) |
2248 | r = q; | |
2f2c539c | 2249 | |
80889bd9 LP |
2250 | q = remove_file(arg_esp_path, "/loader/entries.srel"); |
2251 | if (q < 0 && r >= 0) | |
2252 | r = q; | |
2253 | ||
e44c3229 | 2254 | q = remove_subdirs(arg_esp_path, esp_subdirs); |
fbf45d22 LP |
2255 | if (q < 0 && r >= 0) |
2256 | r = q; | |
2257 | ||
e44c3229 LP |
2258 | q = remove_subdirs(arg_esp_path, dollar_boot_subdirs); |
2259 | if (q < 0 && r >= 0) | |
2260 | r = q; | |
fbf45d22 | 2261 | |
f337f903 | 2262 | q = remove_entry_directory(arg_esp_path); |
6a3fff75 | 2263 | if (q < 0 && r >= 0) |
1c2b6177 | 2264 | r = q; |
6a3fff75 | 2265 | |
e44c3229 | 2266 | if (arg_xbootldr_path) { |
80889bd9 LP |
2267 | /* Remove a subset of these also from the XBOOTLDR partition if it exists */ |
2268 | ||
2269 | q = remove_file(arg_xbootldr_path, "/loader/entries.srel"); | |
2270 | if (q < 0 && r >= 0) | |
2271 | r = q; | |
2272 | ||
e44c3229 LP |
2273 | q = remove_subdirs(arg_xbootldr_path, dollar_boot_subdirs); |
2274 | if (q < 0 && r >= 0) | |
2275 | r = q; | |
6a3fff75 | 2276 | |
f337f903 | 2277 | q = remove_entry_directory(arg_xbootldr_path); |
6a3fff75 | 2278 | if (q < 0 && r >= 0) |
2279 | r = q; | |
7b4d7cc0 KS |
2280 | } |
2281 | ||
e44c3229 LP |
2282 | (void) sync_everything(); |
2283 | ||
2284 | if (!arg_touch_variables) | |
2285 | return r; | |
2286 | ||
6e916539 JJ |
2287 | if (arg_arch_all) { |
2288 | log_info("Not changing EFI variables with --all-architectures."); | |
2289 | return r; | |
2290 | } | |
2291 | ||
2292 | char *path = strjoina("/EFI/systemd/systemd-boot", get_efi_arch(), ".efi"); | |
2293 | q = remove_variables(uuid, path, true); | |
e44c3229 LP |
2294 | if (q < 0 && r >= 0) |
2295 | r = q; | |
2296 | ||
2297 | q = remove_loader_variables(); | |
2298 | if (q < 0 && r >= 0) | |
2299 | r = q; | |
2300 | ||
d3226d77 | 2301 | return r; |
7b4d7cc0 KS |
2302 | } |
2303 | ||
a2aa605d | 2304 | static int verb_is_installed(int argc, char *argv[], void *userdata) { |
a2aa605d LP |
2305 | int r; |
2306 | ||
18eb56c3 ZJS |
2307 | r = acquire_esp(/* privileged_mode= */ false, |
2308 | /* graceful= */ arg_graceful, | |
2309 | NULL, NULL, NULL, NULL, NULL); | |
957b2423 ZJS |
2310 | if (r < 0) |
2311 | return r; | |
2312 | ||
2313 | r = are_we_installed(arg_esp_path); | |
a2aa605d LP |
2314 | if (r < 0) |
2315 | return r; | |
2316 | ||
d9f048b5 | 2317 | if (r > 0) { |
14e6e444 ZJS |
2318 | if (!arg_quiet) |
2319 | puts("yes"); | |
d9f048b5 ZJS |
2320 | return EXIT_SUCCESS; |
2321 | } else { | |
14e6e444 ZJS |
2322 | if (!arg_quiet) |
2323 | puts("no"); | |
a2aa605d LP |
2324 | return EXIT_FAILURE; |
2325 | } | |
a2aa605d LP |
2326 | } |
2327 | ||
39ddc32a JJ |
2328 | static int parse_timeout(const char *arg1, char16_t **ret_timeout, size_t *ret_timeout_size) { |
2329 | char utf8[DECIMAL_STR_MAX(usec_t)]; | |
2330 | char16_t *encoded; | |
2331 | usec_t timeout; | |
2332 | int r; | |
2333 | ||
2334 | assert(arg1); | |
2335 | assert(ret_timeout); | |
2336 | assert(ret_timeout_size); | |
2337 | ||
2338 | if (streq(arg1, "menu-force")) | |
2339 | timeout = USEC_INFINITY; | |
2340 | else if (streq(arg1, "menu-hidden")) | |
2341 | timeout = 0; | |
2342 | else { | |
2343 | r = parse_time(arg1, &timeout, USEC_PER_SEC); | |
2344 | if (r < 0) | |
2345 | return log_error_errno(r, "Failed to parse timeout '%s': %m", arg1); | |
2346 | if (timeout != USEC_INFINITY && timeout > UINT32_MAX * USEC_PER_SEC) | |
2347 | log_warning("Timeout is too long and will be treated as 'menu-force' instead."); | |
2348 | } | |
2349 | ||
2350 | xsprintf(utf8, USEC_FMT, MIN(timeout / USEC_PER_SEC, UINT32_MAX)); | |
6663b509 | 2351 | |
39ddc32a JJ |
2352 | encoded = utf8_to_utf16(utf8, strlen(utf8)); |
2353 | if (!encoded) | |
2354 | return log_oom(); | |
6663b509 | 2355 | |
39ddc32a JJ |
2356 | *ret_timeout = encoded; |
2357 | *ret_timeout_size = char16_strlen(encoded) * 2 + 2; | |
2358 | return 0; | |
2359 | } | |
2360 | ||
c4b84347 | 2361 | static int parse_loader_entry_target_arg(const char *arg1, char16_t **ret_target, size_t *ret_target_size) { |
6663b509 | 2362 | char16_t *encoded = NULL; |
c4b84347 | 2363 | int r; |
6663b509 LP |
2364 | |
2365 | assert(arg1); | |
2366 | assert(ret_target); | |
2367 | assert(ret_target_size); | |
2368 | ||
c4b84347 | 2369 | if (streq(arg1, "@current")) { |
e6f055cb | 2370 | r = efi_get_variable(EFI_LOADER_VARIABLE(LoaderEntrySelected), NULL, (void *) ret_target, ret_target_size); |
c4b84347 ДГ |
2371 | if (r < 0) |
2372 | return log_error_errno(r, "Failed to get EFI variable 'LoaderEntrySelected': %m"); | |
6663b509 | 2373 | |
c4b84347 | 2374 | } else if (streq(arg1, "@oneshot")) { |
e6f055cb | 2375 | r = efi_get_variable(EFI_LOADER_VARIABLE(LoaderEntryOneShot), NULL, (void *) ret_target, ret_target_size); |
c4b84347 ДГ |
2376 | if (r < 0) |
2377 | return log_error_errno(r, "Failed to get EFI variable 'LoaderEntryOneShot': %m"); | |
6663b509 | 2378 | |
c4b84347 | 2379 | } else if (streq(arg1, "@default")) { |
e6f055cb | 2380 | r = efi_get_variable(EFI_LOADER_VARIABLE(LoaderEntryDefault), NULL, (void *) ret_target, ret_target_size); |
c4b84347 ДГ |
2381 | if (r < 0) |
2382 | return log_error_errno(r, "Failed to get EFI variable 'LoaderEntryDefault': %m"); | |
6663b509 | 2383 | |
ee4fd9cb | 2384 | } else if (arg1[0] == '@' && !streq(arg1, "@saved")) |
6663b509 LP |
2385 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unsupported special entry identifier: %s", arg1); |
2386 | else { | |
c4b84347 ДГ |
2387 | encoded = utf8_to_utf16(arg1, strlen(arg1)); |
2388 | if (!encoded) | |
2389 | return log_oom(); | |
6663b509 | 2390 | |
c4b84347 ДГ |
2391 | *ret_target = encoded; |
2392 | *ret_target_size = char16_strlen(encoded) * 2 + 2; | |
2393 | } | |
6663b509 | 2394 | |
c4b84347 ДГ |
2395 | return 0; |
2396 | } | |
2397 | ||
39ddc32a | 2398 | static int verb_set_efivar(int argc, char *argv[], void *userdata) { |
d88c96ff LP |
2399 | int r; |
2400 | ||
80a2381d LB |
2401 | if (arg_root) |
2402 | return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), | |
2403 | "Acting on %s, skipping EFI variable setup.", | |
2404 | arg_image ? "image" : "root directory"); | |
2405 | ||
baaa35ad ZJS |
2406 | if (!is_efi_boot()) |
2407 | return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), | |
2408 | "Not booted with UEFI."); | |
d88c96ff | 2409 | |
e6f055cb | 2410 | if (access(EFIVAR_PATH(EFI_LOADER_VARIABLE(LoaderInfo)), F_OK) < 0) { |
d88c96ff LP |
2411 | if (errno == ENOENT) { |
2412 | log_error_errno(errno, "Not booted with a supported boot loader."); | |
2413 | return -EOPNOTSUPP; | |
2414 | } | |
2415 | ||
2416 | return log_error_errno(errno, "Failed to detect whether boot loader supports '%s' operation: %m", argv[0]); | |
2417 | } | |
2418 | ||
baaa35ad ZJS |
2419 | if (detect_container() > 0) |
2420 | return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), | |
2421 | "'%s' operation not supported in a container.", | |
2422 | argv[0]); | |
d88c96ff | 2423 | |
baaa35ad ZJS |
2424 | if (!arg_touch_variables) |
2425 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), | |
39ddc32a | 2426 | "'%s' operation cannot be combined with --no-variables.", |
baaa35ad | 2427 | argv[0]); |
d88c96ff | 2428 | |
39ddc32a JJ |
2429 | const char *variable; |
2430 | int (* arg_parser)(const char *, char16_t **, size_t *); | |
2431 | ||
2432 | if (streq(argv[0], "set-default")) { | |
2433 | variable = EFI_LOADER_VARIABLE(LoaderEntryDefault); | |
2434 | arg_parser = parse_loader_entry_target_arg; | |
2435 | } else if (streq(argv[0], "set-oneshot")) { | |
2436 | variable = EFI_LOADER_VARIABLE(LoaderEntryOneShot); | |
2437 | arg_parser = parse_loader_entry_target_arg; | |
2438 | } else if (streq(argv[0], "set-timeout")) { | |
2439 | variable = EFI_LOADER_VARIABLE(LoaderConfigTimeout); | |
2440 | arg_parser = parse_timeout; | |
2441 | } else if (streq(argv[0], "set-timeout-oneshot")) { | |
2442 | variable = EFI_LOADER_VARIABLE(LoaderConfigTimeoutOneShot); | |
2443 | arg_parser = parse_timeout; | |
2444 | } else | |
2445 | assert_not_reached(); | |
d88c96ff LP |
2446 | |
2447 | if (isempty(argv[1])) { | |
e6f055cb | 2448 | r = efi_set_variable(variable, NULL, 0); |
d88c96ff | 2449 | if (r < 0 && r != -ENOENT) |
e6f055cb | 2450 | return log_error_errno(r, "Failed to remove EFI variable '%s': %m", variable); |
d88c96ff | 2451 | } else { |
39ddc32a JJ |
2452 | _cleanup_free_ char16_t *value = NULL; |
2453 | size_t value_size = 0; | |
d88c96ff | 2454 | |
39ddc32a | 2455 | r = arg_parser(argv[1], &value, &value_size); |
c4b84347 ДГ |
2456 | if (r < 0) |
2457 | return r; | |
39ddc32a | 2458 | r = efi_set_variable(variable, value, value_size); |
d88c96ff | 2459 | if (r < 0) |
e6f055cb | 2460 | return log_error_errno(r, "Failed to update EFI variable '%s': %m", variable); |
d88c96ff LP |
2461 | } |
2462 | ||
2463 | return 0; | |
2464 | } | |
2465 | ||
e44c3229 LP |
2466 | static int verb_random_seed(int argc, char *argv[], void *userdata) { |
2467 | int r; | |
2468 | ||
80a2381d | 2469 | r = find_esp_and_warn(arg_root, arg_esp_path, false, &arg_esp_path, NULL, NULL, NULL, NULL, NULL); |
351de38e LP |
2470 | if (r == -ENOKEY) { |
2471 | /* find_esp_and_warn() doesn't warn about ENOKEY, so let's do that on our own */ | |
2472 | if (!arg_graceful) | |
2473 | return log_error_errno(r, "Unable to find ESP."); | |
2474 | ||
2475 | log_notice("No ESP found, not initializing random seed."); | |
2476 | return 0; | |
2477 | } | |
e44c3229 LP |
2478 | if (r < 0) |
2479 | return r; | |
2480 | ||
2481 | r = install_random_seed(arg_esp_path); | |
2482 | if (r < 0) | |
2483 | return r; | |
2484 | ||
2485 | (void) sync_everything(); | |
2486 | return 0; | |
2487 | } | |
2488 | ||
2536752d | 2489 | static int verb_systemd_efi_options(int argc, char *argv[], void *userdata) { |
4e5aa791 ZJS |
2490 | int r; |
2491 | ||
2492 | if (argc == 1) { | |
ad2d6880 | 2493 | _cleanup_free_ char *line = NULL, *new = NULL; |
4e5aa791 | 2494 | |
2536752d | 2495 | r = systemd_efi_options_variable(&line); |
ad2d6880 ZJS |
2496 | if (r == -ENODATA) |
2497 | log_debug("No SystemdOptions EFI variable present in cache."); | |
2498 | else if (r < 0) | |
2499 | return log_error_errno(r, "Failed to read SystemdOptions EFI variable from cache: %m"); | |
2500 | else | |
2501 | puts(line); | |
2502 | ||
2503 | r = systemd_efi_options_efivarfs_if_newer(&new); | |
2504 | if (r == -ENODATA) { | |
2505 | if (line) | |
2506 | log_notice("Note: SystemdOptions EFI variable has been removed since boot."); | |
2507 | } else if (r < 0) | |
2508 | log_warning_errno(r, "Failed to check SystemdOptions EFI variable in efivarfs, ignoring: %m"); | |
2509 | else if (new && !streq_ptr(line, new)) | |
2510 | log_notice("Note: SystemdOptions EFI variable has been modified since boot. New value: %s", | |
2511 | new); | |
4e5aa791 | 2512 | } else { |
e6f055cb | 2513 | r = efi_set_variable_string(EFI_SYSTEMD_VARIABLE(SystemdOptions), argv[1]); |
4e5aa791 ZJS |
2514 | if (r < 0) |
2515 | return log_error_errno(r, "Failed to set SystemdOptions EFI variable: %m"); | |
2516 | } | |
2517 | ||
2518 | return 0; | |
2519 | } | |
2520 | ||
002914e6 LP |
2521 | static int verb_reboot_to_firmware(int argc, char *argv[], void *userdata) { |
2522 | int r; | |
2523 | ||
2524 | if (argc < 2) { | |
2525 | r = efi_get_reboot_to_firmware(); | |
2526 | if (r > 0) { | |
2527 | puts("active"); | |
2528 | return EXIT_SUCCESS; /* success */ | |
2529 | } | |
2530 | if (r == 0) { | |
2531 | puts("supported"); | |
2532 | return 1; /* recognizable error #1 */ | |
2533 | } | |
2534 | if (r == -EOPNOTSUPP) { | |
2535 | puts("not supported"); | |
2536 | return 2; /* recognizable error #2 */ | |
2537 | } | |
2538 | ||
2539 | log_error_errno(r, "Failed to query reboot-to-firmware state: %m"); | |
2540 | return 3; /* other kind of error */ | |
2541 | } else { | |
2542 | r = parse_boolean(argv[1]); | |
2543 | if (r < 0) | |
2544 | return log_error_errno(r, "Failed to parse argument: %s", argv[1]); | |
2545 | ||
2546 | r = efi_set_reboot_to_firmware(r); | |
2547 | if (r < 0) | |
2548 | return log_error_errno(r, "Failed to set reboot-to-firmware option: %m"); | |
2549 | ||
2550 | return 0; | |
2551 | } | |
2552 | } | |
2553 | ||
2f2c539c | 2554 | static int bootctl_main(int argc, char *argv[]) { |
2f2c539c | 2555 | static const Verb verbs[] = { |
2536752d ZJS |
2556 | { "help", VERB_ANY, VERB_ANY, 0, help }, |
2557 | { "status", VERB_ANY, 1, VERB_DEFAULT, verb_status }, | |
2558 | { "install", VERB_ANY, 1, 0, verb_install }, | |
2559 | { "update", VERB_ANY, 1, 0, verb_install }, | |
2560 | { "remove", VERB_ANY, 1, 0, verb_remove }, | |
2561 | { "is-installed", VERB_ANY, 1, 0, verb_is_installed }, | |
2562 | { "list", VERB_ANY, 1, 0, verb_list }, | |
39ddc32a JJ |
2563 | { "set-default", 2, 2, 0, verb_set_efivar }, |
2564 | { "set-oneshot", 2, 2, 0, verb_set_efivar }, | |
2565 | { "set-timeout", 2, 2, 0, verb_set_efivar }, | |
2566 | { "set-timeout-oneshot", 2, 2, 0, verb_set_efivar }, | |
2536752d ZJS |
2567 | { "random-seed", VERB_ANY, 1, 0, verb_random_seed }, |
2568 | { "systemd-efi-options", VERB_ANY, 2, 0, verb_systemd_efi_options }, | |
002914e6 | 2569 | { "reboot-to-firmware", VERB_ANY, 2, 0, verb_reboot_to_firmware }, |
2f2c539c LP |
2570 | {} |
2571 | }; | |
2572 | ||
2573 | return dispatch_verb(argc, argv, verbs, NULL); | |
2574 | } | |
2575 | ||
608f8ec9 | 2576 | static int run(int argc, char *argv[]) { |
80a2381d | 2577 | _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; |
80a2381d | 2578 | _cleanup_(umount_and_rmdir_and_freep) char *unlink_dir = NULL; |
601185b4 | 2579 | int r; |
7b4d7cc0 KS |
2580 | |
2581 | log_parse_environment(); | |
2582 | log_open(); | |
2583 | ||
341890de | 2584 | /* If we run in a container, automatically turn off EFI file system access */ |
2f2c539c LP |
2585 | if (detect_container() > 0) |
2586 | arg_touch_variables = false; | |
2587 | ||
7b4d7cc0 | 2588 | r = parse_argv(argc, argv); |
601185b4 | 2589 | if (r <= 0) |
608f8ec9 | 2590 | return r; |
57db6f18 | 2591 | |
80a2381d LB |
2592 | /* Open up and mount the image */ |
2593 | if (arg_image) { | |
2594 | assert(!arg_root); | |
2595 | ||
2596 | r = mount_image_privately_interactively( | |
2597 | arg_image, | |
2598 | DISSECT_IMAGE_GENERIC_ROOT | | |
2599 | DISSECT_IMAGE_RELAX_VAR_CHECK, | |
2600 | &unlink_dir, | |
e330f97a | 2601 | &loop_device); |
80a2381d LB |
2602 | if (r < 0) |
2603 | return r; | |
2604 | ||
2605 | arg_root = strdup(unlink_dir); | |
2606 | if (!arg_root) | |
2607 | return log_oom(); | |
2608 | } | |
2609 | ||
608f8ec9 | 2610 | return bootctl_main(argc, argv); |
7b4d7cc0 | 2611 | } |
608f8ec9 | 2612 | |
002914e6 | 2613 | DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); |