blob: 876173cae79972304a8e091218314e926c497ebe [file] [log] [blame]
package efivarfs
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io/fs"
"math"
"regexp"
"strconv"
"github.com/google/uuid"
)
func decodeString(varData []byte) (string, error) {
efiStringRaw, err := Encoding.NewDecoder().Bytes(varData)
if err != nil {
// Pass the decoding error unwrapped.
return "", err
}
// Remove the null suffix.
return string(bytes.TrimSuffix(efiStringRaw, []byte{0})), nil
}
// ReadLoaderDevicePartUUID reads the ESP UUID from an EFI variable.
func ReadLoaderDevicePartUUID() (uuid.UUID, error) {
efiVar, _, err := Read(ScopeSystemd, "LoaderDevicePartUUID")
if err != nil {
return uuid.Nil, err
}
strContent, err := decodeString(efiVar)
if err != nil {
return uuid.Nil, fmt.Errorf("decoding string failed: %w", err)
}
out, err := uuid.Parse(strContent)
if err != nil {
return uuid.Nil, fmt.Errorf("value in LoaderDevicePartUUID could not be parsed as UUID: %w", err)
}
return out, nil
}
// Technically UEFI mandates that only upper-case hex indices are valid, but in
// practice even vendors themselves ship firmware with lowercase hex indices,
// thus accept these here as well.
var bootVarRegexp = regexp.MustCompile(`^Boot([0-9A-Fa-f]{4})$`)
// AddBootEntry creates an new EFI boot entry variable and returns its
// non-negative index on success.
func AddBootEntry(be *LoadOption) (int, error) {
varNames, err := List(ScopeGlobal)
if err != nil {
return -1, fmt.Errorf("failed to list EFI variables: %w", err)
}
presentEntries := make(map[int]bool)
// Technically these are sorted, but due to the lower/upper case issue
// we cannot rely on this fact.
for _, varName := range varNames {
s := bootVarRegexp.FindStringSubmatch(varName)
if s == nil {
continue
}
idx, err := strconv.ParseUint(s[1], 16, 16)
if err != nil {
// This cannot be hit as all regexp matches are parseable.
// A quick fuzz run agrees.
panic(err)
}
presentEntries[int(idx)] = true
}
idx := -1
for i := 0; i < math.MaxUint16; i++ {
if !presentEntries[i] {
idx = i
break
}
}
if idx == -1 {
return -1, errors.New("all 2^16 boot entry variables are occupied")
}
err = SetBootEntry(idx, be)
if err != nil {
return -1, fmt.Errorf("failed to set new boot entry: %w", err)
}
return idx, nil
}
// GetBootEntry returns the boot entry at the given index.
func GetBootEntry(idx int) (*LoadOption, error) {
raw, _, err := Read(ScopeGlobal, fmt.Sprintf("Boot%04X", idx))
if errors.Is(err, fs.ErrNotExist) {
// Try non-spec-conforming lowercase entry
raw, _, err = Read(ScopeGlobal, fmt.Sprintf("Boot%04x", idx))
}
if err != nil {
return nil, err
}
return UnmarshalLoadOption(raw)
}
// SetBootEntry writes the given boot entry to the given index.
func SetBootEntry(idx int, be *LoadOption) error {
bem, err := be.Marshal()
if err != nil {
return fmt.Errorf("while marshaling the EFI boot entry: %w", err)
}
return Write(ScopeGlobal, fmt.Sprintf("Boot%04X", idx), AttrNonVolatile|AttrRuntimeAccess, bem)
}
// SetBootOrder replaces contents of the boot order variable with the order
// specified in ord.
func SetBootOrder(ord *BootOrder) error {
return Write(ScopeGlobal, "BootOrder", AttrNonVolatile|AttrRuntimeAccess, ord.Marshal())
}
// GetBootOrder returns the current boot order of the system.
func GetBootOrder() (*BootOrder, error) {
raw, _, err := Read(ScopeGlobal, "BootOrder")
if err != nil {
return nil, err
}
ord, err := UnmarshalBootOrder(raw)
if err != nil {
return nil, fmt.Errorf("invalid boot order structure: %w", err)
}
return ord, nil
}
// SetBootNext sets the boot entry used for the next boot only. It automatically
// resets after the next boot.
func SetBootNext(entryIdx uint16) error {
data := make([]byte, 2)
binary.LittleEndian.PutUint16(data, entryIdx)
return Write(ScopeGlobal, "BootNext", AttrNonVolatile|AttrRuntimeAccess, data)
}