loader: Add support for grub-emu to kexec Linux menu entries

The GRUB emulator is used as a debugging utility but it could also be
used as a user-space bootloader if there is support to boot an operating
system.

The Linux kernel is already able to (re)boot another kernel via the
kexec boot mechanism. So the grub-emu tool could rely on this feature
and have linux and initrd commands that are used to pass a kernel,
initramfs image and command line parameters to kexec for booting
a selected menu entry.

By default the systemctl kexec option is used so systemd can shutdown
all of the running services before doing a reboot using kexec. But if
this is not present, it can fall back to executing the kexec user-space
tool directly. The ability to force a kexec-reboot when systemctl kexec
fails must only be used in controlled environments to avoid possible
filesystem corruption and data loss.

Signed-off-by: Raymund Will <rw@suse.com>
Signed-off-by: John Jolly <jjolly@suse.com>
Signed-off-by: Javier Martinez Canillas <javierm@redhat.com>
Signed-off-by: Robbie Harwood <rharwood@redhat.com>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
This commit is contained in:
Raymund Will 2022-10-24 14:33:50 -04:00 committed by Daniel Kiper
parent 62037e01b2
commit e364307f6a
9 changed files with 231 additions and 12 deletions

View File

@ -923,17 +923,17 @@ magic.
@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
@ -959,6 +959,20 @@ use more complicated instructions. @xref{DOS/Windows}, for more
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

View File

@ -307,6 +307,7 @@ KERNEL_HEADER_FILES += $(top_srcdir)/include/grub/emu/net.h
KERNEL_HEADER_FILES += $(top_srcdir)/include/grub/emu/hostdisk.h
KERNEL_HEADER_FILES += $(top_srcdir)/include/grub/emu/hostfile.h
KERNEL_HEADER_FILES += $(top_srcdir)/include/grub/extcmd.h
KERNEL_HEADER_FILES += $(top_srcdir)/include/grub/emu/exec.h
if COND_GRUB_EMU_SDL
KERNEL_HEADER_FILES += $(top_srcdir)/include/grub/sdl.h
endif

View File

@ -1826,9 +1826,9 @@ module = {
arm64 = loader/arm64/linux.c;
riscv32 = loader/riscv/linux.c;
riscv64 = loader/riscv/linux.c;
emu = loader/emu/linux.c;
common = loader/linux.c;
common = lib/cmdline.c;
enable = noemu;
};
module = {

View File

@ -107,6 +107,7 @@ static struct argp_option options[] = {
N_("use GRUB files in the directory DIR [default=%s]"), 0},
{"verbose", 'v', 0, 0, N_("print verbose messages."), 0},
{"hold", 'H', N_("SECS"), OPTION_ARG_OPTIONAL, N_("wait until a debugger will attach"), 0},
{"kexec", 'X', 0, 0, N_("use kexec to boot Linux kernels via systemctl (pass twice to enable dangerous fallback to non-systemctl)."), 0},
{ 0, 0, 0, 0, 0, 0 }
};
@ -164,6 +165,9 @@ argp_parser (int key, char *arg, struct argp_state *state)
case 'v':
verbosity++;
break;
case 'X':
grub_util_set_kexecute ();
break;
case ARGP_KEY_ARG:
{

View File

@ -39,6 +39,7 @@
#include <grub/emu/misc.h>
int verbosity;
int kexecute;
void
grub_util_warn (const char *fmt, ...)
@ -82,7 +83,7 @@ grub_util_error (const char *fmt, ...)
vfprintf (stderr, fmt, ap);
va_end (ap);
fprintf (stderr, ".\n");
exit (1);
grub_exit ();
}
void *
@ -153,6 +154,9 @@ xasprintf (const char *fmt, ...)
void
grub_exit (void)
{
#if defined (GRUB_KERNEL)
grub_reboot ();
#endif
exit (1);
}
#endif
@ -214,3 +218,15 @@ grub_util_load_image (const char *path, char *buf)
fclose (fp);
}
void
grub_util_set_kexecute (void)
{
kexecute++;
}
int
grub_util_get_kexecute (void)
{
return kexecute;
}

View File

@ -0,0 +1,178 @@
/*
* 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);
}

View File

@ -23,6 +23,8 @@
#include <stdarg.h>
#include <sys/types.h>
#include <grub/symbol.h>
pid_t
grub_util_exec_pipe (const char *const *argv, int *fd);
pid_t
@ -32,7 +34,7 @@ int
grub_util_exec_redirect_all (const char *const *argv, const char *stdin_file,
const char *stdout_file, const char *stderr_file);
int
grub_util_exec (const char *const *argv);
EXPORT_FUNC(grub_util_exec) (const char *const *argv);
int
grub_util_exec_redirect (const char *const *argv, const char *stdin_file,
const char *stdout_file);

View File

@ -22,6 +22,7 @@
#include <grub/disk.h>
#include <grub/partition.h>
#include <sys/types.h>
#include <grub/symbol.h>
#include <grub/osdep/hostfile.h>
int
@ -29,7 +30,7 @@ grub_util_is_directory (const char *path);
int
grub_util_is_special_file (const char *path);
int
grub_util_is_regular (const char *path);
EXPORT_FUNC(grub_util_is_regular) (const char *path);
char *
grub_util_path_concat (size_t n, ...);

View File

@ -57,6 +57,9 @@ void EXPORT_FUNC(grub_util_warn) (const char *fmt, ...) __attribute__ ((format (
void EXPORT_FUNC(grub_util_info) (const char *fmt, ...) __attribute__ ((format (GNU_PRINTF, 1, 2)));
void EXPORT_FUNC(grub_util_error) (const char *fmt, ...) __attribute__ ((format (GNU_PRINTF, 1, 2), noreturn));
void EXPORT_FUNC(grub_util_set_kexecute) (void);
int EXPORT_FUNC(grub_util_get_kexecute) (void) WARN_UNUSED_RESULT;
grub_uint64_t EXPORT_FUNC (grub_util_get_cpu_time_ms) (void);
#ifdef HAVE_DEVICE_MAPPER