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