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