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  }