]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/boot/bootctl.c
man/run0: Describe environment variables set (#32622)
[thirdparty/systemd.git] / src / boot / bootctl.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <getopt.h>
4
5 #include "blockdev-util.h"
6 #include "bootctl.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"
14 #include "build.h"
15 #include "devnum-util.h"
16 #include "dissect-image.h"
17 #include "escape.h"
18 #include "find-esp.h"
19 #include "main-func.h"
20 #include "mount-util.h"
21 #include "pager.h"
22 #include "parse-argument.h"
23 #include "pretty-print.h"
24 #include "utf8.h"
25 #include "varlink.h"
26 #include "varlink-io.systemd.BootControl.h"
27 #include "verbs.h"
28 #include "virt.h"
29
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)
35
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;
59
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);
68
69 int acquire_esp(
70 int unprivileged_mode,
71 bool graceful,
72 uint32_t *ret_part,
73 uint64_t *ret_pstart,
74 uint64_t *ret_psize,
75 sd_id128_t *ret_uuid,
76 dev_t *ret_devid) {
77
78 char *np;
79 int r;
80
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
85 * this). */
86
87 r = find_esp_and_warn(arg_root, arg_esp_path, unprivileged_mode, &np, ret_part, ret_pstart, ret_psize, ret_uuid, ret_devid);
88 if (r == -ENOKEY) {
89 if (graceful)
90 return log_full_errno(arg_quiet ? LOG_DEBUG : LOG_INFO, r,
91 "Couldn't find EFI system partition, skipping.");
92
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.");
96 }
97 if (r < 0)
98 return r;
99
100 free_and_replace(arg_esp_path, np);
101 log_debug("Using EFI System Partition at %s.", arg_esp_path);
102
103 return 0;
104 }
105
106 int acquire_xbootldr(
107 int unprivileged_mode,
108 sd_id128_t *ret_uuid,
109 dev_t *ret_devid) {
110
111 char *np;
112 int r;
113
114 r = find_xbootldr_and_warn(arg_root, arg_xbootldr_path, unprivileged_mode, &np, ret_uuid, ret_devid);
115 if (r == -ENOKEY) {
116 log_debug_errno(r, "Didn't find an XBOOTLDR partition, using the ESP as $BOOT.");
117 arg_xbootldr_path = mfree(arg_xbootldr_path);
118
119 if (ret_uuid)
120 *ret_uuid = SD_ID128_NULL;
121 if (ret_devid)
122 *ret_devid = 0;
123 return 0;
124 }
125 if (r < 0)
126 return r;
127
128 free_and_replace(arg_xbootldr_path, np);
129 log_debug("Using XBOOTLDR partition at %s as $BOOT.", arg_xbootldr_path);
130
131 return 1;
132 }
133
134 static int help(int argc, char *argv[], void *userdata) {
135 _cleanup_free_ char *link = NULL;
136 int r;
137
138 pager_open(arg_pager_flags);
139
140 r = terminal_urlify_man("bootctl", "1", &link);
141 if (r < 0)
142 return log_oom();
143
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"
166 " variables\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,
206 link,
207 ansi_underline(),
208 ansi_normal(),
209 ansi_highlight(),
210 ansi_normal());
211
212 return 0;
213 }
214
215 static int parse_argv(int argc, char *argv[]) {
216 enum {
217 ARG_ESP_PATH = 0x100,
218 ARG_BOOT_PATH,
219 ARG_ROOT,
220 ARG_IMAGE,
221 ARG_IMAGE_POLICY,
222 ARG_INSTALL_SOURCE,
223 ARG_VERSION,
224 ARG_NO_VARIABLES,
225 ARG_NO_PAGER,
226 ARG_GRACEFUL,
227 ARG_MAKE_ENTRY_DIRECTORY,
228 ARG_ENTRY_TOKEN,
229 ARG_JSON,
230 ARG_ARCH_ALL,
231 ARG_EFI_BOOT_OPTION_DESCRIPTION,
232 ARG_DRY_RUN,
233 };
234
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 },
260 {}
261 };
262
263 int c, r;
264
265 assert(argc >= 0);
266 assert(argv);
267
268 while ((c = getopt_long(argc, argv, "hpxRq", options, NULL)) >= 0)
269 switch (c) {
270
271 case 'h':
272 help(0, NULL, NULL);
273 return 0;
274
275 case ARG_VERSION:
276 return version();
277
278 case ARG_ESP_PATH:
279 r = free_and_strdup(&arg_esp_path, optarg);
280 if (r < 0)
281 return log_oom();
282 break;
283
284 case ARG_BOOT_PATH:
285 r = free_and_strdup(&arg_xbootldr_path, optarg);
286 if (r < 0)
287 return log_oom();
288 break;
289
290 case ARG_ROOT:
291 r = parse_path_argument(optarg, /* suppress_root= */ true, &arg_root);
292 if (r < 0)
293 return r;
294 break;
295
296 case ARG_IMAGE:
297 r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image);
298 if (r < 0)
299 return r;
300 break;
301
302 case ARG_IMAGE_POLICY:
303 r = parse_image_policy_argument(optarg, &arg_image_policy);
304 if (r < 0)
305 return r;
306 break;
307
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;
315 else
316 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
317 "Unexpected parameter for --install-source=: %s", optarg);
318
319 break;
320
321 case 'p':
322 arg_print_esp_path = true;
323 break;
324
325 case 'x':
326 arg_print_dollar_boot_path = true;
327 break;
328
329 case 'R':
330 arg_print_root_device++;
331 break;
332
333 case ARG_NO_VARIABLES:
334 arg_touch_variables = false;
335 break;
336
337 case ARG_NO_PAGER:
338 arg_pager_flags |= PAGER_DISABLE;
339 break;
340
341 case ARG_GRACEFUL:
342 arg_graceful = true;
343 break;
344
345 case 'q':
346 arg_quiet = true;
347 break;
348
349 case ARG_ENTRY_TOKEN:
350 r = parse_boot_entry_token_type(optarg, &arg_entry_token_type, &arg_entry_token);
351 if (r < 0)
352 return r;
353 break;
354
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 */
358 else {
359 r = parse_boolean_argument("--make-entry-directory=", optarg, NULL);
360 if (r < 0)
361 return r;
362
363 arg_make_entry_directory = r;
364 }
365 break;
366
367 case ARG_JSON:
368 r = parse_json_argument(optarg, &arg_json_format_flags);
369 if (r <= 0)
370 return r;
371 break;
372
373 case ARG_ARCH_ALL:
374 arg_arch_all = true;
375 break;
376
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;
380
381 escaped = cescape(optarg);
382 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
383 "Invalid --efi-boot-option-description=: %s", strna(escaped));
384 }
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);
390 if (r < 0)
391 return r;
392 break;
393
394 case ARG_DRY_RUN:
395 arg_dry_run = true;
396 break;
397
398 case '?':
399 return -EINVAL;
400
401 default:
402 assert_not_reached();
403 }
404
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.");
408
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.",
413 argv[optind]);
414
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.");
417
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=.");
420
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");
423
424 r = varlink_invocation(VARLINK_ALLOW_ACCEPT);
425 if (r < 0)
426 return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m");
427 if (r > 0) {
428 arg_varlink = true;
429 arg_pager_flags |= PAGER_DISABLE;
430 }
431
432 return 1;
433 }
434
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 },
455 {}
456 };
457
458 return dispatch_verb(argc, argv, verbs, NULL);
459 }
460
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;
464 int r;
465
466 log_setup();
467
468 /* If we run in a container, automatically turn off EFI file system access */
469 if (detect_container() > 0)
470 arg_touch_variables = false;
471
472 r = parse_argv(argc, argv);
473 if (r <= 0)
474 return r;
475
476 if (arg_varlink) {
477 _cleanup_(varlink_server_unrefp) VarlinkServer *varlink_server = NULL;
478
479 /* Invocation as Varlink service */
480
481 r = varlink_server_new(&varlink_server, VARLINK_SERVER_ROOT_ONLY);
482 if (r < 0)
483 return log_error_errno(r, "Failed to allocate Varlink server: %m");
484
485 r = varlink_server_add_interface(varlink_server, &vl_interface_io_systemd_BootControl);
486 if (r < 0)
487 return log_error_errno(r, "Failed to add Varlink interface: %m");
488
489 r = varlink_server_bind_method_many(
490 varlink_server,
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);
494 if (r < 0)
495 return log_error_errno(r, "Failed to bind Varlink methods: %m");
496
497 r = varlink_server_loop_auto(varlink_server);
498 if (r < 0)
499 return log_error_errno(r, "Failed to run Varlink event loop: %m");
500
501 return EXIT_SUCCESS;
502 }
503
504 if (arg_print_root_device > 0) {
505 _cleanup_free_ char *path = NULL;
506 dev_t devno;
507
508 r = blockdev_get_root(LOG_ERR, &devno);
509 if (r < 0)
510 return r;
511 if (r == 0) {
512 log_error("Root file system not backed by a (single) whole block device.");
513 return 80; /* some recognizable error code */
514 }
515
516 if (arg_print_root_device > 1) {
517 r = block_get_whole_disk(devno, &devno);
518 if (r < 0)
519 log_debug_errno(r, "Unable to find whole block device for root block device, ignoring: %m");
520 }
521
522 r = device_path_make_canonical(S_IFBLK, devno, &path);
523 if (r < 0)
524 return log_error_errno(r,
525 "Failed to format canonical device path for devno '" DEVNUM_FORMAT_STR "': %m",
526 DEVNUM_FORMAT_VAL(devno));
527
528 puts(path);
529 return 0;
530 }
531
532 /* Open up and mount the image */
533 if (arg_image) {
534 assert(!arg_root);
535
536 r = mount_image_privately_interactively(
537 arg_image,
538 arg_image_policy,
539 DISSECT_IMAGE_GENERIC_ROOT |
540 DISSECT_IMAGE_RELAX_VAR_CHECK |
541 DISSECT_IMAGE_ALLOW_USERSPACE_VERITY,
542 &mounted_dir,
543 /* ret_dir_fd= */ NULL,
544 &loop_device);
545 if (r < 0)
546 return r;
547
548 arg_root = strdup(mounted_dir);
549 if (!arg_root)
550 return log_oom();
551 }
552
553 return bootctl_main(argc, argv);
554 }
555
556 DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);