From 03c6e324810ba361c2689600a76fe706195e67f8 Mon Sep 17 00:00:00 2001 From: Mary Strodl Date: Thu, 12 Dec 2024 09:30:59 -0500 Subject: [PATCH] bin/admin-upgrade: add kexec support Adds a new `--kexec` flag to `ostree admin upgrade` which will cause the deployment to be loaded into kexec after the upgrade completes. It is particularly useful in conjunction with the `--reboot` flag to perform a reboot into the new deployment without waiting for the (often slow) firmware initialization to take place. (And in my case, allows me to avoid a normal reboot, which can be unreliable on my hardware). After an image has been loaded (using the `kexec_file_load` syscall), the `systemctl-reboot` command (which is called when the existing `-r` flag is included) will trigger a kexec on the loaded image rather than a normal reboot. From `systemctl(1)`: If a new kernel has been loaded via kexec --load, a kexec will be performed instead of a reboot, unless "SYSTEMCTL_SKIP_AUTO_KEXEC=1" has been set. If a new root file system has been set up on "/run/nextroot/", a soft-reboot will be performed instead of a reboot, unless "SYSTEMCTL_SKIP_AUTO_SOFT_REBOOT=1" has been set. A good in-depth technical explanation of kexec can be found here: https://web.archive.org/web/20090505132901/http://www.ibm.com/developerworks/linux/library/l-kexec.html My implementation uses the `kexec_file_load` syscall rather than the older `kexec_load` syscall, which allows the kernel to verify the signatures of the new kernel. It is supported on Linux 3.17 and newer. I assume this probably won't be an issue, but if it is, it's not that hard to put a preprocessor directive around the kexec stuff to disable it for older kernels. Even RHEL is new enough now to not be an issue :) Closes: #435 --- man/ostree-admin-upgrade.xml | 8 ++++ src/libostree/ostree-sysroot-deploy.c | 49 +++++++++++++++++++++++++ src/libostree/ostree-sysroot-upgrader.c | 8 +++- src/libostree/ostree-sysroot-upgrader.h | 2 + src/libostree/ostree-sysroot.c | 4 +- src/libostree/ostree-sysroot.h | 4 +- src/ostree/ot-admin-builtin-upgrade.c | 8 +++- 7 files changed, 78 insertions(+), 5 deletions(-) diff --git a/man/ostree-admin-upgrade.xml b/man/ostree-admin-upgrade.xml index ccf7a65bd2..38645af903 100644 --- a/man/ostree-admin-upgrade.xml +++ b/man/ostree-admin-upgrade.xml @@ -111,6 +111,14 @@ License along with this library. If not, see . Reboot after a successful upgrade. + + + , + + + Load new deployment into kexec after a successful upgrade. + + diff --git a/src/libostree/ostree-sysroot-deploy.c b/src/libostree/ostree-sysroot-deploy.c index 953b6523d0..9df12429cd 100644 --- a/src/libostree/ostree-sysroot-deploy.c +++ b/src/libostree/ostree-sysroot-deploy.c @@ -30,6 +30,7 @@ #include #include #include +#include #ifdef HAVE_LIBMOUNT #include @@ -2390,6 +2391,51 @@ ostree_sysroot_write_deployments (OstreeSysroot *self, GPtrArray *new_deployment error); } +static gboolean +kexec_load_kernel (OstreeSysroot *self, OstreeDeployment *deployment, + GCancellable *cancellable, GError **error) +{ + GLNX_AUTO_PREFIX_ERROR ("Loading kernel into kexec", error); + OstreeBootconfigParser *bootconfig = ostree_deployment_get_bootconfig (deployment); + const char *kargs = ostree_bootconfig_parser_get(bootconfig, "options"); + g_autofree char *deployment_dirpath = ostree_sysroot_get_deployment_dirpath (self, deployment); + glnx_autofd int deployment_dfd = -1; + if (!glnx_opendirat (self->sysroot_fd, deployment_dirpath, FALSE, &deployment_dfd, error)) + return FALSE; + + /* Find the kernel/initramfs in the tree */ + g_autoptr (OstreeKernelLayout) kernel_layout = NULL; + if (!get_kernel_from_tree (self, deployment_dfd, &kernel_layout, cancellable, error)) + return FALSE; + + unsigned long flags = 0; + glnx_autofd int kernel_fd = -1; + glnx_autofd int initrd_fd = -1; + + if (!glnx_openat_rdonly (kernel_layout->boot_dfd, kernel_layout->kernel_srcpath, + TRUE, &kernel_fd, error)) + return FALSE; + + /* initramfs is optional */ + if (kernel_layout->initramfs_srcpath) + { + if (!glnx_openat_rdonly (kernel_layout->boot_dfd, kernel_layout->initramfs_srcpath, + TRUE, &initrd_fd, error)) + { + return FALSE; + } + } + else + { + flags |= KEXEC_FILE_NO_INITRAMFS; + } + + if (syscall (SYS_kexec_file_load, kernel_fd, initrd_fd, strlen (kargs) + 1, kargs, flags)) + return glnx_throw_errno_prefix(error, "kexec_file_load"); + + return TRUE; +} + /* Handle writing out a new bootloader config. One reason this needs to be a * helper function is to handle wrapping it with temporarily remounting /boot * rw. @@ -2994,6 +3040,9 @@ ostree_sysroot_write_deployments_with_options (OstreeSysroot *self, GPtrArray *n return FALSE; } + if (new_deployments->len && !kexec_load_kernel(self, new_deployments->pdata[0], cancellable, error)) + return FALSE; + { g_autofree char *msg = g_strdup_printf ("%s; bootconfig swap: %s; bootversion: %s, deployment count change: %i", diff --git a/src/libostree/ostree-sysroot-upgrader.c b/src/libostree/ostree-sysroot-upgrader.c index 94654c8668..2b632aca11 100644 --- a/src/libostree/ostree-sysroot-upgrader.c +++ b/src/libostree/ostree-sysroot-upgrader.c @@ -601,6 +601,10 @@ ostree_sysroot_upgrader_deploy (OstreeSysrootUpgrader *self, GCancellable *cance /* Experimental flag to enable staging */ gboolean stage = (self->flags & OSTREE_SYSROOT_UPGRADER_FLAGS_STAGE) > 0 || getenv ("OSTREE_EX_STAGE_DEPLOYMENTS") != NULL; + OstreeSysrootSimpleWriteDeploymentFlags write_deployment_flags = OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_NONE; + if ((self->flags & OSTREE_SYSROOT_UPGRADER_FLAGS_LOAD_KEXEC) > 0) { + write_deployment_flags |= OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_LOAD_KEXEC; + } if (stage) { if (!ostree_sysroot_stage_tree (self->sysroot, self->osname, self->new_revision, self->origin, @@ -616,7 +620,7 @@ ostree_sysroot_upgrader_deploy (OstreeSysrootUpgrader *self, GCancellable *cance return FALSE; if (!ostree_sysroot_simple_write_deployment (self->sysroot, self->osname, new_deployment, - self->merge_deployment, 0, cancellable, error)) + self->merge_deployment, write_deployment_flags, cancellable, error)) return FALSE; } @@ -635,6 +639,8 @@ ostree_sysroot_upgrader_flags_get_type (void) "OSTREE_SYSROOT_UPGRADER_FLAGS_IGNORE_UNCONFIGURED", "ignore-unconfigured" }, { OSTREE_SYSROOT_UPGRADER_FLAGS_STAGE, "OSTREE_SYSROOT_UPGRADER_FLAGS_STAGE", "stage" }, + { OSTREE_SYSROOT_UPGRADER_FLAGS_LOAD_KEXEC, "OSTREE_SYSROOT_UPGRADER_FLAGS_LOAD_KEXEC", + "kexec" }, { 0, NULL, NULL } }; GType g_define_type_id = g_flags_register_static (g_intern_static_string ("OstreeSysrootUpgraderFlags"), values); diff --git a/src/libostree/ostree-sysroot-upgrader.h b/src/libostree/ostree-sysroot-upgrader.h index 5d1e8c2aa5..d8478c9ec4 100644 --- a/src/libostree/ostree-sysroot-upgrader.h +++ b/src/libostree/ostree-sysroot-upgrader.h @@ -36,6 +36,7 @@ G_BEGIN_DECLS * unconfigured-state key * @OSTREE_SYSROOT_UPGRADER_FLAGS_STAGE: Enable "staging" (finalization at shutdown); recommended * (Since: 2021.4) + * @OSTREE_SYSROOT_UPGRADER_FLAGS_LOAD_KEXEC: Enable loading the new deployment into kexec * * Flags controlling operation of an #OstreeSysrootUpgrader. */ @@ -44,6 +45,7 @@ typedef enum OSTREE_SYSROOT_UPGRADER_FLAGS_NONE = (1 << 0), OSTREE_SYSROOT_UPGRADER_FLAGS_IGNORE_UNCONFIGURED = (1 << 1), OSTREE_SYSROOT_UPGRADER_FLAGS_STAGE = (1 << 2), + OSTREE_SYSROOT_UPGRADER_FLAGS_LOAD_KEXEC = (1 << 3), } OstreeSysrootUpgraderFlags; _OSTREE_PUBLIC diff --git a/src/libostree/ostree-sysroot.c b/src/libostree/ostree-sysroot.c index 925c66a7e3..4564e1809c 100644 --- a/src/libostree/ostree-sysroot.c +++ b/src/libostree/ostree-sysroot.c @@ -1913,6 +1913,8 @@ ostree_sysroot_simple_write_deployment (OstreeSysroot *sysroot, const char *osna OstreeSysrootSimpleWriteDeploymentFlags flags, GCancellable *cancellable, GError **error) { + const gboolean load_kexec + = (flags & OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_LOAD_KEXEC) > 0; const gboolean postclean = (flags & OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_NO_CLEAN) == 0; const gboolean make_default = !((flags & OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_NOT_DEFAULT) > 0); @@ -1987,7 +1989,7 @@ ostree_sysroot_simple_write_deployment (OstreeSysroot *sysroot, const char *osna if (!added_new) g_ptr_array_add (new_deployments, g_object_ref (new_deployment)); - OstreeSysrootWriteDeploymentsOpts write_opts = { .do_postclean = postclean }; + OstreeSysrootWriteDeploymentsOpts write_opts = { .do_postclean = postclean, .load_kexec = load_kexec }; if (!ostree_sysroot_write_deployments_with_options (sysroot, new_deployments, &write_opts, cancellable, error)) return FALSE; diff --git a/src/libostree/ostree-sysroot.h b/src/libostree/ostree-sysroot.h index 3c23f8dd5a..1feca4c7d0 100644 --- a/src/libostree/ostree-sysroot.h +++ b/src/libostree/ostree-sysroot.h @@ -161,7 +161,8 @@ typedef struct { gboolean do_postclean; gboolean disable_auto_early_prune; - gboolean unused_bools[7]; + gboolean load_kexec; + gboolean unused_bools[6]; int unused_ints[7]; gpointer unused_ptrs[7]; } OstreeSysrootWriteDeploymentsOpts; @@ -259,6 +260,7 @@ typedef enum OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_NO_CLEAN = (1 << 2), OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN_PENDING = (1 << 3), OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_RETAIN_ROLLBACK = (1 << 4), + OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_LOAD_KEXEC = (1 << 5), } OstreeSysrootSimpleWriteDeploymentFlags; _OSTREE_PUBLIC diff --git a/src/ostree/ot-admin-builtin-upgrade.c b/src/ostree/ot-admin-builtin-upgrade.c index 96b1575995..51c950dac6 100644 --- a/src/ostree/ot-admin-builtin-upgrade.c +++ b/src/ostree/ot-admin-builtin-upgrade.c @@ -31,6 +31,7 @@ #include static gboolean opt_reboot; +static gboolean opt_kexec; static gboolean opt_allow_downgrade; static gboolean opt_pull_only; static gboolean opt_deploy_only; @@ -42,6 +43,7 @@ static GOptionEntry options[] = { { "os", 0, 0, G_OPTION_ARG_STRING, &opt_osname, "Use a different operating system root than the current one", "OSNAME" }, { "reboot", 'r', 0, G_OPTION_ARG_NONE, &opt_reboot, "Reboot after a successful upgrade", NULL }, + { "kexec", 'k', 0, G_OPTION_ARG_NONE, &opt_kexec, "Stage new kernel in kexec", NULL }, { "allow-downgrade", 0, 0, G_OPTION_ARG_NONE, &opt_allow_downgrade, "Permit deployment of chronologically older trees", NULL }, { "override-commit", 0, 0, G_OPTION_ARG_STRING, &opt_override_commit, @@ -72,16 +74,18 @@ ot_admin_builtin_upgrade (int argc, char **argv, OstreeCommandInvocation *invoca "Cannot simultaneously specify --pull-only and --deploy-only"); return FALSE; } - else if (opt_pull_only && opt_reboot) + else if (opt_pull_only && (opt_reboot || opt_kexec)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Cannot simultaneously specify --pull-only and --reboot"); + "Cannot simultaneously specify --pull-only and --reboot or --kexec"); return FALSE; } OstreeSysrootUpgraderFlags flags = 0; if (opt_stage) flags |= OSTREE_SYSROOT_UPGRADER_FLAGS_STAGE; + if (opt_kexec) + flags |= OSTREE_SYSROOT_UPGRADER_FLAGS_LOAD_KEXEC; g_autoptr (OstreeSysrootUpgrader) upgrader = ostree_sysroot_upgrader_new_for_os_with_flags ( sysroot, opt_osname, flags, cancellable, error);