github.com/linuxboot/fiano@v1.2.0/pkg/visitors/extract.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 "flag" 10 "fmt" 11 "os" 12 "path/filepath" 13 14 "github.com/linuxboot/fiano/pkg/uefi" 15 ) 16 17 var ( 18 force = flag.Bool("force", false, "force extract to non empty directory") 19 remove = flag.Bool("remove", false, "remove existing directory before extracting") 20 ) 21 22 // Extract extracts any Firmware node to DirPath 23 type Extract struct { 24 BasePath string 25 DirPath string 26 Index *uint64 27 } 28 29 // extractBinary simply dumps the binary to a specified directory and filename. 30 // It creates the directory if it doesn't already exist, and dumps the buffer to it. 31 // It returns the filepath of the binary, and an error if it exists. 32 // This is meant as a helper function for other Extract functions. 33 func (v *Extract) extractBinary(buf []byte, filename string) (string, error) { 34 // Create the directory if it doesn't exist 35 dirPath := filepath.Join(v.BasePath, v.DirPath) 36 if err := os.MkdirAll(dirPath, 0755); err != nil { 37 return "", err 38 } 39 40 // Dump the binary. 41 fp := filepath.Join(dirPath, filename) 42 if err := os.WriteFile(fp, buf, 0666); err != nil { 43 // Make sure we return "" since we don't want an invalid path to be serialized out. 44 return "", err 45 } 46 // Return only the relative path from the root of the tree 47 return filepath.Join(v.DirPath, filename), nil 48 } 49 50 // Run wraps Visit and performs some setup and teardown tasks. 51 func (v *Extract) Run(f uefi.Firmware) error { 52 // Optionally remove directory if it already exists. 53 if *remove { 54 if err := os.RemoveAll(v.BasePath); err != nil { 55 return err 56 } 57 } 58 59 if !*force { 60 // Check that directory does not exist or is empty. 61 files, err := os.ReadDir(v.BasePath) 62 if err == nil { 63 if len(files) != 0 { 64 return errors.New("existing directory not empty, use --force to override") 65 } 66 } else if !os.IsNotExist(err) { 67 // Error was not EEXIST, we do not know what went wrong. 68 return err 69 } 70 } 71 72 // Create the directory if it does not exist. 73 if err := os.MkdirAll(v.BasePath, 0755); err != nil { 74 return err 75 } 76 77 // Reset the index 78 *v.Index = 0 79 if err := f.Apply(v); err != nil { 80 return err 81 } 82 83 // Output summary json. 84 json, err := uefi.MarshalFirmware(f) 85 if err != nil { 86 return err 87 } 88 return os.WriteFile(filepath.Join(v.BasePath, "summary.json"), json, 0666) 89 } 90 91 // Visit applies the Extract visitor to any Firmware type. 92 func (v *Extract) Visit(f uefi.Firmware) error { 93 // The visitor must be cloned before modification; otherwise, the 94 // sibling's values are modified. 95 v2 := *v 96 97 var err error 98 switch f := f.(type) { 99 100 case *uefi.FirmwareVolume: 101 v2.DirPath = filepath.Join(v.DirPath, fmt.Sprintf("%#x", f.FVOffset)) 102 if len(f.Files) == 0 { 103 f.ExtractPath, err = v2.extractBinary(f.Buf(), "fv.bin") 104 } else { 105 f.ExtractPath, err = v2.extractBinary(f.Buf()[:f.DataOffset], "fvh.bin") 106 } 107 108 case *uefi.File: 109 // For files we use the GUID as the folder name. 110 v2.DirPath = filepath.Join(v.DirPath, f.Header.GUID.String()) 111 // Crappy hack to make unique ids unique 112 v2.DirPath = filepath.Join(v2.DirPath, fmt.Sprint(*v.Index)) 113 *v.Index++ 114 if len(f.Sections) == 0 && f.NVarStore == nil { 115 f.ExtractPath, err = v2.extractBinary(f.Buf(), fmt.Sprintf("%v.ffs", f.Header.GUID)) 116 } 117 118 case *uefi.Section: 119 // For sections we use the file order as the folder name. 120 v2.DirPath = filepath.Join(v.DirPath, fmt.Sprint(f.FileOrder)) 121 if len(f.Encapsulated) == 0 { 122 f.ExtractPath, err = v2.extractBinary(f.Buf(), fmt.Sprintf("%v.sec", f.FileOrder)) 123 } 124 125 case *uefi.NVar: 126 // For NVar we use the GUID as the folder name the Name as file name and add the offset to links to make them unique 127 v2.DirPath = filepath.Join(v.DirPath, f.GUID.String()) 128 if f.IsValid() { 129 if f.NVarStore == nil { 130 if f.Type == uefi.LinkNVarEntry { 131 f.ExtractPath, err = v2.extractBinary(f.Buf()[f.DataOffset:], fmt.Sprintf("%v-%#x.bin", f.Name, f.Offset)) 132 } else { 133 f.ExtractPath, err = v2.extractBinary(f.Buf()[f.DataOffset:], fmt.Sprintf("%v.bin", f.Name)) 134 } 135 } 136 } else { 137 f.ExtractPath, err = v2.extractBinary(f.Buf(), fmt.Sprintf("%#x.nvar", f.Offset)) 138 } 139 140 case *uefi.FlashDescriptor: 141 v2.DirPath = filepath.Join(v.DirPath, "ifd") 142 f.ExtractPath, err = v2.extractBinary(f.Buf(), "flashdescriptor.bin") 143 144 case *uefi.BIOSRegion: 145 v2.DirPath = filepath.Join(v.DirPath, "bios") 146 if len(f.Elements) == 0 { 147 f.ExtractPath, err = v2.extractBinary(f.Buf(), "biosregion.bin") 148 } 149 150 case *uefi.MERegion: 151 v2.DirPath = filepath.Join(v.DirPath, "me") 152 f.ExtractPath, err = v2.extractBinary(f.Buf(), "meregion.bin") 153 154 case *uefi.RawRegion: 155 v2.DirPath = filepath.Join(v.DirPath, f.Type().String()) 156 f.ExtractPath, err = v2.extractBinary(f.Buf(), fmt.Sprintf("%#x.bin", f.FlashRegion().BaseOffset())) 157 158 case *uefi.BIOSPadding: 159 v2.DirPath = filepath.Join(v.DirPath, fmt.Sprintf("biospad_%#x", f.Offset)) 160 f.ExtractPath, err = v2.extractBinary(f.Buf(), "pad.bin") 161 } 162 if err != nil { 163 return err 164 } 165 166 return f.ApplyChildren(&v2) 167 } 168 169 func init() { 170 var fileIndex uint64 171 RegisterCLI("extract", "extract dir\n extract the files to directory `dir`", 1, func(args []string) (uefi.Visitor, error) { 172 return &Extract{ 173 BasePath: args[0], 174 DirPath: ".", 175 Index: &fileIndex, 176 }, nil 177 }) 178 }