entry.py 28.5 KB
Newer Older
1
# SPDX-License-Identifier: GPL-2.0+
2 3 4 5 6
# Copyright (c) 2016 Google, Inc
#
# Base class for all entries
#

7
from collections import namedtuple
S
Simon Glass 已提交
8
import importlib
9 10
import os
import sys
11

S
Simon Glass 已提交
12
from dtoc import fdt_util
S
Simon Glass 已提交
13
from patman import tools
S
Simon Glass 已提交
14
from patman.tools import ToHex, ToHexSize
S
Simon Glass 已提交
15
from patman import tout
16 17 18

modules = {}

19 20
our_path = os.path.dirname(os.path.realpath(__file__))

21 22 23 24 25

# An argument which can be passed to entries on the command line, in lieu of
# device-tree properties.
EntryArg = namedtuple('EntryArg', ['name', 'datatype'])

26 27 28 29
# Information about an entry for use when displaying summaries
EntryInfo = namedtuple('EntryInfo', ['indent', 'name', 'etype', 'size',
                                     'image_pos', 'uncomp_size', 'offset',
                                     'entry'])
30

31
class Entry(object):
32
    """An Entry in the section
33 34

    An entry corresponds to a single node in the device-tree description
35
    of the section. Each entry ends up being a part of the final section.
36 37 38 39 40 41 42
    Entries can be placed either right next to each other, or with padding
    between them. The type of the entry determines the data that is in it.

    This class is not used by itself. All entry objects are subclasses of
    Entry.

    Attributes:
43
        section: Section object containing this entry
44
        node: The node that created this entry
45 46
        offset: Offset of entry within the section, None if not known yet (in
            which case it will be calculated by Pack())
47
        size: Entry size in bytes, None if not known
48 49
        pre_reset_size: size as it was before ResetForPack(). This allows us to
            keep track of the size we started with and detect size changes
50 51
        uncomp_size: Size of uncompressed data in bytes, if the entry is
            compressed, else None
52
        contents_size: Size of contents in bytes, 0 by default
53
        align: Entry start offset alignment, or None
54
        align_size: Entry size alignment, or None
55
        align_end: Entry end offset alignment, or None
56 57 58
        pad_before: Number of pad bytes before the contents, 0 if none
        pad_after: Number of pad bytes after the contents, 0 if none
        data: Contents of entry (string of bytes)
59
        compress: Compression algoithm used (e.g. 'lz4'), 'none' if none
60 61
        orig_offset: Original offset value read from node
        orig_size: Original size value read from node
62
    """
63
    def __init__(self, section, etype, node, name_prefix=''):
64 65
        # Put this here to allow entry-docs and help to work without libfdt
        global state
S
Simon Glass 已提交
66
        from binman import state
67

68
        self.section = section
69 70
        self.etype = etype
        self._node = node
71
        self.name = node and (name_prefix + node.name) or 'none'
72
        self.offset = None
73
        self.size = None
74
        self.pre_reset_size = None
75
        self.uncomp_size = None
76
        self.data = None
77 78 79 80 81 82
        self.contents_size = 0
        self.align = None
        self.align_size = None
        self.align_end = None
        self.pad_before = 0
        self.pad_after = 0
83
        self.offset_unset = False
84
        self.image_pos = None
S
Simon Glass 已提交
85
        self._expand_size = False
86
        self.compress = 'none'
87 88

    @staticmethod
89
    def Lookup(node_path, etype):
90
        """Look up the entry class for a node.
91 92

        Args:
93 94 95
            node_node: Path name of Node object containing information about
                       the entry to create (used for errors)
            etype:   Entry type to use
96 97

        Returns:
98
            The entry class object if found, else None
99
        """
100 101
        # Convert something like 'u-boot@0' to 'u_boot' since we are only
        # interested in the type.
102
        module_name = etype.replace('-', '_')
