http-boot-config/config/prober/prober.go

193 lines
4.3 KiB
Go
Raw Normal View History

2023-08-13 15:59:05 +00:00
package prober
import (
"bytes"
"errors"
"fmt"
"os/exec"
"regexp"
"strconv"
"strings"
)
2023-08-22 20:39:23 +00:00
var efiBootmgrRegexp = regexp.MustCompile(`Boot(?P<id>[0-9A-F]+)\* (?P<name>.+)\t(.+\(.+,.+,(?P<disk_id>[0-9a-f-]+),.+,.+\))/File\((?P<filepath>.+)\)`)
2023-08-13 15:59:05 +00:00
var activeRegexp = regexp.MustCompile(`BootCurrent: (\d+)`)
2023-08-22 20:39:23 +00:00
var bootOrderRegexp = regexp.MustCompile(`BootOrder: ((?:[0-9A-F]{4},?)+)`)
const HTTPBootName = "httpboot.efi"
const HTTPBootLabel = "HTTP_BOOT"
const execPath = "/usr/bin/efibootmgr"
2023-08-13 15:59:05 +00:00
type EfiApp struct {
ID int
Name string
Path string
2023-08-19 08:54:18 +00:00
DiskID string
2023-08-13 15:59:05 +00:00
Active bool
}
func efiAppFromBootMgrOutput(rawVal string, activeId int) (app EfiApp, ok bool) {
match := efiBootmgrRegexp.FindStringSubmatch(rawVal)
if len(match) == 0 {
return
}
result := make(map[string]string)
for i, name := range efiBootmgrRegexp.SubexpNames() {
if i != 0 && name != "" {
result[name] = match[i]
}
}
id, ok := result["id"]
if !ok {
return
}
2023-08-22 20:39:23 +00:00
idInt, err := strconv.ParseInt(id, 16, 32)
2023-08-13 15:59:05 +00:00
if err != nil {
return app, false
}
name, ok := result["name"]
if !ok {
return
}
filepath, ok := result["filepath"]
if !ok {
return
}
2023-08-19 08:54:18 +00:00
disk_id, ok := result["disk_id"]
if !ok {
return
}
2023-08-13 15:59:05 +00:00
return EfiApp{
2023-08-22 20:39:23 +00:00
ID: int(idInt),
2023-08-13 15:59:05 +00:00
Name: strings.TrimSpace(name),
Path: strings.TrimSpace(filepath),
2023-08-22 20:39:23 +00:00
Active: int(idInt) == activeId,
2023-08-19 08:54:18 +00:00
DiskID: disk_id,
2023-08-13 15:59:05 +00:00
}, true
}
func getActiveEFIApp(output string) (int, error) {
match := activeRegexp.FindStringSubmatch(output)
if len(match) == 0 {
return -1, errors.New("no active boot image found")
}
strId := match[1]
id, err := strconv.Atoi(strId)
if err != nil {
return -1, err
}
return id, nil
}
2023-08-22 20:39:23 +00:00
func GetEFIApps() (apps []EfiApp, err error) {
2023-08-13 15:59:05 +00:00
cmd := exec.Command("/usr/bin/efibootmgr")
var out bytes.Buffer
cmd.Stdout = &out
err = cmd.Run()
if err != nil {
return
}
if cmd.ProcessState.ExitCode() != 0 {
return nil, fmt.Errorf("error running efibootmgr, returncode %d", cmd.ProcessState.ExitCode())
}
outStr := out.String()
activeBootID, err := getActiveEFIApp(outStr)
if err != nil {
return
}
for _, l := range strings.Split(outStr, "\n") {
app, ok := efiAppFromBootMgrOutput(l, activeBootID)
if !ok {
continue
}
apps = append(apps, app)
}
return
}
2023-08-22 20:39:23 +00:00
func Installed() (bool, error) {
apps, err := GetEFIApps()
if err != nil {
return false, fmt.Errorf("failed to get list of EFI applications: %w", err)
}
for _, a := range apps {
if a.Name == HTTPBootLabel {
return true, nil
}
}
return false, nil
}
func getHTTPBoot() (EfiApp, error) {
apps, err := GetEFIApps()
if err != nil {
return EfiApp{}, fmt.Errorf("failed to get installed EFI applications: %w", err)
}
for _, a := range apps {
if a.Name == HTTPBootLabel {
return a, nil
}
}
return EfiApp{}, errors.New("HTTP_BOOT not found")
}
func IsHTTPBootNext() (present bool, err error) {
httpBootApp, err := getHTTPBoot()
if err != nil {
return false, fmt.Errorf("failed to get HTTP_BOOT config: %w", err)
}
cmd := exec.Command("/usr/bin/efibootmgr")
var out bytes.Buffer
cmd.Stdout = &out
err = cmd.Run()
if err != nil {
return
}
if cmd.ProcessState.ExitCode() != 0 {
return false, fmt.Errorf("error running efibootmgr, returncode %d", cmd.ProcessState.ExitCode())
}
outStr := out.String()
match := bootOrderRegexp.FindStringSubmatch(outStr)
if len(match) == 0 {
return false, errors.New("no boot order found")
}
orderedBootOptionsStr := strings.Split(match[1], ",")
var orderedBootOptionsInt []int
for _, o := range orderedBootOptionsStr {
if oInt, convEerr := strconv.ParseInt(o, 16, 32); err != nil {
return false, fmt.Errorf("invalid value for boot option: %w", convEerr)
} else {
orderedBootOptionsInt = append(orderedBootOptionsInt, int(oInt))
}
}
return orderedBootOptionsInt[0] == httpBootApp.ID, nil
}
func Install(path, device string) (err error) {
partNum := device[len(device)-1:]
diskDev := device[:len(device)-1]
cmd := exec.Command(execPath, "--create", "--label", HTTPBootLabel, "--loader", path, "--part", partNum, "--disk", diskDev)
var errBuf bytes.Buffer
cmd.Stderr = &errBuf
err = cmd.Run()
var exitErr exec.ExitError
e := &exitErr
if err != nil && !errors.As(err, &e) {
return err
}
if cmd.ProcessState.ExitCode() != 0 {
stderr := errBuf.String()
return fmt.Errorf("error setting variable, returncode %d, stderr %q", cmd.ProcessState.ExitCode(), stderr)
}
return nil
}