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