-
Notifications
You must be signed in to change notification settings - Fork 458
/
sdboot.go
233 lines (188 loc) · 6.2 KB
/
sdboot.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
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Package sdboot provides the interface to the Systemd-Boot bootloader: config management, installation, etc.
package sdboot
import (
"context"
"errors"
"fmt"
"log"
"os"
"path/filepath"
"strings"
"github.com/ecks/uefi/efi/efivario"
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader/mount"
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader/options"
"github.com/siderolabs/talos/pkg/imager/utils"
"github.com/siderolabs/talos/pkg/machinery/constants"
)
// Config describe sd-boot state.
type Config struct {
Default string
Fallback string
}
func isUEFIBoot() bool {
// https://renenyffenegger.ch/notes/Linux/fhs/sys/firmware/efi/index
_, err := os.Stat("/sys/firmware/efi")
return err == nil
}
// IsBootedUsingSDBoot returns true if the system is booted using sd-boot.
func IsBootedUsingSDBoot() bool {
// https://www.freedesktop.org/software/systemd/man/systemd-stub.html#EFI%20Variables
// https://www.freedesktop.org/software/systemd/man/systemd-stub.html#StubInfo
_, err := os.Stat(SystemdBootStubInfoPath)
return err == nil
}
// New creates a new sdboot bootloader config.
func New() *Config {
return &Config{}
}
// Probe for existing sd-boot bootloader.
//
//nolint:gocyclo
func Probe(ctx context.Context, disk string) (*Config, error) {
// if not UEFI boot, nothing to do
if !isUEFIBoot() {
return nil, nil
}
if !IsBootedUsingSDBoot() {
return nil, nil
}
// read /boot/EFI and find if sd-boot is already being used
// this is to make sure sd-boot from Talos is being used and not sd-boot from another distro
if err := mount.PartitionOp(ctx, disk, constants.EFIPartitionLabel, func() error {
// list existing boot*.efi files in boot folder
files, err := filepath.Glob(filepath.Join(constants.EFIMountPoint, "EFI", "boot", "BOOT*.efi"))
if err != nil {
return err
}
if len(files) == 0 {
return fmt.Errorf("no boot*.efi files found in %q", filepath.Join(constants.EFIMountPoint, "EFI", "boot"))
}
return nil
}); err != nil {
return nil, err
}
// here we need to read the EFI vars to see if we have any defaults
// and populate config accordingly
// https://www.freedesktop.org/software/systemd/man/systemd-boot.html#LoaderEntryDefault
// this should be set on install/upgrades
efiCtx := efivario.NewDefaultContext()
bootedEntry, err := ReadVariable(efiCtx, LoaderEntrySelectedName)
if err != nil {
return nil, err
}
log.Printf("booted entry: %q", bootedEntry)
if opErr := mount.PartitionOp(ctx, disk, constants.EFIPartitionLabel, func() error {
// list existing UKIs, and check if the current one is present
files, err := filepath.Glob(filepath.Join(constants.EFIMountPoint, "EFI", "Linux", "Talos-*.efi"))
if err != nil {
return err
}
for _, file := range files {
if strings.EqualFold(filepath.Base(file), bootedEntry) {
return nil
}
}
return fmt.Errorf("booted entry %q not found", bootedEntry)
}); opErr != nil {
return nil, opErr
}
return &Config{
Default: bootedEntry,
}, nil
}
// UEFIBoot returns true if bootloader is UEFI-only.
func (c *Config) UEFIBoot() bool {
return true
}
// Install the bootloader.
//
// Assumes that EFI partition is already mounted.
// Writes down the UKI and updates the EFI variables.
//
//nolint:gocyclo
func (c *Config) Install(options options.InstallOptions) error {
var sdbootFilename string
switch options.Arch {
case "amd64":
sdbootFilename = "BOOTX64.efi"
case "arm64":
sdbootFilename = "BOOTAA64.efi"
default:
return fmt.Errorf("unsupported architecture: %s", options.Arch)
}
// list existing UKIs, and clean up all but the current one (used to boot)
files, err := filepath.Glob(filepath.Join(options.MountPrefix, constants.EFIMountPoint, "EFI", "Linux", "Talos-*.efi"))
if err != nil {
return err
}
// writing UKI by version-based filename here
ukiPath := fmt.Sprintf("%s-%s.efi", "Talos", options.Version)
for _, file := range files {
if strings.EqualFold(filepath.Base(file), c.Default) {
if !strings.EqualFold(c.Default, ukiPath) {
// set fallback to the current default unless it matches the new install
c.Fallback = c.Default
}
continue
}
options.Printf("removing old UKI: %s", file)
if err = os.Remove(file); err != nil {
return err
}
}
if err := utils.CopyFiles(
options.Printf,
utils.SourceDestination(
options.BootAssets.UKIPath,
filepath.Join(options.MountPrefix, constants.EFIMountPoint, "EFI", "Linux", ukiPath),
),
utils.SourceDestination(
options.BootAssets.SDBootPath,
filepath.Join(options.MountPrefix, constants.EFIMountPoint, "EFI", "boot", sdbootFilename),
),
); err != nil {
return err
}
// don't update EFI variables if we're installing to a loop device
if !options.ImageMode {
options.Printf("updating EFI variables")
efiCtx := efivario.NewDefaultContext()
// set the new entry as a default one
if err := WriteVariable(efiCtx, LoaderEntryDefaultName, ukiPath); err != nil {
return err
}
// set default 5 second boot timeout
if err := WriteVariable(efiCtx, LoaderConfigTimeoutName, "5"); err != nil {
return err
}
}
return nil
}
// PreviousLabel returns the label of the previous bootloader version.
func (c *Config) PreviousLabel() string {
return c.Fallback
}
// Revert the bootloader to the previous version.
func (c *Config) Revert(ctx context.Context) error {
if err := mount.PartitionOp(ctx, "", constants.EFIPartitionLabel, func() error {
// use c.Default as the current entry, list other UKIs, find the one which is not c.Default, and update EFI var
files, err := filepath.Glob(filepath.Join(constants.EFIMountPoint, "EFI", "Linux", "Talos-*.efi"))
if err != nil {
return err
}
for _, file := range files {
if strings.EqualFold(filepath.Base(file), c.Default) {
continue
}
log.Printf("reverting to previous UKI: %s", file)
return WriteVariable(efivario.NewDefaultContext(), LoaderEntryDefaultName, filepath.Base(file))
}
return errors.New("previous UKI not found")
}); err != nil {
return err
}
return nil
}