patman: Drop references to __future__
[oweals/u-boot.git] / tools / binman / image.py
index 835b66c99f5f188a74e9934860552e74760fa370..3e961733f9ec211b6ab86fa1dc6d2d5f06e79668 100644 (file)
@@ -5,18 +5,23 @@
 # Class for an image, the output of binman
 #
 
-from __future__ import print_function
-
 from collections import OrderedDict
+import fnmatch
 from operator import attrgetter
+import os
 import re
 import sys
 
+from entry import Entry
+from etype import fdtmap
+from etype import image_header
+from etype import section
+import fdt
 import fdt_util
-import bsection
 import tools
+import tout
 
-class Image:
+class Image(section.Entry_section):
     """A Image, representing an output from binman
 
     An image is comprised of a collection of entries each containing binary
@@ -24,85 +29,298 @@ class Image:
 
     This class implements the various operations needed for images.
 
-    Atrtributes:
-        _node: Node object that contains the image definition in device tree
-        _name: Image name
-        _size: Image size in bytes, or None if not known yet
-        _filename: Output filename for image
-        _sections: Sections present in this image (may be one or more)
+    Attributes:
+        filename: Output filename for image
+        image_node: Name of node containing the description for this image
+        fdtmap_dtb: Fdt object for the fdtmap when loading from a file
+        fdtmap_data: Contents of the fdtmap when loading from a file
+        allow_repack: True to add properties to allow the image to be safely
+            repacked later
 
     Args:
+        copy_to_orig: Copy offset/size to orig_offset/orig_size after reading
+            from the device tree
         test: True if this is being called from a test of Images. This this case
             there is no device tree defining the structure of the section, so
             we create a section manually.
     """
-    def __init__(self, name, node, test=False):
-        self._node = node
-        self._name = name
-        self._size = None
-        self._filename = '%s.bin' % self._name
-        if test:
-            self._section = bsection.Section('main-section', self._node, True)
-        else:
-            self._ReadNode()
-
-    def _ReadNode(self):
-        """Read properties from the image node"""
-        self._size = fdt_util.GetInt(self._node, 'size')
+    def __init__(self, name, node, copy_to_orig=True, test=False):
+        section.Entry_section.__init__(self, None, 'section', node, test=test)
+        self.copy_to_orig = copy_to_orig
+        self.name = 'main-section'
+        self.image_name = name
+        self._filename = '%s.bin' % self.image_name
+        self.fdtmap_dtb = None
+        self.fdtmap_data = None
+        self.allow_repack = False
+        if not test:
+            self.ReadNode()
+
+    def ReadNode(self):
+        section.Entry_section.ReadNode(self)
         filename = fdt_util.GetString(self._node, 'filename')
         if filename:
             self._filename = filename
-        self._section = bsection.Section('main-section', self._node)
+        self.allow_repack = fdt_util.GetBool(self._node, 'allow-repack')
 
-    def GetEntryContents(self):
-        """Call ObtainContents() for the section
-        """
-        self._section.GetEntryContents()
+    @classmethod
+    def FromFile(cls, fname):
+        """Convert an image file into an Image for use in binman
 
-    def GetEntryPositions(self):
-        """Handle entries that want to set the position/size of other entries
+        Args:
+            fname: Filename of image file to read
 
