blob: b4183ad8ad2ab305c994362caa538a9ee157936d [file] [log] [blame]
Lorenz Brun35fcf032023-06-29 04:15:58 +02001package update
2
3import (
4 "archive/zip"
5 "bytes"
6 "context"
Lorenz Brund79881d2023-11-30 19:02:06 +01007 "crypto/sha256"
Lorenz Brund14be0e2023-07-31 16:46:14 +02008 "debug/pe"
Lorenz Brund79881d2023-11-30 19:02:06 +01009 _ "embed"
Lorenz Brun35fcf032023-06-29 04:15:58 +020010 "errors"
11 "fmt"
12 "io"
13 "net/http"
14 "os"
15 "path/filepath"
16 "regexp"
17 "strconv"
Lorenz Brund14be0e2023-07-31 16:46:14 +020018 "strings"
Lorenz Brun35fcf032023-06-29 04:15:58 +020019
20 "github.com/cenkalti/backoff/v4"
Lorenz Brund14be0e2023-07-31 16:46:14 +020021 "golang.org/x/sys/unix"
Lorenz Brun35fcf032023-06-29 04:15:58 +020022 "google.golang.org/grpc/codes"
23 "google.golang.org/grpc/status"
Lorenz Brun54a5a052023-10-02 16:40:11 +020024 "google.golang.org/protobuf/proto"
Lorenz Brun35fcf032023-06-29 04:15:58 +020025
Serge Bazanski3c5d0632024-09-12 10:49:12 +000026 "source.monogon.dev/go/logging"
Lorenz Brun54a5a052023-10-02 16:40:11 +020027 abloaderpb "source.monogon.dev/metropolis/node/core/abloader/spec"
Tim Windelschmidt9f21f532024-05-07 15:14:20 +020028 "source.monogon.dev/osbase/blockdev"
Tim Windelschmidtc2290c22024-08-15 19:56:00 +020029 "source.monogon.dev/osbase/build/mkimage/osimage"
Tim Windelschmidt9f21f532024-05-07 15:14:20 +020030 "source.monogon.dev/osbase/efivarfs"
31 "source.monogon.dev/osbase/gpt"
32 "source.monogon.dev/osbase/kexec"
Lorenz Brun35fcf032023-06-29 04:15:58 +020033)
34
35// Service contains data and functionality to perform A/B updates on a
36// Metropolis node.
37type Service struct {
38 // Path to the mount point of the EFI System Partition (ESP).
39 ESPPath string
Tim Windelschmidt8e87a062023-07-31 01:33:10 +000040 // gpt.Partition of the ESP System Partition.
41 ESPPart *gpt.Partition
Lorenz Brun35fcf032023-06-29 04:15:58 +020042 // Partition number (1-based) of the ESP in the GPT partitions array.
43 ESPPartNumber uint32
Tim Windelschmidt8e87a062023-07-31 01:33:10 +000044
Lorenz Brun35fcf032023-06-29 04:15:58 +020045 // Logger service for the update service.
Serge Bazanski3c5d0632024-09-12 10:49:12 +000046 Logger logging.Leveled
Lorenz Brun35fcf032023-06-29 04:15:58 +020047}
48
49type Slot int
50
51const (
52 SlotInvalid Slot = 0
53 SlotA Slot = 1
54 SlotB Slot = 2
55)
56
57// Other returns the "other" slot, i.e. returns slot A for B and B for A.
58// It returns SlotInvalid for any s which is not SlotA or SlotB.
59func (s Slot) Other() Slot {
60 switch s {
61 case SlotA:
62 return SlotB
63 case SlotB:
64 return SlotA
65 default:
66 return SlotInvalid
67 }
68}
69
70func (s Slot) String() string {
71 switch s {
72 case SlotA:
73 return "A"
74 case SlotB:
75 return "B"
76 default:
77 return "<invalid slot>"
78 }
79}
80
81func (s Slot) EFIBootPath() string {
82 switch s {
83 case SlotA:
84 return osimage.EFIBootAPath
85 case SlotB:
86 return osimage.EFIBootBPath
87 default:
88 return ""
89 }
90}
91
92var slotRegexp = regexp.MustCompile(`PARTLABEL=METROPOLIS-SYSTEM-([AB])`)
93
94// ProvideESP is a convenience function for providing information about the
95// ESP after the update service has been instantiated.
Tim Windelschmidt8e87a062023-07-31 01:33:10 +000096func (s *Service) ProvideESP(path string, partNum uint32, part *gpt.Partition) {
Lorenz Brun35fcf032023-06-29 04:15:58 +020097 s.ESPPath = path
98 s.ESPPartNumber = partNum
Tim Windelschmidt8e87a062023-07-31 01:33:10 +000099 s.ESPPart = part
Lorenz Brun35fcf032023-06-29 04:15:58 +0200100}
101
102// CurrentlyRunningSlot returns the slot the current system is booted from.
103func (s *Service) CurrentlyRunningSlot() Slot {
104 cmdline, err := os.ReadFile("/proc/cmdline")
105 if err != nil {
106 return SlotInvalid
107 }
108 slotMatches := slotRegexp.FindStringSubmatch(string(cmdline))
109 if len(slotMatches) != 2 {
110 return SlotInvalid
111 }
112 switch slotMatches[1] {
113 case "A":
114 return SlotA
115 case "B":
116 return SlotB
117 default:
118 panic("unreachable")
119 }
120}
121
122var bootVarRegexp = regexp.MustCompile(`^Boot([0-9A-Fa-f]{4})$`)
123
Lorenz Brun35fcf032023-06-29 04:15:58 +0200124// MarkBootSuccessful must be called after each boot if some implementation-
125// defined criteria for a successful boot are met. If an update has been
126// installed and booted and this function is called, the updated version is
127// marked as default. If an issue occurs during boot and so this function is
128// not called the old version will be started again on next boot.
129func (s *Service) MarkBootSuccessful() error {
130 if s.ESPPath == "" {
131 return errors.New("no ESP information provided to update service, cannot continue")
132 }
Lorenz Brund79881d2023-11-30 19:02:06 +0100133 if err := s.fixupEFI(); err != nil {
134 s.Logger.Errorf("Error when checking boot entry configuration: %v", err)
135 }
136 if err := s.fixupPreloader(); err != nil {
137 s.Logger.Errorf("Error when fixing A/B preloader: %v", err)
138 }
Lorenz Brun35fcf032023-06-29 04:15:58 +0200139 activeSlot := s.CurrentlyRunningSlot()
Lorenz Brun54a5a052023-10-02 16:40:11 +0200140 abState, err := s.getABState()
Lorenz Brun35fcf032023-06-29 04:15:58 +0200141 if err != nil {
Lorenz Brun54a5a052023-10-02 16:40:11 +0200142 s.Logger.Warningf("Error while getting A/B loader state, recreating: %v", err)
143 abState = &abloaderpb.ABLoaderData{
144 ActiveSlot: abloaderpb.Slot(activeSlot),
Lorenz Brun35fcf032023-06-29 04:15:58 +0200145 }
Lorenz Brun54a5a052023-10-02 16:40:11 +0200146 err := s.setABState(abState)
147 if err != nil {
148 return fmt.Errorf("while recreating A/B loader state: %w", err)
Lorenz Brun35fcf032023-06-29 04:15:58 +0200149 }
150 }
Lorenz Brun54a5a052023-10-02 16:40:11 +0200151 if Slot(abState.ActiveSlot) != activeSlot {
152 err := s.setABState(&abloaderpb.ABLoaderData{
153 ActiveSlot: abloaderpb.Slot(activeSlot),
154 })
155 if err != nil {
156 return fmt.Errorf("while setting next A/B slot: %w", err)
Lorenz Brun35fcf032023-06-29 04:15:58 +0200157 }
158 s.Logger.Infof("Permanently activated slot %v", activeSlot)
159 } else {
160 s.Logger.Infof("Normal boot from slot %v", activeSlot)
161 }
162
163 return nil
164}
165
Lorenz Brunca6da6a2024-09-09 17:55:15 +0200166// Rollback sets the currently-inactive slot as the next boot slot. This is
167// intended to recover from scenarios where roll-forward fixing is difficult.
168// Only the next boot slot is set to make sure that the node is not
169// made unbootable accidentally. On successful bootup that code can switch the
170// active slot to itself.
171func (s *Service) Rollback() error {
172 if s.ESPPath == "" {
173 return errors.New("no ESP information provided to update service, cannot continue")
174 }
175 activeSlot := s.CurrentlyRunningSlot()
176 abState, err := s.getABState()
177 if err != nil {
178 return fmt.Errorf("no valid A/B loader state, cannot rollback: %w", err)
179 }
180 nextSlot := activeSlot.Other()
181 err = s.setABState(&abloaderpb.ABLoaderData{
182 ActiveSlot: abState.ActiveSlot,
183 NextSlot: abloaderpb.Slot(nextSlot),
184 })
185 if err != nil {
186 return fmt.Errorf("while setting next A/B slot: %w", err)
187 }
188 s.Logger.Warningf("Rollback requested, NextSlot set to %v", nextSlot)
189 return nil
190}
191
Lorenz Brun1640c282024-09-09 17:50:48 +0200192// KexecLoadNext loads the slot to be booted next into the kexec staging area.
193// The next slot can then be launched by executing kexec via the reboot
194// syscall. Calling this function counts as a next boot for the purposes of
195// A/B state tracking, so it should not be called without kexecing afterwards.
196func (s *Service) KexecLoadNext() error {
197 state, err := s.getABState()
198 if err != nil {
199 return fmt.Errorf("bad A/B state: %w", err)
200 }
201 slotToLoad := Slot(state.ActiveSlot)
202 if state.NextSlot != abloaderpb.Slot_NONE {
203 slotToLoad = Slot(state.NextSlot)
204 state.NextSlot = abloaderpb.Slot_NONE
205 err = s.setABState(state)
206 if err != nil {
207 return fmt.Errorf("while updating A/B state: %w", err)
208 }
209 }
210 boot, err := os.Open(filepath.Join(s.ESPPath, slotToLoad.EFIBootPath()))
211 if err != nil {
212 return fmt.Errorf("failed to open boot file for slot %v: %w", slotToLoad, err)
213 }
214 defer boot.Close()
215 if err := s.stageKexec(boot, slotToLoad); err != nil {
216 return fmt.Errorf("failed to stage next slot for kexec: %w", err)
217 }
218 return nil
219}
220
Lorenz Brun35fcf032023-06-29 04:15:58 +0200221func openSystemSlot(slot Slot) (*blockdev.Device, error) {
222 switch slot {
223 case SlotA:
224 return blockdev.Open("/dev/system-a")
225 case SlotB:
226 return blockdev.Open("/dev/system-b")
227 default:
228 return nil, errors.New("invalid slot identifier given")
229 }
230}
231
Lorenz Brun54a5a052023-10-02 16:40:11 +0200232func (s *Service) getABState() (*abloaderpb.ABLoaderData, error) {
233 abDataRaw, err := os.ReadFile(filepath.Join(s.ESPPath, "EFI/metropolis/loader_state.pb"))
234 if err != nil {
235 return nil, err
236 }
237 var abData abloaderpb.ABLoaderData
238 if err := proto.Unmarshal(abDataRaw, &abData); err != nil {
239 return nil, err
240 }
241 return &abData, nil
242}
243
244func (s *Service) setABState(d *abloaderpb.ABLoaderData) error {
245 abDataRaw, err := proto.Marshal(d)
246 if err != nil {
247 return fmt.Errorf("while marshaling: %w", err)
248 }
249 if err := os.WriteFile(filepath.Join(s.ESPPath, "EFI/metropolis/loader_state.pb"), abDataRaw, 0666); err != nil {
250 return err
251 }
252 return nil
253}
254
Lorenz Brun35fcf032023-06-29 04:15:58 +0200255// InstallBundle installs the bundle at the given HTTP(S) URL into the currently
256// inactive slot and sets that slot to boot next. If it doesn't return an error,
257// a reboot boots into the new slot.
Lorenz Brund14be0e2023-07-31 16:46:14 +0200258func (s *Service) InstallBundle(ctx context.Context, bundleURL string, withKexec bool) error {
Lorenz Brun35fcf032023-06-29 04:15:58 +0200259 if s.ESPPath == "" {
260 return errors.New("no ESP information provided to update service, cannot continue")
261 }
262 // Download into a buffer as ZIP files cannot efficiently be read from
263 // HTTP in Go as the ReaderAt has no way of indicating continuous sections,
264 // thus a ton of small range requests would need to be used, causing
265 // a huge latency penalty as well as costing a lot of money on typical
266 // object storages. This should go away when we switch to a better bundle
267 // format which can be streamed.
268 var bundleRaw bytes.Buffer
269 b := backoff.NewExponentialBackOff()
270 err := backoff.Retry(func() error {
271 return s.tryDownloadBundle(ctx, bundleURL, &bundleRaw)
272 }, backoff.WithContext(b, ctx))
273 if err != nil {
Tim Windelschmidt5f1a7de2024-09-19 02:00:14 +0200274 return fmt.Errorf("error downloading Metropolis bundle: %w", err)
Lorenz Brun35fcf032023-06-29 04:15:58 +0200275 }
276 bundle, err := zip.NewReader(bytes.NewReader(bundleRaw.Bytes()), int64(bundleRaw.Len()))
277 if err != nil {
278 return fmt.Errorf("failed to open node bundle: %w", err)
279 }
280 efiPayload, err := bundle.Open("kernel_efi.efi")
281 if err != nil {
282 return fmt.Errorf("invalid bundle: %w", err)
283 }
284 defer efiPayload.Close()
285 systemImage, err := bundle.Open("verity_rootfs.img")
286 if err != nil {
287 return fmt.Errorf("invalid bundle: %w", err)
288 }
289 defer systemImage.Close()
290 activeSlot := s.CurrentlyRunningSlot()
291 if activeSlot == SlotInvalid {
292 return errors.New("unable to determine active slot, cannot continue")
293 }
294 targetSlot := activeSlot.Other()
295
Lorenz Brun35fcf032023-06-29 04:15:58 +0200296 systemPart, err := openSystemSlot(targetSlot)
297 if err != nil {
298 return status.Errorf(codes.Internal, "Inactive system slot unavailable: %v", err)
299 }
300 defer systemPart.Close()
301 if _, err := io.Copy(blockdev.NewRWS(systemPart), systemImage); err != nil {
302 return status.Errorf(codes.Unavailable, "Failed to copy system image: %v", err)
303 }
304
305 bootFile, err := os.Create(filepath.Join(s.ESPPath, targetSlot.EFIBootPath()))
306 if err != nil {
307 return fmt.Errorf("failed to open boot file: %w", err)
308 }
309 defer bootFile.Close()
310 if _, err := io.Copy(bootFile, efiPayload); err != nil {
311 return fmt.Errorf("failed to write boot file: %w", err)
312 }
313
Lorenz Brund14be0e2023-07-31 16:46:14 +0200314 if withKexec {
315 if err := s.stageKexec(bootFile, targetSlot); err != nil {
316 return fmt.Errorf("while kexec staging: %w", err)
317 }
318 } else {
Lorenz Brun54a5a052023-10-02 16:40:11 +0200319 err := s.setABState(&abloaderpb.ABLoaderData{
320 ActiveSlot: abloaderpb.Slot(activeSlot),
321 NextSlot: abloaderpb.Slot(targetSlot),
322 })
323 if err != nil {
324 return fmt.Errorf("while setting next A/B slot: %w", err)
Lorenz Brund14be0e2023-07-31 16:46:14 +0200325 }
Lorenz Brun35fcf032023-06-29 04:15:58 +0200326 }
327
328 return nil
329}
330
331func (*Service) tryDownloadBundle(ctx context.Context, bundleURL string, bundleRaw *bytes.Buffer) error {
332 bundleReq, err := http.NewRequestWithContext(ctx, "GET", bundleURL, nil)
Tim Windelschmidt096654a2024-04-18 23:10:19 +0200333 if err != nil {
334 return fmt.Errorf("failed to create request: %w", err)
335 }
Lorenz Brun35fcf032023-06-29 04:15:58 +0200336 bundleRes, err := http.DefaultClient.Do(bundleReq)
337 if err != nil {
338 return fmt.Errorf("HTTP request failed: %w", err)
339 }
340 defer bundleRes.Body.Close()
341 switch bundleRes.StatusCode {
342 case http.StatusTooEarly, http.StatusTooManyRequests,
343 http.StatusInternalServerError, http.StatusBadGateway,
344 http.StatusServiceUnavailable, http.StatusGatewayTimeout:
345 return fmt.Errorf("HTTP error %d", bundleRes.StatusCode)
346 default:
347 // Non-standard code range used for proxy-related issue by various
348 // vendors. Treat as non-permanent error.
349 if bundleRes.StatusCode >= 520 && bundleRes.StatusCode < 599 {
350 return fmt.Errorf("HTTP error %d", bundleRes.StatusCode)
351 }
352 if bundleRes.StatusCode != 200 {
353 return backoff.Permanent(fmt.Errorf("HTTP error %d", bundleRes.StatusCode))
354 }
355 }
356 if _, err := bundleRaw.ReadFrom(bundleRes.Body); err != nil {
357 bundleRaw.Reset()
358 return err
359 }
360 return nil
361}
Lorenz Brund14be0e2023-07-31 16:46:14 +0200362
363// newMemfile creates a new file which is not located on a specific filesystem,
364// but is instead backed by anonymous memory.
365func newMemfile(name string, flags int) (*os.File, error) {
366 fd, err := unix.MemfdCreate(name, flags)
367 if err != nil {
368 return nil, fmt.Errorf("memfd_create: %w", err)
369 }
370 return os.NewFile(uintptr(fd), name), nil
371}
372
373// stageKexec stages the kernel, command line and initramfs if available for
374// a future kexec. It extracts the relevant data from the EFI boot executable.
375func (s *Service) stageKexec(bootFile io.ReaderAt, targetSlot Slot) error {
376 bootPE, err := pe.NewFile(bootFile)
377 if err != nil {
378 return fmt.Errorf("unable to open bootFile as PE: %w", err)
379 }
380 var cmdlineRaw []byte
381 cmdlineSection := bootPE.Section(".cmdline")
382 if cmdlineSection == nil {
383 return fmt.Errorf("no .cmdline section in boot PE")
384 }
385 cmdlineRaw, err = cmdlineSection.Data()
386 if err != nil {
387 return fmt.Errorf("while reading .cmdline PE section: %w", err)
388 }
389 cmdline := string(bytes.TrimRight(cmdlineRaw, "\x00"))
390 cmdline = strings.ReplaceAll(cmdline, "METROPOLIS-SYSTEM-X", fmt.Sprintf("METROPOLIS-SYSTEM-%s", targetSlot))
391 kernelFile, err := newMemfile("kernel", 0)
392 if err != nil {
393 return fmt.Errorf("failed to create kernel memfile: %w", err)
394 }
395 defer kernelFile.Close()
396 kernelSection := bootPE.Section(".linux")
397 if kernelSection == nil {
398 return fmt.Errorf("no .linux section in boot PE")
399 }
400 if _, err := io.Copy(kernelFile, kernelSection.Open()); err != nil {
401 return fmt.Errorf("while copying .linux PE section: %w", err)
402 }
403
404 initramfsSection := bootPE.Section(".initrd")
405 var initramfsFile *os.File
406 if initramfsSection != nil && initramfsSection.Size > 0 {
407 initramfsFile, err = newMemfile("initramfs", 0)
408 if err != nil {
409 return fmt.Errorf("failed to create initramfs memfile: %w", err)
410 }
411 defer initramfsFile.Close()
412 if _, err := io.Copy(initramfsFile, initramfsSection.Open()); err != nil {
413 return fmt.Errorf("while copying .initrd PE section: %w", err)
414 }
415 }
416 if err := kexec.FileLoad(kernelFile, initramfsFile, cmdline); err != nil {
417 return fmt.Errorf("while staging new kexec kernel: %w", err)
418 }
419 return nil
420}
Lorenz Brund79881d2023-11-30 19:02:06 +0100421
Tim Windelschmidt1f51cf42024-10-01 17:04:28 +0200422//go:embed metropolis/node/core/abloader/abloader.efi
Lorenz Brund79881d2023-11-30 19:02:06 +0100423var abloader []byte
424
425func (s *Service) fixupPreloader() error {
426 abLoaderFile, err := os.Open(filepath.Join(s.ESPPath, osimage.EFIPayloadPath))
427 if err != nil {
428 s.Logger.Warningf("A/B preloader not available, attempting to restore: %v", err)
429 } else {
430 expectedSum := sha256.Sum256(abloader)
431 h := sha256.New()
432 _, err := io.Copy(h, abLoaderFile)
433 abLoaderFile.Close()
434 if err == nil {
435 if bytes.Equal(h.Sum(nil), expectedSum[:]) {
436 // A/B Preloader is present and has correct hash
437 return nil
438 } else {
439 s.Logger.Infof("Replacing A/B preloader with current version: %x %x", h.Sum(nil), expectedSum[:])
440 }
441 } else {
442 s.Logger.Warningf("Error while reading A/B preloader, restoring: %v", err)
443 }
444 }
445 preloader, err := os.Create(filepath.Join(s.ESPPath, "preloader.swp"))
446 if err != nil {
447 return fmt.Errorf("while creating preloader swap file: %w", err)
448 }
449 if _, err := preloader.Write(abloader); err != nil {
450 return fmt.Errorf("while writing preloader swap file: %w", err)
451 }
452 if err := preloader.Sync(); err != nil {
453 return fmt.Errorf("while sync'ing preloader swap file: %w", err)
454 }
455 preloader.Close()
456 if err := os.Rename(filepath.Join(s.ESPPath, "preloader.swp"), filepath.Join(s.ESPPath, osimage.EFIPayloadPath)); err != nil {
457 return fmt.Errorf("while swapping preloader: %w", err)
458 }
459 s.Logger.Info("Successfully wrote current preloader")
460 return nil
461}
462
463// fixupEFI checks for the existence and correctness of the EFI boot entry
464// repairs/recreates it if needed.
465func (s *Service) fixupEFI() error {
466 varNames, err := efivarfs.List(efivarfs.ScopeGlobal)
467 if err != nil {
468 return fmt.Errorf("failed to list EFI variables: %w", err)
469 }
Tim Windelschmidt5e460a92024-04-11 01:33:09 +0200470 var validBootEntryIdx = -1
Lorenz Brund79881d2023-11-30 19:02:06 +0100471 for _, varName := range varNames {
472 m := bootVarRegexp.FindStringSubmatch(varName)
473 if m == nil {
474 continue
475 }
476 idx, err := strconv.ParseUint(m[1], 16, 16)
477 if err != nil {
478 // This cannot be hit as all regexp matches are parseable.
479 panic(err)
480 }
481 e, err := efivarfs.GetBootEntry(int(idx))
482 if err != nil {
483 s.Logger.Warningf("Unable to get boot entry %d, skipping: %v", idx, err)
484 continue
485 }
486 if len(e.FilePath) != 2 {
487 // Not our entry, ours always have two parts
488 continue
489 }
490 switch p := e.FilePath[0].(type) {
491 case *efivarfs.HardDrivePath:
492 gptMatch, ok := p.PartitionMatch.(*efivarfs.PartitionGPT)
493 if ok && gptMatch.PartitionUUID != s.ESPPart.ID {
494 // Not related to our ESP
495 continue
496 }
497 default:
498 continue
499 }
500 switch p := e.FilePath[1].(type) {
501 case efivarfs.FilePath:
502 if string(p) == osimage.EFIPayloadPath {
503 if validBootEntryIdx == -1 {
504 validBootEntryIdx = int(idx)
505 } else {
506 // Another valid boot entry already exists, delete this one
507 err := efivarfs.DeleteBootEntry(int(idx))
508 if err == nil {
509 s.Logger.Infof("Deleted duplicate boot entry %q", e.Description)
510 } else {
511 s.Logger.Warningf("Error while deleting duplicate boot entry %q: %v", e.Description, err)
512 }
513 }
514 } else if strings.Contains(e.Description, "Metropolis") {
515 err := efivarfs.DeleteBootEntry(int(idx))
516 if err == nil {
517 s.Logger.Infof("Deleted orphaned boot entry %q", e.Description)
518 } else {
519 s.Logger.Warningf("Error while deleting orphaned boot entry %q: %v", e.Description, err)
520 }
521 }
522 default:
523 continue
524 }
525 }
526 if validBootEntryIdx == -1 {
527 validBootEntryIdx, err = efivarfs.AddBootEntry(&efivarfs.LoadOption{
528 Description: "Metropolis",
529 FilePath: efivarfs.DevicePath{
530 &efivarfs.HardDrivePath{
531 PartitionNumber: 1,
532 PartitionStartBlock: s.ESPPart.FirstBlock,
533 PartitionSizeBlocks: s.ESPPart.SizeBlocks(),
534 PartitionMatch: efivarfs.PartitionGPT{
535 PartitionUUID: s.ESPPart.ID,
536 },
537 },
538 efivarfs.FilePath(osimage.EFIPayloadPath),
539 },
540 })
541 if err == nil {
542 s.Logger.Infof("Restored missing EFI boot entry for Metropolis")
543 } else {
Tim Windelschmidt5f1a7de2024-09-19 02:00:14 +0200544 return fmt.Errorf("while restoring missing EFI boot entry for Metropolis: %w", err)
Lorenz Brund79881d2023-11-30 19:02:06 +0100545 }
546 }
547 bootOrder, err := efivarfs.GetBootOrder()
548 if err != nil {
Tim Windelschmidt5f1a7de2024-09-19 02:00:14 +0200549 return fmt.Errorf("failed to get EFI boot order: %w", err)
Lorenz Brund79881d2023-11-30 19:02:06 +0100550 }
551 for _, bentry := range bootOrder {
552 if bentry == uint16(validBootEntryIdx) {
553 // Our boot entry is in the boot order, everything's ok
554 return nil
555 }
556 }
557 newBootOrder := append(efivarfs.BootOrder{uint16(validBootEntryIdx)}, bootOrder...)
558 if err := efivarfs.SetBootOrder(newBootOrder); err != nil {
559 return fmt.Errorf("while setting EFI boot order: %w", err)
560 }
561 return nil
562}