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  }