bin/admin-upgrade: add kexec support
authorMary Strodl <ipadlover8322@gmail.com>
Thu, 12 Dec 2024 14:30:59 +0000 (09:30 -0500)
committerMary Strodl <ipadlover8322@gmail.com>
Mon, 23 Dec 2024 18:59:37 +0000 (13:59 -0500)
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
Makefile-libostree.am
apidoc/ostree-sections.txt
man/ostree-admin-switch.xml
man/ostree-admin-upgrade.xml
src/libostree/libostree-devel.sym
src/libostree/ostree-sysroot-deploy.c
src/libostree/ostree-sysroot-upgrader.c
src/libostree/ostree-sysroot-upgrader.h
src/libostree/ostree-sysroot.h
src/ostree/ot-admin-builtin-switch.c
src/ostree/ot-admin-builtin-upgrade.c

index 11a7bbedd3cc5375327b3de1ffe87d03f7f963f3..915b20b8c2b45eed5d07c462d01f87add2f8dfab 100644 (file)
@@ -175,9 +175,9 @@ endif # USE_GPGME
 symbol_files = $(top_srcdir)/src/libostree/libostree-released.sym
 
 # Uncomment this include when adding new development symbols.
-#if BUILDOPT_IS_DEVEL_BUILD
-#symbol_files += $(top_srcdir)/src/libostree/libostree-devel.sym
-#endif
+if BUILDOPT_IS_DEVEL_BUILD
+symbol_files += $(top_srcdir)/src/libostree/libostree-devel.sym
+endif
 
 # http://blog.jgc.org/2007/06/escaping-comma-and-space-in-gnu-make.html
 wl_versionscript_arg = -Wl,--version-script=
index b46e606c6aec87926ea25958dc382cc2b73b8aaa..e934b85932d4e797a67cc926cea1e6d5a9027bc2 100644 (file)
@@ -582,6 +582,7 @@ ostree_sysroot_repo
 ostree_sysroot_get_repo
 ostree_sysroot_get_staged_deployment
 ostree_sysroot_init_osname
+ostree_sysroot_deployment_kexec_load
 ostree_sysroot_deployment_set_kargs
 ostree_sysroot_deployment_set_kargs_in_place
 ostree_sysroot_deployment_set_mutable
index 6795c49b97e2af952c31f484882e39d3f74781a4..12848c90463e6b77f88737036958d521a468e6dc 100644 (file)
@@ -65,6 +65,22 @@ License along with this library. If not, see <https://www.gnu.org/licenses/>.
         <title>Options</title>
 
         <variablelist>
+            <varlistentry>
+                <term><option>--reboot</option>,<option>-r</option></term>
+
+                <listitem><para>
+                    Reboot after a successful switch.
+                </para></listitem>
+            </varlistentry>
+
+            <varlistentry>
+                <term><option>--kexec</option>,<option>-k</option></term>
+
+                <listitem><para>
+                    Load new deployment into kexec after a successful switch.
+                </para></listitem>
+            </varlistentry>
+
             <varlistentry>
                 <term><option>--os</option>="STATEROOT"</term>
 
index ccf7a65bd2b23d5c79108f0bd0f145c12e902d6b..38645af903aaf25dac3d6ac2ffd47faab9e2e204 100644 (file)
@@ -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>
index 6640e11c78d7a370a8191ff9a97b285a29df0b8f..0af241dcfa49d0ca5142d61ca0a7845d224ed0e7 100644 (file)
    - uncomment the include in Makefile-libostree.am
 */
 
