binman: Support listing an image

Add support for listing the entries in an image. This relies on the image
having an FDT map.

Signed-off-by: Simon Glass <sjg@chromium.org>
This commit is contained in:
Simon Glass 2019-07-08 14:25:48 -06:00
parent 8beb11ea6e
commit 61f564d15f
6 changed files with 352 additions and 1 deletions

View file

@ -490,6 +490,49 @@ see README.entries. This is generated from the source code using:
binman entry-docs >tools/binman/README.entries
Listing images
--------------
It is possible to list the entries in an existing firmware image created by
binman, provided that there is an 'fdtmap' entry in the image. For example:
$ binman ls -i image.bin
Name Image-pos Size Entry-type Offset Uncomp-size
----------------------------------------------------------------------
main-section c00 section 0
u-boot 0 4 u-boot 0
section 5fc section 4
cbfs 100 400 cbfs 0
u-boot 138 4 u-boot 38
u-boot-dtb 180 108 u-boot-dtb 80 3b5
u-boot-dtb 500 1ff u-boot-dtb 400 3b5
fdtmap 6fc 381 fdtmap 6fc
image-header bf8 8 image-header bf8
This shows the hierarchy of the image, the position, size and type of each
entry, the offset of each entry within its parent and the uncompressed size if
the entry is compressed.
It is also possible to list just some files in an image, e.g.
$ binman ls -i image.bin section/cbfs
Name Image-pos Size Entry-type Offset Uncomp-size
--------------------------------------------------------------------
cbfs 100 400 cbfs 0
u-boot 138 4 u-boot 38
u-boot-dtb 180 108 u-boot-dtb 80 3b5
or with wildcards:
$ binman ls -i image.bin "*cb*" "*head*"
Name Image-pos Size Entry-type Offset Uncomp-size
----------------------------------------------------------------------
cbfs 100 400 cbfs 0
u-boot 138 4 u-boot 38
u-boot-dtb 180 108 u-boot-dtb 80 3b5
image-header bf8 8 image-header bf8
Hashing Entries
---------------
@ -825,7 +868,6 @@ Some ideas:
- Add an option to decode an image into the constituent binaries
- Support building an image for a board (-b) more completely, with a
configurable build directory
- Support listing files in images
- Support logging of binman's operations, with different levels of verbosity
- Support updating binaries in an image (with no size change / repacking)
- Support updating binaries in an image (with repacking)

View file

