m/p/kexec: add minimal kexec library

This adds a minimal kexec library which wraps the kexec_file_load
syscall in a Go-style interface.

Change-Id: Ia69b47ec6a305b19b238f30a7515aabdccb44bb9
Reviewed-on: https://review.monogon.dev/c/monogon/+/903
Tested-by: Jenkins CI
Reviewed-by: Sergiusz Bazanski <serge@monogon.tech>
diff --git a/metropolis/pkg/kexec/kexec.go b/metropolis/pkg/kexec/kexec.go
new file mode 100644
index 0000000..dc330ec
--- /dev/null
+++ b/metropolis/pkg/kexec/kexec.go
@@ -0,0 +1,35 @@
+//go:build amd64 || arm64 || riscv64
+// +build amd64 arm64 riscv64
+
+// Package kexec allows executing subsequent kernels from Linux userspace.
+package kexec
+
+import (
+	"fmt"
+	"os"
+	"runtime"
+
+	"golang.org/x/sys/unix"
+)
+
+// FileLoad loads the given kernel as the new kernel with the given initramfs
+// and cmdline. 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 {
+	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, cmdline, 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
+}