+LIBOSTREE_2024.11 {
+global:
+  ostree_sysroot_deployment_kexec_load;
+} LIBOSTREE_2024.7;
+
 /* Stub section for the stable release *after* this development one; don't
  * edit this other than to update the year.  This is just a copy/paste
  * source.  Replace $LASTSTABLE with the last stable version, and $NEWVERSION
index 953b6523d04a070411c4fba34c1a73876edff1e1..e3e333a1d40020dd3d884193f6c15b7adbd13eda 100644 (file)
@@ -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>
@@ -4265,3 +4266,63 @@ ostree_sysroot_deployment_set_mutable (OstreeSysroot *self, OstreeDeployment *de
 
   return TRUE;
 }
+
+/**
+ * ostree_sysroot_deployment_kexec_load
+ * @self: Sysroot
+ * @deployment: Deployment to prepare a kexec for
+ * @cancellable: Cancellable
+ * @error: Error
+ *
+ * Prepare the specified deployment for a kexec.
+ */
+gboolean
+ostree_sysroot_deployment_kexec_load (OstreeSysroot *self, OstreeDeployment *deployment,
+                                      GCancellable *cancellable, GError **error)
+{
+#ifdef SYS_kexec_file_load
+  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;
+#else
+  g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+               "This version of ostree is not compiled with kexec support");
+  return FALSE;
+#endif // SYS_kexec_file_load
+}
index 94654c8668ab541b42a3105588f2d3fb1d3c5829..ac7336daad08bff43fcc926e27735dcb3a900e01 100644 (file)
@@ -601,6 +601,7 @@ 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 (stage)
     {
       if (!ostree_sysroot_stage_tree (self->sysroot, self->osname, self->new_revision, self->origin,
@@ -616,8 +617,15 @@ 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;
+
+
+      if ((self->flags & OSTREE_SYSROOT_UPGRADER_FLAGS_KEXEC) > 0)
+        {
+          if (!ostree_sysroot_deployment_kexec_load(self->sysroot, new_deployment, cancellable, error))
+            return FALSE;
+        }
     }
 
   return TRUE;
@@ -635,6 +643,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_KEXEC, "OSTREE_SYSROOT_UPGRADER_FLAGS_KEXEC",
+                "kexec" },
               { 0, NULL, NULL } };
       GType g_define_type_id
           = g_flags_register_static (g_intern_static_string ("OstreeSysrootUpgraderFlags"), values);
index 5d1e8c2aa57739b9b98dcc17e4a1169b9fd0457a..5e2f5ed8a8e1e384b38b396841db7863f156372d 100644 (file)
@@ -44,6 +44,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_KEXEC = (1 << 3),
 } OstreeSysrootUpgraderFlags;
 
 _OSTREE_PUBLIC
index 3c23f8dd5a95ce48bbc22c7b177aff221302da83..73c2a408ac8aa13c39105c28faf055c252e6ec54 100644 (file)
@@ -268,4 +268,9 @@ gboolean ostree_sysroot_simple_write_deployment (OstreeSysroot *sysroot, const c
                                                  OstreeSysrootSimpleWriteDeploymentFlags flags,
                                                  GCancellable *cancellable, GError **error);
 
+_OSTREE_PUBLIC
+gboolean
+ostree_sysroot_deployment_kexec_load (OstreeSysroot *self, OstreeDeployment *deployment,
+                                      GCancellable *cancellable, GError **error);
+
 G_END_DECLS
index 592673639fc69708c9b6ef8d971ded5d40730404..c8a5461227be5185246b3ecd6d0f515a8198d255 100644 (file)
 #include <unistd.h>
 
 static gboolean opt_reboot;
+static gboolean opt_kexec;
 static char *opt_osname;
 
 static GOptionEntry options[]
     = { { "reboot", 'r', 0, G_OPTION_ARG_NONE, &opt_reboot, "Reboot after switching trees", NULL },
+        { "kexec", 'k', 0, G_OPTION_ARG_NONE, &opt_kexec, "Stage new kernel in kexec", NULL },
         { "os", 0, 0, G_OPTION_ARG_STRING, &opt_osname,
           "Use a different operating system root than the current one", "OSNAME" },
         { NULL } };
@@ -56,8 +58,12 @@ ot_admin_builtin_switch (int argc, char **argv, OstreeCommandInvocation *invocat
 
   const char *new_provided_refspec = argv[1];
 
+  OstreeSysrootUpgraderFlags flags = OSTREE_SYSROOT_UPGRADER_FLAGS_IGNORE_UNCONFIGURED;
+  if (opt_kexec)
+    flags |= OSTREE_SYSROOT_UPGRADER_FLAGS_KEXEC;
+
   g_autoptr (OstreeSysrootUpgrader) upgrader = ostree_sysroot_upgrader_new_for_os_with_flags (
-      sysroot, opt_osname, OSTREE_SYSROOT_UPGRADER_FLAGS_IGNORE_UNCONFIGURED, cancellable, error);
+      sysroot, opt_osname, flags, cancellable, error);
   if (!upgrader)
     return FALSE;
 
index 96b1575995cef3d9d3b4e0a7c21c65d88898c368..4c9dbbedf2b0b91a9cecac6446d20babf96d346a 100644 (file)
@@ -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;
@@ -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_KEXEC;
 
   g_autoptr (OstreeSysrootUpgrader) upgrader = ostree_sysroot_upgrader_new_for_os_with_flags (
       sysroot, opt_osname, flags, cancellable, error);