github.com/linuxboot/fiano@v1.2.0/pkg/visitors/insert.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 "errors" 9 "fmt" 10 "os" 11 "strconv" 12 "strings" 13 14 "github.com/linuxboot/fiano/pkg/uefi" 15 ) 16 17 // InsertType defines the insert type operation that is requested 18 type InsertType int 19 20 // Insert Types 21 const ( 22 23 // == Deprectated == 24 25 // These first two specify a firmware volume. 26 27 // InsertTypeFront inserts a file at the beginning of the firmware volume, 28 // which is specified by 1) FVname GUID, or (File GUID/File name) of a file 29 // inside that FV. 30 InsertTypeFront InsertType = iota 31 // InsertTypeEnd inserts a file at the end of the specified firmware volume. 32 InsertTypeEnd 33 34 // These two specify a File to insert before or after 35 // InsertTypeAfter inserts after the specified file, 36 // which is specified by a File GUID or File name. 37 InsertTypeAfter 38 // InsertTypeBefore inserts before the specified file. 39 InsertTypeBefore 40 // InsertTypeDXE inserts into the Dxe Firmware Volume. This works by searching 41 // for the DxeCore first to identify the Dxe Firmware Volume. 42 InsertTypeDXE 43 44 // == Not deprecated == 45 46 // InsertTypeReplaceFFS replaces the found file with the new FFS. This is used 47 // as a shortcut for remove and insert combined, but also when we want to make 48 // sure that the starting offset of the new file is the same as the old. 49 InsertTypeReplaceFFS 50 // TODO: Add InsertIn 51 52 // InsertTypeInsert is generalization of all InsertTypeInsert* above. Arguments: 53 // * The first argument specifies the type of what to insert (possible values: "file" or "pad_file") 54 // * The second argument specifies the content of what to insert: 55 // - If the first argument is "file" then a path to the file content is expected. 56 // - If the first argument is "pad_file" then the size is expected. 57 // * The third argument specifies the preposition of where to insert to (possible values: "front", "end", "after", "before"). 58 // * The forth argument specifies the preposition object of where to insert to. It could be FV_or_File GUID_or_name. 59 // For example combination "end 5C60F367-A505-419A-859E-2A4FF6CA6FE5" means to insert to the end of volume 60 // "5C60F367-A505-419A-859E-2A4FF6CA6FE5". 61 // 62 // A complete example: "pad_file 256 after FC510EE7-FFDC-11D4-BD41-0080C73C8881" means to insert a pad file 63 // of size 256 bytes after file with GUID "FC510EE7-FFDC-11D4-BD41-0080C73C8881". 64 InsertTypeInsert 65 ) 66 67 var insertTypeNames = map[InsertType]string{ 68 InsertTypeInsert: "insert", 69 InsertTypeReplaceFFS: "replace_ffs", 70 71 // Deprecated: 72 InsertTypeFront: "insert_front", 73 InsertTypeEnd: "insert_end", 74 InsertTypeAfter: "insert_after", 75 InsertTypeBefore: "insert_before", 76 InsertTypeDXE: "insert_dxe", 77 } 78 79 // String creates a string representation for the insert type. 80 func (i InsertType) String() string { 81 if t, ok := insertTypeNames[i]; ok { 82 return t 83 } 84 return "UNKNOWN" 85 } 86 87 // InsertWhatType defines the type of inserting object 88 type InsertWhatType int 89 90 const ( 91 InsertWhatTypeUndefined = InsertWhatType(iota) 92 InsertWhatTypeFile 93 InsertWhatTypePadFile 94 95 EndOfInsertWhatType 96 ) 97 98 // String implements fmt.Stringer. 99 func (t InsertWhatType) String() string { 100 switch t { 101 case InsertWhatTypeUndefined: 102 return "undefined" 103 case InsertWhatTypeFile: 104 return "file" 105 case InsertWhatTypePadFile: 106 return "pad_file" 107 } 108 return fmt.Sprintf("unknown_%d", t) 109 } 110 111 // ParseInsertWhatType converts a string to InsertWhatType 112 func ParseInsertWhatType(s string) InsertWhatType { 113 // TODO: it is currently O(n), optimize 114 115 s = strings.Trim(strings.ToLower(s), " \t") 116 for t := InsertWhatTypeUndefined; t < EndOfInsertWhatType; t++ { 117 if t.String() == s { 118 return t 119 } 120 } 121 return InsertWhatTypeUndefined 122 } 123 124 // InsertWherePreposition defines the type of inserting object 125 type InsertWherePreposition int 126 127 const ( 128 InsertWherePrepositionUndefined = InsertWherePreposition(iota) 129 InsertWherePrepositionFront 130 InsertWherePrepositionEnd 131 InsertWherePrepositionAfter 132 InsertWherePrepositionBefore 133 134 EndOfInsertWherePreposition 135 ) 136 137 // String implements fmt.Stringer. 138 func (p InsertWherePreposition) String() string { 139 switch p { 140 case InsertWherePrepositionUndefined: 141 return "undefined" 142 case InsertWherePrepositionFront: 143 return "front" 144 case InsertWherePrepositionEnd: 145 return "end" 146 case InsertWherePrepositionAfter: 147 return "after" 148 case InsertWherePrepositionBefore: 149 return "before" 150 } 151 return fmt.Sprintf("unknown_%d", p) 152 } 153 154 // ParseInsertWherePreposition converts a string to InsertWherePreposition 155 func ParseInsertWherePreposition(s string) InsertWherePreposition { 156 // TODO: it is currently O(n), optimize 157 158 s = strings.Trim(strings.ToLower(s), " \t") 159 for t := InsertWherePrepositionUndefined; t < EndOfInsertWherePreposition; t++ { 160 if t.String() == s { 161 return t 162 } 163 } 164 return InsertWherePrepositionUndefined 165 } 166 167 // Insert inserts a firmware file into an FV 168 type Insert struct { 169 // TODO: use InsertWherePreposition to define the location, instead of InsertType 170 171 // Input 172 Predicate func(f uefi.Firmware) bool 173 NewFile *uefi.File 174 InsertType 175 176 // Matched File 177 FileMatch uefi.Firmware 178 } 179 180 // Run wraps Visit and performs some setup and teardown tasks. 181 func (v *Insert) Run(f uefi.Firmware) error { 182 // First run "find" to generate a position to insert into. 183 find := Find{ 184 Predicate: v.Predicate, 185 } 186 if err := find.Run(f); err != nil { 187 return err 188 } 189 190 if numMatch := len(find.Matches); numMatch > 1 { 191 return fmt.Errorf("more than one match, only one match allowed! got %v", find.Matches) 192 } else if numMatch == 0 { 193 return errors.New("no matches found") 194 } 195 196 // Find should only match a file or a firmware volume. If it's an FV, we can 197 // edit the FV directly. 198 if fvMatch, ok := find.Matches[0].(*uefi.FirmwareVolume); ok { 199 switch v.InsertType { 200 case InsertTypeFront: 201 fvMatch.Files = append([]*uefi.File{v.NewFile}, fvMatch.Files...) 202 case InsertTypeEnd: 203 fvMatch.Files = append(fvMatch.Files, v.NewFile) 204 default: 205 return fmt.Errorf("matched FV but insert operation was %s, which only matches Files", 206 v.InsertType.String()) 207 } 208 return nil 209 } 210 var ok bool 211 if v.FileMatch, ok = find.Matches[0].(*uefi.File); !ok { 212 return fmt.Errorf("match was not a file or a firmware volume: got %T, unable to insert", find.Matches[0]) 213 } 214 // Match is a file, apply visitor. 215 return f.Apply(v) 216 } 217 218 // Visit applies the Insert visitor to any Firmware type. 219 func (v *Insert) Visit(f uefi.Firmware) error { 220 switch f := f.(type) { 221 case *uefi.FirmwareVolume: 222 for i := 0; i < len(f.Files); i++ { 223 if f.Files[i] == v.FileMatch { 224 // TODO: use InsertWherePreposition to define the location, instead of InsertType 225 switch v.InsertType { 226 case InsertTypeFront: 227 f.Files = append([]*uefi.File{v.NewFile}, f.Files...) 228 case InsertTypeDXE: 229 fallthrough 230 case InsertTypeEnd: 231 f.Files = append(f.Files, v.NewFile) 232 case InsertTypeAfter: 233 f.Files = append(f.Files[:i+1], append([]*uefi.File{v.NewFile}, f.Files[i+1:]...)...) 234 case InsertTypeBefore: 235 f.Files = append(f.Files[:i], append([]*uefi.File{v.NewFile}, f.Files[i:]...)...) 236 case InsertTypeReplaceFFS: 237 f.Files = append(f.Files[:i], append([]*uefi.File{v.NewFile}, f.Files[i+1:]...)...) 238 } 239 return nil 240 } 241 } 242 } 243 244 return f.ApplyChildren(v) 245 } 246 247 func parseFile(filePath string) (*uefi.File, error) { 248 fileBytes, err := os.ReadFile(filePath) 249 if err != nil { 250 return nil, fmt.Errorf("unable to read file '%s': %w", filePath, err) 251 } 252 253 file, err := uefi.NewFile(fileBytes) 254 if err != nil { 255 return nil, fmt.Errorf("unable to parse file '%s': %w", filePath, err) 256 } 257 258 return file, nil 259 } 260 261 func genInsertRegularFileCLI(iType InsertType) func(args []string) (uefi.Visitor, error) { 262 return func(args []string) (uefi.Visitor, error) { 263 var pred FindPredicate 264 var err error 265 var filename string 266 267 if iType == InsertTypeDXE { 268 pred = FindFileTypePredicate(uefi.FVFileTypeDXECore) 269 filename = args[0] 270 } else { 271 pred, err = FindFileFVPredicate(args[0]) 272 if err != nil { 273 return nil, err 274 } 275 filename = args[1] 276 } 277 278 file, err := parseFile(filename) 279 if err != nil { 280 return nil, fmt.Errorf("unable to parse file '%s': %w", args[1], err) 281 } 282 283 // Insert File. 284 return &Insert{ 285 Predicate: pred, 286 NewFile: file, 287 InsertType: iType, 288 }, nil 289 } 290 } 291 292 func genInsertFileCLI() func(args []string) (uefi.Visitor, error) { 293 return func(args []string) (uefi.Visitor, error) { 294 whatType := ParseInsertWhatType(args[0]) 295 if whatType == InsertWhatTypeUndefined { 296 return nil, fmt.Errorf("unknown what-type: '%s'", args[0]) 297 } 298 299 var file *uefi.File 300 switch whatType { 301 case InsertWhatTypeFile: 302 var err error 303 file, err = parseFile(args[1]) 304 if err != nil { 305 return nil, fmt.Errorf("unable to parse file '%s': %w", args[1], err) 306 } 307 case InsertWhatTypePadFile: 308 padSize, err := strconv.ParseUint(args[1], 0, 64) 309 if err != nil { 310 return nil, fmt.Errorf("unable to parse pad file size '%s': %w", args[1], err) 311 } 312 file, err = uefi.CreatePadFile(padSize) 313 if err != nil { 314 return nil, fmt.Errorf("unable to create a pad file of size %d: %w", padSize, err) 315 } 316 default: 317 return nil, fmt.Errorf("what-type '%s' is not supported, yet", whatType) 318 } 319 320 wherePreposition := ParseInsertWherePreposition(args[2]) 321 if wherePreposition == InsertWherePrepositionUndefined { 322 return nil, fmt.Errorf("unknown where-preposition: '%s'", args[2]) 323 } 324 325 pred, err := FindFileFVPredicate(args[3]) 326 if err != nil { 327 return nil, fmt.Errorf("unable to parse the predicate parameters '%s': %w", args[0], err) 328 } 329 330 // TODO: use InsertWherePreposition to define the location, instead of InsertType 331 var insertType InsertType 332 switch wherePreposition { 333 case InsertWherePrepositionFront: 334 insertType = InsertTypeFront 335 case InsertWherePrepositionEnd: 336 insertType = InsertTypeEnd 337 case InsertWherePrepositionAfter: 338 insertType = InsertTypeAfter 339 case InsertWherePrepositionBefore: 340 insertType = InsertTypeBefore 341 default: 342 return nil, fmt.Errorf("where-preposition '%s' is not supported, yet", wherePreposition) 343 } 344 345 // Insert File. 346 return &Insert{ 347 Predicate: pred, 348 NewFile: file, 349 // TODO: use InsertWherePreposition to define the location, instead of InsertType 350 InsertType: insertType, 351 }, nil 352 } 353 } 354 355 func init() { 356 RegisterCLI(insertTypeNames[InsertTypeInsert], 357 "insert a file", 4, genInsertFileCLI()) 358 RegisterCLI(insertTypeNames[InsertTypeReplaceFFS], 359 "replace a file with another file", 2, genInsertRegularFileCLI(InsertTypeReplaceFFS)) 360 RegisterCLI(insertTypeNames[InsertTypeFront], 361 "(deprecated) insert a file at the beginning of a firmware volume", 2, genInsertRegularFileCLI(InsertTypeFront)) 362 RegisterCLI(insertTypeNames[InsertTypeEnd], 363 "(deprecated) insert a file at the end of a firmware volume", 2, genInsertRegularFileCLI(InsertTypeEnd)) 364 RegisterCLI(insertTypeNames[InsertTypeDXE], 365 "(deprecated) insert a file at the end of the DXE firmware volume", 1, genInsertRegularFileCLI(InsertTypeDXE)) 366 RegisterCLI(insertTypeNames[InsertTypeAfter], 367 "(deprecated) insert a file after another file", 2, genInsertRegularFileCLI(InsertTypeAfter)) 368 RegisterCLI(insertTypeNames[InsertTypeBefore], 369 "(deprecated) insert a file before another file", 2, genInsertRegularFileCLI(InsertTypeBefore)) 370 }