binman: Write fake blobs to the output directory
[platform/kernel/u-boot.git] / tools / binman / entry.py
index e5d0aa5..bac90bb 100644 (file)
@@ -7,6 +7,7 @@
 from collections import namedtuple
 import importlib
 import os
+import pathlib
 import sys
 
 from dtoc import fdt_util
@@ -48,9 +49,11 @@ class Entry(object):
         uncomp_size: Size of uncompressed data in bytes, if the entry is
             compressed, else None
         contents_size: Size of contents in bytes, 0 by default
-        align: Entry start offset alignment, or None
+        align: Entry start offset alignment relative to the start of the
+            containing section, or None
         align_size: Entry size alignment, or None
-        align_end: Entry end offset alignment, or None
+        align_end: Entry end offset alignment relative to the start of the
+            containing section, or None
         pad_before: Number of pad bytes before the contents when it is placed
             in the containing section, 0 if none. The pad bytes become part of
             the entry.
@@ -58,13 +61,18 @@ class Entry(object):
             the containing section, 0 if none. The pad bytes become part of
             the entry.
         data: Contents of entry (string of bytes). This does not include
-            padding created by pad_before or pad_after
+            padding created by pad_before or pad_after. If the entry is
+            compressed, this contains the compressed data.
+        uncomp_data: Original uncompressed data, if this entry is compressed,
+            else None
         compress: Compression algoithm used (e.g. 'lz4'), 'none' if none
         orig_offset: Original offset value read from node
         orig_size: Original size value read from node
         missing: True if this entry is missing its contents
         allow_missing: Allow children of this entry to be missing (used by
             subclasses such as Entry_section)
+        allow_fake: Allow creating a dummy fake file if the blob file is not
+            available. This is mainly used for testing.
         external: True if this entry contains an external binary blob
     """
     def __init__(self, section, etype, node, name_prefix=''):
@@ -81,6 +89,7 @@ class Entry(object):
         self.pre_reset_size = None
         self.uncomp_size = None
         self.data = None
+        self.uncomp_data = None
         self.contents_size = 0
         self.align = None
         self.align_size = None
@@ -89,29 +98,38 @@ class Entry(object):
         self.pad_after = 0
         self.offset_unset = False
         self.image_pos = None
-        self._expand_size = False
+        self.expand_size = False
         self.compress = 'none'
         self.missing = False
+        self.faked = False
         self.external = False
         self.allow_missing = False
+        self.allow_fake = False
 
     @staticmethod
-    def Lookup(node_path, etype):
+    def FindEntryClass(etype, expanded):
         """Look up the entry class for a node.
 
         Args:
             node_node: Path name of Node object containing information about
                        the entry to create (used for errors)
             etype:   Entry type to use
+            expanded: Use the expanded version of etype
 
         Returns:
-            The entry class object if found, else None
+            The entry class object if found, else None if not found and expanded
+                is True, else a tuple:
+                    module name that could not be found
+                    exception received
         """
         # Convert something like 'u-boot@0' to 'u_boot' since we are only
         # interested in the type.
         module_name = etype.replace('-', '_')
+
         if '@' in module_name:
             module_name = module_name.split('@')[0]
+        if expanded:
+            module_name += '_expanded'
         module = modules.get(module_name)
 
         # Also allow entry-type modules to be brought in from the etype directory.
@@ -121,29 +139,78 @@ class Entry(object):
             try:
                 module = importlib.import_module('binman.etype.' + module_name)
             except ImportError as e:
-                raise ValueError("Unknown entry type '%s' in node '%s' (expected etype/%s.py, error '%s'" %
-                                 (etype, node_path, module_name, e))
+                if expanded:
+                    return None
+                return module_name, e
             modules[module_name] = module
 
         # Look up the expected class name
         return getattr(module, 'Entry_%s' % module_name)
 
     @staticmethod
-    def Create(section, node, etype=None):
+    def Lookup(node_path, etype, expanded, missing_etype=False):
+        """Look up the entry class for a node.
+
+        Args:
+            node_node (str): Path name of Node object containing information
+                about the entry to create (used for errors)
+            etype (str):   Entry type to use
+            expanded (bool): Use the expanded version of etype
+            missing_etype (bool): True to default to a blob etype if the
+                requested etype is not found
+
+        Returns:
+            The entry class object if found, else None if not found and expanded
+                is True
+
+        Raise:
+            ValueError if expanded is False and the class is not found
+        """
+        # Convert something like 'u-boot@0' to 'u_boot' since we are only
+        # interested in the type.
+        cls = Entry.FindEntryClass(etype, expanded)
+        if cls is None:
+            return None
+        elif isinstance(cls, tuple):
+            if missing_etype:
+                cls = Entry.FindEntryClass('blob', False)
+            if isinstance(cls, tuple): # This should not fail
+                module_name, e = cls
+                raise ValueError(
+                    "Unknown entry type '%s' in node '%s' (expected etype/%s.py, error '%s'" %
+                    (etype, node_path, module_name, e))
+        return cls
+
+    @staticmethod
+    def Create(section, node, etype=None, expanded=False, missing_etype=False):
         """Create a new entry for a node.
 
         Args:
