github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/pkg/uefivars/boot/boot.go (about) 1 // Copyright 2015-2020 the u-root Authors. All rights reserved 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 // 5 // SPDX-License-Identifier: BSD-3-Clause 6 // 7 8 package boot 9 10 import ( 11 "encoding/binary" 12 "fmt" 13 "log" 14 "strconv" 15 "strings" 16 17 "github.com/mvdan/u-root-coreutils/pkg/uefivars" 18 ) 19 20 const ( 21 BootUUID = "8be4df61-93ca-11d2-aa0d-00e098032b8c" 22 ) 23 24 // BootEntryVar is a boot entry. It will have the name BootXXXX where XXXX is 25 // hexadecimal. 26 type BootEntryVar struct { 27 Number uint16 // from the var name 28 EfiLoadOption 29 } 30 31 // EfiLoadOption defines the data struct used for vars such as BootXXXX. 32 // 33 // As defined in UEFI spec v2.8A: 34 // 35 // typedef struct _EFI_LOAD_OPTION { 36 // UINT32 Attributes; 37 // UINT16 FilePathListLength; 38 // // CHAR16 Description[]; 39 // // EFI_DEVICE_PATH_PROTOCOL FilePathList[]; 40 // // UINT8 OptionalData[]; 41 // } EFI_LOAD_OPTION; 42 type EfiLoadOption struct { 43 Attributes uint32 44 FilePathListLength uint16 45 Description string 46 FilePathList EfiDevicePathProtocolList 47 OptionalData []byte 48 } 49 type BootEntryVars []*BootEntryVar 50 51 // Gets BootXXXX var, if it exists 52 func ReadBootVar(num uint16) (*BootEntryVar, error) { 53 v, err := uefivars.ReadVar(BootUUID, fmt.Sprintf("Boot%04X", num)) 54 if err != nil { 55 return nil, fmt.Errorf("reading var Boot%04X: %w", num, err) 56 } 57 return BootVar(v), nil 58 } 59 60 // Reads BootCurrent, and from there gets the BootXXXX var referenced. 61 func ReadCurrentBootVar() (*BootEntryVar, error) { 62 curr := ReadBootCurrent() 63 if curr == nil { 64 return nil, nil 65 } 66 return ReadBootVar(curr.Current) 67 } 68 69 func (b BootEntryVar) String() string { 70 opts, err := uefivars.DecodeUTF16(b.OptionalData) 71 if err != nil { 72 opts = string(b.OptionalData) 73 } 74 return fmt.Sprintf("Boot%04X: attrs=0x%x, desc=%q, path=%s, opts=%x", b.Number, b.Attributes, b.Description, b.FilePathList.String(), opts) 75 } 76 77 // AllBootEntryVars returns list of boot entries (BootXXXX). Note that 78 // BootCurrent, BootOptionSupport, BootNext, BootOrder, etc do not count as 79 // boot entries. 80 func AllBootEntryVars() BootEntryVars { 81 // BootEntries() is somewhat redundant, but parses the vars into BootEntryVar{} 82 return BootEntries(uefivars.ReadVars(BootEntryFilter)) 83 } 84 85 // AllBootVars returns all uefi vars that use the boot UUID and whose names begin with Boot. 86 // 87 // These include: 88 // 89 // - BootXXXX (individual boot entries, XXXX is hex) 90 // - BootCurrent (marks whichever BootXXXX entry was used this boot) 91 // - BootOptionSupport 92 // - BootNext (can specify a particular entry to use next boot) 93 // - BootOrder (the order in which entries are tried) 94 func AllBootVars() uefivars.EfiVars { 95 return uefivars.ReadVars(BootVarFilter) 96 } 97 98 // A VarNameFilter passing boot-related vars. These are a superset of those 99 // returned by BootEntryFilter. 100 func BootVarFilter(uuid, name string) bool { 101 return uuid == BootUUID && strings.HasPrefix(name, "Boot") 102 } 103 104 // A VarNameFilter passing boot entries. These are a subset of the vars 105 // returned by BootVarFilter. 106 func BootEntryFilter(uuid, name string) bool { 107 if !BootVarFilter(uuid, name) { 108 return false 109 } 110 // Boot entries begin with BootXXXX-, where XXXX is hex. 111 // First, check for the dash. 112 if len(name) != 8 { 113 return false 114 } 115 // Try to parse XXXX as hex. If it parses, it's a boot entry. 116 _, err := strconv.ParseUint(name[4:], 16, 16) 117 return err == nil 118 } 119 120 // BootVar decodes an efivar as a boot entry. use IsBootEntry() to screen first. 121 func BootVar(v uefivars.EfiVar) (b *BootEntryVar) { 122 num, err := strconv.ParseUint(v.Name[4:], 16, 16) 123 if err != nil { 124 log.Printf("error parsing boot var %s: %s", v.Name, err) 125 } 126 b = new(BootEntryVar) 127 b.Number = uint16(num) 128 b.Attributes = binary.LittleEndian.Uint32(v.Data[:4]) 129 b.FilePathListLength = binary.LittleEndian.Uint16(v.Data[4:6]) 130 131 // Description is null-terminated utf16 132 var i uint16 133 for i = 6; ; i += 2 { 134 if v.Data[i] == 0 { 135 break 136 } 137 } 138 b.Description, err = uefivars.DecodeUTF16(v.Data[6:i]) 139 if err != nil { 140 log.Printf("reading description: %s (%d -> %x)", err, i, v.Data[6:i]) 141 } 142 b.OptionalData = v.Data[i+2+b.FilePathListLength:] 143 144 b.FilePathList, err = ParseFilePathList(v.Data[i+2 : i+2+b.FilePathListLength]) 145 if err != nil { 146 log.Printf("parsing FilePathList in %s: %s", b.String(), err) 147 } 148 return 149 } 150 151 // BootCurrentVar represents the UEFI BootCurrent var. 152 type BootCurrentVar struct { 153 uefivars.EfiVar 154 Current uint16 155 } 156 157 // BootCurrent returns the BootCurrent var, if any, from the given list. 158 func BootCurrent(vars uefivars.EfiVars) *BootCurrentVar { 159 for _, v := range vars { 160 if v.Name == "BootCurrent" { 161 return &BootCurrentVar{ 162 EfiVar: v, 163 Current: uint16(v.Data[1])<<8 | uint16(v.Data[0]), 164 } 165 } 166 } 167 return nil 168 } 169 170 // ReadBootCurrent reads and returns the BootCurrent var. 171 func ReadBootCurrent() *BootCurrentVar { 172 v, err := uefivars.ReadVar(BootUUID, "BootCurrent") 173 if err != nil { 174 log.Printf("reading uefi BootCurrent var: %s", err) 175 return nil 176 } 177 return &BootCurrentVar{ 178 EfiVar: v, 179 Current: uint16(v.Data[1])<<8 | uint16(v.Data[0]), 180 } 181 } 182 183 // BootEntries takes a list of efi vars and parses any that are boot entries, 184 // returning a list of them. 185 func BootEntries(vars uefivars.EfiVars) (bootvars BootEntryVars) { 186 for _, v := range vars { 187 if IsBootEntry(v) { 188 bootvars = append(bootvars, BootVar(v)) 189 } 190 } 191 return 192 } 193 194 // IsBootEntry returns true if the given var is a boot entry. 195 func IsBootEntry(e uefivars.EfiVar) bool { 196 if e.UUID != BootUUID || len(e.Name) != 8 || e.Name[:4] != "Boot" { 197 return false 198 } 199 _, err := strconv.ParseUint(e.Name[4:], 16, 16) 200 return err == nil 201 }