/
vars.go
330 lines (292 loc) · 7.83 KB
/
vars.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
// Copyright (C) 2015-2020 the Gprovision Authors. All Rights Reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//
// SPDX-License-Identifier: BSD-3-Clause
//
package uefi
import (
"bytes"
"encoding/binary"
"fmt"
"io/ioutil"
"os"
fp "path/filepath"
"strconv"
"strings"
"unicode/utf16"
"unicode/utf8"
"github.com/purecloudlabs/gprovision/pkg/log"
)
//http://kurtqiao.github.io/uefi/2015/01/13/uefi-boot-manager.html
var efiVarDir = "/sys/firmware/efi/vars"
const (
bootUuid = "8be4df61-93ca-11d2-aa0d-00e098032b8c"
)
//a generic efi var
type EfiVar struct {
uuid, name string
data []byte
}
type EfiVars []EfiVar
func ReadVar(uuid, name string) (e EfiVar, err error) {
path := fp.Join(efiVarDir, name+"-"+uuid, "data")
e.uuid = uuid
e.name = name
e.data, err = ioutil.ReadFile(path)
return
}
//Returns all efi variables
func AllVars() (vars EfiVars) { return ReadVars(nil) }
//Returns efi variables matching filter
func ReadVars(filt VarFilter) (vars EfiVars) {
entries, err := fp.Glob(fp.Join(efiVarDir, "*-*"))
if err != nil {
log.Logf("error reading efi vars: %s", err)
return
}
for _, entry := range entries {
base := fp.Base(entry)
n := strings.Count(base, "-")
if n < 5 {
log.Logf("skipping %s - not a valid var?", base)
continue
}
components := strings.SplitN(base, "-", 2)
if filt != nil && !filt(components[1], components[0]) {
continue
}
info, err := os.Stat(entry)
if err == nil && info.IsDir() {
v, err := ReadVar(components[1], components[0])
if err != nil {
log.Logf("reading efi var %s: %s", base, err)
continue
}
vars = append(vars, v)
}
}
return
}
//A boot entry. Will have the name BootXXXX where XXXX is hexadecimal
type BootEntryVar struct {
Number uint16 //from the var name
EfiLoadOption
}
/* EfiLoadOption defines the data struct used for vars such as BootXXXX.
As defined in UEFI spec v2.8A:
typedef struct _EFI_LOAD_OPTION {
UINT32 Attributes;
UINT16 FilePathListLength;
// CHAR16 Description[];
// EFI_DEVICE_PATH_PROTOCOL FilePathList[];
// UINT8 OptionalData[];
} EFI_LOAD_OPTION;
*/
type EfiLoadOption struct {
Attributes uint32
FilePathListLength uint16
Description string
FilePathList EfiDevicePathProtocolList
OptionalData []byte
}
type BootEntryVars []*BootEntryVar
// Gets BootXXXX var, if it exists
func ReadBootVar(num uint16) (b *BootEntryVar) {
v, err := ReadVar(bootUuid, fmt.Sprintf("Boot%04X", num))
if err != nil {
log.Logf("reading var Boot%04X: %s", num, err)
return
}
return v.BootVar()
}
// Reads BootCurrent, and from there gets the BootXXXX var referenced.
func ReadCurrentBootVar() (b *BootEntryVar) {
curr := ReadBootCurrent()
if curr == nil {
return nil
}
return ReadBootVar(curr.Current)
}
//decodes an efivar as a boot entry. use IsBootEntry() to screen first.
func (v EfiVar) BootVar() (b *BootEntryVar) {
num, err := strconv.ParseUint(v.name[4:], 16, 16)
if err != nil {
log.Logf("error parsing boot var %s: %s", v.name, err)
}
b = new(BootEntryVar)
b.Number = uint16(num)
b.Attributes = binary.LittleEndian.Uint32(v.data[:4])
b.FilePathListLength = binary.LittleEndian.Uint16(v.data[4:6])
//Description is null-terminated utf16
var i uint16
for i = 6; ; i += 2 {
if v.data[i] == 0 {
break
}
}
b.Description, err = DecodeUTF16(v.data[6:i])
if err != nil {
log.Logf("reading description: %s (%d -> %x)", err, i, v.data[6:i])
}
b.OptionalData = v.data[i+2+b.FilePathListLength:]
b.FilePathList, err = ParseFilePathList(v.data[i+2 : i+2+b.FilePathListLength])
if err != nil {
log.Logf("parsing FilePathList in %s: %s", b.String(), err)
}
return
}
func (b BootEntryVar) String() string {
opts, err := DecodeUTF16(b.OptionalData)
if err != nil {
opts = string(b.OptionalData)
}
//could decode uefi path... worth it?
return fmt.Sprintf("Boot%04X: attrs=0x%x, desc=%q, path=%s, opts=%x", b.Number, b.Attributes, b.Description, b.FilePathList.String(), opts)
}
func (b BootEntryVar) Remove() error {
return RemoveBootEntry(b.Number)
}
//returns list of boot entries (BootXXXX)
//note that BootCurrent, BootOptionSupport, BootNext, BootOrder, etc do not count as boot entries.
func AllBootEntryVars() BootEntryVars {
//return AllVars().BootEntries()
//BootEntries() is somewhat redundant, but parses the vars into BootEntryVar{}
return ReadVars(BootEntryFilter).BootEntries()
}
//Return all boot-related uefi vars
func AllBootVars() EfiVars {
return ReadVars(BootVarFilter)
}
//A type of function used to filter efi vars
type VarFilter func(uuid, name string) bool
// A VarNameFilter passing boot-related vars. These are a superset of those
// returned by BootEntryFilter.
func BootVarFilter(uuid, name string) bool {
return uuid == bootUuid && strings.HasPrefix(name, "Boot")
}
// A VarNameFilter passing boot entries. These are a subset of the vars
// returned by BootVarFilter.
func BootEntryFilter(uuid, name string) bool {
if !BootVarFilter(uuid, name) {
return false
}
// Boot entries begin with BootXXXX-, where XXXX is hex.
//First, check for the dash.
if len(name) != 8 {
return false
}
// Try to parse XXXX as hex. If it parses, it's a boot entry.
_, err := strconv.ParseUint(name[4:], 16, 16)
return err == nil
}
//Returns a filter negating the given filter.
func NotFilter(f VarFilter) VarFilter {
return func(u, n string) bool { return !f(u, n) }
}
//Returns true only if all given filters return true.
func AndFilter(filters ...VarFilter) VarFilter {
return func(u, n string) bool {
for _, f := range filters {
if !f(u, n) {
return false
}
}
return true
}
}
func (vars EfiVars) Filter(filt VarFilter) EfiVars {
var res EfiVars
for _, v := range vars {
if filt(v.uuid, v.name) {
res = append(res, v)
}
}
return res
}
type BootCurrentVar struct {
EfiVar
Current uint16
}
//returns the BootCurrent var
func (vars EfiVars) BootCurrent() *BootCurrentVar {
for _, v := range vars {
if v.name == "BootCurrent" {
return &BootCurrentVar{
EfiVar: v,
Current: uint16(v.data[1])<<8 | uint16(v.data[0]),
}
}
}
return nil
}
func ReadBootCurrent() *BootCurrentVar {
v, err := ReadVar(bootUuid, "BootCurrent")
if err != nil {
log.Logf("reading uefi BootCurrent var: %s", err)
return nil
}
return &BootCurrentVar{
EfiVar: v,
Current: uint16(v.data[1])<<8 | uint16(v.data[0]),
}
}
//from a list of efi vars, parse any that are boot entries and return list of them
func (vars EfiVars) BootEntries() (bootvars BootEntryVars) {
for _, v := range vars {
if v.IsBootEntry() {
bootvars = append(bootvars, v.BootVar())
}
}
return
}
func (e EfiVar) IsBootEntry() bool {
if e.uuid != bootUuid || len(e.name) != 8 || e.name[:4] != "Boot" {
return false
}
_, err := strconv.ParseUint(e.name[4:], 16, 16)
return err == nil
}
//filter list of boot entries to exclude entries we didn't create
func (entries BootEntryVars) Ours() (bootvars BootEntryVars) {
for _, b := range entries {
if b.IsOurs() {
bootvars = append(bootvars, b)
}
}
return
}
func (b BootEntryVar) IsOurs() bool {
switch BootLabel(b.Description) {
case BootLabelFR:
return true
case BootLabelNorm:
return true
case BootLabelErase:
return true
}
return false
}
//https://gist.github.com/bradleypeabody/185b1d7ed6c0c2ab6cec
func DecodeUTF16(b []byte) (string, error) {
if len(b)%2 != 0 {
return "", fmt.Errorf("Must have even length byte slice")
}
u16s := make([]uint16, 1)
ret := &bytes.Buffer{}
b8buf := make([]byte, 4)
lb := len(b)
for i := 0; i < lb; i += 2 {
u16s[0] = bytesToU16(b[i : i+2])
r := utf16.Decode(u16s)
n := utf8.EncodeRune(b8buf, r[0])
ret.Write(b8buf[:n])
}
return ret.String(), nil
}
func bytesToU16(b []byte) uint16 {
if len(b) != 2 {
log.Fatalf("bytesToU16: bad len %d (%x)", len(b), b)
}
return uint16(b[0]) + (uint16(b[1]) << 8)
}