-            section: Section object containing this node
-            node:    Node object containing information about the entry to
-                     create
-            etype:   Entry type to use, or None to work it out (used for tests)
+            section (entry_Section):  Section object containing this node
+            node (Node): Node object containing information about the entry to
+                create
+            etype (str): Entry type to use, or None to work it out (used for
+                tests)
+            expanded (bool): Use the expanded version of etype
+            missing_etype (bool): True to default to a blob etype if the
+                requested etype is not found
 
         Returns:
             A new Entry object of the correct type (a subclass of Entry)
         """
         if not etype:
             etype = fdt_util.GetString(node, 'type', node.name)
-        obj = Entry.Lookup(node.path, etype)
+        obj = Entry.Lookup(node.path, etype, expanded, missing_etype)
+        if obj and expanded:
+            # Check whether to use the expanded entry
+            new_etype = etype + '-expanded'
+            can_expand = not fdt_util.GetBool(node, 'no-expanded')
+            if can_expand and obj.UseExpanded(node, etype, new_etype):
+                etype = new_etype
+            else:
+                obj = None
+        if not obj:
+            obj = Entry.Lookup(node.path, etype, False, missing_etype)
 
         # Call its constructor to get the object we want.
         return obj(section, etype, node)
@@ -174,6 +241,8 @@ class Entry(object):
         if tools.NotPowerOfTwo(self.align):
             raise ValueError("Node '%s': Alignment %s must be a power of two" %
                              (self._node.path, self.align))
+        if self.section and self.align is None:
+            self.align = self.section.align_default
         self.pad_before = fdt_util.GetInt(self._node, 'pad-before', 0)
         self.pad_after = fdt_util.GetInt(self._node, 'pad-after', 0)
         self.align_size = fdt_util.GetInt(self._node, 'align-size')
@@ -199,19 +268,39 @@ class Entry(object):
             Dict:
                 key: Filename from this entry (without the path)
                 value: Tuple:
-                    Fdt object for this dtb, or None if not available
+                    Entry object for this dtb
                     Filename of file containing this dtb
         """
         return {}
 
     def ExpandEntries(self):
+        """Expand out entries which produce other entries
+
+        Some entries generate subnodes automatically, from which sub-entries
+        are then created. This method allows those to be added to the binman
+        definition for the current image. An entry which implements this method
+        should call state.AddSubnode() to add a subnode and can add properties
+        with state.AddString(), etc.
+
+        An example is 'files', which produces a section containing a list of
+        files.
+        """
         pass
 
-    def AddMissingProperties(self):
-        """Add new properties to the device tree as needed for this entry"""
-        for prop in ['offset', 'size', 'image-pos']:
+    def AddMissingProperties(self, have_image_pos):
+        """Add new properties to the device tree as needed for this entry
+
+        Args:
+            have_image_pos: True if this entry has an image position. This can
+                be False if its parent section is compressed, since compression
+                groups all entries together into a compressed block of data,
+                obscuring the start of each individual child entry
+        """
+        for prop in ['offset', 'size']:
             if not prop in self._node.props:
                 state.AddZeroProp(self._node, prop)
+        if have_image_pos and 'image-pos' not in self._node.props:
+            state.AddZeroProp(self._node, 'image-pos')
         if self.GetImage().allow_repack:
             if self.orig_offset is not None:
                 state.AddZeroProp(self._node, 'orig-offset', True)
