github.com/linuxboot/fiano@v1.2.0/pkg/visitors/find.go (about) 1 // Copyright 2018 the LinuxBoot 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 package visitors 6 7 import ( 8 "encoding/json" 9 "fmt" 10 "io" 11 "os" 12 "regexp" 13 14 "github.com/linuxboot/fiano/pkg/guid" 15 "github.com/linuxboot/fiano/pkg/log" 16 "github.com/linuxboot/fiano/pkg/uefi" 17 ) 18 19 // FindPredicate is used to filter matches in the Find visitor. 20 type FindPredicate = func(f uefi.Firmware) bool 21 22 // Find a firmware file given its name or GUID. 23 type Find struct { 24 // Input 25 // Only when this functions returns true will the file appear in the 26 // `Matches` slice. 27 Predicate FindPredicate 28 29 // Output 30 Matches []uefi.Firmware 31 32 // JSON is written to this writer. 33 W io.Writer 34 35 // Private 36 currentFile *uefi.File 37 } 38 39 // Run wraps Visit and performs some setup and teardown tasks. 40 func (v *Find) Run(f uefi.Firmware) error { 41 if err := f.Apply(v); err != nil { 42 return err 43 } 44 if v.W != nil { 45 b, err := json.MarshalIndent(v.Matches, "", "\t") 46 if err != nil { 47 log.Fatalf("%v", err) 48 } 49 fmt.Fprintln(v.W, string(b)) 50 } 51 return nil 52 } 53 54 // Visit applies the Find visitor to any Firmware type. 55 func (v *Find) Visit(f uefi.Firmware) error { 56 switch f := f.(type) { 57 58 case *uefi.File: 59 // Clone the visitor so the `currentFile` is passed only to descendents. 60 v2 := &Find{ 61 Predicate: v.Predicate, 62 currentFile: f, 63 } 64 65 if v.Predicate(f) { 66 v.Matches = append(v.Matches, f) 67 // Don't match with direct descendents. 68 v2.currentFile = nil 69 } 70 71 err := f.ApplyChildren(v2) 72 v.Matches = append(v.Matches, v2.Matches...) // Merge together 73 return err 74 75 case *uefi.Section: 76 if v.currentFile != nil && v.Predicate(f) { 77 v.Matches = append(v.Matches, v.currentFile) 78 v.currentFile = nil // Do not double-match with a sibling if there are duplicate names. 79 } 80 return f.ApplyChildren(v) 81 82 case *uefi.NVar: 83 // don't find NVar embedded in NVar (TODO have a parameter for that ?) 84 if v.Predicate(f) { 85 v.Matches = append(v.Matches, f) 86 } 87 return nil 88 89 default: 90 if v.Predicate(f) { 91 v.Matches = append(v.Matches, f) 92 } 93 return f.ApplyChildren(v) 94 } 95 } 96 97 // FindFileGUIDPredicate is a generic predicate for searching file GUIDs only. 98 func FindFileGUIDPredicate(r guid.GUID) FindPredicate { 99 return func(f uefi.Firmware) bool { 100 if f, ok := f.(*uefi.File); ok { 101 return f.Header.GUID == r 102 } 103 return false 104 } 105 } 106 107 // FindFileTypePredicate is a generic predicate for searching file types only. 108 func FindFileTypePredicate(t uefi.FVFileType) FindPredicate { 109 return func(f uefi.Firmware) bool { 110 if f, ok := f.(*uefi.File); ok { 111 return f.Header.Type == t 112 } 113 return false 114 } 115 } 116 117 // FindFilePredicate is a generic predicate for searching files and UI sections only. 118 func FindFilePredicate(r string) (func(f uefi.Firmware) bool, error) { 119 ciRE, err := regexp.Compile("^(?i)(" + r + ")$") 120 if err != nil { 121 return nil, err 122 } 123 return func(f uefi.Firmware) bool { 124 switch f := f.(type) { 125 case *uefi.File: 126 return ciRE.MatchString(f.Header.GUID.String()) 127 case *uefi.Section: 128 return ciRE.MatchString(f.Name) 129 } 130 return false 131 }, nil 132 } 133 134 // FindFileFVPredicate is a generic predicate for searching FVs, files and UI sections. 135 func FindFileFVPredicate(r string) (func(f uefi.Firmware) bool, error) { 136 ciRE, err := regexp.Compile("^(?i)" + r + "$") 137 if err != nil { 138 return nil, err 139 } 140 return func(f uefi.Firmware) bool { 141 switch f := f.(type) { 142 case *uefi.FirmwareVolume: 143 return ciRE.MatchString(f.FVName.String()) 144 case *uefi.File: 145 return ciRE.MatchString(f.Header.GUID.String()) 146 case *uefi.Section: 147 return ciRE.MatchString(f.Name) 148 } 149 return false 150 }, nil 151 } 152 153 // FindNotPredicate is a generic predicate which takes the logical NOT of an existing predicate. 154 func FindNotPredicate(predicate FindPredicate) FindPredicate { 155 return func(f uefi.Firmware) bool { 156 return !predicate(f) 157 } 158 } 159 160 // FindAndPredicate is a generic predicate which takes the logical OR of two existing predicates. 161 func FindAndPredicate(predicate1 FindPredicate, predicate2 FindPredicate) FindPredicate { 162 return func(f uefi.Firmware) bool { 163 return predicate1(f) && predicate2(f) 164 } 165 } 166 167 // FindExactlyOne does a find using a supplied predicate and errors if there's more than one. 168 func FindExactlyOne(f uefi.Firmware, pred func(f uefi.Firmware) bool) (uefi.Firmware, error) { 169 find := &Find{ 170 Predicate: pred, 171 } 172 if err := find.Run(f); err != nil { 173 return nil, err 174 } 175 // There should only be one match, there should only be one Dxe Core. 176 if mlen := len(find.Matches); mlen != 1 { 177 return nil, fmt.Errorf("expected exactly one match, got %v, matches were: %v", mlen, find.Matches) 178 } 179 return find.Matches[0], nil 180 } 181 182 // FindEnclosingFV finds the FV that contains a file. 183 func FindEnclosingFV(f uefi.Firmware, file *uefi.File) (*uefi.FirmwareVolume, error) { 184 pred := func(f uefi.Firmware) bool { 185 switch f := f.(type) { 186 case *uefi.FirmwareVolume: 187 for _, v := range f.Files { 188 if v == file { 189 return true 190 } 191 } 192 } 193 return false 194 } 195 dxeFV, err := FindExactlyOne(f, pred) 196 if err != nil { 197 return nil, fmt.Errorf("unable to find DXE FV, got: %v", err) 198 } 199 // result must be a FV. 200 fv, ok := dxeFV.(*uefi.FirmwareVolume) 201 if !ok { 202 return nil, fmt.Errorf("result was not a firmware volume! was type %T", dxeFV) 203 } 204 205 return fv, nil 206 } 207 208 // FindDXEFV is a helper function to quickly retrieve the firmware volume that contains the DxeCore. 209 func FindDXEFV(f uefi.Firmware) (*uefi.FirmwareVolume, error) { 210 // We identify the Dxe Firmware Volume via the presence of the DxeCore 211 // This will cause problems if there are multiple dxe volumes. 212 pred := FindFileTypePredicate(uefi.FVFileTypeDXECore) 213 dxeCore, err := FindExactlyOne(f, pred) 214 if err != nil { 215 return nil, fmt.Errorf("unable to find DXE Core, got: %v", err) 216 } 217 218 // result must be a File. 219 file, ok := dxeCore.(*uefi.File) 220 if !ok { 221 return nil, fmt.Errorf("result was not a file! was type %T", file) 222 } 223 return FindEnclosingFV(f, file) 224 } 225 226 // FindNVarPredicate is a generic predicate for searching NVar only. 227 func FindNVarPredicate(r string) (func(f uefi.Firmware) bool, error) { 228 searchRE, err := regexp.Compile("^(" + r + ")$") 229 if err != nil { 230 return nil, err 231 } 232 return func(f uefi.Firmware) bool { 233 switch f := f.(type) { 234 case *uefi.NVar: 235 return searchRE.MatchString(f.Name) 236 } 237 return false 238 }, nil 239 } 240 241 func init() { 242 RegisterCLI("find", "find a file by GUID or Name", 1, func(args []string) (uefi.Visitor, error) { 243 pred, err := FindFilePredicate(args[0]) 244 if err != nil { 245 return nil, err 246 } 247 return &Find{ 248 Predicate: pred, 249 W: os.Stdout, 250 }, nil 251 }) 252 RegisterCLI("find_nvar", "find an NVAR by Name", 1, func(args []string) (uefi.Visitor, error) { 253 pred, err := FindNVarPredicate(args[0]) 254 if err != nil { 255 return nil, err 256 } 257 return &Find{ 258 Predicate: pred, 259 W: os.Stdout, 260 }, nil 261 }) 262 }