blob: 4d05037d5efecf644c0b57c19b1252e4f51cff31 [file] [log] [blame]
//go:build amd64 || arm64 || riscv64
// +build amd64 arm64 riscv64
// Package kexec allows executing subsequent kernels from Linux userspace.
package kexec
import (
"bufio"
"errors"
"fmt"
"io"
"os"
"runtime"
"strings"
"golang.org/x/sys/unix"
)
// FileLoad loads the given kernel as the new kernel with the given initramfs
// and cmdline. It also performs auxiliary work like adding the ACPI RSDP
// physical address to command line if using EFI. The kernel can be started by
// calling unix.Reboot(unix.LINUX_REBOOT_CMD_KEXEC).
// The underlying syscall is only available on x86_64, arm64 and riscv.
// Parts of this function are taken from u-root's kexec package.
func FileLoad(kernel, initramfs *os.File, cmdline string) error {
passedCmdline := cmdline
systab, err := os.Open("/sys/firmware/efi/systab")
if os.IsNotExist(err) {
// No EFI, nothing to do
} else if err != nil {
return fmt.Errorf("unable to open EFI systab: %w", err)
} else {
s := bufio.NewScanner(systab)
for s.Scan() {
if errors.Is(s.Err(), io.EOF) {
// We have no RSDP, no need to pass it
break
}
if err != nil {
return fmt.Errorf("failed to read EFI systab: %w", err)
}
parts := strings.SplitN(s.Text(), "=", 2)
// There are two ACPI RDSP revisions, 1.0 and 2.0.
// Linux guarantees that the 2.0 always comes before the
// 1.0 so just matching and breaking is good enough.
if parts[0] == "ACPI20" || parts[0] == "ACPI" {
// Technically this could be passed through as parsing a hexa-
// decimal address and printing it back does nothing, but in
// case unexpected values show up this could cause very hard-
// to-debug crashes when the new kernel boots.
var acpiRsdp int64
if _, err := fmt.Sscanf(parts[1], "0x%x", &acpiRsdp); err != nil {
return fmt.Errorf("failed to parse EFI systab ACP RSDP address: %w", err)
}
passedCmdline += fmt.Sprintf(" acpi_rsdp=0x%x", acpiRsdp)
break
}
}
}
var flags int
var initramfsfd int
if initramfs != nil {
initramfsfd = int(initramfs.Fd())
} else {
flags |= unix.KEXEC_FILE_NO_INITRAMFS
}
if err := unix.KexecFileLoad(int(kernel.Fd()), initramfsfd, passedCmdline, flags); err != nil {
return fmt.Errorf("SYS_kexec_file_load(%d, %d, %s, %x) = %v", kernel.Fd(), initramfsfd, cmdline, flags, err)
}
runtime.KeepAlive(kernel)
runtime.KeepAlive(initramfs)
return nil
}