@@ -229,7 +318,8 @@ class Entry(object):
         state.SetInt(self._node, 'offset', self.offset)
         state.SetInt(self._node, 'size', self.size)
         base = self.section.GetRootSkipAtStart() if self.section else 0
-        state.SetInt(self._node, 'image-pos', self.image_pos - base)
+        if self.image_pos is not None:
+            state.SetInt(self._node, 'image-pos', self.image_pos - base)
         if self.GetImage().allow_repack:
             if self.orig_offset is not None:
                 state.SetInt(self._node, 'orig-offset', self.orig_offset, True)
@@ -390,6 +480,11 @@ class Entry(object):
         """Convenience function to raise an error referencing a node"""
         raise ValueError("Node '%s': %s" % (self._node.path, msg))
 
+    def Info(self, msg):
+        """Convenience function to log info referencing a node"""
+        tag = "Info '%s'" % self._node.path
+        tout.Detail('%30s: %s' % (tag, msg))
+
     def Detail(self, msg):
         """Convenience function to log detail referencing a node"""
         tag = "Node '%s'" % self._node.path
@@ -418,8 +513,7 @@ class Entry(object):
                 missing.append(prop.name)
             values.append(value)
         if missing:
-            self.Raise('Missing required properties/entry args: %s' %
-                       (', '.join(missing)))
+            self.GetImage().MissingArgs(self, missing)
         return values
 
     def GetPath(self):
@@ -430,10 +524,36 @@ class Entry(object):
         """
         return self._node.path
 
-    def GetData(self):
+    def GetData(self, required=True):
+        """Get the contents of an entry
+
+        Args:
+            required: True if the data must be present, False if it is OK to
+                return None
+
+        Returns:
+            bytes content of the entry, excluding any padding. If the entry is
+                compressed, the compressed data is returned
+        """
         self.Detail('GetData: size %s' % ToHexSize(self.data))
         return self.data
 
+    def GetPaddedData(self, data=None):
+        """Get the data for an entry including any padding
+
+        Gets the entry data and uses its section's pad-byte value to add padding
+        before and after as defined by the pad-before and pad-after properties.
+
+        This does not consider alignment.
+
+        Returns:
+            Contents of the entry along with any pad bytes before and
+            after it (bytes)
+        """
+        if data is None:
+            data = self.GetData()
+        return self.section.GetPaddedDataForEntry(self, data)
+
     def GetOffsets(self):
         """Get the offsets for siblings
 