103 104
        if '@' in module_name:
            module_name = module_name.split('@')[0]
105 106
        module = modules.get(module_name)

107 108
        # Also allow entry-type modules to be brought in from the etype directory.

109 110 111
        # Import the module if we have not already done so.
        if not module:
            try:
S
Simon Glass 已提交
112
                module = importlib.import_module('binman.etype.' + module_name)
113 114 115
            except ImportError as e:
                raise ValueError("Unknown entry type '%s' in node '%s' (expected etype/%s.py, error '%s'" %
                                 (etype, node_path, module_name, e))
116 117
            modules[module_name] = module

118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
        # Look up the expected class name
        return getattr(module, 'Entry_%s' % module_name)

    @staticmethod
    def Create(section, node, etype=None):
        """Create a new entry for a node.

        Args:
            section: Section object containing this node
            node:    Node object containing information about the entry to
                     create
            etype:   Entry type to use, or None to work it out (used for tests)

        Returns:
            A new Entry object of the correct type (a subclass of Entry)
        """
        if not etype:
            etype = fdt_util.GetString(node, 'type', node.name)
136
        obj = Entry.Lookup(node.path, etype)
137

138
        # Call its constructor to get the object we want.
139
        return obj(section, etype, node)
140 141 142 143

    def ReadNode(self):
        """Read entry information from the node

144 145
        This must be called as the first thing after the Entry is created.

146 147
        This reads all the fields we recognise from the node, ready for use.
        """
148 149
        if 'pos' in self._node.props:
            self.Raise("Please use 'offset' instead of 'pos'")
150
        self.offset = fdt_util.GetInt(self._node, 'offset')
151
        self.size = fdt_util.GetInt(self._node, 'size')
152 153 154 155 156
        self.orig_offset = fdt_util.GetInt(self._node, 'orig-offset')
        self.orig_size = fdt_util.GetInt(self._node, 'orig-size')
        if self.GetImage().copy_to_orig:
            self.orig_offset = self.offset
            self.orig_size = self.size
157

158 159 160 161 162
        # These should not be set in input files, but are set in an FDT map,
        # which is also read by this code.
        self.image_pos = fdt_util.GetInt(self._node, 'image-pos')
        self.uncomp_size = fdt_util.GetInt(self._node, 'uncomp-size')

163 164 165 166 167 168 169 170
        self.align = fdt_util.GetInt(self._node, 'align')
        if tools.NotPowerOfTwo(self.align):
            raise ValueError("Node '%s': Alignment %s must be a power of two" %
                             (self._node.path, self.align))
        self.pad_before = fdt_util.GetInt(self._node, 'pad-before', 0)
        self.pad_after = fdt_util.GetInt(self._node, 'pad-after', 0)
        self.align_size = fdt_util.GetInt(self._node, 'align-size')
        if tools.NotPowerOfTwo(self.align_size):
171 172
            self.Raise("Alignment size %s must be a power of two" %
                       self.align_size)
173
        self.align_end = fdt_util.GetInt(self._node, 'align-end')
174
        self.offset_unset = fdt_util.GetBool(self._node, 'offset-unset')
S
Simon Glass 已提交
175
        self.expand_size = fdt_util.GetBool(self._node, 'expand-size')
176

177 178 179
    def GetDefaultFilename(self):
        return None

180 181
    def GetFdts(self):
        """Get the device trees used by this entry
182 183

        Returns:
184 185 186
            Empty dict, if this entry is not a .dtb, otherwise:
            Dict:
                key: Filename from this entry (without the path)
187 188 189
                value: Tuple:
                    Fdt object for this dtb, or None if not available
                    Filename of file containing this dtb
190
        """
191
        return {}
192

S
Simon Glass 已提交
193 194 195
    def ExpandEntries(self):
        pass

196 197
    def AddMissingProperties(self):
        """Add new properties to the device tree as needed for this entry"""
198
        for prop in ['offset', 'size', 'image-pos']:
