bootctl: add "link" command

This commit is contained in:
Lennart Poettering
2026-05-01 07:10:31 +02:00
parent e68bf712be
commit e48d8a1ea0
11 changed files with 1515 additions and 5 deletions
File diff suppressed because it is too large Load Diff
+8
View File
@@ -0,0 +1,8 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "shared-forward.h"
int verb_link(int argc, char *argv[], uintptr_t data, void *userdata);
int vl_method_link(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata);
+122
View File
@@ -9,6 +9,7 @@
#include "bootctl.h"
#include "bootctl-cleanup.h"
#include "bootctl-install.h"
#include "bootctl-link.h"
#include "bootctl-random-seed.h"
#include "bootctl-reboot-to-firmware.h"
#include "bootctl-set-efivar.h"
@@ -16,6 +17,7 @@
#include "bootctl-uki.h"
#include "bootctl-unlink.h"
#include "bootctl-util.h"
#include "bootspec-util.h"
#include "build.h"
#include "crypto-util.h"
#include "devnum-util.h"
@@ -82,6 +84,12 @@ char *arg_private_key = NULL;
KeySourceType arg_private_key_source_type = OPENSSL_KEY_SOURCE_FILE;
char *arg_private_key_source = NULL;
bool arg_oldest = false;
uint64_t arg_keep_free = KEEP_FREE_BYTES_DEFAULT;
char *arg_entry_title = NULL;
char *arg_entry_version = NULL;
uint64_t arg_entry_commit = 0;
char **arg_extras = NULL;
unsigned arg_tries_left = UINT_MAX;
STATIC_DESTRUCTOR_REGISTER(arg_esp_path, freep);
STATIC_DESTRUCTOR_REGISTER(arg_xbootldr_path, freep);
@@ -95,6 +103,9 @@ STATIC_DESTRUCTOR_REGISTER(arg_certificate, freep);
STATIC_DESTRUCTOR_REGISTER(arg_certificate_source, freep);
STATIC_DESTRUCTOR_REGISTER(arg_private_key, freep);
STATIC_DESTRUCTOR_REGISTER(arg_private_key_source, freep);
STATIC_DESTRUCTOR_REGISTER(arg_entry_title, freep);
STATIC_DESTRUCTOR_REGISTER(arg_entry_version, freep);
STATIC_DESTRUCTOR_REGISTER(arg_extras, strv_freep);
static const char* const install_source_table[_INSTALL_SOURCE_MAX] = {
[INSTALL_SOURCE_IMAGE] = "image",
@@ -366,6 +377,9 @@ VERB_SCOPE_NOARG(, verb_list, "list",
VERB_SCOPE(, verb_unlink, "unlink", "ID", VERB_ANY, 2, 0,
"Remove boot loader entry");
VERB_SCOPE(, verb_link, "link", "KERNEL", 2, 2, 0,
"Create boot loader entry for specified kernel");
VERB_SCOPE_NOARG(, verb_cleanup, "cleanup",
"Remove files in ESP not referenced in any boot entry");
@@ -641,8 +655,115 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) {
return r;
break;
OPTION_LONG("keep-free", "BYTES",
"How much space to keep free on ESP/XBOOTLDR"):
if (isempty(opts.arg))
arg_keep_free = KEEP_FREE_BYTES_DEFAULT;
else {
r = parse_size(opts.arg, 1024, &arg_keep_free);
if (r < 0)
return log_error_errno(r, "Failed to parse --keep-free=: %s", opts.arg);
}
break;
OPTION_LONG("entry-title", "TITLE",
"Selects the entry title for the new boot menu entry"):
if (isempty(opts.arg)) {
arg_entry_title = mfree(arg_entry_title);
break;
}
if (!efi_loader_entry_title_valid(opts.arg))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid boot menu entry title: %s", opts.arg);
r = free_and_strdup_warn(&arg_entry_title, opts.arg);
if (r < 0)
return r;
break;
OPTION_LONG("entry-version", "VERSION",
"Selects the entry version for the new boot menu entry"):
if (isempty(opts.arg)) {
arg_entry_version = mfree(arg_entry_version);
break;
}
if (!version_is_valid_versionspec(opts.arg))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid boot menu entry version: %s", opts.arg);
r = free_and_strdup_warn(&arg_entry_version, opts.arg);
if (r < 0)
return r;
break;
OPTION_LONG("entry-commit", "NR",
"Selects the entry commit version for the new boot menu entry"): {
if (isempty(opts.arg)) {
arg_entry_commit = 0;
break;
}
uint64_t n;
r = safe_atou64(opts.arg, &n);
if (r < 0)
return log_error_errno(r, "Failed to parse --entry-commit= parameter: %s", opts.arg);
if (!entry_commit_valid(n))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid entry commit number.");
arg_entry_commit = n;
break;
}
OPTION('X', "extra", "PATH",
"Pass extra resource (confext, sysext, credential) to the invoked UKI of the boot menu entry"): {
if (isempty(opts.arg)) {
arg_extras = strv_free(arg_extras);
break;
}
_cleanup_free_ char *x = NULL;
r = parse_path_argument(opts.arg, /* suppress_root= */ false, &x);
if (r < 0)
return r;
_cleanup_free_ char *fn = NULL;
r = path_extract_filename(x, &fn);
if (r < 0)
return log_error_errno(r, "Failed to extract filename from '%s': %m", x);
if (!efi_loader_entry_resource_filename_valid(fn))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Extra filename '%s' is not suitable for reference in a boot menu entry.", fn);
r = strv_consume(&arg_extras, TAKE_PTR(x));
if (r < 0)
return log_oom();
strv_uniq(arg_extras);
break;
}
OPTION_LONG("tries-left", "NR",
"Set boot menu entries tries-left counter to the specified value"): {
if (isempty(opts.arg)) {
arg_tries_left = UINT_MAX;
break;
}
unsigned u;
r = safe_atou(opts.arg, &u);
if (r < 0)
return log_error_errno(r, "Failed to parse tries left counter: %s", opts.arg);
if (u >= UINT_MAX)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Tries left counter too large, refusing: %u", u);
arg_tries_left = u;
break;
}}
char **args = option_parser_get_args(&opts);
if (!!arg_print_esp_path + !!arg_print_dollar_boot_path + (arg_print_root_device > 0) + arg_print_loader_path + arg_print_stub_path + arg_print_efi_architecture > 1)
@@ -711,6 +832,7 @@ static int vl_server(void) {
"io.systemd.BootControl.SetRebootToFirmware", vl_method_set_reboot_to_firmware,
"io.systemd.BootControl.GetRebootToFirmware", vl_method_get_reboot_to_firmware,
"io.systemd.BootControl.Install", vl_method_install,
"io.systemd.BootControl.Link", vl_method_link,
"io.systemd.BootControl.Unlink", vl_method_unlink);
if (r < 0)
return log_error_errno(r, "Failed to bind Varlink methods: %m");
+10
View File
@@ -52,6 +52,12 @@ extern char *arg_private_key;
extern KeySourceType arg_private_key_source_type;
extern char *arg_private_key_source;
extern bool arg_oldest;
extern uint64_t arg_keep_free;
extern char *arg_entry_title;
extern char *arg_entry_version;
extern uint64_t arg_entry_commit;
extern char **arg_extras;
extern unsigned arg_tries_left;
static inline const char* arg_dollar_boot_path(void) {
/* $BOOT shall be the XBOOTLDR partition if it exists, and otherwise the ESP */
@@ -68,3 +74,7 @@ int acquire_xbootldr(int unprivileged_mode, int *ret_fd, sd_id128_t *ret_uuid, d
* string, but we limit the length to something reasonable to prevent from the firmware
* having to deal with a potentially too long string. */
#define EFI_BOOT_OPTION_DESCRIPTION_MAX ((size_t) 255)
/* Before we "materialize" a new entry, let's ensure we have this much space free still on the partition, by
* default */
#define KEEP_FREE_BYTES_DEFAULT (5U * U64_MB)
+1
View File
@@ -3,6 +3,7 @@
bootctl_sources = files(
'bootctl.c',
'bootctl-install.c',
'bootctl-link.c',
'bootctl-random-seed.c',
'bootctl-reboot-to-firmware.c',
'bootctl-set-efivar.c',