6b2dd0f
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
9fdaa79
From: Raymund Will <rw@suse.com>
ed1787d
Date: Mon, 24 Oct 2022 14:33:50 -0400
ed1787d
Subject: [PATCH] loader: Add support for grub-emu to kexec Linux menu entries
ec4acbb
ed1787d
The GRUB emulator is used as a debugging utility but it could also be
ed1787d
used as a user-space bootloader if there is support to boot an operating
ed1787d
system.
ed1787d
ed1787d
The Linux kernel is already able to (re)boot another kernel via the
ed1787d
kexec boot mechanism. So the grub-emu tool could rely on this feature
ed1787d
and have linux and initrd commands that are used to pass a kernel,
ed1787d
initramfs image and command line parameters to kexec for booting
ed1787d
a selected menu entry.
ed1787d
ed1787d
By default the systemctl kexec option is used so systemd can shutdown
ed1787d
all of the running services before doing a reboot using kexec. But if
ed1787d
this is not present, it can fall back to executing the kexec user-space
ed1787d
tool directly. The ability to force a kexec-reboot when systemctl kexec
ed1787d
fails must only be used in controlled environments to avoid possible
ed1787d
filesystem corruption and data loss.
9fdaa79
9fdaa79
Signed-off-by: Raymund Will <rw@suse.com>
ed1787d
Signed-off-by: John Jolly <jjolly@suse.com>
ed1787d
Signed-off-by: Javier Martinez Canillas <javierm@redhat.com>
9fdaa79
Signed-off-by: Robbie Harwood <rharwood@redhat.com>
ed1787d
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
ed1787d
(cherry picked from commit e364307f6acc2f631b4c1fefda0791b9ce1f205f)
ed1787d
[rharwood: conflicts around makefile and grub_exit return code]
ec4acbb
---
ed1787d
 grub-core/Makefile.core.def  |   3 -
ec4acbb
 grub-core/kern/emu/main.c    |   4 +
ec4acbb
 grub-core/kern/emu/misc.c    |  18 ++++-
ed1787d
 grub-core/loader/emu/linux.c | 178 +++++++++++++++++++++++++++++++++++++++++++
ec4acbb
 include/grub/emu/exec.h      |   4 +-
ec4acbb
 include/grub/emu/hostfile.h  |   3 +-
ec4acbb
 include/grub/emu/misc.h      |   3 +
ed1787d
 docs/grub.texi               |  30 ++++++--
ec4acbb
 grub-core/Makefile.am        |   1 +
ed1787d
 9 files changed, 230 insertions(+), 14 deletions(-)
ec4acbb
 create mode 100644 grub-core/loader/emu/linux.c
ec4acbb
ec4acbb
diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def
851216d
index 741a033978..f21da23213 100644
ec4acbb
--- a/grub-core/Makefile.core.def
ec4acbb
+++ b/grub-core/Makefile.core.def
851216d
@@ -1864,11 +1864,8 @@ module = {
ed1787d
   riscv32 = loader/riscv/linux.c;
ed1787d
   riscv64 = loader/riscv/linux.c;
ed1787d
   emu = loader/emu/linux.c;
ed1787d
-
ec4acbb
   common = loader/linux.c;
ec4acbb
   common = lib/cmdline.c;
ec4acbb
-  enable = noemu;
ed1787d
-
ad4aff0
   efi = loader/efi/linux.c;
ad4aff0
 };
ed1787d
 
ec4acbb
diff --git a/grub-core/kern/emu/main.c b/grub-core/kern/emu/main.c
ed1787d
index 12277c34d2..68e2b283bb 100644
ec4acbb
--- a/grub-core/kern/emu/main.c
ec4acbb
+++ b/grub-core/kern/emu/main.c
ec4acbb
@@ -107,6 +107,7 @@ static struct argp_option options[] = {
ec4acbb
    N_("use GRUB files in the directory DIR [default=%s]"), 0},
ec4acbb
   {"verbose",     'v', 0,      0, N_("print verbose messages."), 0},
