package prober import ( "bytes" "errors" "fmt" "os/exec" "regexp" "strconv" "strings" ) // var efiBootmgrRegexp = regexp.MustCompile(`Boot(?P[0-9A-F]+)\* (?P.+)\t(.+\(.+,.+,(?P[0-9a-f-]+),.+,.+\))/File\((?P.+)\)`) var efiBootmgrRegexp = regexp.MustCompile(`Boot(?P[0-9A-F]+)\* (?P.+)\t(?P.+\))`) 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 DevicePath 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 } devicePath, ok := result["device_path"] if !ok { return } return EfiApp{ ID: int(idInt), Name: strings.TrimSpace(name), DevicePath: strings.TrimSpace(devicePath), Active: int(idInt) == activeId, }, 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 }