| //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 |
| } |