Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions kexec/cmd/grub/grub.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
type Config struct {
Name string `json:"name,omitempty"`
Kernel string `json:"kernel"`
Initramfs string `json:"initramfs,omitempty"`
Initramfs []string `json:"initramfs,omitempty"`
KernelArgs string `json:"kernel_args,omitempty"`
Multiboot string `json:"multiboot_kernel,omitempty"`
MultibootArgs string `json:"multiboot_args,omitempty"`
Expand Down Expand Up @@ -53,7 +53,7 @@ func ParseGrubCfg(grubcfg string) (configs []Config, defaultConfig int64) {
// if a "menuentry", start a new boot config
if cfg != nil {
// save the previous boot config, if any
if cfg.Kernel != "" && cfg.Initramfs != "" {
if cfg.Kernel != "" && len(cfg.Initramfs) > 0 {
// only consider valid boot configs, i.e. the ones that have
// both kernel and initramfs
configs = append(configs, *cfg)
Expand Down Expand Up @@ -84,8 +84,7 @@ func ParseGrubCfg(grubcfg string) (configs []Config, defaultConfig int64) {
cfg.Kernel = kernel
cfg.KernelArgs = cmdline
case "initrd", "initrd16", "initrdefi":
initrd := sline[1]
cfg.Initramfs = initrd
cfg.Initramfs = sline[1:]
case "multiboot", "multiboot2":
multiboot := sline[1]
cmdline := strings.Join(sline[2:], " ")
Expand All @@ -106,7 +105,7 @@ func ParseGrubCfg(grubcfg string) (configs []Config, defaultConfig int64) {
}

// append last kernel config if it wasn't already
if inMenuEntry && cfg.Kernel != "" && cfg.Initramfs != "" {
if inMenuEntry && cfg.Kernel != "" && len(cfg.Initramfs) > 0 {
configs = append(configs, *cfg)
}
return configs, defaultConfig
Expand Down
22 changes: 21 additions & 1 deletion kexec/cmd/grub/grub_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ import (
"testing"
)

var multiInitrdConfig = `
menuentry 'Ubuntu' --class ubuntu --class gnu-linux {
linux /boot/vmlinuz-6.8.0-51-generic root=/dev/sda2 ro quiet
initrd /boot/amd64-microcode.img /boot/initrd.img-6.8.0-51-generic
}
`

var workingConfig = `#
# DO NOT EDIT THIS FILE
#
Expand Down Expand Up @@ -387,13 +394,26 @@ func TestGetDefaultCconfig(t *testing.T) {
&Config{
"'Ubuntu' ",
"/boot/vmlinuz-4.15.0-135-generic",
"/boot/initrd.img-4.15.0-135-generic",
[]string{"/boot/initrd.img-4.15.0-135-generic"},
"root=/dev/mapper/code--vg-root ro",
"",
"",
nil,
},
},
{
"multi-initrd",
args{multiInitrdConfig},
&Config{
"'Ubuntu' ",
"/boot/vmlinuz-6.8.0-51-generic",
[]string{"/boot/amd64-microcode.img", "/boot/initrd.img-6.8.0-51-generic"},
"root=/dev/sda2 ro quiet",
"",
"",
nil,
},
},
}
// Should determine our "default configuration"
for _, tt := range tests {
Expand Down
60 changes: 49 additions & 11 deletions kexec/cmd/kexec.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package cmd

import (
"fmt"
"io/ioutil"
"io"
"os"
"path/filepath"
"syscall"
Expand Down Expand Up @@ -33,8 +33,8 @@ var kexecCmd = &cobra.Command{
cmdLine := os.Getenv("CMD_LINE")
grubCfgPath := os.Getenv("GRUBCFG_PATH")

// These two strings contain the updated paths including the mountAction path
var kernelMountPath, initrdMountPath string
var kernelMountPath string
var initrdMountPaths []string

if blockDevice == "" {
log.Fatalf("No Block Device speified with Environment Variable [BLOCK_DEVICE]")
Expand All @@ -60,7 +60,7 @@ var kexecCmd = &cobra.Command{
// If we specify no kernelPath then we will fallback to autodetect and ignore the initrd and cmdline that may be passed
// by environment variables
if kernelPath == "" {
grubFile, err := ioutil.ReadFile(fmt.Sprintf("%s/%s", mountAction, grubCfgPath))
grubFile, err := os.ReadFile(fmt.Sprintf("%s/%s", mountAction, grubCfgPath))
if err != nil {
log.Fatal(err)
}
Expand All @@ -70,27 +70,30 @@ var kexecCmd = &cobra.Command{
}
log.Infof("Loaded boot config: %#v", bootConfig)
kernelMountPath = filepath.Join(mountAction, bootConfig.Kernel)
initrdMountPath = filepath.Join(mountAction, bootConfig.Initramfs)
for _, p := range bootConfig.Initramfs {
initrdMountPaths = append(initrdMountPaths, filepath.Join(mountAction, p))
}
// Overwrite the cmdline with what is found in grub.conf, unless something specific is added
if cmdLine == "" {
cmdLine = bootConfig.KernelArgs
}
} else {
kernelMountPath = filepath.Join(mountAction, kernelPath)
initrdMountPath = filepath.Join(mountAction, initrdPath)
initrdMountPaths = []string{filepath.Join(mountAction, initrdPath)}
}
// /mountAction/boot/vmlinuz
kernel, err := os.Open(kernelMountPath) // For read access.

kernel, err := os.Open(kernelMountPath)
if err != nil {
log.Fatal(err)
}
// /mountAction/boot/vmlinuz
initrd, err := os.Open(initrdMountPath) // For read access.

initrd, err := openInitrd(initrdMountPaths)
if err != nil {
log.Fatal(err)
}
defer os.Remove(initrd.Name())

log.Infof("Running Kexec: kernel: %s, initrd: %s, cmdLine: %v", kernelMountPath, initrdMountPath, cmdLine)
log.Infof("Running Kexec: kernel: %s, initrd: %v, cmdLine: %v", kernelMountPath, initrdMountPaths, cmdLine)
// Load the kernel configuration into memory
err = unix.KexecFileLoad(int(kernel.Fd()), int(initrd.Fd()), cmdLine, 0)
if err != nil {
Expand All @@ -109,3 +112,38 @@ func Execute() {
os.Exit(1)
}
}

// openInitrd returns a file ready for KexecFileLoad. For a single initrd it
// opens the file directly; for multiple initrds it concatenates them into a
// temp file (the Linux kernel accepts concatenated cpio archives).
func openInitrd(paths []string) (*os.File, error) {
if len(paths) == 1 {
return os.Open(paths[0])
}
if err := os.MkdirAll("/tmp", 0755); err != nil {
return nil, fmt.Errorf("creating /tmp: %w", err)
}
tmp, err := os.CreateTemp("/tmp", "initrd-*")
if err != nil {
return nil, fmt.Errorf("creating temp initrd: %w", err)
}
for _, p := range paths {
f, err := os.Open(p)
if err != nil {
tmp.Close()
os.Remove(tmp.Name())
return nil, fmt.Errorf("opening initrd %s: %w", p, err)
}
_, err = io.Copy(tmp, f)
f.Close()
if err != nil {
tmp.Close()
os.Remove(tmp.Name())
return nil, fmt.Errorf("copying initrd %s: %w", p, err)
}
}
name := tmp.Name()
tmp.Close()
// Reopen read-only — KexecFileLoad rejects files with writable fds open (ETXTBSY).
return os.Open(name)
}
Loading