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