Blame SOURCES/0311-loader-Add-support-for-grub-emu-to-kexec-Linux-menu-.patch

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