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
|
@node General boot methods
|
||||||
@section How to boot operating systems
|
@section How to boot operating systems
|
||||||
|
|
||||||
GRUB has two distinct boot methods. One of the two is to load an
|
GRUB has three distinct boot methods: loading an operating system
|
||||||
operating system directly, and the other is to chain-load another boot
|
directly, using kexec from userspace, and chainloading another
|
||||||
loader which then will load an operating system actually. Generally
|
bootloader. Generally speaking, the first two are more desirable
|
||||||
speaking, the former is more desirable, because you don't need to
|
because you don't need to install or maintain other boot loaders and
|
||||||
install or maintain other boot loaders and GRUB is flexible enough to
|
GRUB is flexible enough to load an operating system from an arbitrary
|
||||||
load an operating system from an arbitrary disk/partition. However,
|
disk/partition. However, chainloading is sometimes required, as GRUB
|
||||||
the latter is sometimes required, since GRUB doesn't support all the
|
doesn't support all existing operating systems natively.
|
||||||
existing operating systems natively.
|
|
||||||
|
|
||||||
@menu
|
@menu
|
||||||
* Loading an operating system directly::
|
* Loading an operating system directly::
|
||||||
|
* Kexec::
|
||||||
* Chain-loading::
|
* Chain-loading::
|
||||||
@end menu
|
@end menu
|
||||||
|
|
||||||
@ -959,6 +959,20 @@ use more complicated instructions. @xref{DOS/Windows}, for more
|
|||||||
information.
|
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
|
@node Chain-loading
|
||||||
@subsection Chain-loading an OS
|
@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/hostdisk.h
|
||||||
KERNEL_HEADER_FILES += $(top_srcdir)/include/grub/emu/hostfile.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/extcmd.h
|
||||||
|
KERNEL_HEADER_FILES += $(top_srcdir)/include/grub/emu/exec.h
|
||||||
if COND_GRUB_EMU_SDL
|
if COND_GRUB_EMU_SDL
|
||||||
KERNEL_HEADER_FILES += $(top_srcdir)/include/grub/sdl.h
|
KERNEL_HEADER_FILES += $(top_srcdir)/include/grub/sdl.h
|
||||||
endif
|
endif
|
||||||
|
|||||||
@ -1826,9 +1826,9 @@ module = {
|
|||||||
arm64 = loader/arm64/linux.c;
|
arm64 = loader/arm64/linux.c;
|
||||||
riscv32 = loader/riscv/linux.c;
|
riscv32 = loader/riscv/linux.c;
|
||||||
riscv64 = loader/riscv/linux.c;
|
riscv64 = loader/riscv/linux.c;
|
||||||
|
emu = loader/emu/linux.c;
|
||||||
common = loader/linux.c;
|
common = loader/linux.c;
|
||||||
common = lib/cmdline.c;
|
common = lib/cmdline.c;
|
||||||
enable = noemu;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
module = {
|
module = {
|
||||||
|
|||||||
@ -107,6 +107,7 @@ static struct argp_option options[] = {
|
|||||||
N_("use GRUB files in the directory DIR [default=%s]"), 0},
|
N_("use GRUB files in the directory DIR [default=%s]"), 0},
|
||||||
{"verbose", 'v', 0, 0, N_("print verbose messages."), 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},
|
{"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 }
|
{ 0, 0, 0, 0, 0, 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -164,6 +165,9 @@ argp_parser (int key, char *arg, struct argp_state *state)
|
|||||||
case 'v':
|
case 'v':
|
||||||
verbosity++;
|
verbosity++;
|
||||||
break;
|
break;
|
||||||
|
case 'X':
|
||||||
|
grub_util_set_kexecute ();
|
||||||
|
break;
|
||||||
|
|
||||||
case ARGP_KEY_ARG:
|
case ARGP_KEY_ARG:
|
||||||
{
|
{
|
||||||
|
|||||||
@ -39,6 +39,7 @@
|
|||||||
#include <grub/emu/misc.h>
|
#include <grub/emu/misc.h>
|
||||||
|
|
||||||
int verbosity;
|
int verbosity;
|
||||||
|
int kexecute;
|
||||||
|
|
||||||
void
|
void
|
||||||
grub_util_warn (const char *fmt, ...)
|
grub_util_warn (const char *fmt, ...)
|
||||||
@ -82,7 +83,7 @@ grub_util_error (const char *fmt, ...)
|
|||||||
vfprintf (stderr, fmt, ap);
|
vfprintf (stderr, fmt, ap);
|
||||||
va_end (ap);
|
va_end (ap);
|
||||||
fprintf (stderr, ".\n");
|
fprintf (stderr, ".\n");
|
||||||
exit (1);
|
grub_exit ();
|
||||||
}
|
}
|
||||||
|
|
||||||
void *
|
void *
|
||||||
@ -153,6 +154,9 @@ xasprintf (const char *fmt, ...)
|
|||||||
void
|
void
|
||||||
grub_exit (void)
|
grub_exit (void)
|
||||||
{
|
{
|
||||||
|
#if defined (GRUB_KERNEL)
|
||||||
|
grub_reboot ();
|
||||||
|
#endif
|
||||||
exit (1);
|
exit (1);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -214,3 +218,15 @@ grub_util_load_image (const char *path, char *buf)
|
|||||||
|
|
||||||
fclose (fp);
|
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 <stdarg.h>
|
||||||
|
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
#include <grub/symbol.h>
|
||||||
|
|
||||||
pid_t
|
pid_t
|
||||||
grub_util_exec_pipe (const char *const *argv, int *fd);
|
grub_util_exec_pipe (const char *const *argv, int *fd);
|
||||||
pid_t
|
pid_t
|
||||||
@ -32,7 +34,7 @@ int
|
|||||||
grub_util_exec_redirect_all (const char *const *argv, const char *stdin_file,
|
grub_util_exec_redirect_all (const char *const *argv, const char *stdin_file,
|
||||||
const char *stdout_file, const char *stderr_file);
|
const char *stdout_file, const char *stderr_file);
|
||||||
int
|
int
|
||||||
grub_util_exec (const char *const *argv);
|
EXPORT_FUNC(grub_util_exec) (const char *const *argv);
|
||||||
int
|
int
|
||||||
grub_util_exec_redirect (const char *const *argv, const char *stdin_file,
|
grub_util_exec_redirect (const char *const *argv, const char *stdin_file,
|
||||||
const char *stdout_file);
|
const char *stdout_file);
|
||||||
|
|||||||
@ -22,6 +22,7 @@
|
|||||||
#include <grub/disk.h>
|
#include <grub/disk.h>
|
||||||
#include <grub/partition.h>
|
#include <grub/partition.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
#include <grub/symbol.h>
|
||||||
#include <grub/osdep/hostfile.h>
|
#include <grub/osdep/hostfile.h>
|
||||||
|
|
||||||
int
|
int
|
||||||
@ -29,7 +30,7 @@ grub_util_is_directory (const char *path);
|
|||||||
int
|
int
|
||||||
grub_util_is_special_file (const char *path);
|
grub_util_is_special_file (const char *path);
|
||||||
int
|
int
|
||||||
grub_util_is_regular (const char *path);
|
EXPORT_FUNC(grub_util_is_regular) (const char *path);
|
||||||
|
|
||||||
char *
|
char *
|
||||||
grub_util_path_concat (size_t n, ...);
|
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_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_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);
|
grub_uint64_t EXPORT_FUNC (grub_util_get_cpu_time_ms) (void);
|
||||||
|
|
||||||
#ifdef HAVE_DEVICE_MAPPER
|
#ifdef HAVE_DEVICE_MAPPER
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user