199
            if not prop in self._node.props:
200
                state.AddZeroProp(self._node, prop)
201 202 203 204 205 206
        if self.GetImage().allow_repack:
            if self.orig_offset is not None:
                state.AddZeroProp(self._node, 'orig-offset', True)
            if self.orig_size is not None:
                state.AddZeroProp(self._node, 'orig-size', True)

207 208
        if self.compress != 'none':
            state.AddZeroProp(self._node, 'uncomp-size')
S
Simon Glass 已提交
209 210 211
        err = state.CheckAddHashProp(self._node)
        if err:
            self.Raise(err)
212 213 214

    def SetCalculatedProperties(self):
        """Set the value of device-tree properties calculated by binman"""
215 216
        state.SetInt(self._node, 'offset', self.offset)
        state.SetInt(self._node, 'size', self.size)
217 218
        base = self.section.GetRootSkipAtStart() if self.section else 0
        state.SetInt(self._node, 'image-pos', self.image_pos - base)
219 220 221 222 223
        if self.GetImage().allow_repack:
            if self.orig_offset is not None:
                state.SetInt(self._node, 'orig-offset', self.orig_offset, True)
            if self.orig_size is not None:
                state.SetInt(self._node, 'orig-size', self.orig_size, True)
224 225
        if self.uncomp_size is not None:
            state.SetInt(self._node, 'uncomp-size', self.uncomp_size)
S
Simon Glass 已提交
226
        state.CheckSetHashValue(self._node, self.GetData)
227

S
Simon Glass 已提交
228
    def ProcessFdt(self, fdt):
229 230 231 232 233 234 235 236 237 238 239
        """Allow entries to adjust the device tree

        Some entries need to adjust the device tree for their purposes. This
        may involve adding or deleting properties.

        Returns:
            True if processing is complete
            False if processing could not be completed due to a dependency.
                This will cause the entry to be retried after others have been
                called
        """
S
Simon Glass 已提交
240 241
        return True

242 243 244 245 246 247 248 249 250
    def SetPrefix(self, prefix):
        """Set the name prefix for a node

        Args:
            prefix: Prefix to set, or '' to not use a prefix
        """
        if prefix:
            self.name = prefix + self.name

251 252 253 254 255 256
    def SetContents(self, data):
        """Set the contents of an entry

        This sets both the data and content_size properties

        Args:
257
            data: Data to set to the contents (bytes)
258 259 260 261 262
        """
        self.data = data
        self.contents_size = len(self.data)

    def ProcessContentsUpdate(self, data):
263
        """Update the contents of an entry, after the size is fixed
264

265 266
        This checks that the new data is the same size as the old. If the size
        has changed, this triggers a re-run of the packing algorithm.
267 268

        Args:
269
            data: Data to set to the contents (bytes)
270 271 272 273

        Raises:
            ValueError if the new data size is not the same as the old
        """
274
        size_ok = True
275
        new_size = len(data)
276 277 278 279 280 281 282 283
        if state.AllowEntryExpansion() and new_size > self.contents_size:
            # self.data will indicate the new size needed
            size_ok = False
        elif state.AllowEntryContraction() and new_size < self.contents_size:
            size_ok = False

        # If not allowed to change, try to deal with it or give up
        if size_ok:
284
            if new_size > self.contents_size:
285 286 287 288 289 290 291 292 293 294 295
                self.Raise('Cannot update entry size from %d to %d' %
                        (self.contents_size, new_size))

            # Don't let the data shrink. Pad it if necessary
            if size_ok and new_size < self.contents_size:
                data += tools.GetBytes(0, self.contents_size - new_size)

        if not size_ok:
            tout.Debug("Entry '%s' size change from %s to %s" % (
                self._node.path, ToHex(self.contents_size),
                ToHex(new_size)))
296
        self.SetContents(data)
297
        return size_ok
298