-        This calls each entry's GetPositions() method. If it returns a list
-        of entries to update, it updates them.
+        Returns:
+            Image object on success
+
+        Raises:
+            ValueError if something goes wrong
         """
-        self._section.GetEntryPositions()
+        data = tools.ReadFile(fname)
+        size = len(data)
+
+        # First look for an image header
+        pos = image_header.LocateHeaderOffset(data)
+        if pos is None:
+            # Look for the FDT map
+            pos = fdtmap.LocateFdtmap(data)
+        if pos is None:
+            raise ValueError('Cannot find FDT map in image')
+
+        # We don't know the FDT size, so check its header first
+        probe_dtb = fdt.Fdt.FromData(
+            data[pos + fdtmap.FDTMAP_HDR_LEN:pos + 256])
+        dtb_size = probe_dtb.GetFdtObj().totalsize()
+        fdtmap_data = data[pos:pos + dtb_size + fdtmap.FDTMAP_HDR_LEN]
+        fdt_data = fdtmap_data[fdtmap.FDTMAP_HDR_LEN:]
+        out_fname = tools.GetOutputFilename('fdtmap.in.dtb')
+        tools.WriteFile(out_fname, fdt_data)
+        dtb = fdt.Fdt(out_fname)
+        dtb.Scan()
+
+        # Return an Image with the associated nodes
+        root = dtb.GetRoot()
+        image = Image('image', root, copy_to_orig=False)
+
+        image.image_node = fdt_util.GetString(root, 'image-node', 'image')
+        image.fdtmap_dtb = dtb
+        image.fdtmap_data = fdtmap_data
+        image._data = data
+        image._filename = fname
+        image.image_name, _ = os.path.splitext(fname)
+        return image
+
+    def Raise(self, msg):
+        """Convenience function to raise an error referencing an image"""
+        raise ValueError("Image '%s': %s" % (self._node.path, msg))
 
     def PackEntries(self):
         """Pack all entries into the image"""
-        self._section.PackEntries()
+        section.Entry_section.Pack(self, 0)
 
-    def CheckSize(self):
-        """Check that the image contents does not exceed its size, etc."""
-        self._size = self._section.CheckSize()
-
-    def CheckEntries(self):
-        """Check that entries do not overlap or extend outside the image"""
-        self._section.CheckEntries()
+    def SetImagePos(self):
+        # This first section in the image so it starts at 0
+        section.Entry_section.SetImagePos(self, 0)
 
     def ProcessEntryContents(self):
         """Call the ProcessContents() method for each entry
 
         This is intended to adjust the contents as needed by the entry type.
