ADR 0508 §9: the operator sees whether every boot menu lists the fallback kernel, and the room left on /boot.
90 lines
2.9 KiB
Go
90 lines
2.9 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 (
|
|
"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"`
|
|
}
|
|
|
|
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{}}
|
|
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
|
|
}
|