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