Merge tag 'dm-pull-15oct19' of https://gitlab.denx.de/u-boot/custodians/u-boot-dm
[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 __future__ import print_function
8
9 from collections import namedtuple
10
11 # importlib was introduced in Python 2.7 but there was a report of it not
12 # working in 2.7.12, so we work around this:
13 # http://lists.denx.de/pipermail/u-boot/2016-October/269729.html
14 try:
15     import importlib
16     have_importlib = True
17 except:
18     have_importlib = False
19
20 import os
21 import sys
22
23 import fdt_util
24 import tools
25 from tools import ToHex, ToHexSize
26 import tout
27
28 modules = {}
29
30 our_path = os.path.dirname(os.path.realpath(__file__))
31
32
33 # An argument which can be passed to entries on the command line, in lieu of
34 # device-tree properties.
35 EntryArg = namedtuple('EntryArg', ['name', 'datatype'])
36
37 # Information about an entry for use when displaying summaries
38 EntryInfo = namedtuple('EntryInfo', ['indent', 'name', 'etype', 'size',
39                                      'image_pos', 'uncomp_size', 'offset',
40                                      'entry'])
41
42 class Entry(object):
43     """An Entry in the section
44
45     An entry corresponds to a single node in the device-tree description
46     of the section. Each entry ends up being a part of the final section.
47     Entries can be placed either right next to each other, or with padding
48     between them. The type of the entry determines the data that is in it.
49
50     This class is not used by itself. All entry objects are subclasses of
51     Entry.
52
53     Attributes:
54         section: Section object containing this entry
55         node: The node that created this entry
56         offset: Offset of entry within the section, None if not known yet (in
57             which case it will be calculated by Pack())
58         size: Entry size in bytes, None if not known
59         uncomp_size: Size of uncompressed data in bytes, if the entry is
60             compressed, else None
61         contents_size: Size of contents in bytes, 0 by default
62         align: Entry start offset alignment, or None
63         align_size: Entry size alignment, or None
64         align_end: Entry end offset alignment, or None
65         pad_before: Number of pad bytes before the contents, 0 if none
66         pad_after: Number of pad bytes after the contents, 0 if none
67         data: Contents of entry (string of bytes)
68         compress: Compression algoithm used (e.g. 'lz4'), 'none' if none
69         orig_offset: Original offset value read from node
70         orig_size: Original size value read from node
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         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.uncomp_size = None
84         self.data = None
85         self.contents_size = 0
86         self.align = None
87         self.align_size = None
88         self.align_end = None
89         self.pad_before = 0
90         self.pad_after = 0
91         self.offset_unset = False
92         self.image_pos = None
93         self._expand_size = False
94         self.compress = 'none'
95
96     @staticmethod
97     def Lookup(node_path, etype):
98         """Look up the entry class for a node.
99
100         Args:
101             node_node: Path name of Node object containing information about
102                        the entry to create (used for errors)
103             etype:   Entry type to use
104
105         Returns:
106             The entry class object if found, else None
107         """
108         # Convert something like 'u-boot@0' to 'u_boot' since we are only
109         # interested in the type.
110         module_name = etype.replace('-', '_')
111         if '@' in module_name:
112             module_name = module_name.split('@')[0]
113         module = modules.get(module_name)
114
115         # Also allow entry-type modules to be brought in from the etype directory.
116
117         # Import the module if we have not already done so.
118         if not module:
119             old_path = sys.path
120             sys.path.insert(0, os.path.join(our_path, 'etype'))
121             try:
122                 if have_importlib:
123                     module = importlib.import_module(module_name)
124                 else:
125                     module = __import__(module_name)
126             except ImportError as e:
127                 raise ValueError("Unknown entry type '%s' in node '%s' (expected etype/%s.py, error '%s'" %
128                                  (etype, node_path, module_name, e))
129             finally:
130                 sys.path = old_path
131             modules[module_name] = module
132
133         # Look up the expected class name
134         return getattr(module, 'Entry_%s' % module_name)
135
136     @staticmethod
137     def Create(section, node, etype=None):
138         """Create a new entry for a node.
139
140         Args:
141             section: Section object containing this node
142             node:    Node object containing information about the entry to
143                      create
144             etype:   Entry type to use, or None to work it out (used for tests)
145
146         Returns:
147             A new Entry object of the correct type (a subclass of Entry)
148         """
149         if not etype:
150             etype = fdt_util.GetString(node, 'type', node.name)
151         obj = Entry.Lookup(node.path, etype)
152
153         # Call its constructor to get the object we want.
154         return obj(section, etype, node)
155
156     def ReadNode(self):
157         """Read entry information from the node
158
159         This must be called as the first thing after the Entry is created.
160
161         This reads all the fields we recognise from the node, ready for use.
162         """
163         if 'pos' in self._node.props:
164             self.Raise("Please use 'offset' instead of 'pos'")
165         self.offset = fdt_util.GetInt(self._node, 'offset')
166         self.size = fdt_util.GetInt(self._node, 'size')
167         self.orig_offset = fdt_util.GetInt(self._node, 'orig-offset')
168         self.orig_size = fdt_util.GetInt(self._node, 'orig-size')
169         if self.GetImage().copy_to_orig:
170             self.orig_offset = self.offset
171             self.orig_size = self.size
172
173         # These should not be set in input files, but are set in an FDT map,
174         # which is also read by this code.
175         self.image_pos = fdt_util.GetInt(self._node, 'image-pos')
176         self.uncomp_size = fdt_util.GetInt(self._node, 'uncomp-size')
177
178         self.align = fdt_util.GetInt(self._node, 'align')
179         if tools.NotPowerOfTwo(self.align):
180             raise ValueError("Node '%s': Alignment %s must be a power of two" %
181                              (self._node.path, self.align))
182         self.pad_before = fdt_util.GetInt(self._node, 'pad-before', 0)
183         self.pad_after = fdt_util.GetInt(self._node, 'pad-after', 0)
184         self.align_size = fdt_util.GetInt(self._node, 'align-size')
185         if tools.NotPowerOfTwo(self.align_size):
186             self.Raise("Alignment size %s must be a power of two" %
187                        self.align_size)
188         self.align_end = fdt_util.GetInt(self._node, 'align-end')
189         self.offset_unset = fdt_util.GetBool(self._node, 'offset-unset')
190         self.expand_size = fdt_util.GetBool(self._node, 'expand-size')
191
192     def GetDefaultFilename(self):
193         return None
194
195     def GetFdts(self):
196         """Get the device trees used by this entry
197
198         Returns:
199             Empty dict, if this entry is not a .dtb, otherwise:
200             Dict:
201                 key: Filename from this entry (without the path)
202                 value: Tuple:
203                     Fdt object for this dtb, or None if not available
204                     Filename of file containing this dtb
205         """
206         return {}
207
208     def ExpandEntries(self):
209         pass
210
211     def AddMissingProperties(self):
212         """Add new properties to the device tree as needed for this entry"""
213         for prop in ['offset', 'size', 'image-pos']:
214             if not prop in self._node.props:
215                 state.AddZeroProp(self._node, prop)
216         if self.GetImage().allow_repack:
217             if self.orig_offset is not None:
218                 state.AddZeroProp(self._node, 'orig-offset', True)
219             if self.orig_size is not None:
220                 state.AddZeroProp(self._node, 'orig-size', True)
221
222         if self.compress != 'none':
223             state.AddZeroProp(self._node, 'uncomp-size')
224         err = state.CheckAddHashProp(self._node)
225         if err:
226             self.Raise(err)
227
228     def SetCalculatedProperties(self):
229         """Set the value of device-tree properties calculated by binman"""
230         state.SetInt(self._node, 'offset', self.offset)
231         state.SetInt(self._node, 'size', self.size)
232         base = self.section.GetRootSkipAtStart() if self.section else 0
233         state.SetInt(self._node, 'image-pos', self.image_pos - base)
234         if self.GetImage().allow_repack:
235             if self.orig_offset is not None:
236                 state.SetInt(self._node, 'orig-offset', self.orig_offset, True)
237             if self.orig_size is not None:
238                 state.SetInt(self._node, 'orig-size', self.orig_size, True)
239         if self.uncomp_size is not None:
240             state.SetInt(self._node, 'uncomp-size', self.uncomp_size)
241         state.CheckSetHashValue(self._node, self.GetData)
242
243     def ProcessFdt(self, fdt):
244         """Allow entries to adjust the device tree
245
246         Some entries need to adjust the device tree for their purposes. This
247         may involve adding or deleting properties.
248
249         Returns:
250             True if processing is complete
251             False if processing could not be completed due to a dependency.
252                 This will cause the entry to be retried after others have been
253                 called
254         """
255         return True
256
257     def SetPrefix(self, prefix):
258         """Set the name prefix for a node
259
260         Args:
261             prefix: Prefix to set, or '' to not use a prefix
262         """
263         if prefix:
264             self.name = prefix + self.name
265
266     def SetContents(self, data):
267         """Set the contents of an entry
268
269         This sets both the data and content_size properties
270
271         Args:
272             data: Data to set to the contents (bytes)
273         """
274         self.data = data
275         self.contents_size = len(self.data)
276
277     def ProcessContentsUpdate(self, data):
278         """Update the contents of an entry, after the size is fixed
279
280         This checks that the new data is the same size as the old. If the size
281         has changed, this triggers a re-run of the packing algorithm.
282
283         Args:
284             data: Data to set to the contents (bytes)
285
286         Raises:
287             ValueError if the new data size is not the same as the old
288         """
289         size_ok = True
290         new_size = len(data)
291         if state.AllowEntryExpansion() and new_size > self.contents_size:
292             # self.data will indicate the new size needed
293             size_ok = False
294         elif state.AllowEntryContraction() and new_size < self.contents_size:
295             size_ok = False
296
297         # If not allowed to change, try to deal with it or give up
298         if size_ok:
299             if new_size > self.contents_size:
300                 self.Raise('Cannot update entry size from %d to %d' %
301                         (self.contents_size, new_size))
302
303             # Don't let the data shrink. Pad it if necessary
304             if size_ok and new_size < self.contents_size:
305                 data += tools.GetBytes(0, self.contents_size - new_size)
306
307         if not size_ok:
308             tout.Debug("Entry '%s' size change from %s to %s" % (
309                 self._node.path, ToHex(self.contents_size),
310                 ToHex(new_size)))
311         self.SetContents(data)
312         return size_ok
313
314     def ObtainContents(self):
315         """Figure out the contents of an entry.
316
317         Returns:
318             True if the contents were found, False if another call is needed
319             after the other entries are processed.
320         """
321         # No contents by default: subclasses can implement this
322         return True
323
324     def ResetForPack(self):
325         """Reset offset/size fields so that packing can be done again"""
326         self.Detail('ResetForPack: offset %s->%s, size %s->%s' %
327                     (ToHex(self.offset), ToHex(self.orig_offset),
328                      ToHex(self.size), ToHex(self.orig_size)))
329         self.offset = self.orig_offset
330         self.size = self.orig_size
331
332     def Pack(self, offset):
333         """Figure out how to pack the entry into the section
334
335         Most of the time the entries are not fully specified. There may be
336         an alignment but no size. In that case we take the size from the
337         contents of the entry.
338
339         If an entry has no hard-coded offset, it will be placed at @offset.
340
341         Once this function is complete, both the offset and size of the
342         entry will be know.
343
344         Args:
345             Current section offset pointer
346
347         Returns:
348             New section offset pointer (after this entry)
349         """
350         self.Detail('Packing: offset=%s, size=%s, content_size=%x' %
351                     (ToHex(self.offset), ToHex(self.size),
352                      self.contents_size))
353         if self.offset is None:
354             if self.offset_unset:
355                 self.Raise('No offset set with offset-unset: should another '
356                            'entry provide this correct offset?')
357             self.offset = tools.Align(offset, self.align)
358         needed = self.pad_before + self.contents_size + self.pad_after
359         needed = tools.Align(needed, self.align_size)
360         size = self.size
361         if not size:
362             size = needed
363         new_offset = self.offset + size
364         aligned_offset = tools.Align(new_offset, self.align_end)
365         if aligned_offset != new_offset:
366             size = aligned_offset - self.offset
367             new_offset = aligned_offset
368
369         if not self.size:
370             self.size = size
371
372         if self.size < needed:
373             self.Raise("Entry contents size is %#x (%d) but entry size is "
374                        "%#x (%d)" % (needed, needed, self.size, self.size))
375         # Check that the alignment is correct. It could be wrong if the
376         # and offset or size values were provided (i.e. not calculated), but
377         # conflict with the provided alignment values
378         if self.size != tools.Align(self.size, self.align_size):
379             self.Raise("Size %#x (%d) does not match align-size %#x (%d)" %
380                   (self.size, self.size, self.align_size, self.align_size))
381         if self.offset != tools.Align(self.offset, self.align):
382             self.Raise("Offset %#x (%d) does not match align %#x (%d)" %
383                   (self.offset, self.offset, self.align, self.align))
384         self.Detail('   - packed: offset=%#x, size=%#x, content_size=%#x, next_offset=%x' %
385                     (self.offset, self.size, self.contents_size, new_offset))
386
387         return new_offset
388
389     def Raise(self, msg):
390         """Convenience function to raise an error referencing a node"""
391         raise ValueError("Node '%s': %s" % (self._node.path, msg))
392
393     def Detail(self, msg):
394         """Convenience function to log detail referencing a node"""
395         tag = "Node '%s'" % self._node.path
396         tout.Detail('%30s: %s' % (tag, msg))
397
398     def GetEntryArgsOrProps(self, props, required=False):
399         """Return the values of a set of properties
400
401         Args:
402             props: List of EntryArg objects
403
404         Raises:
405             ValueError if a property is not found
406         """
407         values = []
408         missing = []
409         for prop in props:
410             python_prop = prop.name.replace('-', '_')
411             if hasattr(self, python_prop):
412                 value = getattr(self, python_prop)
413             else:
414                 value = None
415             if value is None:
416                 value = self.GetArg(prop.name, prop.datatype)
417             if value is None and required:
418                 missing.append(prop.name)
419             values.append(value)
420         if missing:
421             self.Raise('Missing required properties/entry args: %s' %
422                        (', '.join(missing)))
423         return values
424
425     def GetPath(self):
426         """Get the path of a node
427
428         Returns:
429             Full path of the node for this entry
430         """
431         return self._node.path
432
433     def GetData(self):
434         self.Detail('GetData: size %s' % ToHexSize(self.data))
435         return self.data
436
437     def GetOffsets(self):
438         """Get the offsets for siblings
439
440         Some entry types can contain information about the position or size of
441         other entries. An example of this is the Intel Flash Descriptor, which
442         knows where the Intel Management Engine section should go.
443
444         If this entry knows about the position of other entries, it can specify
445         this by returning values here
446
447         Returns:
448             Dict:
449                 key: Entry type
450                 value: List containing position and size of the given entry
451                     type. Either can be None if not known
452         """
453         return {}
454
455     def SetOffsetSize(self, offset, size):
456         """Set the offset and/or size of an entry
457
458         Args:
459             offset: New offset, or None to leave alone
460             size: New size, or None to leave alone
461         """
462         if offset is not None:
463             self.offset = offset
464         if size is not None:
465             self.size = size
466
467     def SetImagePos(self, image_pos):
468         """Set the position in the image
469
470         Args:
471             image_pos: Position of this entry in the image
472         """
473         self.image_pos = image_pos + self.offset
474
475     def ProcessContents(self):
476         """Do any post-packing updates of entry contents
477
478         This function should call ProcessContentsUpdate() to update the entry
479         contents, if necessary, returning its return value here.
480
481         Args:
482             data: Data to set to the contents (bytes)
483
484         Returns:
485             True if the new data size is OK, False if expansion is needed
486
487         Raises:
488             ValueError if the new data size is not the same as the old and
489                 state.AllowEntryExpansion() is False
490         """
491         return True
492
493     def WriteSymbols(self, section):
494         """Write symbol values into binary files for access at run time
495
496         Args:
497           section: Section containing the entry
498         """
499         pass
500
501     def CheckOffset(self):
502         """Check that the entry offsets are correct
503
504         This is used for entries which have extra offset requirements (other
505         than having to be fully inside their section). Sub-classes can implement
506         this function and raise if there is a problem.
507         """
508         pass
509
510     @staticmethod
511     def GetStr(value):
512         if value is None:
513             return '<none>  '
514         return '%08x' % value
515
516     @staticmethod
517     def WriteMapLine(fd, indent, name, offset, size, image_pos):
518         print('%s  %s%s  %s  %s' % (Entry.GetStr(image_pos), ' ' * indent,
519                                     Entry.GetStr(offset), Entry.GetStr(size),
520                                     name), file=fd)
521
522     def WriteMap(self, fd, indent):
523         """Write a map of the entry to a .map file
524
525         Args:
526             fd: File to write the map to
527             indent: Curent indent level of map (0=none, 1=one level, etc.)
528         """
529         self.WriteMapLine(fd, indent, self.name, self.offset, self.size,
530                           self.image_pos)
531
532     def GetEntries(self):
533         """Return a list of entries contained by this entry
534
535         Returns:
536             List of entries, or None if none. A normal entry has no entries
537                 within it so will return None
538         """
539         return None
540
541     def GetArg(self, name, datatype=str):
542         """Get the value of an entry argument or device-tree-node property
543
544         Some node properties can be provided as arguments to binman. First check
545         the entry arguments, and fall back to the device tree if not found
546
547         Args:
548             name: Argument name
549             datatype: Data type (str or int)
550
551         Returns:
552             Value of argument as a string or int, or None if no value
553
554         Raises:
555             ValueError if the argument cannot be converted to in
556         """
557         value = state.GetEntryArg(name)
558         if value is not None:
559             if datatype == int:
560                 try:
561                     value = int(value)
562                 except ValueError:
563                     self.Raise("Cannot convert entry arg '%s' (value '%s') to integer" %
564                                (name, value))
565             elif datatype == str:
566                 pass
567             else:
568                 raise ValueError("GetArg() internal error: Unknown data type '%s'" %
569                                  datatype)
570         else:
571             value = fdt_util.GetDatatype(self._node, name, datatype)
572         return value
573
574     @staticmethod
575     def WriteDocs(modules, test_missing=None):
576         """Write out documentation about the various entry types to stdout
577
578         Args:
579             modules: List of modules to include
580             test_missing: Used for testing. This is a module to report
581                 as missing
582         """
583         print('''Binman Entry Documentation
584 ===========================
585
586 This file describes the entry types supported by binman. These entry types can
587 be placed in an image one by one to build up a final firmware image. It is
588 fairly easy to create new entry types. Just add a new file to the 'etype'
589 directory. You can use the existing entries as examples.
590
591 Note that some entries are subclasses of others, using and extending their
592 features to produce new behaviours.
593
594
595 ''')
596         modules = sorted(modules)
597
598         # Don't show the test entry
599         if '_testing' in modules:
600             modules.remove('_testing')
601         missing = []
602         for name in modules:
603             if name.startswith('__'):
604                 continue
605             module = Entry.Lookup(name, 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         self.contents_size = self.size
773         ok = self.ProcessContentsUpdate(data)
774         self.Detail('WriteData: size=%x, ok=%s' % (len(data), ok))
775         section_ok = self.section.WriteChildData(self)
776         return ok and section_ok
777
778     def WriteChildData(self, child):
779         """Handle writing the data in a child entry
780
781         This should be called on the child's parent section after the child's
782         data has been updated. It
783
784         This base-class implementation does nothing, since the base Entry object
785         does not have any children.
786
787         Args:
788             child: Child Entry that was written
789
790         Returns:
791             True if the section could be updated successfully, False if the
792                 data is such that the section could not updat
793         """
794         return True
795
796     def GetSiblingOrder(self):
797         """Get the relative order of an entry amoung its siblings
798
799         Returns:
800             'start' if this entry is first among siblings, 'end' if last,
801                 otherwise None
802         """
803         entries = list(self.section.GetEntries().values())
804         if entries:
805             if self == entries[0]:
806                 return 'start'
807             elif self == entries[-1]:
808                 return 'end'
809         return 'middle'