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