ec4acbb
   {"hold",     'H', N_("SECS"),      OPTION_ARG_OPTIONAL, N_("wait until a debugger will attach"), 0},
ed1787d
+  {"kexec",       'X', 0,      0, N_("use kexec to boot Linux kernels via systemctl (pass twice to enable dangerous fallback to non-systemctl)."), 0},
ec4acbb
   { 0, 0, 0, 0, 0, 0 }
ec4acbb
 };
ec4acbb
 
ec4acbb
@@ -164,6 +165,9 @@ argp_parser (int key, char *arg, struct argp_state *state)
ec4acbb
     case 'v':
ec4acbb
       verbosity++;
ec4acbb
       break;
ec4acbb
+    case 'X':
ed1787d
+      grub_util_set_kexecute ();
ec4acbb
+      break;
ec4acbb
 
ec4acbb
     case ARGP_KEY_ARG:
ec4acbb
       {
ec4acbb
diff --git a/grub-core/kern/emu/misc.c b/grub-core/kern/emu/misc.c
ed1787d
index d278c2921f..02d27c3440 100644
ec4acbb
--- a/grub-core/kern/emu/misc.c
ec4acbb
+++ b/grub-core/kern/emu/misc.c
e153146
@@ -39,6 +39,7 @@
ec4acbb
 #include <grub/emu/misc.h>
ec4acbb
 
ec4acbb
 int verbosity;
ec4acbb
+int kexecute;
ec4acbb
 
ec4acbb
 void
ec4acbb
 grub_util_warn (const char *fmt, ...)
e153146
@@ -82,7 +83,7 @@ grub_util_error (const char *fmt, ...)
ec4acbb
   vfprintf (stderr, fmt, ap);
ec4acbb
   va_end (ap);
ec4acbb
   fprintf (stderr, ".\n");
ec4acbb
-  exit (1);
ec4acbb
+  grub_exit (1);
ec4acbb
 }
ec4acbb
 
ec4acbb
 void *
46968b6
@@ -154,6 +155,9 @@ void
ec4acbb
 __attribute__ ((noreturn))
ec4acbb
 grub_exit (int rc)
ec4acbb
 {
ec4acbb
+#if defined (GRUB_KERNEL)
ed1787d
+  grub_reboot ();
ec4acbb
+#endif
ec4acbb
   exit (rc < 0 ? 1 : rc);
ec4acbb
 }
ec4acbb
 #endif
46968b6
@@ -215,3 +219,15 @@ grub_util_load_image (const char *path, char *buf)
ec4acbb
 
ec4acbb
   fclose (fp);
ec4acbb
 }
