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