Files
jschoubben 62c0eb90cb kernel: kernel_boot reads microcode from each initramfs image (issue 1977, L6)
Replaces the state record read (root's 0700) with images_with_microcode per /boot/initramfs-*.img:
true when the first 64 KiB hold a path under kernel/x86/microcode/; an unreadable image goes to
image_errors with its error. Test: 1977/AC5.

Builds: L6 (issue 1977 note 3)
2026-11-12 20:54:34 +01:00

137 lines
4.6 KiB
Go

package main
// kernel_boot: which loader booted the machine, its entries, the kernels in /boot, the entry it booted and
// whether the fallback kernel is on the menu (novox/hq ADR 0508 §9, issue 1841). Reads only.
import (
"bytes"
"io"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
"syscall"
"unicode/utf16"
)
const loaderSelected = "sys/firmware/efi/efivars/LoaderEntrySelected-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f"
// BootState is what kernel_boot answers.
type BootState struct {
Loader string `json:"loader"`
Entries []string `json:"entries"`
Kernels []string `json:"kernels"`
Booted string `json:"booted"`
FallbackPresent bool `json:"fallback_present"`
BootFreeKiB int64 `json:"boot_free_kib"`
// MicrocodeRevision is the revision the CPU runs, the first "microcode" line of /proc/cpuinfo ("" when it has none).
MicrocodeRevision string `json:"microcode_revision"`
// ImagesWithMicrocode is, per /boot/initramfs-*.img, whether the image's early uncompressed cpio (its first 64 KiB)
// holds a path under kernel/x86/microcode/ (novox/hq issue 1977).
ImagesWithMicrocode map[string]bool `json:"images_with_microcode"`
// ImageErrors names each image that could not be read and why: such an image is in no verdict above.
ImageErrors []ImageError `json:"image_errors"`
}
// ImageError is an initramfs image that could not be read.
type ImageError struct {
Image string `json:"image"`
Error string `json:"error"`
}
var cpuMicrocode = regexp.MustCompile(`(?m)^microcode\s*:\s*(\S+)`)
// microcode reads the revision the CPU runs and which initramfs images carry microcode.
func (m *Machine) microcode(b *BootState) {
b.ImagesWithMicrocode = map[string]bool{}
b.ImageErrors = []ImageError{}
if raw, err := m.read("proc/cpuinfo"); err == nil {
if r := cpuMicrocode.FindStringSubmatch(raw); r != nil {
b.MicrocodeRevision = r[1]
}
}
images, _ := filepath.Glob(m.path("boot/initramfs-*.img"))
sort.Strings(images)
for _, img := range images {
name := filepath.Base(img)
f, err := os.Open(img)
if err != nil {
b.ImageErrors = append(b.ImageErrors, ImageError{name, err.Error()})
continue
}
head := make([]byte, 64*1024)
n, err := io.ReadFull(f, head)
f.Close()
if err != nil && err != io.ErrUnexpectedEOF && err != io.EOF {
b.ImageErrors = append(b.ImageErrors, ImageError{name, err.Error()})
continue
}
b.ImagesWithMicrocode[name] = bytes.Contains(head[:n], []byte("kernel/x86/microcode/"))
}
}
var grubKernel = regexp.MustCompile(`(?m)^\s*linux\s+(?:/boot)?/(vmlinuz-\S+)`)
var grubTitle = regexp.MustCompile(`(?m)^\s*menuentry\s+'([^']*)'`)
var bootImage = regexp.MustCompile(`BOOT_IMAGE=(?:/boot)?/?(\S+)`)
// Boot reads the machine, the loader the way the boot-entries script does: the loader's EFI variable means
// systemd-boot, a grub.cfg without it GRUB.
func (m *Machine) Boot() (*BootState, error) {
b := &BootState{Loader: "none", Entries: []string{}, Kernels: []string{}}
m.microcode(b)
files, _ := filepath.Glob(m.path("boot/vmlinuz-*"))
for _, f := range files {
b.Kernels = append(b.Kernels, filepath.Base(f))
}
sort.Strings(b.Kernels)
hasKernel := func(name string) bool {
for _, k := range b.Kernels {
if k == name {
return true
}
}
return false
}
var s syscall.Statfs_t
if err := syscall.Statfs(m.path("boot"), &s); err == nil {
b.BootFreeKiB = int64(s.Bavail) * int64(s.Bsize) / 1024
}
if raw, err := os.ReadFile(m.path(loaderSelected)); err == nil {
b.Loader = "systemd-boot"
if len(raw) > 4 {
var u []uint16
for i := 4; i+1 < len(raw); i += 2 {
u = append(u, uint16(raw[i])|uint16(raw[i+1])<<8)
}
b.Booted = strings.TrimRight(string(utf16.Decode(u)), "\x00")
}
files, _ := filepath.Glob(m.path("boot/loader/entries/*.conf"))
for _, f := range files {
b.Entries = append(b.Entries, filepath.Base(f))
}
sort.Strings(b.Entries)
if raw, err := os.ReadFile(m.path("boot/loader/entries/mesh-linux-lts.conf")); err == nil {
b.FallbackPresent = strings.Contains(string(raw), "/vmlinuz-linux-lts") && hasKernel("vmlinuz-linux-lts")
}
return b, nil
}
if raw, err := os.ReadFile(m.path("boot/grub/grub.cfg")); err == nil {
b.Loader = "grub"
for _, t := range grubTitle.FindAllStringSubmatch(string(raw), -1) {
b.Entries = append(b.Entries, t[1])
}
for _, k := range grubKernel.FindAllStringSubmatch(string(raw), -1) {
if k[1] == "vmlinuz-linux-lts" && hasKernel("vmlinuz-linux-lts") {
b.FallbackPresent = true
}
}
if cmd, err := m.read("proc/cmdline"); err == nil {
if i := bootImage.FindStringSubmatch(cmd); i != nil {
b.Booted = i[1]
}
}
}
return b, nil
}