@@ -498,7 +618,7 @@ class Entry(object):
         """
         pass
 
-    def CheckOffset(self):
+    def CheckEntries(self):
         """Check that the entry offsets are correct
 
         This is used for entries which have extra offset requirements (other
@@ -600,7 +720,7 @@ features to produce new behaviours.
             modules.remove('_testing')
         missing = []
         for name in modules:
-            module = Entry.Lookup('WriteDocs', name)
+            module = Entry.Lookup('WriteDocs', name, False)
             docs = getattr(module, '__doc__')
             if test_missing == name:
                 docs = None
@@ -700,7 +820,7 @@ features to produce new behaviours.
         self.AddEntryInfo(entries, indent, self.name, self.etype, self.size,
                           self.image_pos, self.uncomp_size, self.offset, self)
 
-    def ReadData(self, decomp=True):
+    def ReadData(self, decomp=True, alt_format=None):
         """Read the data for an entry from the image
 
         This is used when the image has been read in and we want to extract the
@@ -717,19 +837,20 @@ features to produce new behaviours.
         # although compressed sections are currently not supported
         tout.Debug("ReadChildData section '%s', entry '%s'" %
                    (self.section.GetPath(), self.GetPath()))
-        data = self.section.ReadChildData(self, decomp)
+        data = self.section.ReadChildData(self, decomp, alt_format)
         return data
 
-    def ReadChildData(self, child, decomp=True):
+    def ReadChildData(self, child, decomp=True, alt_format=None):
         """Read the data for a particular child entry
 
         This reads data from the parent and extracts the piece that relates to
         the given child.
 
         Args:
-            child: Child entry to read data for (must be valid)
-            decomp: True to decompress any compressed data before returning it;
-                False to return the raw, uncompressed data
+            child (Entry): Child entry to read data for (must be valid)
+            decomp (bool): True to decompress any compressed data before
+                returning it; False to return the raw, uncompressed data
+            alt_format (str): Alternative format to read in, or None
 
         Returns:
             Data for the child (bytes)
@@ -742,6 +863,20 @@ features to produce new behaviours.
         self.ProcessContentsUpdate(data)
         self.Detail('Loaded data size %x' % len(data))
 
+    def GetAltFormat(self, data, alt_format):
+        """Read the data for an extry in an alternative format
+
+        Supported formats are list in the documentation for each entry. An
+        example is fdtmap which provides .
+
+        Args:
+            data (bytes): Data to convert (this should have been produced by the
+                entry)
+            alt_format (str): Format to use
+
+        """
+        pass
+
     def GetImage(self):
         """Get the image containing this entry
 
@@ -780,7 +915,8 @@ features to produce new behaviours.
         """Handle writing the data in a child entry
 
         This should be called on the child's parent section after the child's
-        data has been updated. It
+        data has been updated. It should update any data structures needed to
+        validate that the update is successful.
 
         This base-class implementation does nothing, since the base Entry object
         does not have any children.
@@ -790,7 +926,7 @@ features to produce new behaviours.
 
         Returns:
             True if the section could be updated successfully, False if the
-                data is such that the section could not updat
+                data is such that the section could not update
         """
         return True
 
@@ -818,6 +954,14 @@ features to produce new behaviours.
         # This is meaningless for anything other than sections
         pass
 
+    def SetAllowFakeBlob(self, allow_fake):
+        """Set whether a section allows to create a fake blob
+
+        Args:
+            allow_fake: True if allowed, False if not allowed
+        """
+        pass
+
     def CheckMissing(self, missing_list):
         """Check if any entries in this section have missing external blobs
 
@@ -829,6 +973,36 @@ features to produce new behaviours.
         if self.missing:
             missing_list.append(self)
 
+    def check_fake_fname(self, fname):
+        """If the file is missing and the entry allows fake blobs, fake it
+
+        Sets self.faked to True if faked
+
+        Args:
+            fname (str): Filename to check
+
+        Returns:
+            fname (str): Filename of faked file
+        """
+        if self.allow_fake and not pathlib.Path(fname).is_file():
+            outfname = tools.GetOutputFilename(os.path.basename(fname))
+            with open(outfname, "wb") as out:
+                out.truncate(1024)
+            self.faked = True
+            return outfname
+        return fname
+
+    def CheckFakedBlobs(self, faked_blobs_list):
+        """Check if any entries in this section have faked external blobs
+
+        If there are faked blobs, the entries are added to the list
+
+        Args:
+            fake_blobs_list: List of Entry objects to be added to
+        """
+        # This is meaningless for anything other than blobs
+        pass
+
     def GetAllowMissing(self):
         """Get whether a section allows missing external blobs
 
@@ -854,7 +1028,40 @@ features to produce new behaviours.
         Returns:
             Compressed data (first word is the compressed size)
         """
+        self.uncomp_data = indata
         if self.compress != 'none':
             self.uncomp_size = len(indata)
         data = tools.Compress(indata, self.compress)
         return data
+
+    @classmethod
+    def UseExpanded(cls, node, etype, new_etype):
+        """Check whether to use an expanded entry type
+
+        This is called by Entry.Create() when it finds an expanded version of
+        an entry type (e.g. 'u-boot-expanded'). If this method returns True then
+        it will be used (e.g. in place of 'u-boot'). If it returns False, it is
+        ignored.
+
+        Args:
+            node:     Node object containing information about the entry to
+                      create
+            etype:    Original entry type being used
+            new_etype: New entry type proposed
+
+        Returns:
+            True to use this entry type, False to use the original one
+        """
+        tout.Info("Node '%s': etype '%s': %s selected" %
+                  (node.path, etype, new_etype))
+        return True
+
+    def CheckAltFormats(self, alt_formats):
+        """Add any alternative formats supported by this entry type
+
+        Args:
+            alt_formats (dict): Dict to add alt_formats to:
+                key: Name of alt format
+                value: Help text
+        """
+        pass