mirror of
https://xff.cz/git/u-boot/
synced 2025-09-02 09:12:08 +02:00
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:
@@ -490,6 +490,49 @@ see README.entries. This is generated from the source code using:
|
|||||||
binman entry-docs >tools/binman/README.entries
|
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
|
Hashing Entries
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
@@ -825,7 +868,6 @@ Some ideas:
|
|||||||
- Add an option to decode an image into the constituent binaries
|
- Add an option to decode an image into the constituent binaries
|
||||||
- Support building an image for a board (-b) more completely, with a
|
- Support building an image for a board (-b) more completely, with a
|
||||||
configurable build directory
|
configurable build directory
|
||||||
- Support listing files in images
|
|
||||||
- Support logging of binman's operations, with different levels of verbosity
|
- 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 no size change / repacking)
|
||||||
- Support updating binaries in an image (with repacking)
|
- Support updating binaries in an image (with repacking)
|
||||||
|
@@ -65,6 +65,12 @@ controlled by a description in the board device tree.'''
|
|||||||
entry_parser = subparsers.add_parser('entry-docs',
|
entry_parser = subparsers.add_parser('entry-docs',
|
||||||
help='Write out entry documentation (see README.entries)')
|
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 = subparsers.add_parser('test', help='Run tests')
|
||||||
test_parser.add_argument('-P', '--processes', type=int,
|
test_parser.add_argument('-P', '--processes', type=int,
|
||||||
help='set number of processes to use for running tests')
|
help='set number of processes to use for running tests')
|
||||||
|
@@ -67,6 +67,37 @@ def WriteEntryDocs(modules, test_missing=None):
|
|||||||
from entry import Entry
|
from entry import Entry
|
||||||
Entry.WriteDocs(modules, test_missing)
|
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):
|
def Binman(args):
|
||||||
"""The main control code for binman
|
"""The main control code for binman
|
||||||
|
|
||||||
@@ -87,6 +118,10 @@ def Binman(args):
|
|||||||
command.Run(pager, fname)
|
command.Run(pager, fname)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
if args.cmd == 'ls':
|
||||||
|
ListEntries(args.image, args.paths)
|
||||||
|
return 0
|
||||||
|
|
||||||
# Try to figure out which device tree contains our image description
|
# Try to figure out which device tree contains our image description
|
||||||
if args.dt:
|
if args.dt:
|
||||||
dtb_fname = args.dt
|
dtb_fname = args.dt
|
||||||
|
@@ -2341,6 +2341,88 @@ class TestFunctional(unittest.TestCase):
|
|||||||
image = Image.FromFile(image_fname)
|
image = Image.FromFile(image_fname)
|
||||||
self.assertIn("Cannot find FDT map in image", str(e.exception))
|
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__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@@ -8,6 +8,7 @@
|
|||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
import fnmatch
|
||||||
from operator import attrgetter
|
from operator import attrgetter
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
@@ -147,3 +148,152 @@ class Image(section.Entry_section):
|
|||||||
entries = []
|
entries = []
|
||||||
self.ListEntries(entries, 0)
|
self.ListEntries(entries, 0)
|
||||||
return entries
|
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
|
||||||
|
36
tools/binman/test/130_list_fdtmap.dts
Normal file
36
tools/binman/test/130_list_fdtmap.dts
Normal 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";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
Reference in New Issue
Block a user