@ -65,6 +65,12 @@ controlled by a description in the board device tree.'''
entry_parser = subparsers.add_parser('entry-docs',
help='Write out entry documentation (see README.entries)')
list_parser = subparsers.add_parser('ls', help='List files in an image')
list_parser.add_argument('-i', '--image', type=str, required=True,
help='Image filename to list')
list_parser.add_argument('paths', type=str, nargs='*',
help='Paths within file to list (wildcard)')
test_parser = subparsers.add_parser('test', help='Run tests')
test_parser.add_argument('-P', '--processes', type=int,
help='set number of processes to use for running tests')

View file

@ -67,6 +67,37 @@ def WriteEntryDocs(modules, test_missing=None):
from entry import Entry
Entry.WriteDocs(modules, test_missing)
def ListEntries(image_fname, entry_paths):
"""List the entries in an image
This decodes the supplied image and displays a table of entries from that
image, preceded by a header.
Args:
image_fname: Image filename to process
entry_paths: List of wildcarded paths (e.g. ['*dtb*', 'u-boot*',
'section/u-boot'])
"""
image = Image.FromFile(image_fname)
entries, lines, widths = image.GetListEntries(entry_paths)
num_columns = len(widths)
for linenum, line in enumerate(lines):
if linenum == 1:
# Print header line
print('-' * (sum(widths) + num_columns * 2))
out = ''
for i, item in enumerate(line):
width = -widths[i]
if item.startswith('>'):
width = -width
item = item[1:]
txt = '%*s ' % (width, item)
out += txt
print(out.rstrip())
def Binman(args):
"""The main control code for binman
@ -87,6 +118,10 @@ def Binman(args):
command.Run(pager, fname)
return 0
if args.cmd == 'ls':
ListEntries(args.image, args.paths)
return 0
# Try to figure out which device tree contains our image description
if args.dt:
dtb_fname = args.dt

View file

@ -2341,6 +2341,88 @@ class TestFunctional(unittest.TestCase):
image = Image.FromFile(image_fname)
self.assertIn("Cannot find FDT map in image", str(e.exception))
def testListCmd(self):
"""Test listing the files in an image using an Fdtmap"""
self._CheckLz4()
data = self._DoReadFileRealDtb('130_list_fdtmap.dts')
# lz4 compression size differs depending on the version
image = control.images['image']
entries = image.GetEntries()
section_size = entries['section'].size
fdt_size = entries['section'].GetEntries()['u-boot-dtb'].size
fdtmap_offset = entries['fdtmap'].offset
image_fname = tools.GetOutputFilename('image.bin')
with test_util.capture_sys_output() as (stdout, stderr):
self._DoBinman('ls', '-i', image_fname)
lines = stdout.getvalue().splitlines()
expected = [
'Name Image-pos Size Entry-type Offset Uncomp-size',
'----------------------------------------------------------------------',
'main-section 0 c00 section 0',
' u-boot 0 4 u-boot 0',
' section 100 %x section 100' % section_size,
' cbfs 100 400 cbfs 0',
' u-boot 138 4 u-boot 38',
' u-boot-dtb 180 10f u-boot-dtb 80 3c9',
' u-boot-dtb 500 %x u-boot-dtb 400 3c9' % fdt_size,
' fdtmap %x 395 fdtmap %x' %
(fdtmap_offset, fdtmap_offset),
' image-header bf8 8 image-header bf8',
]
self.assertEqual(expected, lines)
def testListCmdFail(self):
"""Test failing to list an image"""
self._DoReadFile('005_simple.dts')
image_fname = tools.GetOutputFilename('image.bin')
with self.assertRaises(ValueError) as e:
self._DoBinman('ls', '-i', image_fname)
self.assertIn("Cannot find FDT map in image", str(e.exception))
def _RunListCmd(self, paths, expected):
"""List out entries and check the result
Args:
paths: List of paths to pass to the list command
expected: Expected list of filenames to be returned, in order
"""
self._CheckLz4()
self._DoReadFileRealDtb('130_list_fdtmap.dts')
image_fname = tools.GetOutputFilename('image.bin')
image = Image.FromFile(image_fname)
lines = image.GetListEntries(paths)[1]
files = [line[0].strip() for line in lines[1:]]
self.assertEqual(expected, files)
def testListCmdSection(self):
"""Test listing the files in a section"""
self._RunListCmd(['section'],
['section', 'cbfs', 'u-boot', 'u-boot-dtb', 'u-boot-dtb'])
def testListCmdFile(self):
"""Test listing a particular file"""
self._RunListCmd(['*u-boot-dtb'], ['u-boot-dtb', 'u-boot-dtb'])
def testListCmdWildcard(self):
"""Test listing a wildcarded file"""
self._RunListCmd(['*boot*'],
['u-boot', 'u-boot', 'u-boot-dtb', 'u-boot-dtb'])
def testListCmdWildcardMulti(self):
"""Test listing a wildcarded file"""
self._RunListCmd(['*cb*', '*head*'],
['cbfs', 'u-boot', 'u-boot-dtb', 'image-header'])
def testListCmdEmpty(self):
"""Test listing a wildcarded file"""
self._RunListCmd(['nothing'], [])
def testListCmdPath(self):
"""Test listing the files in a sub-entry of a section"""
self._RunListCmd(['section/cbfs'], ['cbfs', 'u-boot', 'u-boot-dtb'])
if __name__ == "__main__":
unittest.main()

View file

@ -8,6 +8,7 @@
from __future__ import print_function
from collections import OrderedDict
import fnmatch
from operator import attrgetter
import re
import sys
@ -147,3 +148,152 @@ class Image(section.Entry_section):
entries = []
self.ListEntries(entries, 0)
return entries
def FindEntryPath(self, entry_path):
"""Find an entry at a given path in the image
Args:
entry_path: Path to entry (e.g. /ro-section/u-boot')
Returns:
Entry object corresponding to that past
Raises:
ValueError if no entry found
"""
parts = entry_path.split('/')
entries = self.GetEntries()
parent = '/'
for part in parts:
entry = entries.get(part)
if not entry:
raise ValueError("Entry '%s' not found in '%s'" %
(part, parent))
parent = entry.GetPath()
entries = entry.GetEntries()
return entry
def ReadData(self, decomp=True):
return self._data
def GetListEntries(self, entry_paths):
"""List the entries in an image
This decodes the supplied image and returns a list of entries from that
image, preceded by a header.
Args:
entry_paths: List of paths to match (each can have wildcards). Only
entries whose names match one of these paths will be printed
Returns:
String error message if something went wrong, otherwise
3-Tuple:
List of EntryInfo objects
List of lines, each
List of text columns, each a string
List of widths of each column
"""
def _EntryToStrings(entry):
"""Convert an entry to a list of strings, one for each column
Args:
entry: EntryInfo object containing information to output
Returns:
List of strings, one for each field in entry
"""
def _AppendHex(val):
"""Append a hex value, or an empty string if val is None
Args:
val: Integer value, or None if none
"""
args.append('' if val is None else '>%x' % val)
args = [' ' * entry.indent + entry.name]
_AppendHex(entry.image_pos)
_AppendHex(entry.size)
args.append(entry.etype)
_AppendHex(entry.offset)
_AppendHex(entry.uncomp_size)
return args
def _DoLine(lines, line):
"""Add a line to the output list
This adds a line (a list of columns) to the output list. It also updates
the widths[] array with the maximum width of each column
Args:
lines: List of lines to add to
line: List of strings, one for each column
"""
for i, item in enumerate(line):
widths[i] = max(widths[i], len(item))
lines.append(line)
def _NameInPaths(fname, entry_paths):
"""Check if a filename is in a list of wildcarded paths
Args:
fname: Filename to check
entry_paths: List of wildcarded paths (e.g. ['*dtb*', 'u-boot*',
'section/u-boot'])
Returns:
True if any wildcard matches the filename (using Unix filename
pattern matching, not regular expressions)
False if not
"""
for path in entry_paths:
if fnmatch.fnmatch(fname, path):
return True
return False
entries = self.BuildEntryList()
# This is our list of lines. Each item in the list is a list of strings, one
# for each column
lines = []
HEADER = ['Name', 'Image-pos', 'Size', 'Entry-type', 'Offset',
'Uncomp-size']
num_columns = len(HEADER)
# This records the width of each column, calculated as the maximum width of
# all the strings in that column
widths = [0] * num_columns
_DoLine(lines, HEADER)
# We won't print anything unless it has at least this indent. So at the
# start we will print nothing, unless a path matches (or there are no
# entry paths)
MAX_INDENT = 100
min_indent = MAX_INDENT
path_stack = []
path = ''
indent = 0
selected_entries = []
for entry in entries:
if entry.indent > indent:
path_stack.append(path)
elif entry.indent < indent:
path_stack.pop()
if path_stack:
path = path_stack[-1] + '/' + entry.name
indent = entry.indent
# If there are entry paths to match and we are not looking at a
# sub-entry of a previously matched entry, we need to check the path
if entry_paths and indent <= min_indent:
if _NameInPaths(path[1:], entry_paths):
# Print this entry and all sub-entries (=higher indent)
min_indent = indent
else:
# Don't print this entry, nor any following entries until we get
# a path match
min_indent = MAX_INDENT
continue
_DoLine(lines, _EntryToStrings(entry))
selected_entries.append(entry)
return selected_entries, lines, widths

View file

@ -0,0 +1,36 @@
// SPDX-License-Identifier: GPL-2.0+
/dts-v1/;
/ {
#address-cells = <1>;
#size-cells = <1>;
binman {
size = <0xc00>;
u-boot {
};
section {
align = <0x100>;
cbfs {
size = <0x400>;
u-boot {
cbfs-type = "raw";
};
u-boot-dtb {
cbfs-type = "raw";
cbfs-compress = "lzma";
cbfs-offset = <0x80>;
};
};
u-boot-dtb {
compress = "lz4";
};
};
fdtmap {
};
image-header {
location = "end";
};
};
};