diff --git a/meson.build b/meson.build index 51a5fc82e3..5e97284786 100644 --- a/meson.build +++ b/meson.build @@ -4376,17 +4376,17 @@ executable( install : true, install_dir : rootlibexecdir) -kernel_install = custom_target( +kernel_install = executable( 'kernel-install', - input : kernel_install_in, - output : 'kernel-install', - command : [jinja2_cmdline, '@INPUT@', '@OUTPUT@'], + 'src/kernel-install/kernel-install.c', + include_directories : includes, + link_with : [libshared], + dependencies : [userspace, + versiondep], + install_rpath : rootpkglibdir, install : want_kernel_install, - install_mode : 'rwxr-xr-x', install_dir : bindir) -if want_kernel_install - public_programs += exe -endif +public_programs += kernel_install ukify = custom_target( 'ukify', @@ -4397,19 +4397,22 @@ ukify = custom_target( install_mode : 'rwxr-xr-x', install_dir : rootlibexecdir) if want_ukify - public_programs += ukify + public_programs += ukify endif if want_tests != 'false' and want_kernel_install - args = [kernel_install.full_path(), loaderentry_install, uki_copy_install] + args = [kernel_install.full_path(), loaderentry_install.full_path(), uki_copy_install] + deps = [kernel_install, loaderentry_install] if want_ukify and boot_stubs.length() > 0 - args += [ukify.full_path(), ukify_install, boot_stubs[0]] + args += [ukify.full_path(), ukify_install.full_path(), boot_stubs[0]] + deps += [ukify, ukify_install, boot_stubs[0]] endif test('test-kernel-install', test_kernel_install_sh, env : test_env, - args : args) + args : args, + depends: deps) endif ############################################################ diff --git a/src/kernel-install/kernel-install.c b/src/kernel-install/kernel-install.c new file mode 100644 index 0000000000..de744fe1c4 --- /dev/null +++ b/src/kernel-install/kernel-install.c @@ -0,0 +1,1183 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#include "build.h" +#include "boot-entry.h" +#include "chase.h" +#include "conf-files.h" +#include "env-file.h" +#include "env-util.h" +#include "exec-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "find-esp.h" +#include "id128-util.h" +#include "kernel-image.h" +#include "main-func.h" +#include "mkdir.h" +#include "path-util.h" +#include "pretty-print.h" +#include "rm-rf.h" +#include "stat-util.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" +#include "tmpfile-util.h" +#include "verbs.h" + +static bool arg_verbose = false; + +typedef enum Action { + ACTION_ADD, + ACTION_REMOVE, + ACTION_INSPECT, + _ACTION_MAX, + _ACTION_INVALID = -EINVAL, +} Action; + +typedef enum Layout { + LAYOUT_AUTO, + LAYOUT_UKI, + LAYOUT_BLS, + LAYOUT_OTHER, + _LAYOUT_MAX, + _LAYOUT_INVALID = -EINVAL, +} Layout; + +static const char * const layout_table[_LAYOUT_MAX] = { + [LAYOUT_AUTO] = "auto", + [LAYOUT_UKI] = "uki", + [LAYOUT_BLS] = "bls", + [LAYOUT_OTHER] = "other", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP(layout, Layout); + +typedef struct Context { + int rfd; + Action action; + sd_id128_t machine_id; + bool machine_id_is_random; + KernelImageType kernel_image_type; + Layout layout; + char *layout_other; + char *conf_root; + char *boot_root; + BootEntryTokenType entry_token_type; + char *entry_token; + char *entry_dir; + char *version; + char *kernel; + char **initrds; + char *initrd_generator; + char *uki_generator; + char *staging_area; + char **plugins; + char **argv; + char **envp; +} Context; + +static void context_done(Context *c) { + assert(c); + + free(c->layout_other); + free(c->conf_root); + free(c->boot_root); + free(c->entry_token); + free(c->entry_dir); + free(c->version); + free(c->kernel); + strv_free(c->initrds); + free(c->initrd_generator); + free(c->uki_generator); + if (c->action == ACTION_INSPECT) + free(c->staging_area); + else + rm_rf_physical_and_free(c->staging_area); + strv_free(c->plugins); + strv_free(c->argv); + strv_free(c->envp); + + safe_close(c->rfd); +} + +static int context_open_root(Context *c) { + assert(c); + assert(c->rfd < 0); + + c->rfd = open("/", O_CLOEXEC | O_DIRECTORY | O_PATH); + if (c->rfd < 0) + return log_error_errno(errno, "Failed to open root directory: %m"); + + return 0; +} + +static const char* context_get_layout(const Context *c) { + assert(c); + assert(c->layout >= 0); + + return c->layout_other ?: layout_to_string(c->layout); +} + +static int context_set_layout(Context *c, const char *s, const char *source) { + Layout t; + + assert(c); + assert(source); + + if (c->layout >= 0 || !s) + return 0; + + assert(!c->layout_other); + + t = layout_from_string(s); + if (t >= 0) + c->layout = t; + else if (isempty(s)) + c->layout = LAYOUT_AUTO; + else { + c->layout_other = strdup(s); + if (!c->layout_other) + return log_oom(); + + c->layout = LAYOUT_OTHER; + } + + log_debug("layout=%s set via %s", context_get_layout(c), source); + return 1; +} + +static int context_set_machine_id(Context *c, const char *s, const char *source) { + int r; + + assert(c); + assert(source); + + if (!sd_id128_is_null(c->machine_id) || !s) + return 0; + + r = sd_id128_from_string(s, &c->machine_id); + if (r < 0) + return log_warning_errno(r, "Failed to parse machine ID specified via %s, ignoring.", source); + + if (sd_id128_is_null(c->machine_id)) + return 0; + + log_debug("MACHINE_ID=%s set via %s.", SD_ID128_TO_STRING(c->machine_id), source); + return 1; +} + +static int context_set_string(const char *s, const char *source, const char *name, char **dest) { + char *p; + + assert(source); + assert(name); + assert(dest); + + if (*dest || !s) + return 0; + + p = strdup(s); + if (!p) + return log_oom(); + + log_debug("%s (%s) set via %s.", name, p, source); + + *dest = p; + return 1; +} + +static int context_set_initrd_generator(Context *c, const char *s, const char *source) { + assert(c); + return context_set_string(s, source, "INITRD_GENERATOR", &c->initrd_generator); +} + +static int context_set_uki_generator(Context *c, const char *s, const char *source) { + assert(c); + return context_set_string(s, source, "UKI_GENERATOR", &c->uki_generator); +} + +static int context_set_version(Context *c, const char *s) { + assert(c); + + if (!filename_is_valid(s)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid version specified: %s", s); + + return context_set_string(s, "command line", "kernel version", &c->version); +} + +static int context_set_path(Context *c, int rfd, const char *s, const char *source, const char *name, char **dest) { + char *p; + int r; + + assert(c); + assert(source); + assert(name); + assert(dest); + + if (*dest || !s) + return 0; + + if (rfd >= 0) + r = chaseat(rfd, s, CHASE_AT_RESOLVE_IN_ROOT, &p, /* ret_fd = */ NULL); + else + r = chase(s, /* root = */ NULL, 0, &p, /* ret_fd = */ NULL); + if (r < 0) + return log_warning_errno(r, "Failed to chase path %s for %s specified via %s, ignoring: %m", + s, name, source); + + log_debug("%s (%s) set via %s.", name, p, source); + + *dest = p; + return 1; +} + +static int context_set_boot_root(Context *c, const char *s, const char *source) { + assert(c); + return context_set_path(c, c->rfd, s, source, "BOOT_ROOT", &c->boot_root); +} + +static int context_set_conf_root(Context *c, const char *s, const char *source) { + assert(c); + return context_set_path(c, c->rfd, s, source, "CONF_ROOT", &c->conf_root); +} + +static int context_set_kernel(Context *c, const char *s) { + assert(c); + /* The path specified via command line should be relative to CWD. */ + return context_set_path(c, AT_FDCWD, s, "command line", "kernel image file", &c->kernel); +} + +static int context_set_path_strv(Context *c, int rfd, char* const* strv, const char *source, const char *name, char ***dest) { + _cleanup_strv_free_ char **w = NULL; + int r; + + assert(c); + assert(source); + assert(name); + assert(dest); + + if (*dest) + return 0; + + STRV_FOREACH(s, strv) { + char *p; + + if (rfd >= 0) + r = chaseat(rfd, *s, CHASE_AT_RESOLVE_IN_ROOT, &p, /* ret_fd = */ NULL); + else + r = chase(*s, /* root = */ NULL, 0, &p, /* ret_fd = */ NULL); + if (r < 0) + return log_warning_errno(r, "Failed to chase path %s for %s specified via %s: %m", + *s, name, source); + + r = strv_consume(&w, p); + if (r < 0) + return log_oom(); + } + + if (strv_isempty(w)) + return 0; + + log_debug("%s set via %s", name, source); + + *dest = TAKE_PTR(w); + return 1; +} + +static int context_set_plugins(Context *c, const char *s, const char *source) { + _cleanup_strv_free_ char **v = NULL; + + assert(c); + + if (c->plugins || !s) + return 0; + + v = strv_split(s, NULL); + if (!v) + return log_oom(); + + return context_set_path_strv(c, c->rfd, v, source, "plugins", &c->plugins); +} + +static int context_set_initrds(Context *c, char* const* strv) { + assert(c); + return context_set_path_strv(c, AT_FDCWD, strv, "command line", "initrds", &c->initrds); +} + +static int context_load_environment(Context *c) { + assert(c); + + (void) context_set_machine_id(c, getenv("MACHINE_ID"), "environment"); + (void) context_set_boot_root(c, getenv("BOOT_ROOT"), "environment"); + (void) context_set_conf_root(c, getenv("KERNEL_INSTALL_CONF_ROOT"), "environment"); + (void) context_set_plugins(c, getenv("KERNEL_INSTALL_PLUGINS"), "environment"); + return 0; +} + +static int context_ensure_conf_root(Context *c) { + int r; + + assert(c); + + if (c->conf_root) + return 0; + + r = chaseat(c->rfd, "/etc/kernel", CHASE_AT_RESOLVE_IN_ROOT, &c->conf_root, /* ret_fd = */ NULL); + if (r < 0) + log_debug_errno(r, "Failed to chase /etc/kernel, ignoring: %m"); + + return 0; +} + +static int context_load_install_conf_one(Context *c, const char *path) { + _cleanup_close_ int fd = -EBADF; + _cleanup_free_ char + *conf = NULL, *machine_id = NULL, *boot_root = NULL, *layout = NULL, + *initrd_generator = NULL, *uki_generator = NULL; + int r; + + assert(c); + assert(path); + + conf = path_join(path, "install.conf"); + if (!conf) + return log_oom(); + + r = chaseat(c->rfd, conf, CHASE_AT_RESOLVE_IN_ROOT, NULL, &fd); + if (r == -ENOENT) + return 0; + if (r < 0) + return log_error_errno(r, "Failed to chase %s: %m", conf); + + log_debug("Loading %s…", conf); + + r = parse_env_file_fd(fd, conf, + "MACHINE_ID", &machine_id, + "BOOT_ROOT", &boot_root, + "layout", &layout, + "initrd_generator", &initrd_generator, + "uki_generator", &uki_generator); + if (r < 0) + return log_error_errno(r, "Failed to parse '%s': %m", conf); + + (void) context_set_machine_id(c, machine_id, conf); + (void) context_set_boot_root(c, boot_root, conf); + (void) context_set_layout(c, layout, conf); + (void) context_set_initrd_generator(c, initrd_generator, conf); + (void) context_set_uki_generator(c, uki_generator, conf); + + log_debug("Loaded %s.", conf); + return 1; +} + +static int context_load_install_conf(Context *c) { + int r; + + assert(c); + + if (c->conf_root) { + r = context_load_install_conf_one(c, c->conf_root); + if (r != 0) + return r; + } + + STRV_FOREACH(p, STRV_MAKE("/etc/kernel", "/usr/lib/kernel")) { + r = context_load_install_conf_one(c, *p); + if (r != 0) + return r; + } + + return 0; +} + +static int context_load_machine_info(Context *c) { + _cleanup_close_ int fd = -EBADF; + _cleanup_free_ char *machine_id = NULL; + static const char *path = "/etc/machine-info"; + int r; + + assert(c); + + /* If the user configured an explicit machine ID in /etc/machine-info to use for our purpose, we'll + * use that instead (for compatibility). */ + + if (!sd_id128_is_null(c->machine_id)) + return 0; + + r = chaseat(c->rfd, path, CHASE_AT_RESOLVE_IN_ROOT, NULL, &fd); + if (r == -ENOENT) + return 0; + if (r < 0) + return log_error_errno(r, "Failed to chase %s: %m", path); + + log_debug("Loading %s…", path); + + r = parse_env_file_fd(fd, path, + "KERNEL_INSTALL_MACHINE_ID", &machine_id); + if (r < 0) + return log_error_errno(r, "Failed to parse '%s': %m", path); + + (void) context_set_machine_id(c, machine_id, path); + return 0; +} + +static int context_load_machine_id(Context *c) { + int r; + + assert(c); + + r = id128_get_machine_at(c->rfd, &c->machine_id); + if (r < 0) { + if (ERRNO_IS_MACHINE_ID_UNSET(r)) + return 0; + return log_error_errno(r, "Failed to load machine ID from /etc/machine-id: %m"); + } + + log_debug("MACHINE_ID=%s set via /etc/machine-id.", SD_ID128_TO_STRING(c->machine_id)); + return 1; /* loaded */ +} + +static int context_ensure_machine_id(Context *c) { + int r; + + assert(c); + + if (!sd_id128_is_null(c->machine_id)) + return 0; + + /* If /etc/machine-id is initialized we'll use it. */ + r = context_load_machine_id(c); + if (r != 0) + return r; + + /* Otherwise we'll use a freshly generated one. */ + r = sd_id128_randomize(&c->machine_id); + if (r < 0) + return log_error_errno(r, "Failed to generate random ID: %m"); + + c->machine_id_is_random = true; + log_debug("New machine ID '%s' generated.", SD_ID128_TO_STRING(c->machine_id)); + return 0; +} + +static int context_acquire_xbootldr(Context *c) { + int r; + + assert(c); + assert(!c->boot_root); + + r = find_xbootldr_and_warn_at( + /* rfd = */ c->rfd, + /* path = */ NULL, + /* unprivileged_mode= */ -1, + /* ret_path = */ &c->boot_root, + /* ret_uuid = */ NULL, + /* ret_devid = */ NULL); + if (r == -ENOKEY) { + log_debug_errno(r, "Couldn't find an XBOOTLDR partition."); + return 0; + } + if (r == -EACCES && geteuid() != 0) + return log_error_errno(r, "Failed to determine XBOOTLDR partition: %m"); + if (r < 0) + return r; + + log_debug("Using XBOOTLDR partition at %s as $BOOT_ROOT.", c->boot_root); + return 1; /* found */ +} + +static int context_acquire_esp(Context *c) { + int r; + + assert(c); + assert(!c->boot_root); + + r = find_esp_and_warn_at( + /* rfd = */ c->rfd, + /* path = */ NULL, + /* unprivileged_mode= */ -1, + /* ret_path = */ &c->boot_root, + /* ret_part = */ NULL, + /* ret_pstart = */ NULL, + /* ret_psize = */ NULL, + /* ret_uuid = */ NULL, + /* ret_devid = */ NULL); + if (r == -ENOKEY) { + log_debug_errno(r, "Couldn't find EFI system partition, ignoring."); + return 0; + } + if (r == -EACCES && geteuid() != 0) + return log_error_errno(r, "Failed to determine EFI system partition: %m"); + if (r < 0) + return r; + + log_debug("Using EFI System Partition at %s as $BOOT_ROOT.", c->boot_root); + return 1; /* found */ +} + +static int context_ensure_boot_root(Context *c) { + int r; + + assert(c); + + /* If BOOT_ROOT is specified via environment or install.conf, then use it. */ + if (c->boot_root) + return 0; + + /* Otherwise, use XBOOTLDR partition, if mounted. */ + r = context_acquire_xbootldr(c); + if (r != 0) + return r; + + /* Otherwise, use EFI system partition, if mounted. */ + r = context_acquire_esp(c); + if (r != 0) + return r; + + /* If all else fails, use /boot. */ + r = chaseat(c->rfd, "/boot", CHASE_AT_RESOLVE_IN_ROOT, &c->boot_root, /* ret_fd = */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to chase '/boot': %m"); + + log_debug("KERNEL_INSTALL_BOOT_ROOT autodetection yielded no candidates, using \"%s\".", c->boot_root); + return 0; +} + +static int context_ensure_entry_token(Context *c) { + int r; + + assert(c); + + /* Now that we determined the machine ID to use, let's determine the "token" for the boot loader + * entry to generate. We use that for naming the directory below $BOOT where we want to place the + * kernel/initrd and related resources, as well for naming the .conf boot loader spec entry. + * Typically this is just the machine ID, but it can be anything else, too, if we are told so. */ + + r = boot_entry_token_ensure_at( + c->rfd, + c->conf_root, + c->machine_id, + c->machine_id_is_random, + &c->entry_token_type, + &c->entry_token); + if (r < 0) + return r; + + log_debug("Using entry token: %s", c->entry_token); + return 0; +} + +static int context_load_plugins(Context *c) { + int r; + + assert(c); + + if (c->plugins) + return 0; + + r = conf_files_list_strv_at( + &c->plugins, + ".install", + c->rfd, + CONF_FILES_EXECUTABLE | CONF_FILES_REGULAR | CONF_FILES_FILTER_MASKED, + STRV_MAKE_CONST("/etc/kernel/install.d", "/usr/lib/kernel/install.d")); + if (r < 0) + return log_error_errno(r, "Failed to find plugins: %m"); + + return 0; +} + +static int context_init(Context *c) { + int r; + + assert(c); + + r = context_open_root(c); + if (r < 0) + return r; + + r = context_load_environment(c); + if (r < 0) + return r; + + r = context_ensure_conf_root(c); + if (r < 0) + return r; + + r = context_load_install_conf(c); + if (r < 0) + return r; + + r = context_load_machine_info(c); + if (r < 0) + return r; + + r = context_ensure_machine_id(c); + if (r < 0) + return r; + + r = context_ensure_boot_root(c); + if (r < 0) + return r; + + r = context_ensure_entry_token(c); + if (r < 0) + return r; + + r = context_load_plugins(c); + if (r < 0) + return r; + + return 0; +} + +static int context_inspect_kernel(Context *c) { + assert(c); + + if (!c->kernel) + return 0; + + return inspect_kernel(c->rfd, c->kernel, &c->kernel_image_type, NULL, NULL, NULL); +} + +static int context_ensure_layout(Context *c) { + int r; + + assert(c); + assert(c->boot_root); + assert(c->entry_token); + + if (c->layout >= 0 && c->layout != LAYOUT_AUTO) + return 0; + + /* No layout configured by the administrator. Let's try to figure it out automatically from metadata + * already contained in $BOOT_ROOT. */ + + if (c->kernel_image_type == KERNEL_IMAGE_TYPE_UKI) { + c->layout = LAYOUT_UKI; + log_debug("Kernel image type is %s, using layout=%s.", + kernel_image_type_to_string(c->kernel_image_type), layout_to_string(c->layout)); + return 0; + } + + _cleanup_free_ char *srel_path = path_join(c->boot_root, "loader/entries.srel"); + if (!srel_path) + return log_oom(); + + _cleanup_free_ char *srel = NULL; + r = read_one_line_file_at(c->rfd, srel_path, &srel); + if (r >= 0) { + if (streq(srel, "type1")) + /* The loader/entries.srel file clearly indicates that the installed boot loader + * implements the proper standard upstream boot loader spec for Type #1 entries. + * Let's default to that, then. */ + c->layout = LAYOUT_BLS; + else + /* The loader/entries.srel file indicates some other spec is implemented and owns the + * /loader/entries/ directory. Since we have no idea what that means, let's stay away + * from it by default. */ + c->layout = LAYOUT_OTHER; + + log_debug("%s with '%s' found, using layout=%s.", srel_path, srel, layout_to_string(c->layout)); + return 0; + } else if (r != -ENOENT) + return log_error_errno(r, "Failed to read %s: %m", srel_path); + + _cleanup_free_ char *entry_token_path = path_join(c->boot_root, c->entry_token); + if (!entry_token_path) + return log_oom(); + + r = is_dir_full(c->rfd, entry_token_path, /* follow = */ false); + if (r < 0 && r != -ENOENT) + return log_error_errno(r, "Failed to check if '%s' is a directory: %m", entry_token_path); + if (r > 0) { + /* If the metadata in $BOOT_ROOT doesn't tell us anything, then check if the entry token + * directory already exists. If so, let's assume it's the standard boot loader spec, too. */ + c->layout = LAYOUT_BLS; + log_debug("%s exists, using layout=%s.", entry_token_path, layout_to_string(c->layout)); + return 0; + } + + /* There's no metadata in $BOOT_ROOT, and apparently no entry token directory installed? Then we + * really don't know anything. */ + c->layout = LAYOUT_OTHER; + log_debug("Entry-token directory not found, using layout=%s.", layout_to_string(c->layout)); + return 0; +} + +static int context_set_up_staging_area(Context *c) { + static const char *template = "/tmp/kernel-install.staging.XXXXXX"; + int r; + + assert(c); + + if (c->staging_area) + return 0; + + if (c->action == ACTION_INSPECT) { + /* This is only used for display. The directory will not be created. */ + c->staging_area = strdup(template); + if (!c->staging_area) + return log_oom(); + } else { + r = mkdtemp_malloc(template, &c->staging_area); + if (r < 0) + return log_error_errno(r, "Failed to create staging area: %m"); + } + + return 0; +} + +static int context_build_entry_dir(Context *c) { + assert(c); + assert(c->boot_root); + assert(c->entry_token); + assert(c->version || c->action == ACTION_INSPECT); + + if (c->entry_dir) + return 0; + + c->entry_dir = path_join(c->boot_root, c->entry_token, c->version ?: "KERNEL_VERSION"); + if (!c->entry_dir) + return log_oom(); + + log_debug("Using ENTRY_DIR=%s", c->entry_dir); + return 0; +} + +static bool context_should_make_entry_dir(Context *c) { + assert(c); + + /* Compatibility with earlier versions that used the presence of $BOOT_ROOT/$ENTRY_TOKEN to signal to + * 00-entry-directory to create $ENTRY_DIR to serve as the indication to use or to not use the BLS */ + + return c->layout == LAYOUT_BLS; +} + +static int context_make_entry_dir(Context *c) { + _cleanup_close_ int fd = -EBADF; + + assert(c); + assert(c->entry_dir); + + if (c->action != ACTION_ADD) + return 0; + + if (!context_should_make_entry_dir(c)) + return 0; + + log_debug("mkdir -p %s", c->entry_dir); + fd = chase_and_openat(c->rfd, c->entry_dir, CHASE_AT_RESOLVE_IN_ROOT | CHASE_MKDIR_0755, + O_CLOEXEC | O_CREAT | O_DIRECTORY | O_PATH, NULL); + if (fd < 0) + return log_error_errno(fd, "Failed to make directory '%s': %m", c->entry_dir); + + return 0; +} + +static int context_remove_entry_dir(Context *c) { + _cleanup_free_ char *p = NULL; + _cleanup_close_ int fd = -EBADF; + struct stat st; + int r; + + assert(c); + assert(c->entry_dir); + + if (c->action != ACTION_REMOVE) + return 0; + + if (!context_should_make_entry_dir(c)) + return 0; + + log_debug("rm -rf %s", c->entry_dir); + fd = chase_and_openat(c->rfd, c->entry_dir, CHASE_AT_RESOLVE_IN_ROOT, O_CLOEXEC | O_DIRECTORY, &p); + if (fd < 0) { + if (IN_SET(fd, -ENOTDIR, -ENOENT)) + return 0; + return log_debug_errno(fd, "Failed to chase and open %s, ignoring: %m", c->entry_dir); + } + + if (fstat(fd, &st) < 0) + return log_debug_errno(errno, "Failed to stat %s: %m", p); + + r = rm_rf_children(TAKE_FD(fd), REMOVE_PHYSICAL|REMOVE_MISSING_OK|REMOVE_CHMOD, &st); + if (r < 0) + log_debug_errno(r, "Failed to remove children of %s, ignoring: %m", p); + + if (unlinkat(c->rfd, p, AT_REMOVEDIR) < 0) + log_debug_errno(errno, "Failed to remove %s, ignoring: %m", p); + + return 0; +} + +static int context_build_arguments(Context *c) { + _cleanup_strv_free_ char **a = NULL; + const char *verb; + int r; + + assert(c); + assert(c->entry_dir); + + if (c->argv) + return 0; + + switch (c->action) { + case ACTION_ADD: + assert(c->version); + assert(c->kernel); + verb = "add"; + break; + + case ACTION_REMOVE: + assert(c->version); + assert(!c->kernel); + assert(!c->initrds); + verb = "remove"; + break; + + case ACTION_INSPECT: + assert(!c->version); + assert(!c->initrds); + verb = "add|remove"; + break; + + default: + assert_not_reached(); + } + + a = strv_new("dummy-arg", /* to make strv_free() works for this variable. */ + verb, + c->version ?: "KERNEL_VERSION", + c->entry_dir); + if (!a) + return log_oom(); + + if (c->action == ACTION_ADD) { + r = strv_extend(&a, c->kernel); + if (r < 0) + return log_oom(); + + r = strv_extend_strv(&a, c->initrds, /* filter_duplicates = */ false); + if (r < 0) + return log_oom(); + + } else if (c->action == ACTION_INSPECT) { + r = strv_extend(&a, "[KERNEL_IMAGE]"); + if (r < 0) + return log_oom(); + + r = strv_extend(&a, "[INITRD...]"); + if (r < 0) + return log_oom(); + } + + c->argv = TAKE_PTR(a); + return 0; +} + +static int context_build_environment(Context *c) { + _cleanup_strv_free_ char **e = NULL; + int r; + + assert(c); + + if (c->envp) + return 0; + + r = strv_env_assign_many(&e, + "LC_COLLATE", SYSTEMD_DEFAULT_LOCALE, + "KERNEL_INSTALL_VERBOSE", one_zero(arg_verbose), + "KERNEL_INSTALL_IMAGE_TYPE", kernel_image_type_to_string(c->kernel_image_type), + "KERNEL_INSTALL_MACHINE_ID", SD_ID128_TO_STRING(c->machine_id), + "KERNEL_INSTALL_ENTRY_TOKEN", c->entry_token, + "KERNEL_INSTALL_BOOT_ROOT", c->boot_root, + "KERNEL_INSTALL_LAYOUT", context_get_layout(c), + "KERNEL_INSTALL_INITRD_GENERATOR", strempty(c->initrd_generator), + "KERNEL_INSTALL_UKI_GENERATOR", strempty(c->uki_generator), + "KERNEL_INSTALL_STAGING_AREA", c->staging_area); + if (r < 0) + return log_error_errno(r, "Failed to build environment variables for plugins: %m"); + + c->envp = TAKE_PTR(e); + return 0; +} + +static int context_prepare_execution(Context *c) { + int r; + + assert(c); + + r = context_inspect_kernel(c); + if (r < 0) + return r; + + r = context_ensure_layout(c); + if (r < 0) + return r; + + r = context_set_up_staging_area(c); + if (r < 0) + return r; + + r = context_build_entry_dir(c); + if (r < 0) + return r; + + r = context_build_arguments(c); + if (r < 0) + return r; + + r = context_build_environment(c); + if (r < 0) + return r; + + return 0; +} + +static int context_execute(Context *c) { + int r; + + assert(c); + + r = context_make_entry_dir(c); + if (r < 0) + return r; + + if (DEBUG_LOGGING) { + _cleanup_free_ char *x = strv_join_full(c->plugins, "", "\n ", /* escape_separator = */ false); + log_debug("Using plugins: %s", strna(x)); + + _cleanup_free_ char *y = strv_join_full(c->envp, "", "\n ", /* escape_separator = */ false); + log_debug("Plugin environment: %s", strna(y)); + + _cleanup_free_ char *z = strv_join(strv_skip(c->argv, 1), " "); + log_debug("Plugin arguments: %s", strna(z)); + } + + r = execute_strv( + /* name = */ NULL, + c->plugins, + /* root = */ NULL, + USEC_INFINITY, + /* callbacks = */ NULL, + /* callback_args = */ NULL, + c->argv, + c->envp, + EXEC_DIR_SKIP_REMAINING); + if (r < 0) + return r; + + r = context_remove_entry_dir(c); + if (r < 0) + return r; + + return 0; +} + +static int verb_add(int argc, char *argv[], void *userdata) { + Context *c = ASSERT_PTR(userdata); + int r; + + assert(argc >= 3); + assert(argv); + + c->action = ACTION_ADD; + + r = context_set_version(c, argv[1]); + if (r < 0) + return r; + + r = context_set_kernel(c, argv[2]); + if (r < 0) + return r; + + r = context_set_initrds(c, strv_skip(argv, 3)); + if (r < 0) + return r; + + r = context_prepare_execution(c); + if (r < 0) + return r; + + return context_execute(c); +} + +static int run_as_installkernel(int argc, char *argv[], Context *c) { + /* kernel's install.sh invokes us as + * /sbin/installkernel + * We ignore the last two arguments. */ + if (optind + 2 > argc) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "'installkernel' command requires at least two arguments."); + + return verb_add(3, STRV_MAKE("add", argv[optind], argv[optind+1]), c); +} + +static int verb_remove(int argc, char *argv[], void *userdata) { + Context *c = ASSERT_PTR(userdata); + int r; + + assert(argc == 2); + assert(argv); + + c->action = ACTION_REMOVE; + + r = context_set_version(c, argv[1]); + if (r < 0) + return r; + + r = context_prepare_execution(c); + if (r < 0) + return r; + + return context_execute(c); +} + +static int verb_inspect(int argc, char *argv[], void *userdata) { + Context *c = ASSERT_PTR(userdata); + _cleanup_free_ char *joined = NULL; + int r; + + c->action = ACTION_INSPECT; + + r = context_prepare_execution(c); + if (r < 0) + return r; + + printf("%sBoot Loader Entries:%s\n", ansi_underline(), ansi_normal()); + printf(" $BOOT: %s\n", c->boot_root); + printf(" Token: %s\n", c->entry_token); + puts(""); + + printf("%sUsing plugins:%s\n", ansi_underline(), ansi_normal()); + strv_print_full(c->plugins, " "); + puts(""); + + printf("%sPlugin environment:%s\n", ansi_underline(), ansi_normal()); + strv_print_full(c->envp, " "); + puts(""); + + printf("%sPlugin arguments:%s\n", ansi_underline(), ansi_normal()); + joined = strv_join(strv_skip(c->argv, 1), " "); + printf(" %s\n", strna(joined)); + + return 0; +} + +static bool bypass(void) { + int r; + + r = getenv_bool("KERNEL_INSTALL_BYPASS"); + if (r < 0 && r != -ENXIO) + log_debug_errno(r, "Failed to parse $KERNEL_INSTALL_BYPASS, assuming no."); + if (r <= 0) + return false; + + log_debug("$KERNEL_INSTALL_BYPASS is enabled, skipping execution."); + return true; +} + +static int help(void) { + _cleanup_free_ char *link = NULL; + int r; + + r = terminal_urlify_man("kernel-install", "8", &link); + if (r < 0) + return log_oom(); + + printf("%1$s [OPTIONS...] COMMAND ...\n\n" + "%2$sAdd and remove kernel and initrd images to and from /boot%3$s\n" + "\nUsage:\n" + " %1$s [OPTIONS...] add KERNEL-VERSION KERNEL-IMAGE [INITRD-FILE...]\n" + " %1$s [OPTIONS...] remove KERNEL-VERSION\n" + " %1$s [OPTIONS...] inspect\n" + "\nOptions:\n" + " -h --help Show this help\n" + " --version Show package version\n" + " -v --verbose Increase verbosity\n" + "\nSee the %4$s for details.\n", + program_invocation_short_name, + ansi_highlight(), + ansi_normal(), + link); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + enum { + ARG_VERSION = 0x100, + }; + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "verbose", no_argument, NULL, 'v' }, + {} + }; + int t; + + assert(argc >= 0); + assert(argv); + + while ((t = getopt_long(argc, argv, "hv", options, NULL)) >= 0) + switch (t) { + case 'h': + return help(); + + case ARG_VERSION: + return version(); + + case 'v': + log_set_max_level(LOG_DEBUG); + arg_verbose = true; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached(); + } + + return 1; +} + +static int run(int argc, char* argv[]) { + static const Verb verbs[] = { + { "add", 3, VERB_ANY, 0, verb_add }, + { "remove", 2, 2, 0, verb_remove }, + { "inspect", 1, 1, VERB_DEFAULT, verb_inspect }, + {} + }; + _cleanup_(context_done) Context c = { + .rfd = -EBADF, + .action = _ACTION_INVALID, + .kernel_image_type = KERNEL_IMAGE_TYPE_UNKNOWN, + .layout = _LAYOUT_INVALID, + .entry_token_type = BOOT_ENTRY_TOKEN_AUTO, + }; + int r; + + log_setup(); + + if (bypass()) + return 0; + + r = parse_argv(argc, argv); + if (r <= 0) + return r; + + r = context_init(&c); + if (r < 0) + return r; + + if (invoked_as(argv, "installkernel")) + return run_as_installkernel(argc, argv, &c); + + return dispatch_verb(argc, argv, verbs, &c); +} + +DEFINE_MAIN_FUNCTION(run); diff --git a/src/kernel-install/kernel-install.in b/src/kernel-install/kernel-install.in deleted file mode 100755 index 611f67243c..0000000000 --- a/src/kernel-install/kernel-install.in +++ /dev/null @@ -1,403 +0,0 @@ -#!/bin/sh -# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*- -# ex: ts=8 sw=4 sts=4 et filetype=sh -# SPDX-License-Identifier: LGPL-2.1-or-later -# -# This file is part of systemd. -# -# systemd is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 2.1 of the License, or -# (at your option) any later version. -# -# systemd is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with systemd; If not, see . - -skip_remaining=77 - -set -e - -usage() -{ - echo "Usage:" - echo " kernel-install [OPTIONS...] add KERNEL-VERSION KERNEL-IMAGE [INITRD-FILE...]" - echo " kernel-install [OPTIONS...] remove KERNEL-VERSION" - echo " kernel-install [OPTIONS...] inspect" - echo "Options:" - echo " -h, --help Print this help and exit" - echo " --version Print version string and exit" - echo " -v, --verbose Increase verbosity" -} - -dropindirs_sort() -{ - suffix="$1" - shift - - for d; do - for i in "$d/"*"$suffix"; do - [ -e "$i" ] && echo "${i##*/}" - done - done | sort -Vu | while read -r f; do - for d; do - if [ -e "$d/$f" ]; then - [ -x "$d/$f" ] && echo "$d/$f" - continue 2 - fi - done - done -} - -export LC_COLLATE=C - -for i; do - if [ "$i" = "--help" ] || [ "$i" = "-h" ]; then - usage - exit 0 - fi -done - -for i; do - if [ "$i" = "--version" ]; then - echo "kernel-install {{PROJECT_VERSION}} ({{GIT_VERSION}})" - exit 0 - fi -done - -if [ "$KERNEL_INSTALL_BYPASS" = "1" ]; then - echo "kernel-install: Skipping execution because KERNEL_INSTALL_BYPASS=1" - exit 0 -fi - -export KERNEL_INSTALL_VERBOSE=0 -if [ "$1" = "--verbose" ] || [ "$1" = "-v" ]; then - shift - export KERNEL_INSTALL_VERBOSE=1 - log_verbose() { printf "%s\n" "$*"; } -else - log_verbose() { :; } -fi - -if [ "${0##*/}" = "installkernel" ]; then - COMMAND=add - # kernel's install.sh invokes us as - # /sbin/installkernel - # We ignore the last two arguments. - set -- "${1:?}" "${2:?}" -else - COMMAND="$1" - [ $# -ge 1 ] && shift -fi - -if [ "$COMMAND" = "inspect" ]; then - KERNEL_VERSION="" -else - if [ $# -lt 1 ]; then - echo "Error: not enough arguments" >&2 - exit 1 - fi - - KERNEL_VERSION="$1" - shift -fi - -# These three settings are only settable via install.conf -layout= -initrd_generator= -uki_generator= -# These two settings can be inherited from the environment -_MACHINE_ID_SAVED="$MACHINE_ID" -_BOOT_ROOT_SAVED="$BOOT_ROOT" - -if [ -n "$KERNEL_INSTALL_CONF_ROOT" ]; then - install_conf="$KERNEL_INSTALL_CONF_ROOT/install.conf" -elif [ -f "/etc/kernel/install.conf" ]; then - install_conf="/etc/kernel/install.conf" -elif [ -f "/usr/lib/kernel/install.conf" ]; then - install_conf="/usr/lib/kernel/install.conf" -else - install_conf= -fi - -if [ -f "$install_conf" ]; then - log_verbose "Reading $install_conf…" - # shellcheck source=/dev/null - . "$install_conf" -fi - -[ -n "$layout" ] && log_verbose "$install_conf configures layout=$layout" -[ -n "$initrd_generator" ] && \ - log_verbose "$install_conf configures initrd_generator=$initrd_generator" -[ -n "$uki_generator" ] && \ - log_verbose "$install_conf configures uki_generator=$uki_generator" - -if [ -n "$_MACHINE_ID_SAVED" ]; then - MACHINE_ID="$_MACHINE_ID_SAVED" - log_verbose "MACHINE_ID=$MACHINE_ID set via environment" -else - [ -n "$MACHINE_ID" ] && log_verbose "MACHINE_ID=$MACHINE_ID set via install.conf" -fi - -if [ -n "$_BOOT_ROOT_SAVED" ]; then - BOOT_ROOT="$_BOOT_ROOT_SAVED" - log_verbose "BOOT_ROOT=$BOOT_ROOT set via environment" -else - [ -n "$BOOT_ROOT" ] && log_verbose "BOOT_ROOT=$BOOT_ROOT set via install.conf" -fi - -# If /etc/machine-id is initialized we'll use it, otherwise we'll use a freshly -# generated one. If the user configured an explicit machine ID to use in -# /etc/machine-info to use for our purpose, we'll use that instead (for -# compatibility). -# shellcheck source=/dev/null -if [ -z "$MACHINE_ID" ] && [ -f /etc/machine-info ]; then - . /etc/machine-info - MACHINE_ID="$KERNEL_INSTALL_MACHINE_ID" - [ -n "$MACHINE_ID" ] && \ - log_verbose "machine-id $MACHINE_ID acquired from /etc/machine-info" -fi -if [ -z "$MACHINE_ID" ] && [ -s /etc/machine-id ]; then - read -r MACHINE_ID /dev/null || echo "unknown")" -fi - -if [ "$layout" = "auto" ] || [ -z "$layout" ]; then - # No layout configured by the administrator. Let's try to figure it out - # automatically from metadata already contained in $BOOT_ROOT. - if [ "$KERNEL_INSTALL_IMAGE_TYPE" = "uki" ]; then - layout="uki" - log_verbose "Kernel image is UKI, using layout=$layout" - elif [ -e "$BOOT_ROOT/loader/entries.srel" ]; then - read -r ENTRIES_SREL <"$BOOT_ROOT/loader/entries.srel" - if [ "$ENTRIES_SREL" = "type1" ]; then - # The loader/entries.srel file clearly indicates that the installed - # boot loader implements the proper standard upstream boot loader - # spec for Type #1 entries. Let's default to that, then. - layout="bls" - else - # The loader/entries.srel file indicates some other spec is - # implemented and owns the /loader/entries/ directory. Since we - # have no idea what that means, let's stay away from it by default. - layout="other" - fi - log_verbose "$BOOT_ROOT/loader/entries.srel with '$ENTRIES_SREL' found, using layout=$layout" - - elif [ -d "$BOOT_ROOT/$ENTRY_TOKEN" ]; then - # If the metadata in $BOOT_ROOT doesn't tell us anything, then check if - # the entry token directory already exists. If so, let's assume it's - # the standard boot loader spec, too. - layout="bls" - - log_verbose "$BOOT_ROOT/$ENTRY_TOKEN exists, using layout=$layout" - else - # There's no metadata in $BOOT_ROOT, and apparently no entry token - # directory installed? Then we really don't know anything. - layout="other" - - log_verbose "Entry-token directory not found, using layout=$layout" - fi -fi - -ENTRY_DIR_ABS="$BOOT_ROOT/$ENTRY_TOKEN/$KERNEL_VERSION" -log_verbose "Using ENTRY_DIR_ABS=$ENTRY_DIR_ABS" - -# Provide a directory where to store generated initrds -cleanup() { - [ -n "$KERNEL_INSTALL_STAGING_AREA" ] && rm -rf "$KERNEL_INSTALL_STAGING_AREA" -} - -trap cleanup EXIT - -KERNEL_INSTALL_STAGING_AREA="$(mktemp -d -t kernel-install.staging.XXXXXXX)" - -export KERNEL_INSTALL_MACHINE_ID="$MACHINE_ID" -export KERNEL_INSTALL_ENTRY_TOKEN="$ENTRY_TOKEN" -export KERNEL_INSTALL_BOOT_ROOT="$BOOT_ROOT" -export KERNEL_INSTALL_LAYOUT="$layout" -export KERNEL_INSTALL_INITRD_GENERATOR="$initrd_generator" -export KERNEL_INSTALL_UKI_GENERATOR="$uki_generator" -export KERNEL_INSTALL_STAGING_AREA - -MAKE_ENTRY_DIR_ABS=0 -[ "$layout" = "bls" ] || MAKE_ENTRY_DIR_ABS=1 - -ret=0 - -if [ -z "$KERNEL_INSTALL_PLUGINS" ]; then - KERNEL_INSTALL_PLUGINS="$( - dropindirs_sort ".install" \ - "/etc/kernel/install.d" \ - "/usr/lib/kernel/install.d" - )" -fi - -if [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ]; then - printf '%s\n' "Plugin files:" - for f in $KERNEL_INSTALL_PLUGINS; do - printf '%s\n' "$f" - done -fi - -case "$COMMAND" in - add) - if [ $# -lt 1 ]; then - echo "Error: command 'add' requires a kernel image" >&2 - exit 1 - fi - - if ! [ -f "$1" ]; then - echo "Error: kernel image argument $1 not a file" >&2 - exit 1 - fi - - if [ "$MAKE_ENTRY_DIR_ABS" -eq 0 ]; then - # Compatibility with earlier versions that used the presence of $BOOT_ROOT/$ENTRY_TOKEN - # to signal to 00-entry-directory to create $ENTRY_DIR_ABS - # to serve as the indication to use or to not use the BLS - if [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ]; then - echo "+mkdir -v -p $ENTRY_DIR_ABS" - mkdir -v -p "$ENTRY_DIR_ABS" || exit 1 - else - mkdir -p "$ENTRY_DIR_ABS" || exit 1 - fi - fi - - for f in $KERNEL_INSTALL_PLUGINS; do - log_verbose "+$f add $KERNEL_VERSION $ENTRY_DIR_ABS" "$@" - err=0 - "$f" add "$KERNEL_VERSION" "$ENTRY_DIR_ABS" "$@" || err=$? - [ $err -eq $skip_remaining ] && break - [ $err -ne 0 ] && exit $err - done - ;; - - remove) - for f in $KERNEL_INSTALL_PLUGINS; do - log_verbose "+$f remove $KERNEL_VERSION $ENTRY_DIR_ABS" - err=0 - "$f" remove "$KERNEL_VERSION" "$ENTRY_DIR_ABS" || err=$? - [ $err -eq $skip_remaining ] && break - [ $err -ne 0 ] && exit $err - done - - if [ "$MAKE_ENTRY_DIR_ABS" -eq 0 ]; then - log_verbose "Removing $ENTRY_DIR_ABS/" - rm -rf "$ENTRY_DIR_ABS" - fi - ;; - - inspect) - echo "KERNEL_INSTALL_MACHINE_ID: $KERNEL_INSTALL_MACHINE_ID" - echo "KERNEL_INSTALL_ENTRY_TOKEN: $KERNEL_INSTALL_ENTRY_TOKEN" - echo "KERNEL_INSTALL_BOOT_ROOT: $KERNEL_INSTALL_BOOT_ROOT" - echo "KERNEL_INSTALL_LAYOUT: $KERNEL_INSTALL_LAYOUT" - echo "KERNEL_INSTALL_INITRD_GENERATOR: $KERNEL_INSTALL_INITRD_GENERATOR" - echo "KERNEL_INSTALL_UKI_GENERATOR: $KERNEL_INSTALL_UKI_GENERATOR" - echo "ENTRY_DIR_ABS: $KERNEL_INSTALL_BOOT_ROOT/$ENTRY_TOKEN/\$KERNEL_VERSION" - - # Assert that ENTRY_DIR_ABS actually matches what we are printing here - [ "${ENTRY_DIR_ABS%/*}" = "$KERNEL_INSTALL_BOOT_ROOT/$ENTRY_TOKEN" ] || { echo "Assertion didn't pass." >&2; exit 1; } - ;; - - *) - echo "Error: unknown command '$COMMAND'" >&2 - exit 1 - ;; -esac - -exit "$ret" diff --git a/src/kernel-install/meson.build b/src/kernel-install/meson.build index 744071b9e9..6d568966d6 100644 --- a/src/kernel-install/meson.build +++ b/src/kernel-install/meson.build @@ -1,7 +1,5 @@ # SPDX-License-Identifier: LGPL-2.1-or-later -kernel_install_in = files('kernel-install.in') - ukify_install = custom_target( '60-ukify.install', input : '60-ukify.install.in',