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