@node General boot methods
@section How to boot operating systems
-GRUB has two distinct boot methods. One of the two is to load an
-operating system directly, and the other is to chain-load another boot
-loader which then will load an operating system actually. Generally
-speaking, the former is more desirable, because you don't need to
-install or maintain other boot loaders and GRUB is flexible enough to
-load an operating system from an arbitrary disk/partition. However,
-the latter is sometimes required, since GRUB doesn't support all the
-existing operating systems natively.
+GRUB has three distinct boot methods: loading an operating system
+directly, using kexec from userspace, and chainloading another
+bootloader. Generally speaking, the first two are more desirable
+because you don't need to install or maintain other boot loaders and
+GRUB is flexible enough to load an operating system from an arbitrary
+disk/partition. However, chainloading is sometimes required, as GRUB
+doesn't support all existing operating systems natively.
@menu
* Loading an operating system directly::
+* Kexec::
* Chain-loading::
@end menu
information.
+@node Kexec
+@subsection Kexec with grub2-emu
+
+GRUB can be run in userspace by invoking the grub2-emu tool. It will
+read all configuration scripts as if booting directly (see @xref{Loading
+an operating system directly}). With the @code{--kexec} flag, and
+kexec(8) support from the operating system, the @command{linux} command
+will directly boot the target image. For systems that lack working
+systemctl(1) support for kexec, passing the @code{--kexec} flag twice
+will fallback to invoking kexec(8) directly; note however that this
+fallback may be unsafe outside read-only environments, as it does not
+invoke shutdown machinery.
+
+
@node Chain-loading
@subsection Chain-loading an OS
--- /dev/null
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2022 Free Software Foundation, Inc.
+ *
+ * GRUB is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GRUB is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/loader.h>
+#include <grub/dl.h>
+#include <grub/command.h>
+#include <grub/time.h>
+
+#include <grub/emu/exec.h>
+#include <grub/emu/hostfile.h>
+#include <grub/emu/misc.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+static grub_dl_t my_mod;
+
+static char *kernel_path;
+static char *initrd_path;
+static char *boot_cmdline;
+
+static grub_err_t
+grub_linux_boot (void)
+{
+ grub_err_t rc = GRUB_ERR_NONE;
+ char *initrd_param;
+ const char *kexec[] = {"kexec", "-la", kernel_path, boot_cmdline, NULL, NULL};
+ const char *systemctl[] = {"systemctl", "kexec", NULL};
+ int kexecute = grub_util_get_kexecute ();
+
+ if (initrd_path)
+ {
+ initrd_param = grub_xasprintf ("--initrd=%s", initrd_path);
+ kexec[3] = initrd_param;
+ kexec[4] = boot_cmdline;
+ }
+ else
+ initrd_param = grub_xasprintf ("%s", "");
+
+ grub_dprintf ("linux", "%serforming 'kexec -la %s %s %s'\n",
+ (kexecute) ? "P" : "Not p",
+ kernel_path, initrd_param, boot_cmdline);
+
+ if (kexecute)
+ rc = grub_util_exec (kexec);
+
+ grub_free (initrd_param);
+
+ if (rc != GRUB_ERR_NONE)
+ {
+ grub_error (rc, N_("error trying to perform kexec load operation"));
+ grub_sleep (3);
+ return rc;
+ }
+
+ if (kexecute < 1)
+ grub_fatal (N_("use '"PACKAGE"-emu --kexec' to force a system restart"));
+
+ grub_dprintf ("linux", "Performing 'systemctl kexec' (%s) ",
+ (kexecute==1) ? "do-or-die" : "just-in-case");
+ rc = grub_util_exec (systemctl);
+
+ if (kexecute == 1)
+ grub_fatal (N_("error trying to perform 'systemctl kexec': %d"), rc);
+
+ /*
+ * WARNING: forcible reset should only be used in read-only environments.
+ * grub-emu cannot check for these - users beware.
+ */
+ grub_dprintf ("linux", "Performing 'kexec -ex'");
+ kexec[1] = "-ex";
+ kexec[2] = NULL;
+ rc = grub_util_exec (kexec);
+ if (rc != GRUB_ERR_NONE)
+ grub_fatal (N_("error trying to directly perform 'kexec -ex': %d"), rc);
+
+ return rc;
+}
+
+static grub_err_t
+grub_linux_unload (void)
+{
+ /* Unloading: we're no longer in use. */
+ grub_dl_unref (my_mod);
+ grub_free (boot_cmdline);
+ boot_cmdline = NULL;
+ return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)), int argc,
+ char *argv[])
+{
+ int i;
+ char *tempstr;
+
+ /* Mark ourselves as in-use. */
+ grub_dl_ref (my_mod);
+
+ if (argc == 0)
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
+
+ if (!grub_util_is_regular (argv[0]))
+ return grub_error (GRUB_ERR_FILE_NOT_FOUND,
+ N_("cannot find kernel file %s"), argv[0]);
+
+ grub_free (kernel_path);
+ kernel_path = grub_xasprintf ("%s", argv[0]);
+
+ grub_free (boot_cmdline);
+ boot_cmdline = NULL;
+
+ if (argc > 1)
+ {
+ boot_cmdline = grub_xasprintf ("--command-line=%s", argv[1]);
+ for (i = 2; i < argc; i++)
+ {
+ tempstr = grub_xasprintf ("%s %s", boot_cmdline, argv[i]);
+ grub_free (boot_cmdline);
+ boot_cmdline = tempstr;
+ }
+ }
+
+ grub_loader_set (grub_linux_boot, grub_linux_unload, 0);
+
+ return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+grub_cmd_initrd (grub_command_t cmd __attribute__ ((unused)), int argc,
+ char *argv[])
+{
+ if (argc == 0)
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
+
+ if (!grub_util_is_regular (argv[0]))
+ return grub_error (GRUB_ERR_FILE_NOT_FOUND,
+ N_("Cannot find initrd file %s"), argv[0]);
+
+ grub_free (initrd_path);
+ initrd_path = grub_xasprintf ("%s", argv[0]);
+
+ /* We are done - mark ourselves as on longer in use. */
+ grub_dl_unref (my_mod);
+
+ return GRUB_ERR_NONE;
+}
+
+static grub_command_t cmd_linux, cmd_initrd;
+
+GRUB_MOD_INIT (linux)
+{
+ cmd_linux = grub_register_command ("linux", grub_cmd_linux, 0,
+ N_("Load Linux."));
+ cmd_initrd = grub_register_command ("initrd", grub_cmd_initrd, 0,
+ N_("Load initrd."));
+ my_mod = mod;
+}
+
+GRUB_MOD_FINI (linux)
+{
+ grub_unregister_command (cmd_linux);
+ grub_unregister_command (cmd_initrd);
+}