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 }