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