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