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