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:
parent
62037e01b2
commit
e364307f6a
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 = {
|
||||
|
||||
@ -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:
|
||||
{
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
178
grub-core/loader/emu/linux.c
Normal file
178
grub-core/loader/emu/linux.c
Normal 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);
|
||||
}
|
||||
@ -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);
|
||||
|
||||
@ -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, ...);
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user