299 300 301 302 303 304 305 306 307 308
    def ObtainContents(self):
        """Figure out the contents of an entry.

        Returns:
            True if the contents were found, False if another call is needed
            after the other entries are processed.
        """
        # No contents by default: subclasses can implement this
        return True

309 310
    def ResetForPack(self):
        """Reset offset/size fields so that packing can be done again"""
311 312 313
        self.Detail('ResetForPack: offset %s->%s, size %s->%s' %
                    (ToHex(self.offset), ToHex(self.orig_offset),
                     ToHex(self.size), ToHex(self.orig_size)))
314
        self.pre_reset_size = self.size
315 316 317
        self.offset = self.orig_offset
        self.size = self.orig_size

318
    def Pack(self, offset):
319
        """Figure out how to pack the entry into the section
320 321 322 323 324

        Most of the time the entries are not fully specified. There may be
        an alignment but no size. In that case we take the size from the
        contents of the entry.

325
        If an entry has no hard-coded offset, it will be placed at @offset.
326

327
        Once this function is complete, both the offset and size of the
328 329 330
        entry will be know.

        Args:
331
            Current section offset pointer
332 333

        Returns:
334
            New section offset pointer (after this entry)
335
        """
336 337 338
        self.Detail('Packing: offset=%s, size=%s, content_size=%x' %
                    (ToHex(self.offset), ToHex(self.size),
                     self.contents_size))
339 340 341 342 343
        if self.offset is None:
            if self.offset_unset:
                self.Raise('No offset set with offset-unset: should another '
                           'entry provide this correct offset?')
            self.offset = tools.Align(offset, self.align)
344 345 346 347 348
        needed = self.pad_before + self.contents_size + self.pad_after
        needed = tools.Align(needed, self.align_size)
        size = self.size
        if not size:
            size = needed
349 350 351 352 353
        new_offset = self.offset + size
        aligned_offset = tools.Align(new_offset, self.align_end)
        if aligned_offset != new_offset:
            size = aligned_offset - self.offset
            new_offset = aligned_offset
354 355 356 357 358 359 360 361

        if not self.size:
            self.size = size

        if self.size < needed:
            self.Raise("Entry contents size is %#x (%d) but entry size is "
                       "%#x (%d)" % (needed, needed, self.size, self.size))
        # Check that the alignment is correct. It could be wrong if the
362
        # and offset or size values were provided (i.e. not calculated), but
363 364 365 366
        # conflict with the provided alignment values
        if self.size != tools.Align(self.size, self.align_size):
            self.Raise("Size %#x (%d) does not match align-size %#x (%d)" %
                  (self.size, self.size, self.align_size, self.align_size))
367 368 369
        if self.offset != tools.Align(self.offset, self.align):
            self.Raise("Offset %#x (%d) does not match align %#x (%d)" %
                  (self.offset, self.offset, self.align, self.align))
370 371
        self.Detail('   - packed: offset=%#x, size=%#x, content_size=%#x, next_offset=%x' %
                    (self.offset, self.size, self.contents_size, new_offset))
372

373
        return new_offset
374 375 376 377 378

    def Raise(self, msg):
        """Convenience function to raise an error referencing a node"""
        raise ValueError("Node '%s': %s" % (self._node.path, msg))

379 380 381 382 383
    def Detail(self, msg):
        """Convenience function to log detail referencing a node"""
        tag = "Node '%s'" % self._node.path
        tout.Detail('%30s: %s' % (tag, msg))

384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410
    def GetEntryArgsOrProps(self, props, required=False):
        """Return the values of a set of properties

        Args:
            props: List of EntryArg objects

        Raises:
            ValueError if a property is not found
        """
        values = []
        missing = []
        for prop in props:
            python_prop = prop.name.replace('-', '_')
            if hasattr(self, python_prop):
                value = getattr(self, python_prop)
            else:
                value = None
            if value is None:
                value = self.GetArg(prop.name, prop.datatype)
            if value is None and required:
                missing.append(prop.name)
            values.append(value)
        if missing:
            self.Raise('Missing required properties/entry args: %s' %
                       (', '.join(missing)))
        return values

