| 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) | 
 | } |