192 lines
4.3 KiB
Go
192 lines
4.3 KiB
Go
package prober
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"os/exec"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
var efiBootmgrRegexp = regexp.MustCompile(`Boot(?P<id>[0-9A-F]+)\* (?P<name>.+)\t(.+\(.+,.+,(?P<disk_id>[0-9a-f-]+),.+,.+\))/File\((?P<filepath>.+)\)`)
|
|
var activeRegexp = regexp.MustCompile(`BootCurrent: (\d+)`)
|
|
var bootOrderRegexp = regexp.MustCompile(`BootOrder: ((?:[0-9A-F]{4},?)+)`)
|
|
|
|
const HTTPBootName = "httpboot.efi"
|
|
const HTTPBootLabel = "HTTP_BOOT"
|
|
const execPath = "/usr/bin/efibootmgr"
|
|
|
|
type EfiApp struct {
|
|
ID int
|
|
Name string
|
|
Path string
|
|
DiskID string
|
|
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
|
|
}
|
|
idInt, err := strconv.ParseInt(id, 16, 32)
|
|
if err != nil {
|
|
return app, false
|
|
}
|
|
name, ok := result["name"]
|
|
if !ok {
|
|
return
|
|
}
|
|
filepath, ok := result["filepath"]
|
|
if !ok {
|
|
return
|
|
}
|
|
disk_id, ok := result["disk_id"]
|
|
if !ok {
|
|
return
|
|
}
|
|
return EfiApp{
|
|
ID: int(idInt),
|
|
Name: strings.TrimSpace(name),
|
|
Path: strings.TrimSpace(filepath),
|
|
Active: int(idInt) == activeId,
|
|
DiskID: disk_id,
|
|
}, 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
|
|
}
|
|
|
|
func GetEFIApps() (apps []EfiApp, err error) {
|
|
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
|
|
}
|
|
|
|
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
|
|
}
|