411 412 413 414 415 416 417 418 419
    def GetPath(self):
        """Get the path of a node

        Returns:
            Full path of the node for this entry
        """
        return self._node.path

    def GetData(self):
420
        self.Detail('GetData: size %s' % ToHexSize(self.data))
421 422
        return self.data

423
    def GetOffsets(self):
424 425 426 427 428 429 430 431 432 433 434 435 436
        """Get the offsets for siblings

        Some entry types can contain information about the position or size of
        other entries. An example of this is the Intel Flash Descriptor, which
        knows where the Intel Management Engine section should go.

        If this entry knows about the position of other entries, it can specify
        this by returning values here

        Returns:
            Dict:
                key: Entry type
                value: List containing position and size of the given entry
437
                    type. Either can be None if not known
438
        """
439 440
        return {}

441 442 443 444 445 446 447 448 449 450 451
    def SetOffsetSize(self, offset, size):
        """Set the offset and/or size of an entry

        Args:
            offset: New offset, or None to leave alone
            size: New size, or None to leave alone
        """
        if offset is not None:
            self.offset = offset
        if size is not None:
            self.size = size
452

453 454 455 456 457 458 459 460
    def SetImagePos(self, image_pos):
        """Set the position in the image

        Args:
            image_pos: Position of this entry in the image
        """
        self.image_pos = image_pos + self.offset

461
    def ProcessContents(self):
462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477
        """Do any post-packing updates of entry contents

        This function should call ProcessContentsUpdate() to update the entry
        contents, if necessary, returning its return value here.

        Args:
            data: Data to set to the contents (bytes)

        Returns:
            True if the new data size is OK, False if expansion is needed

        Raises:
            ValueError if the new data size is not the same as the old and
                state.AllowEntryExpansion() is False
        """
        return True
478

479
    def WriteSymbols(self, section):
480 481 482
        """Write symbol values into binary files for access at run time

        Args:
483
          section: Section containing the entry
484 485
        """
        pass
S
Simon Glass 已提交
486

487 488
    def CheckOffset(self):
        """Check that the entry offsets are correct
S
Simon Glass 已提交
489

490
        This is used for entries which have extra offset requirements (other
S
Simon Glass 已提交
491 492 493 494
        than having to be fully inside their section). Sub-classes can implement
        this function and raise if there is a problem.
        """
        pass
495

496 497 498 499 500 501
    @staticmethod
    def GetStr(value):
        if value is None:
            return '<none>  '
        return '%08x' % value

502
    @staticmethod
503
    def WriteMapLine(fd, indent, name, offset, size, image_pos):
504 505 506
        print('%s  %s%s  %s  %s' % (Entry.GetStr(image_pos), ' ' * indent,
                                    Entry.GetStr(offset), Entry.GetStr(size),
                                    name), file=fd)
507

508 509 510 511 512 513 514
    def WriteMap(self, fd, indent):
        """Write a map of the entry to a .map file

        Args:
            fd: File to write the map to
            indent: Curent indent level of map (0=none, 1=one level, etc.)
        """
515 516
        self.WriteMapLine(fd, indent, self.name, self.offset, self.size,
                          self.image_pos)
517

518 519 520 521 522 523 524 525 526
    def GetEntries(self):
        """Return a list of entries contained by this entry

        Returns:
            List of entries, or None if none. A normal entry has no entries
                within it so will return None
        """
        return None

527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542
    def GetArg(self, name, datatype=str):
        """Get the value of an entry argument or device-tree-node property

        Some node properties can be provided as arguments to binman. First check
        the entry arguments, and fall back to the device tree if not found

        Args:
            name: Argument name
            datatype: Data type (str or int)

        Returns:
            Value of argument as a string or int, or None if no value

        Raises:
            ValueError if the argument cannot be converted to in
        """
