treewide: introduce osbase package and move things around
All except localregistry moved from metropolis/pkg to osbase,
localregistry moved to metropolis/test as its only used there anyway.
Change-Id: If1a4bf377364bef0ac23169e1b90379c71b06d72
Reviewed-on: https://review.monogon.dev/c/monogon/+/3079
Tested-by: Jenkins CI
Reviewed-by: Serge Bazanski <serge@monogon.tech>
diff --git a/osbase/kexec/BUILD.bazel b/osbase/kexec/BUILD.bazel
new file mode 100644
index 0000000..3004c5a
--- /dev/null
+++ b/osbase/kexec/BUILD.bazel
@@ -0,0 +1,20 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library")
+
+go_library(
+ name = "kexec",
+ srcs = ["kexec.go"],
+ importpath = "source.monogon.dev/osbase/kexec",
+ visibility = ["//visibility:public"],
+ deps = select({
+ "@io_bazel_rules_go//go/platform:amd64": [
+ "@org_golang_x_sys//unix",
+ ],
+ "@io_bazel_rules_go//go/platform:arm64": [
+ "@org_golang_x_sys//unix",
+ ],
+ "@io_bazel_rules_go//go/platform:riscv64": [
+ "@org_golang_x_sys//unix",
+ ],
+ "//conditions:default": [],
+ }),
+)
diff --git a/osbase/kexec/kexec.go b/osbase/kexec/kexec.go
new file mode 100644
index 0000000..7109903
--- /dev/null
+++ b/osbase/kexec/kexec.go
@@ -0,0 +1,75 @@
+//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 s.Err() != nil {
+ return fmt.Errorf("failed to read EFI systab: %w", s.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
+}