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