github.com/linuxboot/fiano@v1.2.0/pkg/visitors/table.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  	"bytes"
     9  	"fmt"
    10  	"os"
    11  	"strings"
    12  	"text/tabwriter"
    13  
    14  	"github.com/linuxboot/fiano/pkg/knownguids"
    15  	"github.com/linuxboot/fiano/pkg/uefi"
    16  )
    17  
    18  // Table prints the GUIDS, types and sizes as a compact table.
    19  type Table struct {
    20  	W         *tabwriter.Writer
    21  	Scan      bool
    22  	Layout    bool
    23  	Depth     int
    24  	indent    int
    25  	offset    uint64
    26  	curOffset uint64
    27  	printRow  func(v *Table, node, name, typez interface{}, offset, length uint64)
    28  }
    29  
    30  // Run wraps Visit and performs some setup and teardown tasks.
    31  func (v *Table) Run(f uefi.Firmware) error {
    32  	return f.Apply(v)
    33  }
    34  
    35  // Visit applies the Table visitor to any Firmware type.
    36  func (v *Table) Visit(f uefi.Firmware) error {
    37  	var offset uint64
    38  	switch f := f.(type) {
    39  	case *uefi.FlashImage:
    40  		if v.Depth > 0 { // Depth <= 0 means all
    41  			v.Depth++
    42  		}
    43  		return v.printFirmware(f, "Image", "", "", 0, 0)
    44  	case *uefi.FirmwareVolume:
    45  		return v.printFirmware(f, "FV", f.String(), f.FVType, v.offset+f.FVOffset, v.offset+f.FVOffset+f.DataOffset)
    46  	case *uefi.File:
    47  		// TODO: make name part of the file node
    48  		return v.printFirmware(f, "File", f.Header.GUID.String(), f.Header.Type, v.curOffset, v.curOffset+f.DataOffset)
    49  	case *uefi.Section:
    50  		// Reset offset to O for (compressed) section content
    51  		return v.printFirmware(f, "Sec", f.String(), f.Type, v.curOffset, 0)
    52  	case *uefi.FlashDescriptor:
    53  		return v.printFirmware(f, "IFD", "", "", 0, 0)
    54  	case *uefi.BIOSRegion:
    55  		if f.FRegion != nil {
    56  			offset = uint64(f.FRegion.BaseOffset())
    57  		}
    58  		return v.printFirmware(f, "BIOS", "", "", offset, offset)
    59  	case *uefi.BIOSPadding:
    60  		return v.printFirmware(f, "BIOS Pad", "", "", v.offset+f.Offset, 0)
    61  	case *uefi.NVarStore:
    62  		return v.printFirmware(f, "NVAR Store", "", "", v.curOffset, v.curOffset)
    63  	case *uefi.NVar:
    64  		return v.printFirmware(f, "NVAR", f.GUID.String(), f, v.curOffset, v.curOffset+uint64(f.DataOffset))
    65  	case *uefi.MERegion:
    66  		if f.FRegion != nil {
    67  			offset = uint64(f.FRegion.BaseOffset())
    68  		}
    69  		return v.printFirmware(f, "ME", "", "", offset, offset)
    70  	case *uefi.MEFPT:
    71  		return v.printFirmware(f, "$FPT", "", "", v.offset, 0)
    72  	case *uefi.RawRegion:
    73  		if f.FRegion != nil {
    74  			offset = uint64(f.FRegion.BaseOffset())
    75  		}
    76  		return v.printFirmware(f, f.Type().String(), "", "", offset, offset)
    77  	default:
    78  		return v.printFirmware(f, fmt.Sprintf("%T", f), "", "", 0, 0)
    79  	}
    80  }
    81  
    82  func indent(n int) string {
    83  	return strings.Repeat(" ", n)
    84  }
    85  
    86  func scanGUID(v *Table, b []byte) {
    87  	for g := range knownguids.GUIDs {
    88  		if bytes.Contains(b, g[:]) {
    89  			fmt.Fprintf(v.W, "%s\t(RAW)\t%s\n", indent(v.indent), g.String())
    90  		}
    91  		if strings.Contains(string(b), g.String()) {
    92  			fmt.Fprintf(v.W, "%s\t(STRING)\t%s\n", indent(v.indent), g.String())
    93  		}
    94  	}
    95  }
    96  
    97  func (v *Table) printFirmware(f uefi.Firmware, node, name, typez interface{}, offset, dataOffset uint64) error {
    98  	// Init: Print title and select printRow func
    99  	if v.W == nil {
   100  		v.W = tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
   101  		defer func() { v.W.Flush() }()
   102  		if v.Layout {
   103  			fmt.Fprintf(v.W, "%sNode\tGUID/Name/Type\tOffset\tSize\n", indent(v.indent))
   104  			v.printRow = printRowLayout
   105  		} else {
   106  			fmt.Fprintf(v.W, "%sNode\tGUID/Name\tType\tSize\n", indent(v.indent))
   107  			v.printRow = printRowStd
   108  		}
   109  	}
   110  
   111  	// Prepare data and print
   112  	length := uint64(len(f.Buf()))
   113  	if typez == "" {
   114  		if uefi.IsErased(f.Buf(), uefi.Attributes.ErasePolarity) {
   115  			typez = "(empty)"
   116  		}
   117  	}
   118  	v.printRow(v, node, name, typez, offset, length)
   119  	v2 := *v
   120  	v2.indent++
   121  	v2.offset = dataOffset
   122  	v2.curOffset = v2.offset
   123  
   124  	if v.Scan {
   125  		switch s := f.(type) {
   126  		case *uefi.Section:
   127  			switch s.Header.Type {
   128  			case uefi.SectionTypeFirmwareVolumeImage:
   129  			case uefi.SectionTypeDXEDepEx, uefi.SectionTypePEIDepEx, uefi.SectionMMDepEx:
   130  				fmt.Fprintf(v.W, "%s\t%v\n", indent(v.indent), s.DepEx)
   131  			default:
   132  				scanGUID(&v2, s.Buf())
   133  			}
   134  		case *uefi.NVar:
   135  			scanGUID(&v2, s.Buf())
   136  		case *uefi.File:
   137  			if s.Header.Type == uefi.FVFileTypeRaw {
   138  				scanGUID(&v2, s.Buf())
   139  			}
   140  		}
   141  	}
   142  
   143  	// Compute offset and visit children
   144  	if v.Depth <= 0 || v.indent < v.Depth {
   145  		if err := f.ApplyChildren(&v2); err != nil {
   146  			return err
   147  		}
   148  	}
   149  	v.curOffset += length
   150  
   151  	// Print footer
   152  	switch f := f.(type) {
   153  	case *uefi.FirmwareVolume:
   154  		// Print free space at the end of the volume
   155  		v2.printRow(&v2, "Free", "", "", offset+length-f.FreeSpace, f.FreeSpace)
   156  	case *uefi.NVarStore:
   157  		// Print free space and GUID store
   158  		v2.printRow(&v2, "Free", "", "", offset+f.FreeSpaceOffset, f.GUIDStoreOffset-f.FreeSpaceOffset)
   159  		v2.printRow(&v2, "GUIDStore", "", fmt.Sprintf("%d GUID", len(f.GUIDStore)), offset+f.GUIDStoreOffset, f.Length-f.GUIDStoreOffset)
   160  	case *uefi.MERegion:
   161  		v2.printRow(&v2, "Free", "", "", offset+f.FreeSpaceOffset, length-f.FreeSpaceOffset)
   162  	case *uefi.MEFPT:
   163  		// MERegion is not entered, simply print the $FPT content here
   164  		for _, p := range f.Entries {
   165  			var po uint64
   166  			if p.OffsetIsValid() {
   167  				po = offset + uint64(p.Offset)
   168  			}
   169  			v2.printRow(&v2, p.Name, "", p.Type(), po, uint64(p.Length))
   170  		}
   171  	case *uefi.File:
   172  		// Align
   173  		v.curOffset = uefi.Align8(v.curOffset)
   174  	}
   175  	return nil
   176  }
   177  
   178  func printRowLayout(v *Table, node, name, typez interface{}, offset, length uint64) {
   179  	if name == "" {
   180  		name = typez
   181  	}
   182  	fmt.Fprintf(v.W, "%s%v\t%v\t%#08x\t%#08x\n", indent(v.indent), node, name, offset, length)
   183  }
   184  
   185  func printRowStd(v *Table, node, name, typez interface{}, offset, length uint64) {
   186  	fmt.Fprintf(v.W, "%s%v\t%v\t%v\t%#8x\n", indent(v.indent), node, name, typez, length)
   187  }
   188  
   189  func init() {
   190  	RegisterCLI("table", "print out important information in a pretty table", 0, func(args []string) (uefi.Visitor, error) {
   191  		return &Table{}, nil
   192  	})
   193  	RegisterCLI("layout-table", "print out offset and size information of top level firmware volumes in a pretty table", 0, func(args []string) (uefi.Visitor, error) {
   194  		return &Table{Layout: true, Depth: 1}, nil
   195  	})
   196  	RegisterCLI("layout-table-full", "print out offset and size information in a pretty table", 0, func(args []string) (uefi.Visitor, error) {
   197  		return &Table{Layout: true}, nil
   198  	})
   199  	RegisterCLI("scan", "scan the table for GUIDs and print those found", 0, func(args []string) (uefi.Visitor, error) {
   200  		return &Table{Scan: true}, nil
   201  	})
   202  }