543
        value = state.GetEntryArg(name)
544 545 546 547 548 549 550 551 552 553 554 555 556 557 558
        if value is not None:
            if datatype == int:
                try:
                    value = int(value)
                except ValueError:
                    self.Raise("Cannot convert entry arg '%s' (value '%s') to integer" %
                               (name, value))
            elif datatype == str:
                pass
            else:
                raise ValueError("GetArg() internal error: Unknown data type '%s'" %
                                 datatype)
        else:
            value = fdt_util.GetDatatype(self._node, name, datatype)
        return value
559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588

    @staticmethod
    def WriteDocs(modules, test_missing=None):
        """Write out documentation about the various entry types to stdout

        Args:
            modules: List of modules to include
            test_missing: Used for testing. This is a module to report
                as missing
        """
        print('''Binman Entry Documentation
===========================

This file describes the entry types supported by binman. These entry types can
be placed in an image one by one to build up a final firmware image. It is
fairly easy to create new entry types. Just add a new file to the 'etype'
directory. You can use the existing entries as examples.

Note that some entries are subclasses of others, using and extending their
features to produce new behaviours.


''')
        modules = sorted(modules)

        # Don't show the test entry
        if '_testing' in modules:
            modules.remove('_testing')
        missing = []
        for name in modules:
S
Simon Glass 已提交
589
            module = Entry.Lookup('WriteDocs', name)
590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608
            docs = getattr(module, '__doc__')
            if test_missing == name:
                docs = None
            if docs:
                lines = docs.splitlines()
                first_line = lines[0]
                rest = [line[4:] for line in lines[1:]]
                hdr = 'Entry: %s: %s' % (name.replace('_', '-'), first_line)
                print(hdr)
                print('-' * len(hdr))
                print('\n'.join(rest))
                print()
                print()
            else:
                missing.append(name)

        if missing:
            raise ValueError('Documentation is missing for modules: %s' %
                             ', '.join(missing))
S
Simon Glass 已提交
609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626

    def GetUniqueName(self):
        """Get a unique name for a node

        Returns:
            String containing a unique name for a node, consisting of the name
            of all ancestors (starting from within the 'binman' node) separated
            by a dot ('.'). This can be useful for generating unique filesnames
            in the output directory.
        """
        name = self.name
        node = self._node
        while node.parent:
            node = node.parent
            if node.name == 'binman':
                break
            name = '%s.%s' % (node.name, name)
        return name
S
Simon Glass 已提交
627 628 629 630 631 632 633 634 635

    def ExpandToLimit(self, limit):
        """Expand an entry so that it ends at the given offset limit"""
        if self.offset + self.size < limit:
            self.size = limit - self.offset
            # Request the contents again, since changing the size requires that
            # the data grows. This should not fail, but check it to be sure.
            if not self.ObtainContents():
                self.Raise('Cannot obtain contents when expanding entry')
636 637 638 639 640 641 642 643 644

    def HasSibling(self, name):
        """Check if there is a sibling of a given name

        Returns:
            True if there is an entry with this name in the the same section,
                else False
        """
        return name in self.section.GetEntries()
S
Simon Glass 已提交
645 646 647 648 649 650 651 652 653 654 655

    def GetSiblingImagePos(self, name):
        """Return the image position of the given sibling

        Returns:
            Image position of sibling, or None if the sibling has no position,
                or False if there is no such sibling
        """
        if not self.HasSibling(name):
            return False
        return self.section.GetEntries()[name].image_pos
