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  }