ec4acbb
+
ec4acbb
+void
ed1787d
+grub_util_set_kexecute (void)
ec4acbb
+{
ec4acbb
+  kexecute++;
ec4acbb
+}
ec4acbb
+
ec4acbb
+int
ed1787d
+grub_util_get_kexecute (void)
ec4acbb
+{
ec4acbb
+  return kexecute;
ec4acbb
+}
ec4acbb
diff --git a/grub-core/loader/emu/linux.c b/grub-core/loader/emu/linux.c
ec4acbb
new file mode 100644
ed1787d
index 0000000000..0cf378a376
ec4acbb
--- /dev/null
ec4acbb
+++ b/grub-core/loader/emu/linux.c
ed1787d
@@ -0,0 +1,178 @@
ec4acbb
+/*
ec4acbb
+ *  GRUB  --  GRand Unified Bootloader
ed1787d
+ *  Copyright (C) 2022  Free Software Foundation, Inc.
ec4acbb
+ *
ec4acbb
+ *  GRUB is free software: you can redistribute it and/or modify
ec4acbb
+ *  it under the terms of the GNU General Public License as published by
ec4acbb
+ *  the Free Software Foundation, either version 3 of the License, or
ec4acbb
+ *  (at your option) any later version.
ec4acbb
+ *
ec4acbb
+ *  GRUB is distributed in the hope that it will be useful,
ec4acbb
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
ec4acbb
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
ec4acbb
+ *  GNU General Public License for more details.
ec4acbb
+ *
ec4acbb
+ *  You should have received a copy of the GNU General Public License
ec4acbb
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
ec4acbb
+ */
ec4acbb
+
ec4acbb
+#include <grub/loader.h>
ec4acbb
+#include <grub/dl.h>
ec4acbb
+#include <grub/command.h>
ec4acbb
+#include <grub/time.h>
ec4acbb
+
ec4acbb
+#include <grub/emu/exec.h>
ec4acbb
+#include <grub/emu/hostfile.h>
ec4acbb
+#include <grub/emu/misc.h>
ec4acbb
+
ec4acbb
+GRUB_MOD_LICENSE ("GPLv3+");
ec4acbb
+
ec4acbb
+static grub_dl_t my_mod;
ec4acbb
+
ec4acbb
+static char *kernel_path;
ec4acbb
+static char *initrd_path;
ec4acbb
+static char *boot_cmdline;
ec4acbb
+
ec4acbb
+static grub_err_t
ec4acbb
+grub_linux_boot (void)
ec4acbb
+{
ec4acbb
+  grub_err_t rc = GRUB_ERR_NONE;
ec4acbb
+  char *initrd_param;
ed1787d
+  const char *kexec[] = {"kexec", "-la", kernel_path, boot_cmdline, NULL, NULL};
ed1787d
+  const char *systemctl[] = {"systemctl", "kexec", NULL};
ed1787d
+  int kexecute = grub_util_get_kexecute ();
ed1787d
+
ed1787d
+  if (initrd_path)
ed1787d
+    {
ed1787d
+      initrd_param = grub_xasprintf ("--initrd=%s", initrd_path);
ed1787d
+      kexec[3] = initrd_param;
ed1787d
+      kexec[4] = boot_cmdline;
ed1787d
+    }
ed1787d
+  else
ed1787d
+    initrd_param = grub_xasprintf ("%s", "");
ed1787d
+
ed1787d
+  grub_dprintf ("linux", "%serforming 'kexec -la %s %s %s'\n",
ed1787d
+                (kexecute) ? "P" : "Not p",
ed1787d
+                kernel_path, initrd_param, boot_cmdline);
ec4acbb
+
ec4acbb
+  if (kexecute)
ed1787d
+    rc = grub_util_exec (kexec);
ec4acbb
+
ed1787d
+  grub_free (initrd_param);
ed1787d
+
ed1787d
+  if (rc != GRUB_ERR_NONE)
ed1787d
+    {
ed1787d
+      grub_error (rc, N_("error trying to perform kexec load operation"));
ed1787d
+      grub_sleep (3);
ed1787d
+      return rc;
ed1787d
+    }
ec4acbb
+
ec4acbb
+  if (kexecute < 1)
ed1787d
+    grub_fatal (N_("use '"PACKAGE"-emu --kexec' to force a system restart"));
ec4acbb
+
ed1787d
+  grub_dprintf ("linux", "Performing 'systemctl kexec' (%s) ",
ec4acbb
+		(kexecute==1) ? "do-or-die" : "just-in-case");
ec4acbb
+  rc = grub_util_exec (systemctl);
ec4acbb
+
ec4acbb
+  if (kexecute == 1)
ed1787d
+    grub_fatal (N_("error trying to perform 'systemctl kexec': %d"), rc);
ed1787d
+
ed1787d
+  /*
ed1787d
+   * WARNING: forcible reset should only be used in read-only environments.
ed1787d
+   * grub-emu cannot check for these - users beware.
ed1787d
+   */
ed1787d
+  grub_dprintf ("linux", "Performing 'kexec -ex'");
ed1787d
+  kexec[1] = "-ex";
ec4acbb
+  kexec[2] = NULL;
ed1787d
+  rc = grub_util_exec (kexec);
ed1787d
+  if (rc != GRUB_ERR_NONE)
ed1787d
+    grub_fatal (N_("error trying to directly perform 'kexec -ex': %d"), rc);
ec4acbb
+
ec4acbb
+  return rc;
ec4acbb
+}
ec4acbb
+
ec4acbb
+static grub_err_t
ec4acbb
+grub_linux_unload (void)
ec4acbb
+{
ed1787d
+  /* Unloading: we're no longer in use. */
ec4acbb
+  grub_dl_unref (my_mod);
ed1787d
+  grub_free (boot_cmdline);
ec4acbb
+  boot_cmdline = NULL;
ec4acbb
+  return GRUB_ERR_NONE;
ec4acbb
+}
ec4acbb
+
ec4acbb
+static grub_err_t
ed1787d
+grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)), int argc,
ed1787d
+		char *argv[])
ec4acbb
+{
ec4acbb
+  int i;
ec4acbb
+  char *tempstr;
ec4acbb
+
ed1787d
+  /* Mark ourselves as in-use. */
ec4acbb
+  grub_dl_ref (my_mod);
ec4acbb
+
ec4acbb
+  if (argc == 0)
ec4acbb
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
ec4acbb
+
ed1787d
+  if (!grub_util_is_regular (argv[0]))
ed1787d
+    return grub_error (GRUB_ERR_FILE_NOT_FOUND,
ed1787d
+		       N_("cannot find kernel file %s"), argv[0]);
ec4acbb
+
ed1787d
+  grub_free (kernel_path);
ed1787d
+  kernel_path = grub_xasprintf ("%s", argv[0]);
ec4acbb
+
ed1787d
+  grub_free (boot_cmdline);
ed1787d
+  boot_cmdline = NULL;
ec4acbb
+
ed1787d
+  if (argc > 1)
ed1787d
+    {
ed1787d
+      boot_cmdline = grub_xasprintf ("--command-line=%s", argv[1]);
ed1787d
+      for (i = 2; i < argc; i++)
ed1787d
+        {
ed1787d
+          tempstr = grub_xasprintf ("%s %s", boot_cmdline, argv[i]);
ed1787d
+          grub_free (boot_cmdline);
ed1787d
+          boot_cmdline = tempstr;
ed1787d
+        }
ec4acbb
+    }
ec4acbb
+
ec4acbb
+  grub_loader_set (grub_linux_boot, grub_linux_unload, 0);
ec4acbb
+
ec4acbb
+  return GRUB_ERR_NONE;
ec4acbb
+}
ec4acbb
+
ec4acbb
+static grub_err_t
ed1787d
+grub_cmd_initrd (grub_command_t cmd __attribute__ ((unused)), int argc,
ed1787d
+		 char *argv[])
ec4acbb
+{
ec4acbb
+  if (argc == 0)
ec4acbb
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
ec4acbb
+
ed1787d
+  if (!grub_util_is_regular (argv[0]))
ed1787d
+    return grub_error (GRUB_ERR_FILE_NOT_FOUND,
ed1787d
+		       N_("Cannot find initrd file %s"), argv[0]);
ec4acbb
+
ed1787d
+  grub_free (initrd_path);
ed1787d
+  initrd_path = grub_xasprintf ("%s", argv[0]);
ec4acbb
+
ed1787d
+  /* We are done - mark ourselves as on longer in use. */
ec4acbb
+  grub_dl_unref (my_mod);
ec4acbb
+
ec4acbb
+  return GRUB_ERR_NONE;
ec4acbb
+}
ec4acbb
+
ec4acbb
+static grub_command_t cmd_linux, cmd_initrd;
ec4acbb
+
ed1787d
+GRUB_MOD_INIT (linux)
ec4acbb
+{
ed1787d
+  cmd_linux = grub_register_command ("linux", grub_cmd_linux, 0,
ed1787d
+				     N_("Load Linux."));
ed1787d
+  cmd_initrd = grub_register_command ("initrd", grub_cmd_initrd, 0,
ed1787d
+				      N_("Load initrd."));
ec4acbb
+  my_mod = mod;
ec4acbb
+}
ec4acbb
+
ed1787d
+GRUB_MOD_FINI (linux)
ec4acbb
+{
ec4acbb
+  grub_unregister_command (cmd_linux);
ec4acbb
+  grub_unregister_command (cmd_initrd);
ec4acbb
+}
ec4acbb
diff --git a/include/grub/emu/exec.h b/include/grub/emu/exec.h
e622855
index d1073ef86a..1b61b4a2e5 100644
ec4acbb
--- a/include/grub/emu/exec.h
ec4acbb
+++ b/include/grub/emu/exec.h
ec4acbb
@@ -23,6 +23,8 @@
ec4acbb
 #include <stdarg.h>
