Files
systemd/src/kernel-install/kernel-install.c
Daan De Meyer b4afa94c5f kernel-install: Only bypass "add" and "remove"
KERNEL_INSTALL_BYPASS should only bypass verbs that actually change
the system, not harmless verbs such as "inspect".
2023-07-31 20:56:14 +02:00

1263 lines
39 KiB
C

/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <getopt.h>
#include <stdbool.h>
#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 "parse-argument.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;
static char *arg_esp_path = NULL;
static char *arg_xbootldr_path = NULL;
static int arg_make_entry_directory = -1; /* tristate */
STATIC_DESTRUCTOR_REGISTER(arg_esp_path, freep);
STATIC_DESTRUCTOR_REGISTER(arg_xbootldr_path, freep);
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_fclose_ FILE *f = NULL;
_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 = chase_and_fopenat_unlocked(c->rfd, conf, CHASE_AT_RESOLVE_IN_ROOT, "re", NULL, &f);
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(f, 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_fclose_ FILE *f = NULL;
_cleanup_free_ char *machine_id = NULL, *layout = 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) && c->layout >= 0)
return 0;
/* For testing. To make not read host's /etc/machine-info. */
r = getenv_bool("KERNEL_INSTALL_READ_MACHINE_INFO");
if (r < 0 && r != -ENXIO)
log_warning_errno(r, "Failed to read $KERNEL_INSTALL_READ_MACHINE_INFO, assuming yes: %m");
if (r == 0) {
log_debug("Skipping to read /etc/machine-info.");
return 0;
}
r = chase_and_fopenat_unlocked(c->rfd, path, CHASE_AT_RESOLVE_IN_ROOT, "re", NULL, &f);
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(f, path,
"KERNEL_INSTALL_MACHINE_ID", &machine_id,
"KERNEL_INSTALL_LAYOUT", &layout);
if (r < 0)
return log_error_errno(r, "Failed to parse '%s': %m", path);
(void) context_set_machine_id(c, machine_id, path);
(void) context_set_layout(c, layout, 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 = */ arg_xbootldr_path,
/* 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 = */ arg_esp_path,
/* 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 */
if (arg_make_entry_directory < 0)
return c->layout == LAYOUT_BLS;
return arg_make_entry_directory;
}
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, c->kernel ?: "[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 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 verb_add(int argc, char *argv[], void *userdata) {
Context *c = ASSERT_PTR(userdata);
int r;
assert(argc >= 3);
assert(argv);
if (bypass())
return 0;
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 <version> <vmlinuz> <map> <installation-dir>
* 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);
if (argc > 2)
log_debug("Too many arguments specified. 'kernel-install remove' takes only kernel version. "
"Ignoring residual arguments.");
if (bypass())
return 0;
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;
if (argc >= 2) {
r = context_set_kernel(c, argv[1]);
if (r < 0)
return r;
}
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 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 [KERNEL-IMAGE]\n"
"\nOptions:\n"
" -h --help Show this help\n"
" --version Show package version\n"
" -v --verbose Increase verbosity\n"
" --esp-path=PATH Path to the EFI System Partition (ESP)\n"
" --boot-path=PATH Path to the $BOOT partition\n"
" --make-entry-directory=yes|no|auto\n"
" Create $BOOT/ENTRY-TOKEN/ directory\n"
" --entry-token=machine-id|os-id|os-image-id|auto|literal:…\n"
" Entry token to use for this installation\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[], Context *c) {
enum {
ARG_VERSION = 0x100,
ARG_ESP_PATH,
ARG_BOOT_PATH,
ARG_MAKE_ENTRY_DIRECTORY,
ARG_ENTRY_TOKEN,
};
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "verbose", no_argument, NULL, 'v' },
{ "esp-path", required_argument, NULL, ARG_ESP_PATH },
{ "boot-path", required_argument, NULL, ARG_BOOT_PATH },
{ "make-entry-directory", required_argument, NULL, ARG_MAKE_ENTRY_DIRECTORY },
{ "entry-token", required_argument, NULL, ARG_ENTRY_TOKEN },
{}
};
int t, r;
assert(argc >= 0);
assert(argv);
assert(c);
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 ARG_ESP_PATH:
r = parse_path_argument(optarg, /* suppress_root = */ false, &arg_esp_path);
if (r < 0)
return log_oom();
break;
case ARG_BOOT_PATH:
r = parse_path_argument(optarg, /* suppress_root = */ false, &arg_xbootldr_path);
if (r < 0)
return log_oom();
break;
case ARG_MAKE_ENTRY_DIRECTORY:
if (streq(optarg, "auto"))
arg_make_entry_directory = -1;
else {
r = parse_boolean_argument("--make-entry-directory=", optarg, NULL);
if (r < 0)
return r;
arg_make_entry_directory = r;
}
break;
case ARG_ENTRY_TOKEN:
r = parse_boot_entry_token_type(optarg, &c->entry_token_type, &c->entry_token);
if (r < 0)
return r;
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, VERB_ANY, 0, verb_remove },
{ "inspect", 1, 2, 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();
r = parse_argv(argc, argv, &c);
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);