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