656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687

    @staticmethod
    def AddEntryInfo(entries, indent, name, etype, size, image_pos,
                     uncomp_size, offset, entry):
        """Add a new entry to the entries list

        Args:
            entries: List (of EntryInfo objects) to add to
            indent: Current indent level to add to list
            name: Entry name (string)
            etype: Entry type (string)
            size: Entry size in bytes (int)
            image_pos: Position within image in bytes (int)
            uncomp_size: Uncompressed size if the entry uses compression, else
                None
            offset: Entry offset within parent in bytes (int)
            entry: Entry object
        """
        entries.append(EntryInfo(indent, name, etype, size, image_pos,
                                 uncomp_size, offset, entry))

    def ListEntries(self, entries, indent):
        """Add files in this entry to the list of entries

        This can be overridden by subclasses which need different behaviour.

        Args:
            entries: List (of EntryInfo objects) to add to
            indent: Current indent level to add to list
        """
        self.AddEntryInfo(entries, indent, self.name, self.etype, self.size,
                          self.image_pos, self.uncomp_size, self.offset, self)
688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703

    def ReadData(self, decomp=True):
        """Read the data for an entry from the image

        This is used when the image has been read in and we want to extract the
        data for a particular entry from that image.

        Args:
            decomp: True to decompress any compressed data before returning it;
                False to return the raw, uncompressed data

        Returns:
            Entry data (bytes)
        """
        # Use True here so that we get an uncompressed section to work from,
        # although compressed sections are currently not supported
704 705
        tout.Debug("ReadChildData section '%s', entry '%s'" %
                   (self.section.GetPath(), self.GetPath()))
706 707
        data = self.section.ReadChildData(self, decomp)
        return data
708

709
    def ReadChildData(self, child, decomp=True):
710
        """Read the data for a particular child entry
711 712 713 714 715

        This reads data from the parent and extracts the piece that relates to
        the given child.

        Args:
716
            child: Child entry to read data for (must be valid)
717 718 719 720 721 722 723 724
            decomp: True to decompress any compressed data before returning it;
                False to return the raw, uncompressed data

        Returns:
            Data for the child (bytes)
        """
        pass

725 726
    def LoadData(self, decomp=True):
        data = self.ReadData(decomp)
727
        self.contents_size = len(data)
728 729
        self.ProcessContentsUpdate(data)
        self.Detail('Loaded data size %x' % len(data))
730 731 732 733 734 735 736 737

    def GetImage(self):
        """Get the image containing this entry

        Returns:
            Image object containing this entry
        """
        return self.section.GetImage()
738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755

    def WriteData(self, data, decomp=True):
        """Write the data to an entry in the image

        This is used when the image has been read in and we want to replace the
        data for a particular entry in that image.

        The image must be re-packed and written out afterwards.

        Args:
            data: Data to replace it with
            decomp: True to compress the data if needed, False if data is
                already compressed so should be used as is

        Returns:
            True if the data did not result in a resize of this entry, False if
                 the entry must be resized
        """
756 757 758 759
        if self.size is not None:
            self.contents_size = self.size
        else:
            self.contents_size = self.pre_reset_size
760 761
        ok = self.ProcessContentsUpdate(data)
        self.Detail('WriteData: size=%x, ok=%s' % (len(data), ok))
762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781
        section_ok = self.section.WriteChildData(self)
        return ok and section_ok

    def WriteChildData(self, child):
        """Handle writing the data in a child entry

        This should be called on the child's parent section after the child's
        data has been updated. It

        This base-class implementation does nothing, since the base Entry object
        does not have any children.

        Args:
            child: Child Entry that was written

        Returns:
            True if the section could be updated successfully, False if the
                data is such that the section could not updat
        """
        return True
782 783 784 785 786 787 788 789 790 791 792 793 794 795 796

    def GetSiblingOrder(self):
        """Get the relative order of an entry amoung its siblings

        Returns:
            'start' if this entry is first among siblings, 'end' if last,
                otherwise None
        """
        entries = list(self.section.GetEntries().values())
        if entries:
            if self == entries[0]:
                return 'start'
            elif self == entries[-1]:
                return 'end'
        return 'middle'