Skip to content

Commit

Permalink
bin/admin-upgrade: add kexec support
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Mstrodl committed Dec 19, 2024
1 parent 64a38ae commit 27f8347
Show file tree
Hide file tree
Showing 7 changed files with 78 additions and 5 deletions.
8 changes: 8 additions & 0 deletions man/ostree-admin-upgrade.xml
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,14 @@ License along with this library. If not, see <https://www.gnu.org/licenses/>.
Reboot after a successful upgrade.
</para></listitem>
</varlistentry>

<varlistentry>
<term><option>--kexec</option>,<option>-k</option></term>

<listitem><para>
Load new deployment into kexec after a successful upgrade.
</para></listitem>
</varlistentry>

<varlistentry>
<term><option>--allow-downgrade</option></term>
Expand Down
49 changes: 49 additions & 0 deletions src/libostree/ostree-sysroot-deploy.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include <sys/poll.h>
#include <sys/socket.h>
#include <sys/statvfs.h>
#include <linux/kexec.h>

#ifdef HAVE_LIBMOUNT
#include <libmount.h>
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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",
Expand Down
8 changes: 7 additions & 1 deletion src/libostree/ostree-sysroot-upgrader.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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;
}

Expand All @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions src/libostree/ostree-sysroot-upgrader.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand All @@ -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
Expand Down
4 changes: 3 additions & 1 deletion src/libostree/ostree-sysroot.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
4 changes: 3 additions & 1 deletion src/libostree/ostree-sysroot.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
8 changes: 6 additions & 2 deletions src/ostree/ot-admin-builtin-upgrade.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include <unistd.h>

static gboolean opt_reboot;
static gboolean opt_kexec;
static gboolean opt_allow_downgrade;
static gboolean opt_pull_only;
static gboolean opt_deploy_only;
Expand All @@ -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,
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit 27f8347

Please sign in to comment.