1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
5 #include "blockdev-util.h"
7 #include "bootctl-install.h"
8 #include "bootctl-random-seed.h"
9 #include "bootctl-reboot-to-firmware.h"
10 #include "bootctl-set-efivar.h"
11 #include "bootctl-status.h"
12 #include "bootctl-systemd-efi-options.h"
13 #include "bootctl-uki.h"
15 #include "devnum-util.h"
16 #include "dissect-image.h"
19 #include "main-func.h"
20 #include "mount-util.h"
22 #include "parse-argument.h"
23 #include "pretty-print.h"
26 #include "varlink-io.systemd.BootControl.h"
30 /* EFI_BOOT_OPTION_DESCRIPTION_MAX sets the maximum length for the boot option description
31 * stored in NVRAM. The UEFI spec does not specify a minimum or maximum length for this
32 * string, but we limit the length to something reasonable to prevent from the firmware
33 * having to deal with a potentially too long string. */
34 #define EFI_BOOT_OPTION_DESCRIPTION_MAX ((size_t) 255)
36 char *arg_esp_path
= NULL
;
37 char *arg_xbootldr_path
= NULL
;
38 bool arg_print_esp_path
= false;
39 bool arg_print_dollar_boot_path
= false;
40 unsigned arg_print_root_device
= 0;
41 bool arg_touch_variables
= true;
42 PagerFlags arg_pager_flags
= 0;
43 bool arg_graceful
= false;
44 bool arg_quiet
= false;
45 int arg_make_entry_directory
= false; /* tri-state: < 0 for automatic logic */
46 sd_id128_t arg_machine_id
= SD_ID128_NULL
;
47 char *arg_install_layout
= NULL
;
48 BootEntryTokenType arg_entry_token_type
= BOOT_ENTRY_TOKEN_AUTO
;
49 char *arg_entry_token
= NULL
;
50 JsonFormatFlags arg_json_format_flags
= JSON_FORMAT_OFF
;
51 bool arg_arch_all
= false;
52 char *arg_root
= NULL
;
53 char *arg_image
= NULL
;
54 InstallSource arg_install_source
= ARG_INSTALL_SOURCE_AUTO
;
55 char *arg_efi_boot_option_description
= NULL
;
56 bool arg_dry_run
= false;
57 ImagePolicy
*arg_image_policy
= NULL
;
58 bool arg_varlink
= false;
60 STATIC_DESTRUCTOR_REGISTER(arg_esp_path
, freep
);
61 STATIC_DESTRUCTOR_REGISTER(arg_xbootldr_path
, freep
);
62 STATIC_DESTRUCTOR_REGISTER(arg_install_layout
, freep
);
63 STATIC_DESTRUCTOR_REGISTER(arg_entry_token
, freep
);
64 STATIC_DESTRUCTOR_REGISTER(arg_root
, freep
);
65 STATIC_DESTRUCTOR_REGISTER(arg_image
, freep
);
66 STATIC_DESTRUCTOR_REGISTER(arg_efi_boot_option_description
, freep
);
67 STATIC_DESTRUCTOR_REGISTER(arg_image_policy
, image_policy_freep
);
70 int unprivileged_mode
,
81 /* Find the ESP, and log about errors. Note that find_esp_and_warn() will log in all error cases on
82 * its own, except for ENOKEY (which is good, we want to show our own message in that case,
83 * suggesting use of --esp-path=) and EACCESS (only when we request unprivileged mode; in this case
84 * we simply eat up the error here, so that --list and --status work too, without noise about
87 r
= find_esp_and_warn(arg_root
, arg_esp_path
, unprivileged_mode
, &np
, ret_part
, ret_pstart
, ret_psize
, ret_uuid
, ret_devid
);
90 return log_full_errno(arg_quiet
? LOG_DEBUG
: LOG_INFO
, r
,
91 "Couldn't find EFI system partition, skipping.");
93 return log_error_errno(r
,
94 "Couldn't find EFI system partition. It is recommended to mount it to /boot or /efi.\n"
95 "Alternatively, use --esp-path= to specify path to mount point.");
100 free_and_replace(arg_esp_path
, np
);
101 log_debug("Using EFI System Partition at %s.", arg_esp_path
);
106 int acquire_xbootldr(
107 int unprivileged_mode
,
108 sd_id128_t
*ret_uuid
,
114 r
= find_xbootldr_and_warn(arg_root
, arg_xbootldr_path
, unprivileged_mode
, &np
, ret_uuid
, ret_devid
);
116 log_debug_errno(r
, "Didn't find an XBOOTLDR partition, using the ESP as $BOOT.");
117 arg_xbootldr_path
= mfree(arg_xbootldr_path
);
120 *ret_uuid
= SD_ID128_NULL
;
128 free_and_replace(arg_xbootldr_path
, np
);
129 log_debug("Using XBOOTLDR partition at %s as $BOOT.", arg_xbootldr_path
);
134 static int help(int argc
, char *argv
[], void *userdata
) {
135 _cleanup_free_
char *link
= NULL
;
138 pager_open(arg_pager_flags
);
140 r
= terminal_urlify_man("bootctl", "1", &link
);
144 printf("%1$s [OPTIONS...] COMMAND ...\n"
145 "\n%5$sControl EFI firmware boot settings and manage boot loader.%6$s\n"
146 "\n%3$sGeneric EFI Firmware/Boot Loader Commands:%4$s\n"
147 " status Show status of installed boot loader and EFI variables\n"
148 " reboot-to-firmware [BOOL]\n"
149 " Query or set reboot-to-firmware EFI flag\n"
150 "\n%3$sBoot Loader Specification Commands:%4$s\n"
151 " list List boot loader entries\n"
152 " unlink ID Remove boot loader entry\n"
153 " cleanup Remove files in ESP not referenced in any boot entry\n"
154 "\n%3$sBoot Loader Interface Commands:%4$s\n"
155 " set-default ID Set default boot loader entry\n"
156 " set-oneshot ID Set default boot loader entry, for next boot only\n"
157 " set-timeout SECONDS Set the menu timeout\n"
158 " set-timeout-oneshot SECONDS\n"
159 " Set the menu timeout for the next boot only\n"
160 "\n%3$ssystemd-boot Commands:%4$s\n"
161 " install Install systemd-boot to the ESP and EFI variables\n"
162 " update Update systemd-boot in the ESP and EFI variables\n"
163 " remove Remove systemd-boot from the ESP and EFI variables\n"
164 " is-installed Test whether systemd-boot is installed in the ESP\n"
165 " random-seed Initialize or refresh random seed in ESP and EFI\n"
167 "\n%3$sKernel Image Commands:%4$s\n"
168 " kernel-identify Identify kernel image type\n"
169 " kernel-inspect Prints details about the kernel image\n"
170 "\n%3$sOptions:%4$s\n"
171 " -h --help Show this help\n"
172 " --version Print version\n"
173 " --esp-path=PATH Path to the EFI System Partition (ESP)\n"
174 " --boot-path=PATH Path to the $BOOT partition\n"
175 " --root=PATH Operate on an alternate filesystem root\n"
176 " --image=PATH Operate on disk image as filesystem root\n"
177 " --image-policy=POLICY\n"
178 " Specify disk image dissection policy\n"
179 " --install-source=auto|image|host\n"
180 " Where to pick files when using --root=/--image=\n"
181 " -p --print-esp-path Print path to the EFI System Partition mount point\n"
182 " -x --print-boot-path Print path to the $BOOT partition mount point\n"
183 " -R --print-root-device\n"
184 " Print path to the block device node backing the\n"
185 " root file system (returns e.g. /dev/nvme0n1p5)\n"
186 " -RR Print path to the whole disk block device node\n"
187 " backing the root FS (returns e.g. /dev/nvme0n1)\n"
188 " --no-variables Don't touch EFI variables\n"
189 " --no-pager Do not pipe output into a pager\n"
190 " --graceful Don't fail when the ESP cannot be found or EFI\n"
191 " variables cannot be written\n"
192 " -q --quiet Suppress output\n"
193 " --make-entry-directory=yes|no|auto\n"
194 " Create $BOOT/ENTRY-TOKEN/ directory\n"
195 " --entry-token=machine-id|os-id|os-image-id|auto|literal:…\n"
196 " Entry token to use for this installation\n"
197 " --json=pretty|short|off\n"
198 " Generate JSON output\n"
199 " --all-architectures\n"
200 " Install all supported EFI architectures\n"
201 " --efi-boot-option-description=DESCRIPTION\n"
202 " Description of the entry in the boot option list\n"
203 " --dry-run Dry run (unlink and cleanup)\n"
204 "\nSee the %2$s for details.\n",
205 program_invocation_short_name
,
215 static int parse_argv(int argc
, char *argv
[]) {
217 ARG_ESP_PATH
= 0x100,
227 ARG_MAKE_ENTRY_DIRECTORY
,
231 ARG_EFI_BOOT_OPTION_DESCRIPTION
,
235 static const struct option options
[] = {
236 { "help", no_argument
, NULL
, 'h' },
237 { "version", no_argument
, NULL
, ARG_VERSION
},
238 { "esp-path", required_argument
, NULL
, ARG_ESP_PATH
},
239 { "path", required_argument
, NULL
, ARG_ESP_PATH
}, /* Compatibility alias */
240 { "boot-path", required_argument
, NULL
, ARG_BOOT_PATH
},
241 { "root", required_argument
, NULL
, ARG_ROOT
},
242 { "image", required_argument
, NULL
, ARG_IMAGE
},
243 { "image-policy", required_argument
, NULL
, ARG_IMAGE_POLICY
},
244 { "install-source", required_argument
, NULL
, ARG_INSTALL_SOURCE
},
245 { "print-esp-path", no_argument
, NULL
, 'p' },
246 { "print-path", no_argument
, NULL
, 'p' }, /* Compatibility alias */
247 { "print-boot-path", no_argument
, NULL
, 'x' },
248 { "print-root-device", no_argument
, NULL
, 'R' },
249 { "no-variables", no_argument
, NULL
, ARG_NO_VARIABLES
},
250 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
251 { "graceful", no_argument
, NULL
, ARG_GRACEFUL
},
252 { "quiet", no_argument
, NULL
, 'q' },
253 { "make-entry-directory", required_argument
, NULL
, ARG_MAKE_ENTRY_DIRECTORY
},
254 { "make-machine-id-directory", required_argument
, NULL
, ARG_MAKE_ENTRY_DIRECTORY
}, /* Compatibility alias */
255 { "entry-token", required_argument
, NULL
, ARG_ENTRY_TOKEN
},
256 { "json", required_argument
, NULL
, ARG_JSON
},
257 { "all-architectures", no_argument
, NULL
, ARG_ARCH_ALL
},
258 { "efi-boot-option-description", required_argument
, NULL
, ARG_EFI_BOOT_OPTION_DESCRIPTION
},
259 { "dry-run", no_argument
, NULL
, ARG_DRY_RUN
},
268 while ((c
= getopt_long(argc
, argv
, "hpxRq", options
, NULL
)) >= 0)
279 r
= free_and_strdup(&arg_esp_path
, optarg
);
285 r
= free_and_strdup(&arg_xbootldr_path
, optarg
);
291 r
= parse_path_argument(optarg
, /* suppress_root= */ true, &arg_root
);
297 r
= parse_path_argument(optarg
, /* suppress_root= */ false, &arg_image
);
302 case ARG_IMAGE_POLICY
:
303 r
= parse_image_policy_argument(optarg
, &arg_image_policy
);
308 case ARG_INSTALL_SOURCE
:
309 if (streq(optarg
, "auto"))
310 arg_install_source
= ARG_INSTALL_SOURCE_AUTO
;
311 else if (streq(optarg
, "image"))
312 arg_install_source
= ARG_INSTALL_SOURCE_IMAGE
;
313 else if (streq(optarg
, "host"))
314 arg_install_source
= ARG_INSTALL_SOURCE_HOST
;
316 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
317 "Unexpected parameter for --install-source=: %s", optarg
);
322 arg_print_esp_path
= true;
326 arg_print_dollar_boot_path
= true;
330 arg_print_root_device
++;
333 case ARG_NO_VARIABLES
:
334 arg_touch_variables
= false;
338 arg_pager_flags
|= PAGER_DISABLE
;
349 case ARG_ENTRY_TOKEN
:
350 r
= parse_boot_entry_token_type(optarg
, &arg_entry_token_type
, &arg_entry_token
);
355 case ARG_MAKE_ENTRY_DIRECTORY
:
356 if (streq(optarg
, "auto")) /* retained for backwards compatibility */
357 arg_make_entry_directory
= -1; /* yes if machine-id is permanent */
359 r
= parse_boolean_argument("--make-entry-directory=", optarg
, NULL
);
363 arg_make_entry_directory
= r
;
368 r
= parse_json_argument(optarg
, &arg_json_format_flags
);
377 case ARG_EFI_BOOT_OPTION_DESCRIPTION
:
378 if (isempty(optarg
) || !(string_is_safe(optarg
) && utf8_is_valid(optarg
))) {
379 _cleanup_free_
char *escaped
= NULL
;
381 escaped
= cescape(optarg
);
382 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
383 "Invalid --efi-boot-option-description=: %s", strna(escaped
));
385 if (strlen(optarg
) > EFI_BOOT_OPTION_DESCRIPTION_MAX
)
386 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
387 "--efi-boot-option-description= too long: %zu > %zu",
388 strlen(optarg
), EFI_BOOT_OPTION_DESCRIPTION_MAX
);
389 r
= free_and_strdup_warn(&arg_efi_boot_option_description
, optarg
);
402 assert_not_reached();
405 if (!!arg_print_esp_path
+ !!arg_print_dollar_boot_path
+ (arg_print_root_device
> 0) > 1)
406 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
407 "--print-esp-path/-p, --print-boot-path/-x, --print-root-device=/-R cannot be combined.");
409 if ((arg_root
|| arg_image
) && argv
[optind
] && !STR_IN_SET(argv
[optind
], "status", "list",
410 "install", "update", "remove", "is-installed", "random-seed", "unlink", "cleanup"))
411 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
412 "Options --root= and --image= are not supported with verb %s.",
415 if (arg_root
&& arg_image
)
416 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Please specify either --root= or --image=, the combination of both is not supported.");
418 if (arg_install_source
!= ARG_INSTALL_SOURCE_AUTO
&& !arg_root
&& !arg_image
)
419 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "--install-from-host is only supported with --root= or --image=.");
421 if (arg_dry_run
&& argv
[optind
] && !STR_IN_SET(argv
[optind
], "unlink", "cleanup"))
422 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "--dry is only supported with --unlink or --cleanup");
424 r
= varlink_invocation(VARLINK_ALLOW_ACCEPT
);
426 return log_error_errno(r
, "Failed to check if invoked in Varlink mode: %m");
429 arg_pager_flags
|= PAGER_DISABLE
;
435 static int bootctl_main(int argc
, char *argv
[]) {
436 static const Verb verbs
[] = {
437 { "help", VERB_ANY
, VERB_ANY
, 0, help
},
438 { "status", VERB_ANY
, 1, VERB_DEFAULT
, verb_status
},
439 { "install", VERB_ANY
, 1, 0, verb_install
},
440 { "update", VERB_ANY
, 1, 0, verb_install
},
441 { "remove", VERB_ANY
, 1, 0, verb_remove
},
442 { "is-installed", VERB_ANY
, 1, 0, verb_is_installed
},
443 { "kernel-identify", 2, 2, 0, verb_kernel_identify
},
444 { "kernel-inspect", 2, 2, 0, verb_kernel_inspect
},
445 { "list", VERB_ANY
, 1, 0, verb_list
},
446 { "unlink", 2, 2, 0, verb_unlink
},
447 { "cleanup", VERB_ANY
, 1, 0, verb_list
},
448 { "set-default", 2, 2, 0, verb_set_efivar
},
449 { "set-oneshot", 2, 2, 0, verb_set_efivar
},
450 { "set-timeout", 2, 2, 0, verb_set_efivar
},
451 { "set-timeout-oneshot", 2, 2, 0, verb_set_efivar
},
452 { "random-seed", VERB_ANY
, 1, 0, verb_random_seed
},
453 { "systemd-efi-options", VERB_ANY
, 2, 0, verb_systemd_efi_options
},
454 { "reboot-to-firmware", VERB_ANY
, 2, 0, verb_reboot_to_firmware
},
458 return dispatch_verb(argc
, argv
, verbs
, NULL
);
461 static int run(int argc
, char *argv
[]) {
462 _cleanup_(loop_device_unrefp
) LoopDevice
*loop_device
= NULL
;
463 _cleanup_(umount_and_freep
) char *mounted_dir
= NULL
;
468 /* If we run in a container, automatically turn off EFI file system access */
469 if (detect_container() > 0)
470 arg_touch_variables
= false;
472 r
= parse_argv(argc
, argv
);
477 _cleanup_(varlink_server_unrefp
) VarlinkServer
*varlink_server
= NULL
;
479 /* Invocation as Varlink service */
481 r
= varlink_server_new(&varlink_server
, VARLINK_SERVER_ROOT_ONLY
);
483 return log_error_errno(r
, "Failed to allocate Varlink server: %m");
485 r
= varlink_server_add_interface(varlink_server
, &vl_interface_io_systemd_BootControl
);
487 return log_error_errno(r
, "Failed to add Varlink interface: %m");
489 r
= varlink_server_bind_method_many(
491 "io.systemd.BootControl.ListBootEntries", vl_method_list_boot_entries
,
492 "io.systemd.BootControl.SetRebootToFirmware", vl_method_set_reboot_to_firmware
,
493 "io.systemd.BootControl.GetRebootToFirmware", vl_method_get_reboot_to_firmware
);
495 return log_error_errno(r
, "Failed to bind Varlink methods: %m");
497 r
= varlink_server_loop_auto(varlink_server
);
499 return log_error_errno(r
, "Failed to run Varlink event loop: %m");
504 if (arg_print_root_device
> 0) {
505 _cleanup_free_
char *path
= NULL
;
508 r
= blockdev_get_root(LOG_ERR
, &devno
);
512 log_error("Root file system not backed by a (single) whole block device.");
513 return 80; /* some recognizable error code */
516 if (arg_print_root_device
> 1) {
517 r
= block_get_whole_disk(devno
, &devno
);
519 log_debug_errno(r
, "Unable to find whole block device for root block device, ignoring: %m");
522 r
= device_path_make_canonical(S_IFBLK
, devno
, &path
);
524 return log_error_errno(r
,
525 "Failed to format canonical device path for devno '" DEVNUM_FORMAT_STR
"': %m",
526 DEVNUM_FORMAT_VAL(devno
));
532 /* Open up and mount the image */
536 r
= mount_image_privately_interactively(
539 DISSECT_IMAGE_GENERIC_ROOT
|
540 DISSECT_IMAGE_RELAX_VAR_CHECK
|
541 DISSECT_IMAGE_ALLOW_USERSPACE_VERITY
,
543 /* ret_dir_fd= */ NULL
,
548 arg_root
= strdup(mounted_dir
);
553 return bootctl_main(argc
, argv
);
556 DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run
);