blob: 3f435bf098f30a6a41c2ca9616cec585da953e27 [file] [log] [blame]
Lorenz Brunca1cff02023-06-26 17:52:44 +02001package efivarfs
2
3import (
4 "bytes"
5 "encoding/binary"
6 "errors"
7 "fmt"
8 "io/fs"
9 "math"
10 "regexp"
11 "strconv"
12
13 "github.com/google/uuid"
14)
15
16func decodeString(varData []byte) (string, error) {
17 efiStringRaw, err := Encoding.NewDecoder().Bytes(varData)
18 if err != nil {
19 // Pass the decoding error unwrapped.
20 return "", err
21 }
22 // Remove the null suffix.
23 return string(bytes.TrimSuffix(efiStringRaw, []byte{0})), nil
24}
25
26// ReadLoaderDevicePartUUID reads the ESP UUID from an EFI variable.
27func ReadLoaderDevicePartUUID() (uuid.UUID, error) {
28 efiVar, _, err := Read(ScopeSystemd, "LoaderDevicePartUUID")
29 if err != nil {
30 return uuid.Nil, err
31 }
32 strContent, err := decodeString(efiVar)
33 if err != nil {
34 return uuid.Nil, fmt.Errorf("decoding string failed: %w", err)
35 }
36 out, err := uuid.Parse(strContent)
37 if err != nil {
38 return uuid.Nil, fmt.Errorf("value in LoaderDevicePartUUID could not be parsed as UUID: %w", err)
39 }
40 return out, nil
41}
42
43// Technically UEFI mandates that only upper-case hex indices are valid, but in
44// practice even vendors themselves ship firmware with lowercase hex indices,
45// thus accept these here as well.
46var bootVarRegexp = regexp.MustCompile(`^Boot([0-9A-Fa-f]{4})$`)
47
48// AddBootEntry creates an new EFI boot entry variable and returns its
49// non-negative index on success.
50func AddBootEntry(be *LoadOption) (int, error) {
51 varNames, err := List(ScopeGlobal)
52 if err != nil {
53 return -1, fmt.Errorf("failed to list EFI variables: %w", err)
54 }
55 presentEntries := make(map[int]bool)
56 // Technically these are sorted, but due to the lower/upper case issue
57 // we cannot rely on this fact.
58 for _, varName := range varNames {
59 s := bootVarRegexp.FindStringSubmatch(varName)
60 if s == nil {
61 continue
62 }
63 idx, err := strconv.ParseUint(s[1], 16, 16)
64 if err != nil {
65 // This cannot be hit as all regexp matches are parseable.
66 // A quick fuzz run agrees.
67 panic(err)
68 }
69 presentEntries[int(idx)] = true
70 }
71 idx := -1
72 for i := 0; i < math.MaxUint16; i++ {
73 if !presentEntries[i] {
74 idx = i
75 break
76 }
77 }
78 if idx == -1 {
79 return -1, errors.New("all 2^16 boot entry variables are occupied")
80 }
81
82 err = SetBootEntry(idx, be)
83 if err != nil {
84 return -1, fmt.Errorf("failed to set new boot entry: %w", err)
85 }
86 return idx, nil
87}
88
89// GetBootEntry returns the boot entry at the given index.
90func GetBootEntry(idx int) (*LoadOption, error) {
91 raw, _, err := Read(ScopeGlobal, fmt.Sprintf("Boot%04X", idx))
92 if errors.Is(err, fs.ErrNotExist) {
93 // Try non-spec-conforming lowercase entry
94 raw, _, err = Read(ScopeGlobal, fmt.Sprintf("Boot%04x", idx))
95 }
96 if err != nil {
97 return nil, err
98 }
99 return UnmarshalLoadOption(raw)
100}
101
102// SetBootEntry writes the given boot entry to the given index.
103func SetBootEntry(idx int, be *LoadOption) error {
104 bem, err := be.Marshal()
105 if err != nil {
106 return fmt.Errorf("while marshaling the EFI boot entry: %w", err)
107 }
108 return Write(ScopeGlobal, fmt.Sprintf("Boot%04X", idx), AttrNonVolatile|AttrRuntimeAccess, bem)
109}
110
111// SetBootOrder replaces contents of the boot order variable with the order
112// specified in ord.
Lorenz Brun9933ef02023-07-06 18:28:29 +0200113func SetBootOrder(ord BootOrder) error {
Lorenz Brunca1cff02023-06-26 17:52:44 +0200114 return Write(ScopeGlobal, "BootOrder", AttrNonVolatile|AttrRuntimeAccess, ord.Marshal())
115}
116
117// GetBootOrder returns the current boot order of the system.
Lorenz Brun9933ef02023-07-06 18:28:29 +0200118func GetBootOrder() (BootOrder, error) {
Lorenz Brunca1cff02023-06-26 17:52:44 +0200119 raw, _, err := Read(ScopeGlobal, "BootOrder")
120 if err != nil {
121 return nil, err
122 }
123 ord, err := UnmarshalBootOrder(raw)
124 if err != nil {
125 return nil, fmt.Errorf("invalid boot order structure: %w", err)
126 }
127 return ord, nil
128}
129
130// SetBootNext sets the boot entry used for the next boot only. It automatically
131// resets after the next boot.
132func SetBootNext(entryIdx uint16) error {
133 data := make([]byte, 2)
134 binary.LittleEndian.PutUint16(data, entryIdx)
135 return Write(ScopeGlobal, "BootNext", AttrNonVolatile|AttrRuntimeAccess, data)
136}