• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
8from __future__ import print_function
9
10from collections import OrderedDict
11import fnmatch
12from operator import attrgetter
13import os
14import re
15import sys
16
17from entry import Entry
18from etype import fdtmap
19from etype import image_header
20from etype import section
21import fdt
22import fdt_util
23import tools
24import tout
25
26class 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
329