Merge tag 'dm-pull-24jul19-take3' of https://gitlab.denx.de/u-boot/custodians/u-boot-dm
[oweals/u-boot.git] / tools / binman / image.py
1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright (c) 2016 Google, Inc
3 # Written by Simon Glass <sjg@chromium.org>
4 #
5 # Class for an image, the output of binman
6 #
7
8 from __future__ import print_function
9
10 from collections import OrderedDict
11 import fnmatch
12 from operator import attrgetter
13 import re
14 import sys
15
16 from entry import Entry
17 from etype import fdtmap
18 from etype import image_header
19 from etype import section
20 import fdt
21 import fdt_util
22 import tools
23 import tout
24
25 class Image(section.Entry_section):
26     """A Image, representing an output from binman
27
28     An image is comprised of a collection of entries each containing binary
29     data. The image size must be large enough to hold all of this data.
30
31     This class implements the various operations needed for images.
32
33     Attributes:
34         filename: Output filename for image
35
36     Args:
37         test: True if this is being called from a test of Images. This this case
38             there is no device tree defining the structure of the section, so
39             we create a section manually.
40     """
41     def __init__(self, name, node, test=False):
42         self.image = self
43         section.Entry_section.__init__(self, None, 'section', node, test)
44         self.name = 'main-section'
45         self.image_name = name
46         self._filename = '%s.bin' % self.image_name
47         if not test:
48             filename = fdt_util.GetString(self._node, 'filename')
49             if filename:
50                 self._filename = filename
51
52     @classmethod
53     def FromFile(cls, fname):
54         """Convert an image file into an Image for use in binman
55
56         Args:
57             fname: Filename of image file to read
58
59         Returns:
60             Image object on success
61
62         Raises:
63             ValueError if something goes wrong
64         """
65         data = tools.ReadFile(fname)
66         size = len(data)
67
68         # First look for an image header
69         pos = image_header.LocateHeaderOffset(data)
70         if pos is None:
71             # Look for the FDT map
72             pos = fdtmap.LocateFdtmap(data)
73         if pos is None:
74             raise ValueError('Cannot find FDT map in image')
75
76         # We don't know the FDT size, so check its header first
77         probe_dtb = fdt.Fdt.FromData(
78             data[pos + fdtmap.FDTMAP_HDR_LEN:pos + 256])
79         dtb_size = probe_dtb.GetFdtObj().totalsize()
80         fdtmap_data = data[pos:pos + dtb_size + fdtmap.FDTMAP_HDR_LEN]
81         dtb = fdt.Fdt.FromData(fdtmap_data[fdtmap.FDTMAP_HDR_LEN:])
82         dtb.Scan()
83
84         # Return an Image with the associated nodes
85         image = Image('image', dtb.GetRoot())
86         image._data = data
87         return image
88
89     def Raise(self, msg):
90         """Convenience function to raise an error referencing an image"""
91         raise ValueError("Image '%s': %s" % (self._node.path, msg))
92
93     def PackEntries(self):
94         """Pack all entries into the image"""
95         section.Entry_section.Pack(self, 0)
96
97     def SetImagePos(self):
98         # This first section in the image so it starts at 0
99         section.Entry_section.SetImagePos(self, 0)
100
101     def ProcessEntryContents(self):
102         """Call the ProcessContents() method for each entry
103
104         This is intended to adjust the contents as needed by the entry type.
105
106         Returns:
107             True if the new data size is OK, False if expansion is needed
108         """
109         sizes_ok = True
110         for entry in self._entries.values():
111             if not entry.ProcessContents():
112                 sizes_ok = False
113                 tout.Debug("Entry '%s' size change" % self._node.path)
114         return sizes_ok
115
116     def WriteSymbols(self):
117         """Write symbol values into binary files for access at run time"""
118         section.Entry_section.WriteSymbols(self, self)
119
120     def BuildSection(self, fd, base_offset):
121         """Write the section to a file"""
122         fd.seek(base_offset)
123         fd.write(self.GetData())
124
125     def BuildImage(self):
126         """Write the image to a file"""
127         fname = tools.GetOutputFilename(self._filename)
128         with open(fname, 'wb') as fd:
129             self.BuildSection(fd, 0)
130
131     def WriteMap(self):
132         """Write a map of the image to a .map file
133
134         Returns:
135             Filename of map file written
136         """
137         filename = '%s.map' % self.image_name
138         fname = tools.GetOutputFilename(filename)
139         with open(fname, 'w') as fd:
140             print('%8s  %8s  %8s  %s' % ('ImagePos', 'Offset', 'Size', 'Name'),
141                   file=fd)
142             section.Entry_section.WriteMap(self, fd, 0)
143         return fname
144
145     def BuildEntryList(self):
146         """List the files in an image
147
148         Returns:
149             List of entry.EntryInfo objects describing all entries in the image
150         """
151         entries = []
152         self.ListEntries(entries, 0)
153         return entries
154
155     def FindEntryPath(self, entry_path):
156         """Find an entry at a given path in the image
157
158         Args:
159             entry_path: Path to entry (e.g. /ro-section/u-boot')
160
161         Returns:
162             Entry object corresponding to that past
163
164         Raises:
165             ValueError if no entry found
166         """
167         parts = entry_path.split('/')
168         entries = self.GetEntries()
169         parent = '/'
170         for part in parts:
171             entry = entries.get(part)
172             if not entry:
173                 raise ValueError("Entry '%s' not found in '%s'" %
174                                  (part, parent))
175             parent = entry.GetPath()
176             entries = entry.GetEntries()
177         return entry
178
179     def ReadData(self, decomp=True):
180         return self._data
181
182     def GetListEntries(self, entry_paths):
183         """List the entries in an image
184
185         This decodes the supplied image and returns a list of entries from that
186         image, preceded by a header.
187
188         Args:
189             entry_paths: List of paths to match (each can have wildcards). Only
190                 entries whose names match one of these paths will be printed
191
192         Returns:
193             String error message if something went wrong, otherwise
194             3-Tuple:
195                 List of EntryInfo objects
196                 List of lines, each
197                     List of text columns, each a string
198                 List of widths of each column
199         """
200         def _EntryToStrings(entry):
201             """Convert an entry to a list of strings, one for each column
202
203             Args:
204                 entry: EntryInfo object containing information to output
205
206             Returns:
207                 List of strings, one for each field in entry
208             """
209             def _AppendHex(val):
210                 """Append a hex value, or an empty string if val is None
211
212                 Args:
213                     val: Integer value, or None if none
214                 """
215                 args.append('' if val is None else '>%x' % val)
216
217             args = ['  ' * entry.indent + entry.name]
218             _AppendHex(entry.image_pos)
219             _AppendHex(entry.size)
220             args.append(entry.etype)
221             _AppendHex(entry.offset)
222             _AppendHex(entry.uncomp_size)
223             return args
224
225         def _DoLine(lines, line):
226             """Add a line to the output list
227
228             This adds a line (a list of columns) to the output list. It also updates
229             the widths[] array with the maximum width of each column
230
231             Args:
232                 lines: List of lines to add to
233                 line: List of strings, one for each column
234             """
235             for i, item in enumerate(line):
236                 widths[i] = max(widths[i], len(item))
237             lines.append(line)
238
239         def _NameInPaths(fname, entry_paths):
240             """Check if a filename is in a list of wildcarded paths
241
242             Args:
243                 fname: Filename to check
244                 entry_paths: List of wildcarded paths (e.g. ['*dtb*', 'u-boot*',
245                                                              'section/u-boot'])
246
247             Returns:
248                 True if any wildcard matches the filename (using Unix filename
249                     pattern matching, not regular expressions)
250                 False if not
251             """
252             for path in entry_paths:
253                 if fnmatch.fnmatch(fname, path):
254                     return True
255             return False
256
257         entries = self.BuildEntryList()
258
259         # This is our list of lines. Each item in the list is a list of strings, one
260         # for each column
261         lines = []
262         HEADER = ['Name', 'Image-pos', 'Size', 'Entry-type', 'Offset',
263                   'Uncomp-size']
264         num_columns = len(HEADER)
265
266         # This records the width of each column, calculated as the maximum width of
267         # all the strings in that column
268         widths = [0] * num_columns
269         _DoLine(lines, HEADER)
270
271         # We won't print anything unless it has at least this indent. So at the
272         # start we will print nothing, unless a path matches (or there are no
273         # entry paths)
274         MAX_INDENT = 100
275         min_indent = MAX_INDENT
276         path_stack = []
277         path = ''
278         indent = 0
279         selected_entries = []
280         for entry in entries:
281             if entry.indent > indent:
282                 path_stack.append(path)
283             elif entry.indent < indent:
284                 path_stack.pop()
285             if path_stack:
286                 path = path_stack[-1] + '/' + entry.name
287             indent = entry.indent
288
289             # If there are entry paths to match and we are not looking at a
290             # sub-entry of a previously matched entry, we need to check the path
291             if entry_paths and indent <= min_indent:
292                 if _NameInPaths(path[1:], entry_paths):
293                     # Print this entry and all sub-entries (=higher indent)
294                     min_indent = indent
295                 else:
296                     # Don't print this entry, nor any following entries until we get
297                     # a path match
298                     min_indent = MAX_INDENT
299                     continue
300             _DoLine(lines, _EntryToStrings(entry))
301             selected_entries.append(entry)
302         return selected_entries, lines, widths