+
+        Returns:
+            True if the new data size is OK, False if expansion is needed
         """
-        self._section.ProcessEntryContents()
+        sizes_ok = True
+        for entry in self._entries.values():
+            if not entry.ProcessContents():
+                sizes_ok = False
+                tout.Debug("Entry '%s' size change" % self._node.path)
+        return sizes_ok
 
     def WriteSymbols(self):
         """Write symbol values into binary files for access at run time"""
-        self._section.WriteSymbols()
+        section.Entry_section.WriteSymbols(self, self)
 
     def BuildImage(self):
         """Write the image to a file"""
         fname = tools.GetOutputFilename(self._filename)
+        tout.Info("Writing image to '%s'" % fname)
         with open(fname, 'wb') as fd:
-            self._section.BuildSection(fd, 0)
-
-    def GetEntries(self):
-        return self._section.GetEntries()
+            data = self.GetData()
+            fd.write(data)
+        tout.Info("Wrote %#x bytes" % len(data))
 
     def WriteMap(self):
-        """Write a map of the image to a .map file"""
-        filename = '%s.map' % self._name
+        """Write a map of the image to a .map file
+
+        Returns:
+            Filename of map file written
+        """
+        filename = '%s.map' % self.image_name
         fname = tools.GetOutputFilename(filename)
         with open(fname, 'w') as fd:
-            print('%8s  %8s  %s' % ('Position', 'Size', 'Name'), file=fd)
-            self._section.WriteMap(fd, 0)
+            print('%8s  %8s  %8s  %s' % ('ImagePos', 'Offset', 'Size', 'Name'),
+                  file=fd)
+            section.Entry_section.WriteMap(self, fd, 0)
+        return fname
+
+    def BuildEntryList(self):
+        """List the files in an image
+
+        Returns:
+            List of entry.EntryInfo objects describing all entries in the image
+        """
+        entries = []
+        self.ListEntries(entries, 0)
+        return entries
+
+    def FindEntryPath(self, entry_path):
+        """Find an entry at a given path in the image
+
+        Args:
+            entry_path: Path to entry (e.g. /ro-section/u-boot')
+
+        Returns:
+            Entry object corresponding to that past
+
+        Raises:
+            ValueError if no entry found
+        """
+        parts = entry_path.split('/')
+        entries = self.GetEntries()
+        parent = '/'
+        for part in parts:
+            entry = entries.get(part)
+            if not entry:
+                raise ValueError("Entry '%s' not found in '%s'" %
+                                 (part, parent))
+            parent = entry.GetPath()
+            entries = entry.GetEntries()
+        return entry
+
+    def ReadData(self, decomp=True):
+        tout.Debug("Image '%s' ReadData(), size=%#x" %
+                   (self.GetPath(), len(self._data)))
+        return self._data
+
+    def GetListEntries(self, entry_paths):
+        """List the entries in an image
+
+        This decodes the supplied image and returns a list of entries from that
+        image, preceded by a header.
+
+        Args:
+            entry_paths: List of paths to match (each can have wildcards). Only
+                entries whose names match one of these paths will be printed
+
+        Returns:
+            String error message if something went wrong, otherwise
+            3-Tuple:
+                List of EntryInfo objects
+                List of lines, each
+                    List of text columns, each a string
+                List of widths of each column
+        """
+        def _EntryToStrings(entry):
+            """Convert an entry to a list of strings, one for each column
+
+            Args:
+                entry: EntryInfo object containing information to output
+
+            Returns:
+                List of strings, one for each field in entry
+            """
+            def _AppendHex(val):
+                """Append a hex value, or an empty string if val is None
+
+                Args:
+                    val: Integer value, or None if none
+                """
+                args.append('' if val is None else '>%x' % val)
+
+            args = ['  ' * entry.indent + entry.name]
+            _AppendHex(entry.image_pos)
+            _AppendHex(entry.size)
+            args.append(entry.etype)
+            _AppendHex(entry.offset)
+            _AppendHex(entry.uncomp_size)
+            return args
+
+        def _DoLine(lines, line):
+            """Add a line to the output list
+
+            This adds a line (a list of columns) to the output list. It also updates
+            the widths[] array with the maximum width of each column
+
+            Args:
+                lines: List of lines to add to
+                line: List of strings, one for each column
+            """
+            for i, item in enumerate(line):
+                widths[i] = max(widths[i], len(item))
+            lines.append(line)
+
+        def _NameInPaths(fname, entry_paths):
+            """Check if a filename is in a list of wildcarded paths
+
+            Args:
+                fname: Filename to check
+                entry_paths: List of wildcarded paths (e.g. ['*dtb*', 'u-boot*',
+                                                             'section/u-boot'])
+
+            Returns:
+                True if any wildcard matches the filename (using Unix filename
+                    pattern matching, not regular expressions)
+                False if not
+            """
+            for path in entry_paths:
+                if fnmatch.fnmatch(fname, path):
+                    return True
+            return False
+
+        entries = self.BuildEntryList()
+
+        # This is our list of lines. Each item in the list is a list of strings, one
+        # for each column
+        lines = []
+        HEADER = ['Name', 'Image-pos', 'Size', 'Entry-type', 'Offset',
+                  'Uncomp-size']
+        num_columns = len(HEADER)
+
+        # This records the width of each column, calculated as the maximum width of
+        # all the strings in that column
+        widths = [0] * num_columns
+        _DoLine(lines, HEADER)
+
+        # We won't print anything unless it has at least this indent. So at the
+        # start we will print nothing, unless a path matches (or there are no
+        # entry paths)
+        MAX_INDENT = 100
+        min_indent = MAX_INDENT
+        path_stack = []
+        path = ''
+        indent = 0
+        selected_entries = []
+        for entry in entries:
+            if entry.indent > indent:
+                path_stack.append(path)
+            elif entry.indent < indent:
+                path_stack.pop()
+            if path_stack:
+                path = path_stack[-1] + '/' + entry.name
+            indent = entry.indent
+
+            # If there are entry paths to match and we are not looking at a
+            # sub-entry of a previously matched entry, we need to check the path
+            if entry_paths and indent <= min_indent:
+                if _NameInPaths(path[1:], entry_paths):
+                    # Print this entry and all sub-entries (=higher indent)
+                    min_indent = indent
+                else:
+                    # Don't print this entry, nor any following entries until we get
+                    # a path match
+                    min_indent = MAX_INDENT
+                    continue
+            _DoLine(lines, _EntryToStrings(entry))
+            selected_entries.append(entry)
+        return selected_entries, lines, widths