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