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