Lorenz Brun | 5486a9c | 2022-09-12 16:49:30 +0000 | [diff] [blame] | 1 | //go:build amd64 || arm64 || riscv64 |
| 2 | // +build amd64 arm64 riscv64 |
| 3 | |
| 4 | // Package kexec allows executing subsequent kernels from Linux userspace. |
| 5 | package kexec |
| 6 | |
| 7 | import ( |
Lorenz Brun | 0123a1c | 2022-10-24 18:41:48 +0000 | [diff] [blame] | 8 | "bufio" |
| 9 | "errors" |
Lorenz Brun | 5486a9c | 2022-09-12 16:49:30 +0000 | [diff] [blame] | 10 | "fmt" |
Lorenz Brun | 0123a1c | 2022-10-24 18:41:48 +0000 | [diff] [blame] | 11 | "io" |
Lorenz Brun | 5486a9c | 2022-09-12 16:49:30 +0000 | [diff] [blame] | 12 | "os" |
| 13 | "runtime" |
Lorenz Brun | 0123a1c | 2022-10-24 18:41:48 +0000 | [diff] [blame] | 14 | "strings" |
Lorenz Brun | 5486a9c | 2022-09-12 16:49:30 +0000 | [diff] [blame] | 15 | |
| 16 | "golang.org/x/sys/unix" |
| 17 | ) |
| 18 | |
| 19 | // FileLoad loads the given kernel as the new kernel with the given initramfs |
Lorenz Brun | 0123a1c | 2022-10-24 18:41:48 +0000 | [diff] [blame] | 20 | // and cmdline. It also performs auxiliary work like adding the ACPI RSDP |
| 21 | // physical address to command line if using EFI. The kernel can be started by |
| 22 | // calling unix.Reboot(unix.LINUX_REBOOT_CMD_KEXEC). |
| 23 | // The underlying syscall is only available on x86_64, arm64 and riscv. |
Lorenz Brun | 5486a9c | 2022-09-12 16:49:30 +0000 | [diff] [blame] | 24 | // Parts of this function are taken from u-root's kexec package. |
| 25 | func FileLoad(kernel, initramfs *os.File, cmdline string) error { |
Lorenz Brun | 0123a1c | 2022-10-24 18:41:48 +0000 | [diff] [blame] | 26 | passedCmdline := cmdline |
| 27 | systab, err := os.Open("/sys/firmware/efi/systab") |
| 28 | if os.IsNotExist(err) { |
| 29 | // No EFI, nothing to do |
| 30 | } else if err != nil { |
| 31 | return fmt.Errorf("unable to open EFI systab: %w", err) |
| 32 | } else { |
| 33 | s := bufio.NewScanner(systab) |
| 34 | for s.Scan() { |
| 35 | if errors.Is(s.Err(), io.EOF) { |
| 36 | // We have no RSDP, no need to pass it |
| 37 | break |
| 38 | } |
| 39 | if err != nil { |
| 40 | return fmt.Errorf("failed to read EFI systab: %w", err) |
| 41 | } |
| 42 | parts := strings.SplitN(s.Text(), "=", 2) |
| 43 | // There are two ACPI RDSP revisions, 1.0 and 2.0. |
| 44 | // Linux guarantees that the 2.0 always comes before the |
| 45 | // 1.0 so just matching and breaking is good enough. |
| 46 | if parts[0] == "ACPI20" || parts[0] == "ACPI" { |
| 47 | // Technically this could be passed through as parsing a hexa- |
| 48 | // decimal address and printing it back does nothing, but in |
| 49 | // case unexpected values show up this could cause very hard- |
| 50 | // to-debug crashes when the new kernel boots. |
| 51 | var acpiRsdp int64 |
| 52 | if _, err := fmt.Sscanf(parts[1], "0x%x", &acpiRsdp); err != nil { |
| 53 | return fmt.Errorf("failed to parse EFI systab ACP RSDP address: %w", err) |
| 54 | } |
| 55 | passedCmdline += fmt.Sprintf(" acpi_rsdp=0x%x", acpiRsdp) |
| 56 | break |
| 57 | } |
| 58 | } |
| 59 | } |
| 60 | |
Lorenz Brun | 5486a9c | 2022-09-12 16:49:30 +0000 | [diff] [blame] | 61 | var flags int |
| 62 | var initramfsfd int |
| 63 | if initramfs != nil { |
| 64 | initramfsfd = int(initramfs.Fd()) |
| 65 | } else { |
| 66 | flags |= unix.KEXEC_FILE_NO_INITRAMFS |
| 67 | } |
| 68 | |
Lorenz Brun | 0123a1c | 2022-10-24 18:41:48 +0000 | [diff] [blame] | 69 | if err := unix.KexecFileLoad(int(kernel.Fd()), initramfsfd, passedCmdline, flags); err != nil { |
Lorenz Brun | 5486a9c | 2022-09-12 16:49:30 +0000 | [diff] [blame] | 70 | return fmt.Errorf("SYS_kexec_file_load(%d, %d, %s, %x) = %v", kernel.Fd(), initramfsfd, cmdline, flags, err) |
| 71 | } |
| 72 | runtime.KeepAlive(kernel) |
| 73 | runtime.KeepAlive(initramfs) |
| 74 | return nil |
| 75 | } |