uboot/tools/binman/entry.py
<<
>>
Prefs
   1# SPDX-License-Identifier: GPL-2.0+
   2# Copyright (c) 2016 Google, Inc
   3#
   4# Base class for all entries
   5#
   6
   7from collections import namedtuple
   8import importlib
   9import os
  10import sys
  11
  12from dtoc import fdt_util
  13from patman import tools
  14from patman.tools import ToHex, ToHexSize
  15from patman import tout
  16
  17modules = {}
  18
  19
  20# An argument which can be passed to entries on the command line, in lieu of
  21# device-tree properties.
  22EntryArg = namedtuple('EntryArg', ['name', 'datatype'])
  23
  24# Information about an entry for use when displaying summaries
  25EntryInfo = namedtuple('EntryInfo', ['indent', 'name', 'etype', 'size',
  26                                     'image_pos', 'uncomp_size', 'offset',
  27                                     'entry'])
  28
  29class Entry(object):
  30    """An Entry in the section
  31
  32    An entry corresponds to a single node in the device-tree description
  33    of the section. Each entry ends up being a part of the final section.
  34    Entries can be placed either right next to each other, or with padding
  35    between them. The type of the entry determines the data that is in it.
  36
  37    This class is not used by itself. All entry objects are subclasses of
  38    Entry.
  39
  40    Attributes:
  41        section: Section object containing this entry
  42        node: The node that created this entry
  43        offset: Offset of entry within the section, None if not known yet (in
  44            which case it will be calculated by Pack())
  45        size: Entry size in bytes, None if not known
  46        pre_reset_size: size as it was before ResetForPack(). This allows us to
  47            keep track of the size we started with and detect size changes
  48        uncomp_size: Size of uncompressed data in bytes, if the entry is
  49            compressed, else None
  50        contents_size: Size of contents in bytes, 0 by default
  51        align: Entry start offset alignment relative to the start of the
  52            containing section, or None
  53        align_size: Entry size alignment, or None
  54        align_end: Entry end offset alignment relative to the start of the
  55            containing section, or None
  56        pad_before: Number of pad bytes before the contents when it is placed
  57            in the containing section, 0 if none. The pad bytes become part of
  58            the entry.
  59        pad_after: Number of pad bytes after the contents when it is placed in
  60            the containing section, 0 if none. The pad bytes become part of
  61            the entry.
  62        data: Contents of entry (string of bytes). This does not include
  63            padding created by pad_before or pad_after. If the entry is
  64            compressed, this contains the compressed data.
  65        uncomp_data: Original uncompressed data, if this entry is compressed,
  66            else None
  67        compress: Compression algoithm used (e.g. 'lz4'), 'none' if none
  68        orig_offset: Original offset value read from node
  69        orig_size: Original size value read from node
  70        missing: True if this entry is missing its contents
  71        allow_missing: Allow children of this entry to be missing (used by
  72            subclasses such as Entry_section)
  73        allow_fake: Allow creating a dummy fake file if the blob file is not
  74            available. This is mainly used for testing.
  75        external: True if this entry contains an external binary blob
  76    """
  77    def __init__(self, section, etype, node, name_prefix=''):
  78        # Put this here to allow entry-docs and help to work without libfdt
  79        global state
  80        from binman import state
  81
  82        self.section = section
  83        self.etype = etype
  84        self._node = node
  85        self.name = node and (name_prefix + node.name) or 'none'
  86        self.offset = None
  87        self.size = None
  88        self.pre_reset_size = None
  89        self.uncomp_size = None
  90        self.data = None
  91        self.uncomp_data = None
  92        self.contents_size = 0
  93        self.align = None
  94        self.align_size = None
  95        self.align_end = None
  96        self.pad_before = 0
  97        self.pad_after = 0
  98        self.offset_unset = False
  99        self.image_pos = None
 100        self._expand_size = False
 101        self.compress = 'none'
 102        self.missing = False
 103        self.faked = False
 104        self.external = False
 105        self.allow_missing = False
 106        self.allow_fake = False
 107
 108    @staticmethod
 109    def Lookup(node_path, etype, expanded):
 110        """Look up the entry class for a node.
 111
 112        Args:
 113            node_node: Path name of Node object containing information about
 114                       the entry to create (used for errors)
 115            etype:   Entry type to use
 116            expanded: Use the expanded version of etype
 117
 118        Returns:
 119            The entry class object if found, else None if not found and expanded
 120                is True
 121
 122        Raise:
 123            ValueError if expanded is False and the class is not found
 124        """
 125        # Convert something like 'u-boot@0' to 'u_boot' since we are only
 126        # interested in the type.
 127        module_name = etype.replace('-', '_')
 128
 129        if '@' in module_name:
 130            module_name = module_name.split('@')[0]
 131        if expanded:
 132            module_name += '_expanded'
 133        module = modules.get(module_name)
 134
 135        # Also allow entry-type modules to be brought in from the etype directory.
 136
 137        # Import the module if we have not already done so.
 138        if not module:
 139            try:
 140                module = importlib.import_module('binman.etype.' + module_name)
 141            except ImportError as e:
 142                if expanded:
 143                    return None
 144                raise ValueError("Unknown entry type '%s' in node '%s' (expected etype/%s.py, error '%s'" %
 145                                 (etype, node_path, module_name, e))
 146            modules[module_name] = module
 147
 148        # Look up the expected class name
 149        return getattr(module, 'Entry_%s' % module_name)
 150
 151    @staticmethod
 152    def Create(section, node, etype=None, expanded=False):
 153        """Create a new entry for a node.
 154
 155        Args:
 156            section:  Section object containing this node
 157            node:     Node object containing information about the entry to
 158                      create
 159            etype:    Entry type to use, or None to work it out (used for tests)
 160            expanded: True to use expanded versions of entries, where available
 161
 162        Returns:
 163            A new Entry object of the correct type (a subclass of Entry)
 164        """
 165        if not etype:
 166            etype = fdt_util.GetString(node, 'type', node.name)
 167        obj = Entry.Lookup(node.path, etype, expanded)
 168        if obj and expanded:
 169            # Check whether to use the expanded entry
 170            new_etype = etype + '-expanded'
 171            can_expand = not fdt_util.GetBool(node, 'no-expanded')
 172            if can_expand and obj.UseExpanded(node, etype, new_etype):
 173                etype = new_etype
 174            else:
 175                obj = None
 176        if not obj:
 177            obj = Entry.Lookup(node.path, etype, False)
 178
 179        # Call its constructor to get the object we want.
 180        return obj(section, etype, node)
 181
 182    def ReadNode(self):
 183        """Read entry information from the node
 184
 185        This must be called as the first thing after the Entry is created.
 186
 187        This reads all the fields we recognise from the node, ready for use.
 188        """
 189        if 'pos' in self._node.props:
 190            self.Raise("Please use 'offset' instead of 'pos'")
 191        self.offset = fdt_util.GetInt(self._node, 'offset')
 192        self.size = fdt_util.GetInt(self._node, 'size')
 193        self.orig_offset = fdt_util.GetInt(self._node, 'orig-offset')
 194        self.orig_size = fdt_util.GetInt(self._node, 'orig-size')
 195        if self.GetImage().copy_to_orig:
 196            self.orig_offset = self.offset
 197            self.orig_size = self.size
 198
 199        # These should not be set in input files, but are set in an FDT map,
 200        # which is also read by this code.
 201        self.image_pos = fdt_util.GetInt(self._node, 'image-pos')
 202        self.uncomp_size = fdt_util.GetInt(self._node, 'uncomp-size')
 203
 204        self.align = fdt_util.GetInt(self._node, 'align')
 205        if tools.NotPowerOfTwo(self.align):
 206            raise ValueError("Node '%s': Alignment %s must be a power of two" %
 207                             (self._node.path, self.align))
 208        if self.section and self.align is None:
 209            self.align = self.section.align_default
 210        self.pad_before = fdt_util.GetInt(self._node, 'pad-before', 0)
 211        self.pad_after = fdt_util.GetInt(self._node, 'pad-after', 0)
 212        self.align_size = fdt_util.GetInt(self._node, 'align-size')
 213        if tools.NotPowerOfTwo(self.align_size):
 214            self.Raise("Alignment size %s must be a power of two" %
 215                       self.align_size)
 216        self.align_end = fdt_util.GetInt(self._node, 'align-end')
 217        self.offset_unset = fdt_util.GetBool(self._node, 'offset-unset')
 218        self.expand_size = fdt_util.GetBool(self._node, 'expand-size')
 219        self.missing_msg = fdt_util.GetString(self._node, 'missing-msg')
 220
 221        # This is only supported by blobs and sections at present
 222        self.compress = fdt_util.GetString(self._node, 'compress', 'none')
 223
 224    def GetDefaultFilename(self):
 225        return None
 226
 227    def GetFdts(self):
 228        """Get the device trees used by this entry
 229
 230        Returns:
 231            Empty dict, if this entry is not a .dtb, otherwise:
 232            Dict:
 233                key: Filename from this entry (without the path)
 234                value: Tuple:
 235                    Entry object for this dtb
 236                    Filename of file containing this dtb
 237        """
 238        return {}
 239
 240    def ExpandEntries(self):
 241        """Expand out entries which produce other entries
 242
 243        Some entries generate subnodes automatically, from which sub-entries
 244        are then created. This method allows those to be added to the binman
 245        definition for the current image. An entry which implements this method
 246        should call state.AddSubnode() to add a subnode and can add properties
 247        with state.AddString(), etc.
 248
 249        An example is 'files', which produces a section containing a list of
 250        files.
 251        """
 252        pass
 253
 254    def AddMissingProperties(self, have_image_pos):
 255        """Add new properties to the device tree as needed for this entry
 256
 257        Args:
 258            have_image_pos: True if this entry has an image position. This can
 259                be False if its parent section is compressed, since compression
 260                groups all entries together into a compressed block of data,
 261                obscuring the start of each individual child entry
 262        """
 263        for prop in ['offset', 'size']:
 264            if not prop in self._node.props:
 265                state.AddZeroProp(self._node, prop)
 266        if have_image_pos and 'image-pos' not in self._node.props:
 267            state.AddZeroProp(self._node, 'image-pos')
 268        if self.GetImage().allow_repack:
 269            if self.orig_offset is not None:
 270                state.AddZeroProp(self._node, 'orig-offset', True)
 271            if self.orig_size is not None:
 272                state.AddZeroProp(self._node, 'orig-size', True)
 273
 274        if self.compress != 'none':
 275            state.AddZeroProp(self._node, 'uncomp-size')
 276        err = state.CheckAddHashProp(self._node)
 277        if err:
 278            self.Raise(err)
 279
 280    def SetCalculatedProperties(self):
 281        """Set the value of device-tree properties calculated by binman"""
 282        state.SetInt(self._node, 'offset', self.offset)
 283        state.SetInt(self._node, 'size', self.size)
 284        base = self.section.GetRootSkipAtStart() if self.section else 0
 285        if self.image_pos is not None:
 286            state.SetInt(self._node, 'image-pos', self.image_pos - base)
 287        if self.GetImage().allow_repack:
 288            if self.orig_offset is not None:
 289                state.SetInt(self._node, 'orig-offset', self.orig_offset, True)
 290            if self.orig_size is not None:
 291                state.SetInt(self._node, 'orig-size', self.orig_size, True)
 292        if self.uncomp_size is not None:
 293            state.SetInt(self._node, 'uncomp-size', self.uncomp_size)
 294        state.CheckSetHashValue(self._node, self.GetData)
 295
 296    def ProcessFdt(self, fdt):
 297        """Allow entries to adjust the device tree
 298
 299        Some entries need to adjust the device tree for their purposes. This
 300        may involve adding or deleting properties.
 301
 302        Returns:
 303            True if processing is complete
 304            False if processing could not be completed due to a dependency.
 305                This will cause the entry to be retried after others have been
 306                called
 307        """
 308        return True
 309
 310    def SetPrefix(self, prefix):
 311        """Set the name prefix for a node
 312
 313        Args:
 314            prefix: Prefix to set, or '' to not use a prefix
 315        """
 316        if prefix:
 317            self.name = prefix + self.name
 318
 319    def SetContents(self, data):
 320        """Set the contents of an entry
 321
 322        This sets both the data and content_size properties
 323
 324        Args:
 325            data: Data to set to the contents (bytes)
 326        """
 327        self.data = data
 328        self.contents_size = len(self.data)
 329
 330    def ProcessContentsUpdate(self, data):
 331        """Update the contents of an entry, after the size is fixed
 332
 333        This checks that the new data is the same size as the old. If the size
 334        has changed, this triggers a re-run of the packing algorithm.
 335
 336        Args:
 337            data: Data to set to the contents (bytes)
 338
 339        Raises:
 340            ValueError if the new data size is not the same as the old
 341        """
 342        size_ok = True
 343        new_size = len(data)
 344        if state.AllowEntryExpansion() and new_size > self.contents_size:
 345            # self.data will indicate the new size needed
 346            size_ok = False
 347        elif state.AllowEntryContraction() and new_size < self.contents_size:
 348            size_ok = False
 349
 350        # If not allowed to change, try to deal with it or give up
 351        if size_ok:
 352            if new_size > self.contents_size:
 353                self.Raise('Cannot update entry size from %d to %d' %
 354                        (self.contents_size, new_size))
 355
 356            # Don't let the data shrink. Pad it if necessary
 357            if size_ok and new_size < self.contents_size:
 358                data += tools.GetBytes(0, self.contents_size - new_size)
 359
 360        if not size_ok:
 361            tout.Debug("Entry '%s' size change from %s to %s" % (
 362                self._node.path, ToHex(self.contents_size),
 363                ToHex(new_size)))
 364        self.SetContents(data)
 365        return size_ok
 366
 367    def ObtainContents(self):
 368        """Figure out the contents of an entry.
 369
 370        Returns:
 371            True if the contents were found, False if another call is needed
 372            after the other entries are processed.
 373        """
 374        # No contents by default: subclasses can implement this
 375        return True
 376
 377    def ResetForPack(self):
 378        """Reset offset/size fields so that packing can be done again"""
 379        self.Detail('ResetForPack: offset %s->%s, size %s->%s' %
 380                    (ToHex(self.offset), ToHex(self.orig_offset),
 381                     ToHex(self.size), ToHex(self.orig_size)))
 382        self.pre_reset_size = self.size
 383        self.offset = self.orig_offset
 384        self.size = self.orig_size
 385
 386    def Pack(self, offset):
 387        """Figure out how to pack the entry into the section
 388
 389        Most of the time the entries are not fully specified. There may be
 390        an alignment but no size. In that case we take the size from the
 391        contents of the entry.
 392
 393        If an entry has no hard-coded offset, it will be placed at @offset.
 394
 395        Once this function is complete, both the offset and size of the
 396        entry will be know.
 397
 398        Args:
 399            Current section offset pointer
 400
 401        Returns:
 402            New section offset pointer (after this entry)
 403        """
 404        self.Detail('Packing: offset=%s, size=%s, content_size=%x' %
 405                    (ToHex(self.offset), ToHex(self.size),
 406                     self.contents_size))
 407        if self.offset is None:
 408            if self.offset_unset:
 409                self.Raise('No offset set with offset-unset: should another '
 410                           'entry provide this correct offset?')
 411            self.offset = tools.Align(offset, self.align)
 412        needed = self.pad_before + self.contents_size + self.pad_after
 413        needed = tools.Align(needed, self.align_size)
 414        size = self.size
 415        if not size:
 416            size = needed
 417        new_offset = self.offset + size
 418        aligned_offset = tools.Align(new_offset, self.align_end)
 419        if aligned_offset != new_offset:
 420            size = aligned_offset - self.offset
 421            new_offset = aligned_offset
 422
 423        if not self.size:
 424            self.size = size
 425
 426        if self.size < needed:
 427            self.Raise("Entry contents size is %#x (%d) but entry size is "
 428                       "%#x (%d)" % (needed, needed, self.size, self.size))
 429        # Check that the alignment is correct. It could be wrong if the
 430        # and offset or size values were provided (i.e. not calculated), but
 431        # conflict with the provided alignment values
 432        if self.size != tools.Align(self.size, self.align_size):
 433            self.Raise("Size %#x (%d) does not match align-size %#x (%d)" %
 434                  (self.size, self.size, self.align_size, self.align_size))
 435        if self.offset != tools.Align(self.offset, self.align):
 436            self.Raise("Offset %#x (%d) does not match align %#x (%d)" %
 437                  (self.offset, self.offset, self.align, self.align))
 438        self.Detail('   - packed: offset=%#x, size=%#x, content_size=%#x, next_offset=%x' %
 439                    (self.offset, self.size, self.contents_size, new_offset))
 440
 441        return new_offset
 442
 443    def Raise(self, msg):
 444        """Convenience function to raise an error referencing a node"""
 445        raise ValueError("Node '%s': %s" % (self._node.path, msg))
 446
 447    def Info(self, msg):
 448        """Convenience function to log info referencing a node"""
 449        tag = "Info '%s'" % self._node.path
 450        tout.Detail('%30s: %s' % (tag, msg))
 451
 452    def Detail(self, msg):
 453        """Convenience function to log detail referencing a node"""
 454        tag = "Node '%s'" % self._node.path
 455        tout.Detail('%30s: %s' % (tag, msg))
 456
 457    def GetEntryArgsOrProps(self, props, required=False):
 458        """Return the values of a set of properties
 459
 460        Args:
 461            props: List of EntryArg objects
 462
 463        Raises:
 464            ValueError if a property is not found
 465        """
 466        values = []
 467        missing = []
 468        for prop in props:
 469            python_prop = prop.name.replace('-', '_')
 470            if hasattr(self, python_prop):
 471                value = getattr(self, python_prop)
 472            else:
 473                value = None
 474            if value is None:
 475                value = self.GetArg(prop.name, prop.datatype)
 476            if value is None and required:
 477                missing.append(prop.name)
 478            values.append(value)
 479        if missing:
 480            self.GetImage().MissingArgs(self, missing)
 481        return values
 482
 483    def GetPath(self):
 484        """Get the path of a node
 485
 486        Returns:
 487            Full path of the node for this entry
 488        """
 489        return self._node.path
 490
 491    def GetData(self, required=True):
 492        """Get the contents of an entry
 493
 494        Args:
 495            required: True if the data must be present, False if it is OK to
 496                return None
 497
 498        Returns:
 499            bytes content of the entry, excluding any padding. If the entry is
 500                compressed, the compressed data is returned
 501        """
 502        self.Detail('GetData: size %s' % ToHexSize(self.data))
 503        return self.data
 504
 505    def GetPaddedData(self, data=None):
 506        """Get the data for an entry including any padding
 507
 508        Gets the entry data and uses its section's pad-byte value to add padding
 509        before and after as defined by the pad-before and pad-after properties.
 510
 511        This does not consider alignment.
 512
 513        Returns:
 514            Contents of the entry along with any pad bytes before and
 515            after it (bytes)
 516        """
 517        if data is None:
 518            data = self.GetData()
 519        return self.section.GetPaddedDataForEntry(self, data)
 520
 521    def GetOffsets(self):
 522        """Get the offsets for siblings
 523
 524        Some entry types can contain information about the position or size of
 525        other entries. An example of this is the Intel Flash Descriptor, which
 526        knows where the Intel Management Engine section should go.
 527
 528        If this entry knows about the position of other entries, it can specify
 529        this by returning values here
 530
 531        Returns:
 532            Dict:
 533                key: Entry type
 534                value: List containing position and size of the given entry
 535                    type. Either can be None if not known
 536        """
 537        return {}
 538
 539    def SetOffsetSize(self, offset, size):
 540        """Set the offset and/or size of an entry
 541
 542        Args:
 543            offset: New offset, or None to leave alone
 544            size: New size, or None to leave alone
 545        """
 546        if offset is not None:
 547            self.offset = offset
 548        if size is not None:
 549            self.size = size
 550
 551    def SetImagePos(self, image_pos):
 552        """Set the position in the image
 553
 554        Args:
 555            image_pos: Position of this entry in the image
 556        """
 557        self.image_pos = image_pos + self.offset
 558
 559    def ProcessContents(self):
 560        """Do any post-packing updates of entry contents
 561
 562        This function should call ProcessContentsUpdate() to update the entry
 563        contents, if necessary, returning its return value here.
 564
 565        Args:
 566            data: Data to set to the contents (bytes)
 567
 568        Returns:
 569            True if the new data size is OK, False if expansion is needed
 570
 571        Raises:
 572            ValueError if the new data size is not the same as the old and
 573                state.AllowEntryExpansion() is False
 574        """
 575        return True
 576
 577    def WriteSymbols(self, section):
 578        """Write symbol values into binary files for access at run time
 579
 580        Args:
 581          section: Section containing the entry
 582        """
 583        pass
 584
 585    def CheckEntries(self):
 586        """Check that the entry offsets are correct
 587
 588        This is used for entries which have extra offset requirements (other
 589        than having to be fully inside their section). Sub-classes can implement
 590        this function and raise if there is a problem.
 591        """
 592        pass
 593
 594    @staticmethod
 595    def GetStr(value):
 596        if value is None:
 597            return '<none>  '
 598        return '%08x' % value
 599
 600    @staticmethod
 601    def WriteMapLine(fd, indent, name, offset, size, image_pos):
 602        print('%s  %s%s  %s  %s' % (Entry.GetStr(image_pos), ' ' * indent,
 603                                    Entry.GetStr(offset), Entry.GetStr(size),
 604                                    name), file=fd)
 605
 606    def WriteMap(self, fd, indent):
 607        """Write a map of the entry to a .map file
 608
 609        Args:
 610            fd: File to write the map to
 611            indent: Curent indent level of map (0=none, 1=one level, etc.)
 612        """
 613        self.WriteMapLine(fd, indent, self.name, self.offset, self.size,
 614                          self.image_pos)
 615
 616    def GetEntries(self):
 617        """Return a list of entries contained by this entry
 618
 619        Returns:
 620            List of entries, or None if none. A normal entry has no entries
 621                within it so will return None
 622        """
 623        return None
 624
 625    def GetArg(self, name, datatype=str):
 626        """Get the value of an entry argument or device-tree-node property
 627
 628        Some node properties can be provided as arguments to binman. First check
 629        the entry arguments, and fall back to the device tree if not found
 630
 631        Args:
 632            name: Argument name
 633            datatype: Data type (str or int)
 634
 635        Returns:
 636            Value of argument as a string or int, or None if no value
 637
 638        Raises:
 639            ValueError if the argument cannot be converted to in
 640        """
 641        value = state.GetEntryArg(name)
 642        if value is not None:
 643            if datatype == int:
 644                try:
 645                    value = int(value)
 646                except ValueError:
 647                    self.Raise("Cannot convert entry arg '%s' (value '%s') to integer" %
 648                               (name, value))
 649            elif datatype == str:
 650                pass
 651            else:
 652                raise ValueError("GetArg() internal error: Unknown data type '%s'" %
 653                                 datatype)
 654        else:
 655            value = fdt_util.GetDatatype(self._node, name, datatype)
 656        return value
 657
 658    @staticmethod
 659    def WriteDocs(modules, test_missing=None):
 660        """Write out documentation about the various entry types to stdout
 661
 662        Args:
 663            modules: List of modules to include
 664            test_missing: Used for testing. This is a module to report
 665                as missing
 666        """
 667        print('''Binman Entry Documentation
 668===========================
 669
 670This file describes the entry types supported by binman. These entry types can
 671be placed in an image one by one to build up a final firmware image. It is
 672fairly easy to create new entry types. Just add a new file to the 'etype'
 673directory. You can use the existing entries as examples.
 674
 675Note that some entries are subclasses of others, using and extending their
 676features to produce new behaviours.
 677
 678
 679''')
 680        modules = sorted(modules)
 681
 682        # Don't show the test entry
 683        if '_testing' in modules:
 684            modules.remove('_testing')
 685        missing = []
 686        for name in modules:
 687            module = Entry.Lookup('WriteDocs', name, False)
 688            docs = getattr(module, '__doc__')
 689            if test_missing == name:
 690                docs = None
 691            if docs:
 692                lines = docs.splitlines()
 693                first_line = lines[0]
 694                rest = [line[4:] for line in lines[1:]]
 695                hdr = 'Entry: %s: %s' % (name.replace('_', '-'), first_line)
 696                print(hdr)
 697                print('-' * len(hdr))
 698                print('\n'.join(rest))
 699                print()
 700                print()
 701            else:
 702                missing.append(name)
 703
 704        if missing:
 705            raise ValueError('Documentation is missing for modules: %s' %
 706                             ', '.join(missing))
 707
 708    def GetUniqueName(self):
 709        """Get a unique name for a node
 710
 711        Returns:
 712            String containing a unique name for a node, consisting of the name
 713            of all ancestors (starting from within the 'binman' node) separated
 714            by a dot ('.'). This can be useful for generating unique filesnames
 715            in the output directory.
 716        """
 717        name = self.name
 718        node = self._node
 719        while node.parent:
 720            node = node.parent
 721            if node.name == 'binman':
 722                break
 723            name = '%s.%s' % (node.name, name)
 724        return name
 725
 726    def ExpandToLimit(self, limit):
 727        """Expand an entry so that it ends at the given offset limit"""
 728        if self.offset + self.size < limit:
 729            self.size = limit - self.offset
 730            # Request the contents again, since changing the size requires that
 731            # the data grows. This should not fail, but check it to be sure.
 732            if not self.ObtainContents():
 733                self.Raise('Cannot obtain contents when expanding entry')
 734
 735    def HasSibling(self, name):
 736        """Check if there is a sibling of a given name
 737
 738        Returns:
 739            True if there is an entry with this name in the the same section,
 740                else False
 741        """
 742        return name in self.section.GetEntries()
 743
 744    def GetSiblingImagePos(self, name):
 745        """Return the image position of the given sibling
 746
 747        Returns:
 748            Image position of sibling, or None if the sibling has no position,
 749                or False if there is no such sibling
 750        """
 751        if not self.HasSibling(name):
 752            return False
 753        return self.section.GetEntries()[name].image_pos
 754
 755    @staticmethod
 756    def AddEntryInfo(entries, indent, name, etype, size, image_pos,
 757                     uncomp_size, offset, entry):
 758        """Add a new entry to the entries list
 759
 760        Args:
 761            entries: List (of EntryInfo objects) to add to
 762            indent: Current indent level to add to list
 763            name: Entry name (string)
 764            etype: Entry type (string)
 765            size: Entry size in bytes (int)
 766            image_pos: Position within image in bytes (int)
 767            uncomp_size: Uncompressed size if the entry uses compression, else
 768                None
 769            offset: Entry offset within parent in bytes (int)
 770            entry: Entry object
 771        """
 772        entries.append(EntryInfo(indent, name, etype, size, image_pos,
 773                                 uncomp_size, offset, entry))
 774
 775    def ListEntries(self, entries, indent):
 776        """Add files in this entry to the list of entries
 777
 778        This can be overridden by subclasses which need different behaviour.
 779
 780        Args:
 781            entries: List (of EntryInfo objects) to add to
 782            indent: Current indent level to add to list
 783        """
 784        self.AddEntryInfo(entries, indent, self.name, self.etype, self.size,
 785                          self.image_pos, self.uncomp_size, self.offset, self)
 786
 787    def ReadData(self, decomp=True):
 788        """Read the data for an entry from the image
 789
 790        This is used when the image has been read in and we want to extract the
 791        data for a particular entry from that image.
 792
 793        Args:
 794            decomp: True to decompress any compressed data before returning it;
 795                False to return the raw, uncompressed data
 796
 797        Returns:
 798            Entry data (bytes)
 799        """
 800        # Use True here so that we get an uncompressed section to work from,
 801        # although compressed sections are currently not supported
 802        tout.Debug("ReadChildData section '%s', entry '%s'" %
 803                   (self.section.GetPath(), self.GetPath()))
 804        data = self.section.ReadChildData(self, decomp)
 805        return data
 806
 807    def ReadChildData(self, child, decomp=True):
 808        """Read the data for a particular child entry
 809
 810        This reads data from the parent and extracts the piece that relates to
 811        the given child.
 812
 813        Args:
 814            child: Child entry to read data for (must be valid)
 815            decomp: True to decompress any compressed data before returning it;
 816                False to return the raw, uncompressed data
 817
 818        Returns:
 819            Data for the child (bytes)
 820        """
 821        pass
 822
 823    def LoadData(self, decomp=True):
 824        data = self.ReadData(decomp)
 825        self.contents_size = len(data)
 826        self.ProcessContentsUpdate(data)
 827        self.Detail('Loaded data size %x' % len(data))
 828
 829    def GetImage(self):
 830        """Get the image containing this entry
 831
 832        Returns:
 833            Image object containing this entry
 834        """
 835        return self.section.GetImage()
 836
 837    def WriteData(self, data, decomp=True):
 838        """Write the data to an entry in the image
 839
 840        This is used when the image has been read in and we want to replace the
 841        data for a particular entry in that image.
 842
 843        The image must be re-packed and written out afterwards.
 844
 845        Args:
 846            data: Data to replace it with
 847            decomp: True to compress the data if needed, False if data is
 848                already compressed so should be used as is
 849
 850        Returns:
 851            True if the data did not result in a resize of this entry, False if
 852                 the entry must be resized
 853        """
 854        if self.size is not None:
 855            self.contents_size = self.size
 856        else:
 857            self.contents_size = self.pre_reset_size
 858        ok = self.ProcessContentsUpdate(data)
 859        self.Detail('WriteData: size=%x, ok=%s' % (len(data), ok))
 860        section_ok = self.section.WriteChildData(self)
 861        return ok and section_ok
 862
 863    def WriteChildData(self, child):
 864        """Handle writing the data in a child entry
 865
 866        This should be called on the child's parent section after the child's
 867        data has been updated. It
 868
 869        This base-class implementation does nothing, since the base Entry object
 870        does not have any children.
 871
 872        Args:
 873            child: Child Entry that was written
 874
 875        Returns:
 876            True if the section could be updated successfully, False if the
 877                data is such that the section could not updat
 878        """
 879        return True
 880
 881    def GetSiblingOrder(self):
 882        """Get the relative order of an entry amoung its siblings
 883
 884        Returns:
 885            'start' if this entry is first among siblings, 'end' if last,
 886                otherwise None
 887        """
 888        entries = list(self.section.GetEntries().values())
 889        if entries:
 890            if self == entries[0]:
 891                return 'start'
 892            elif self == entries[-1]:
 893                return 'end'
 894        return 'middle'
 895
 896    def SetAllowMissing(self, allow_missing):
 897        """Set whether a section allows missing external blobs
 898
 899        Args:
 900            allow_missing: True if allowed, False if not allowed
 901        """
 902        # This is meaningless for anything other than sections
 903        pass
 904
 905    def SetAllowFakeBlob(self, allow_fake):
 906        """Set whether a section allows to create a fake blob
 907
 908        Args:
 909            allow_fake: True if allowed, False if not allowed
 910        """
 911        pass
 912
 913    def CheckMissing(self, missing_list):
 914        """Check if any entries in this section have missing external blobs
 915
 916        If there are missing blobs, the entries are added to the list
 917
 918        Args:
 919            missing_list: List of Entry objects to be added to
 920        """
 921        if self.missing:
 922            missing_list.append(self)
 923
 924    def CheckFakedBlobs(self, faked_blobs_list):
 925        """Check if any entries in this section have faked external blobs
 926
 927        If there are faked blobs, the entries are added to the list
 928
 929        Args:
 930            fake_blobs_list: List of Entry objects to be added to
 931        """
 932        # This is meaningless for anything other than blobs
 933        pass
 934
 935    def GetAllowMissing(self):
 936        """Get whether a section allows missing external blobs
 937
 938        Returns:
 939            True if allowed, False if not allowed
 940        """
 941        return self.allow_missing
 942
 943    def GetHelpTags(self):
 944        """Get the tags use for missing-blob help
 945
 946        Returns:
 947            list of possible tags, most desirable first
 948        """
 949        return list(filter(None, [self.missing_msg, self.name, self.etype]))
 950
 951    def CompressData(self, indata):
 952        """Compress data according to the entry's compression method
 953
 954        Args:
 955            indata: Data to compress
 956
 957        Returns:
 958            Compressed data (first word is the compressed size)
 959        """
 960        self.uncomp_data = indata
 961        if self.compress != 'none':
 962            self.uncomp_size = len(indata)
 963        data = tools.Compress(indata, self.compress)
 964        return data
 965
 966    @classmethod
 967    def UseExpanded(cls, node, etype, new_etype):
 968        """Check whether to use an expanded entry type
 969
 970        This is called by Entry.Create() when it finds an expanded version of
 971        an entry type (e.g. 'u-boot-expanded'). If this method returns True then
 972        it will be used (e.g. in place of 'u-boot'). If it returns False, it is
 973        ignored.
 974
 975        Args:
 976            node:     Node object containing information about the entry to
 977                      create
 978            etype:    Original entry type being used
 979            new_etype: New entry type proposed
 980
 981        Returns:
 982            True to use this entry type, False to use the original one
 983        """
 984        tout.Info("Node '%s': etype '%s': %s selected" %
 985                  (node.path, etype, new_etype))
 986        return True
 987