ec4acbb
 
ec4acbb
 #include <sys/types.h>
ec4acbb
+#include <grub/symbol.h>
ec4acbb
+
ec4acbb
 pid_t
ec4acbb
 grub_util_exec_pipe (const char *const *argv, int *fd);
ec4acbb
 pid_t
ec4acbb
@@ -32,7 +34,7 @@ int
ec4acbb
 grub_util_exec_redirect_all (const char *const *argv, const char *stdin_file,
ec4acbb
 			     const char *stdout_file, const char *stderr_file);
ec4acbb
 int
ec4acbb
-grub_util_exec (const char *const *argv);
ec4acbb
+EXPORT_FUNC(grub_util_exec) (const char *const *argv);
ec4acbb
 int
ec4acbb
 grub_util_exec_redirect (const char *const *argv, const char *stdin_file,
ec4acbb
 			 const char *stdout_file);
ec4acbb
diff --git a/include/grub/emu/hostfile.h b/include/grub/emu/hostfile.h
e622855
index cfb1e2b566..a61568e36e 100644
ec4acbb
--- a/include/grub/emu/hostfile.h
ec4acbb
+++ b/include/grub/emu/hostfile.h
ec4acbb
@@ -22,6 +22,7 @@
ec4acbb
 #include <grub/disk.h>
ec4acbb
 #include <grub/partition.h>
ec4acbb
 #include <sys/types.h>
ec4acbb
+#include <grub/symbol.h>
ec4acbb
 #include <grub/osdep/hostfile.h>
ec4acbb
 
ec4acbb
 int
ec4acbb
@@ -29,7 +30,7 @@ grub_util_is_directory (const char *path);
ec4acbb
 int
ec4acbb
 grub_util_is_special_file (const char *path);
ec4acbb
 int
ec4acbb
-grub_util_is_regular (const char *path);
ec4acbb
+EXPORT_FUNC(grub_util_is_regular) (const char *path);
ec4acbb
 
ec4acbb
 char *
ec4acbb
 grub_util_path_concat (size_t n, ...);
ec4acbb
diff --git a/include/grub/emu/misc.h b/include/grub/emu/misc.h
e622855
index ff9c48a649..01056954b9 100644
ec4acbb
--- a/include/grub/emu/misc.h
ec4acbb
+++ b/include/grub/emu/misc.h
46968b6
@@ -57,6 +57,9 @@ void EXPORT_FUNC(grub_util_warn) (const char *fmt, ...) __attribute__ ((format (
e153146
 void EXPORT_FUNC(grub_util_info) (const char *fmt, ...) __attribute__ ((format (GNU_PRINTF, 1, 2)));
e153146
 void EXPORT_FUNC(grub_util_error) (const char *fmt, ...) __attribute__ ((format (GNU_PRINTF, 1, 2), noreturn));
ec4acbb
 
ec4acbb
+void EXPORT_FUNC(grub_util_set_kexecute) (void);
ec4acbb
+int EXPORT_FUNC(grub_util_get_kexecute) (void) WARN_UNUSED_RESULT;
ec4acbb
+
ec4acbb
 grub_uint64_t EXPORT_FUNC (grub_util_get_cpu_time_ms) (void);
ec4acbb
 
ec4acbb
 #ifdef HAVE_DEVICE_MAPPER
ed1787d
diff --git a/docs/grub.texi b/docs/grub.texi
851216d
index a4da9c2a1b..1750b72ee9 100644
ed1787d
--- a/docs/grub.texi
ed1787d
+++ b/docs/grub.texi
ed1787d
@@ -923,17 +923,17 @@ magic.
ed1787d
 @node General boot methods
ed1787d
 @section How to boot operating systems
ed1787d
 
ed1787d
-GRUB has two distinct boot methods. One of the two is to load an
ed1787d
-operating system directly, and the other is to chain-load another boot
ed1787d
-loader which then will load an operating system actually. Generally
ed1787d
-speaking, the former is more desirable, because you don't need to
ed1787d
-install or maintain other boot loaders and GRUB is flexible enough to
ed1787d
-load an operating system from an arbitrary disk/partition. However,
ed1787d
-the latter is sometimes required, since GRUB doesn't support all the
ed1787d
-existing operating systems natively.
ed1787d
+GRUB has three distinct boot methods: loading an operating system
ed1787d
+directly, using kexec from userspace, and chainloading another
ed1787d
+bootloader. Generally speaking, the first two are more desirable
ed1787d
+because you don't need to install or maintain other boot loaders and
ed1787d
+GRUB is flexible enough to load an operating system from an arbitrary
ed1787d
+disk/partition. However, chainloading is sometimes required, as GRUB
ed1787d
+doesn't support all existing operating systems natively.
ed1787d
 
ed1787d
 @menu
ed1787d
 * Loading an operating system directly::
ed1787d
+* Kexec::
ed1787d
 * Chain-loading::
ed1787d
 @end menu
ed1787d
 
ed1787d
@@ -959,6 +959,20 @@ use more complicated instructions. @xref{DOS/Windows}, for more
ed1787d
 information.
ed1787d
 
ed1787d
 
ed1787d
+@node Kexec
ed1787d
+@subsection Kexec with grub2-emu
ed1787d
+
ed1787d
+GRUB can be run in userspace by invoking the grub2-emu tool. It will
ed1787d
+read all configuration scripts as if booting directly (see @xref{Loading
ed1787d
+an operating system directly}). With the @code{--kexec} flag, and
ed1787d
+kexec(8) support from the operating system, the @command{linux} command
ed1787d
+will directly boot the target image. For systems that lack working
ed1787d
+systemctl(1) support for kexec, passing the @code{--kexec} flag twice
ed1787d
+will fallback to invoking kexec(8) directly; note however that this
ed1787d
+fallback may be unsafe outside read-only environments, as it does not
ed1787d
+invoke shutdown machinery.
ed1787d
+
ed1787d
+
ed1787d
 @node Chain-loading
ed1787d
 @subsection Chain-loading an OS
ed1787d
 
ec4acbb
diff --git a/grub-core/Makefile.am b/grub-core/Makefile.am
ed1787d
index c2e8a82bce..dd49939aaa 100644
ec4acbb
--- a/grub-core/Makefile.am
ec4acbb
+++ b/grub-core/Makefile.am
ed1787d
@@ -309,6 +309,7 @@ KERNEL_HEADER_FILES += $(top_srcdir)/include/grub/emu/net.h
ec4acbb
 KERNEL_HEADER_FILES += $(top_srcdir)/include/grub/emu/hostdisk.h
ec4acbb
 KERNEL_HEADER_FILES += $(top_srcdir)/include/grub/emu/hostfile.h
ec4acbb
 KERNEL_HEADER_FILES += $(top_srcdir)/include/grub/extcmd.h
ec4acbb
+KERNEL_HEADER_FILES += $(top_srcdir)/include/grub/emu/exec.h
ec4acbb
 if COND_GRUB_EMU_SDL
ec4acbb
 KERNEL_HEADER_FILES += $(top_srcdir)/include/grub/sdl.h
ec4acbb
 endif