1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright (c) 2016 Google, Inc
4 # Base class for all entries
7 from collections import namedtuple
14 from binman import bintool
15 from dtoc import fdt_util
16 from patman import tools
17 from patman.tools import to_hex, to_hex_size
18 from patman import tout
22 # This is imported if needed
25 # An argument which can be passed to entries on the command line, in lieu of
26 # device-tree properties.
27 EntryArg = namedtuple('EntryArg', ['name', 'datatype'])
29 # Information about an entry for use when displaying summaries
30 EntryInfo = namedtuple('EntryInfo', ['indent', 'name', 'etype', 'size',
31 'image_pos', 'uncomp_size', 'offset',
35 """An Entry in the section
37 An entry corresponds to a single node in the device-tree description
38 of the section. Each entry ends up being a part of the final section.
39 Entries can be placed either right next to each other, or with padding
40 between them. The type of the entry determines the data that is in it.
42 This class is not used by itself. All entry objects are subclasses of
46 section: Section object containing this entry
47 node: The node that created this entry
48 offset: Offset of entry within the section, None if not known yet (in
49 which case it will be calculated by Pack())
50 size: Entry size in bytes, None if not known
51 pre_reset_size: size as it was before ResetForPack(). This allows us to
52 keep track of the size we started with and detect size changes
53 uncomp_size: Size of uncompressed data in bytes, if the entry is
55 contents_size: Size of contents in bytes, 0 by default
56 align: Entry start offset alignment relative to the start of the
57 containing section, or None
58 align_size: Entry size alignment, or None
59 align_end: Entry end offset alignment relative to the start of the
60 containing section, or None
61 pad_before: Number of pad bytes before the contents when it is placed
62 in the containing section, 0 if none. The pad bytes become part of
64 pad_after: Number of pad bytes after the contents when it is placed in
65 the containing section, 0 if none. The pad bytes become part of
67 data: Contents of entry (string of bytes). This does not include
68 padding created by pad_before or pad_after. If the entry is
69 compressed, this contains the compressed data.
70 uncomp_data: Original uncompressed data, if this entry is compressed,
72 compress: Compression algoithm used (e.g. 'lz4'), 'none' if none
73 orig_offset: Original offset value read from node
74 orig_size: Original size value read from node
75 missing: True if this entry is missing its contents
76 allow_missing: Allow children of this entry to be missing (used by
77 subclasses such as Entry_section)
78 allow_fake: Allow creating a dummy fake file if the blob file is not
79 available. This is mainly used for testing.
80 external: True if this entry contains an external binary blob
81 bintools: Bintools used by this entry (only populated for Image)
82 missing_bintools: List of missing bintools for this entry
83 update_hash: True if this entry's "hash" subnode should be
84 updated with a hash of the entry contents
85 comp_bintool: Bintools used for compress and decompress data
86 fake_fname: Fake filename, if one was created, else None
87 required_props (dict of str): Properties which must be present. This can
88 be added to by subclasses
92 def __init__(self, section, etype, node, name_prefix=''):
93 # Put this here to allow entry-docs and help to work without libfdt
95 from binman import state
97 self.section = section
100 self.name = node and (name_prefix + node.name) or 'none'
103 self.pre_reset_size = None
104 self.uncomp_size = None
106 self.uncomp_data = None
107 self.contents_size = 0
109 self.align_size = None
110 self.align_end = None
113 self.offset_unset = False
114 self.image_pos = None
115 self.extend_size = False
116 self.compress = 'none'
119 self.external = False
120 self.allow_missing = False
121 self.allow_fake = False
123 self.missing_bintools = []
124 self.update_hash = True
125 self.fake_fname = None
126 self.required_props = []
127 self.comp_bintool = None
130 def FindEntryClass(etype, expanded):
131 """Look up the entry class for a node.
134 node_node: Path name of Node object containing information about
135 the entry to create (used for errors)
136 etype: Entry type to use
137 expanded: Use the expanded version of etype
140 The entry class object if found, else None if not found and expanded
141 is True, else a tuple:
142 module name that could not be found
145 # Convert something like 'u-boot@0' to 'u_boot' since we are only
146 # interested in the type.
147 module_name = etype.replace('-', '_')
149 if '@' in module_name:
150 module_name = module_name.split('@')[0]
152 module_name += '_expanded'
153 module = modules.get(module_name)
155 # Also allow entry-type modules to be brought in from the etype directory.
157 # Import the module if we have not already done so.
160 module = importlib.import_module('binman.etype.' + module_name)
161 except ImportError as e:
164 return module_name, e
165 modules[module_name] = module
167 # Look up the expected class name
168 return getattr(module, 'Entry_%s' % module_name)
171 def Lookup(node_path, etype, expanded, missing_etype=False):
172 """Look up the entry class for a node.
175 node_node (str): Path name of Node object containing information
176 about the entry to create (used for errors)
177 etype (str): Entry type to use
178 expanded (bool): Use the expanded version of etype
179 missing_etype (bool): True to default to a blob etype if the
180 requested etype is not found
183 The entry class object if found, else None if not found and expanded
187 ValueError if expanded is False and the class is not found
189 # Convert something like 'u-boot@0' to 'u_boot' since we are only
190 # interested in the type.
191 cls = Entry.FindEntryClass(etype, expanded)
194 elif isinstance(cls, tuple):
196 cls = Entry.FindEntryClass('blob', False)
197 if isinstance(cls, tuple): # This should not fail
200 "Unknown entry type '%s' in node '%s' (expected etype/%s.py, error '%s'" %
201 (etype, node_path, module_name, e))
205 def Create(section, node, etype=None, expanded=False, missing_etype=False):
206 """Create a new entry for a node.
209 section (entry_Section): Section object containing this node
210 node (Node): Node object containing information about the entry to
212 etype (str): Entry type to use, or None to work it out (used for
214 expanded (bool): Use the expanded version of etype
215 missing_etype (bool): True to default to a blob etype if the
216 requested etype is not found
219 A new Entry object of the correct type (a subclass of Entry)
222 etype = fdt_util.GetString(node, 'type', node.name)
223 obj = Entry.Lookup(node.path, etype, expanded, missing_etype)
225 # Check whether to use the expanded entry
226 new_etype = etype + '-expanded'
227 can_expand = not fdt_util.GetBool(node, 'no-expanded')
228 if can_expand and obj.UseExpanded(node, etype, new_etype):
233 obj = Entry.Lookup(node.path, etype, False, missing_etype)
235 # Call its constructor to get the object we want.
236 return obj(section, etype, node)
239 """Read entry information from the node
241 This must be called as the first thing after the Entry is created.
243 This reads all the fields we recognise from the node, ready for use.
246 if 'pos' in self._node.props:
247 self.Raise("Please use 'offset' instead of 'pos'")
248 if 'expand-size' in self._node.props:
249 self.Raise("Please use 'extend-size' instead of 'expand-size'")
250 self.offset = fdt_util.GetInt(self._node, 'offset')
251 self.size = fdt_util.GetInt(self._node, 'size')
252 self.orig_offset = fdt_util.GetInt(self._node, 'orig-offset')
253 self.orig_size = fdt_util.GetInt(self._node, 'orig-size')
254 if self.GetImage().copy_to_orig:
255 self.orig_offset = self.offset
256 self.orig_size = self.size
258 # These should not be set in input files, but are set in an FDT map,
259 # which is also read by this code.
260 self.image_pos = fdt_util.GetInt(self._node, 'image-pos')
261 self.uncomp_size = fdt_util.GetInt(self._node, 'uncomp-size')
263 self.align = fdt_util.GetInt(self._node, 'align')
264 if tools.not_power_of_two(self.align):
265 raise ValueError("Node '%s': Alignment %s must be a power of two" %
266 (self._node.path, self.align))
267 if self.section and self.align is None:
268 self.align = self.section.align_default
269 self.pad_before = fdt_util.GetInt(self._node, 'pad-before', 0)
270 self.pad_after = fdt_util.GetInt(self._node, 'pad-after', 0)
271 self.align_size = fdt_util.GetInt(self._node, 'align-size')
272 if tools.not_power_of_two(self.align_size):
273 self.Raise("Alignment size %s must be a power of two" %
275 self.align_end = fdt_util.GetInt(self._node, 'align-end')
276 self.offset_unset = fdt_util.GetBool(self._node, 'offset-unset')
277 self.extend_size = fdt_util.GetBool(self._node, 'extend-size')
278 self.missing_msg = fdt_util.GetString(self._node, 'missing-msg')
280 # This is only supported by blobs and sections at present
281 self.compress = fdt_util.GetString(self._node, 'compress', 'none')
283 def GetDefaultFilename(self):
287 """Get the device trees used by this entry
290 Empty dict, if this entry is not a .dtb, otherwise:
292 key: Filename from this entry (without the path)
294 Entry object for this dtb
295 Filename of file containing this dtb
299 def gen_entries(self):
300 """Allow entries to generate other entries
302 Some entries generate subnodes automatically, from which sub-entries
303 are then created. This method allows those to be added to the binman
304 definition for the current image. An entry which implements this method
305 should call state.AddSubnode() to add a subnode and can add properties
306 with state.AddString(), etc.
308 An example is 'files', which produces a section containing a list of
313 def AddMissingProperties(self, have_image_pos):
314 """Add new properties to the device tree as needed for this entry
317 have_image_pos: True if this entry has an image position. This can
318 be False if its parent section is compressed, since compression
319 groups all entries together into a compressed block of data,
320 obscuring the start of each individual child entry
322 for prop in ['offset', 'size']:
323 if not prop in self._node.props:
324 state.AddZeroProp(self._node, prop)
325 if have_image_pos and 'image-pos' not in self._node.props:
326 state.AddZeroProp(self._node, 'image-pos')
327 if self.GetImage().allow_repack:
328 if self.orig_offset is not None:
329 state.AddZeroProp(self._node, 'orig-offset', True)
330 if self.orig_size is not None:
331 state.AddZeroProp(self._node, 'orig-size', True)
333 if self.compress != 'none':
334 state.AddZeroProp(self._node, 'uncomp-size')
337 err = state.CheckAddHashProp(self._node)
341 def SetCalculatedProperties(self):
342 """Set the value of device-tree properties calculated by binman"""
343 state.SetInt(self._node, 'offset', self.offset)
344 state.SetInt(self._node, 'size', self.size)
345 base = self.section.GetRootSkipAtStart() if self.section else 0
346 if self.image_pos is not None:
347 state.SetInt(self._node, 'image-pos', self.image_pos - base)
348 if self.GetImage().allow_repack:
349 if self.orig_offset is not None:
350 state.SetInt(self._node, 'orig-offset', self.orig_offset, True)
351 if self.orig_size is not None:
352 state.SetInt(self._node, 'orig-size', self.orig_size, True)
353 if self.uncomp_size is not None:
354 state.SetInt(self._node, 'uncomp-size', self.uncomp_size)
357 state.CheckSetHashValue(self._node, self.GetData)
359 def ProcessFdt(self, fdt):
360 """Allow entries to adjust the device tree
362 Some entries need to adjust the device tree for their purposes. This
363 may involve adding or deleting properties.
366 True if processing is complete
367 False if processing could not be completed due to a dependency.
368 This will cause the entry to be retried after others have been
373 def SetPrefix(self, prefix):
374 """Set the name prefix for a node
377 prefix: Prefix to set, or '' to not use a prefix
380 self.name = prefix + self.name
382 def SetContents(self, data):
383 """Set the contents of an entry
385 This sets both the data and content_size properties
388 data: Data to set to the contents (bytes)
391 self.contents_size = len(self.data)
393 def ProcessContentsUpdate(self, data):
394 """Update the contents of an entry, after the size is fixed
396 This checks that the new data is the same size as the old. If the size
397 has changed, this triggers a re-run of the packing algorithm.
400 data: Data to set to the contents (bytes)
403 ValueError if the new data size is not the same as the old
407 if state.AllowEntryExpansion() and new_size > self.contents_size:
408 # self.data will indicate the new size needed
410 elif state.AllowEntryContraction() and new_size < self.contents_size:
413 # If not allowed to change, try to deal with it or give up
415 if new_size > self.contents_size:
416 self.Raise('Cannot update entry size from %d to %d' %
417 (self.contents_size, new_size))
419 # Don't let the data shrink. Pad it if necessary
420 if size_ok and new_size < self.contents_size:
421 data += tools.get_bytes(0, self.contents_size - new_size)
424 tout.debug("Entry '%s' size change from %s to %s" % (
425 self._node.path, to_hex(self.contents_size),
427 self.SetContents(data)
430 def ObtainContents(self, skip_entry=None, fake_size=0):
431 """Figure out the contents of an entry.
434 skip_entry (Entry): Entry to skip when obtaining section contents
435 fake_size (int): Size of fake file to create if needed
438 True if the contents were found, False if another call is needed
439 after the other entries are processed.
441 # No contents by default: subclasses can implement this
444 def ResetForPack(self):
445 """Reset offset/size fields so that packing can be done again"""
446 self.Detail('ResetForPack: offset %s->%s, size %s->%s' %
447 (to_hex(self.offset), to_hex(self.orig_offset),
448 to_hex(self.size), to_hex(self.orig_size)))
449 self.pre_reset_size = self.size
450 self.offset = self.orig_offset
451 self.size = self.orig_size
453 def Pack(self, offset):
454 """Figure out how to pack the entry into the section
456 Most of the time the entries are not fully specified. There may be
457 an alignment but no size. In that case we take the size from the
458 contents of the entry.
460 If an entry has no hard-coded offset, it will be placed at @offset.
462 Once this function is complete, both the offset and size of the
466 Current section offset pointer
469 New section offset pointer (after this entry)
471 self.Detail('Packing: offset=%s, size=%s, content_size=%x' %
472 (to_hex(self.offset), to_hex(self.size),
474 if self.offset is None:
475 if self.offset_unset:
476 self.Raise('No offset set with offset-unset: should another '
477 'entry provide this correct offset?')
478 self.offset = tools.align(offset, self.align)
479 needed = self.pad_before + self.contents_size + self.pad_after
480 needed = tools.align(needed, self.align_size)
484 new_offset = self.offset + size
485 aligned_offset = tools.align(new_offset, self.align_end)
486 if aligned_offset != new_offset:
487 size = aligned_offset - self.offset
488 new_offset = aligned_offset
493 if self.size < needed:
494 self.Raise("Entry contents size is %#x (%d) but entry size is "
495 "%#x (%d)" % (needed, needed, self.size, self.size))
496 # Check that the alignment is correct. It could be wrong if the
497 # and offset or size values were provided (i.e. not calculated), but
498 # conflict with the provided alignment values
499 if self.size != tools.align(self.size, self.align_size):
500 self.Raise("Size %#x (%d) does not match align-size %#x (%d)" %
501 (self.size, self.size, self.align_size, self.align_size))
502 if self.offset != tools.align(self.offset, self.align):
503 self.Raise("Offset %#x (%d) does not match align %#x (%d)" %
504 (self.offset, self.offset, self.align, self.align))
505 self.Detail(' - packed: offset=%#x, size=%#x, content_size=%#x, next_offset=%x' %
506 (self.offset, self.size, self.contents_size, new_offset))
510 def Raise(self, msg):
511 """Convenience function to raise an error referencing a node"""
512 raise ValueError("Node '%s': %s" % (self._node.path, msg))
515 """Convenience function to log info referencing a node"""
516 tag = "Info '%s'" % self._node.path
517 tout.detail('%30s: %s' % (tag, msg))
519 def Detail(self, msg):
520 """Convenience function to log detail referencing a node"""
521 tag = "Node '%s'" % self._node.path
522 tout.detail('%30s: %s' % (tag, msg))
524 def GetEntryArgsOrProps(self, props, required=False):
525 """Return the values of a set of properties
528 props: List of EntryArg objects
531 ValueError if a property is not found
536 python_prop = prop.name.replace('-', '_')
537 if hasattr(self, python_prop):
538 value = getattr(self, python_prop)
542 value = self.GetArg(prop.name, prop.datatype)
543 if value is None and required:
544 missing.append(prop.name)
547 self.GetImage().MissingArgs(self, missing)
551 """Get the path of a node
554 Full path of the node for this entry
556 return self._node.path
558 def GetData(self, required=True):
559 """Get the contents of an entry
562 required: True if the data must be present, False if it is OK to
566 bytes content of the entry, excluding any padding. If the entry is
567 compressed, the compressed data is returned
569 self.Detail('GetData: size %s' % to_hex_size(self.data))
572 def GetPaddedData(self, data=None):
573 """Get the data for an entry including any padding
575 Gets the entry data and uses its section's pad-byte value to add padding
576 before and after as defined by the pad-before and pad-after properties.
578 This does not consider alignment.
581 Contents of the entry along with any pad bytes before and
585 data = self.GetData()
586 return self.section.GetPaddedDataForEntry(self, data)
588 def GetOffsets(self):
589 """Get the offsets for siblings
591 Some entry types can contain information about the position or size of
592 other entries. An example of this is the Intel Flash Descriptor, which
593 knows where the Intel Management Engine section should go.
595 If this entry knows about the position of other entries, it can specify
596 this by returning values here
601 value: List containing position and size of the given entry
602 type. Either can be None if not known
606 def SetOffsetSize(self, offset, size):
607 """Set the offset and/or size of an entry
610 offset: New offset, or None to leave alone
611 size: New size, or None to leave alone
613 if offset is not None:
618 def SetImagePos(self, image_pos):
619 """Set the position in the image
622 image_pos: Position of this entry in the image
624 self.image_pos = image_pos + self.offset
626 def ProcessContents(self):
627 """Do any post-packing updates of entry contents
629 This function should call ProcessContentsUpdate() to update the entry
630 contents, if necessary, returning its return value here.
633 data: Data to set to the contents (bytes)
636 True if the new data size is OK, False if expansion is needed
639 ValueError if the new data size is not the same as the old and
640 state.AllowEntryExpansion() is False
644 def WriteSymbols(self, section):
645 """Write symbol values into binary files for access at run time
648 section: Section containing the entry
652 def CheckEntries(self):
653 """Check that the entry offsets are correct
655 This is used for entries which have extra offset requirements (other
656 than having to be fully inside their section). Sub-classes can implement
657 this function and raise if there is a problem.
665 return '%08x' % value
668 def WriteMapLine(fd, indent, name, offset, size, image_pos):
669 print('%s %s%s %s %s' % (Entry.GetStr(image_pos), ' ' * indent,
670 Entry.GetStr(offset), Entry.GetStr(size),
673 def WriteMap(self, fd, indent):
674 """Write a map of the entry to a .map file
677 fd: File to write the map to
678 indent: Curent indent level of map (0=none, 1=one level, etc.)
680 self.WriteMapLine(fd, indent, self.name, self.offset, self.size,
683 # pylint: disable=assignment-from-none
684 def GetEntries(self):
685 """Return a list of entries contained by this entry
688 List of entries, or None if none. A normal entry has no entries
689 within it so will return None
693 def FindEntryByNode(self, find_node):
694 """Find a node in an entry, searching all subentries
696 This does a recursive search.
699 find_node (fdt.Node): Node to find
702 Entry: entry, if found, else None
704 entries = self.GetEntries()
706 for entry in entries.values():
707 if entry._node == find_node:
709 found = entry.FindEntryByNode(find_node)
715 def GetArg(self, name, datatype=str):
716 """Get the value of an entry argument or device-tree-node property
718 Some node properties can be provided as arguments to binman. First check
719 the entry arguments, and fall back to the device tree if not found
723 datatype: Data type (str or int)
726 Value of argument as a string or int, or None if no value
729 ValueError if the argument cannot be converted to in
731 value = state.GetEntryArg(name)
732 if value is not None:
737 self.Raise("Cannot convert entry arg '%s' (value '%s') to integer" %
739 elif datatype == str:
742 raise ValueError("GetArg() internal error: Unknown data type '%s'" %
745 value = fdt_util.GetDatatype(self._node, name, datatype)
749 def WriteDocs(modules, test_missing=None):
750 """Write out documentation about the various entry types to stdout
753 modules: List of modules to include
754 test_missing: Used for testing. This is a module to report
757 print('''Binman Entry Documentation
758 ===========================
760 This file describes the entry types supported by binman. These entry types can
761 be placed in an image one by one to build up a final firmware image. It is
762 fairly easy to create new entry types. Just add a new file to the 'etype'
763 directory. You can use the existing entries as examples.
765 Note that some entries are subclasses of others, using and extending their
766 features to produce new behaviours.
770 modules = sorted(modules)
772 # Don't show the test entry
773 if '_testing' in modules:
774 modules.remove('_testing')
777 module = Entry.Lookup('WriteDocs', name, False)
778 docs = getattr(module, '__doc__')
779 if test_missing == name:
782 lines = docs.splitlines()
783 first_line = lines[0]
784 rest = [line[4:] for line in lines[1:]]
785 hdr = 'Entry: %s: %s' % (name.replace('_', '-'), first_line)
787 # Create a reference for use by rST docs
788 ref_name = f'etype_{module.__name__[6:]}'.lower()
789 print('.. _%s:' % ref_name)
792 print('-' * len(hdr))
793 print('\n'.join(rest))
800 raise ValueError('Documentation is missing for modules: %s' %
803 def GetUniqueName(self):
804 """Get a unique name for a node
807 String containing a unique name for a node, consisting of the name
808 of all ancestors (starting from within the 'binman' node) separated
809 by a dot ('.'). This can be useful for generating unique filesnames
810 in the output directory.
816 if node.name in ('binman', '/'):
818 name = '%s.%s' % (node.name, name)
821 def extend_to_limit(self, limit):
822 """Extend an entry so that it ends at the given offset limit"""
823 if self.offset + self.size < limit:
824 self.size = limit - self.offset
825 # Request the contents again, since changing the size requires that
826 # the data grows. This should not fail, but check it to be sure.
827 if not self.ObtainContents():
828 self.Raise('Cannot obtain contents when expanding entry')
830 def HasSibling(self, name):
831 """Check if there is a sibling of a given name
834 True if there is an entry with this name in the the same section,
837 return name in self.section.GetEntries()
839 def GetSiblingImagePos(self, name):
840 """Return the image position of the given sibling
843 Image position of sibling, or None if the sibling has no position,
844 or False if there is no such sibling
846 if not self.HasSibling(name):
848 return self.section.GetEntries()[name].image_pos
851 def AddEntryInfo(entries, indent, name, etype, size, image_pos,
852 uncomp_size, offset, entry):
853 """Add a new entry to the entries list
856 entries: List (of EntryInfo objects) to add to
857 indent: Current indent level to add to list
858 name: Entry name (string)
859 etype: Entry type (string)
860 size: Entry size in bytes (int)
861 image_pos: Position within image in bytes (int)
862 uncomp_size: Uncompressed size if the entry uses compression, else
864 offset: Entry offset within parent in bytes (int)
867 entries.append(EntryInfo(indent, name, etype, size, image_pos,
868 uncomp_size, offset, entry))
870 def ListEntries(self, entries, indent):
871 """Add files in this entry to the list of entries
873 This can be overridden by subclasses which need different behaviour.
876 entries: List (of EntryInfo objects) to add to
877 indent: Current indent level to add to list
879 self.AddEntryInfo(entries, indent, self.name, self.etype, self.size,
880 self.image_pos, self.uncomp_size, self.offset, self)
882 def ReadData(self, decomp=True, alt_format=None):
883 """Read the data for an entry from the image
885 This is used when the image has been read in and we want to extract the
886 data for a particular entry from that image.
889 decomp: True to decompress any compressed data before returning it;
890 False to return the raw, uncompressed data
895 # Use True here so that we get an uncompressed section to work from,
896 # although compressed sections are currently not supported
897 tout.debug("ReadChildData section '%s', entry '%s'" %
898 (self.section.GetPath(), self.GetPath()))
899 data = self.section.ReadChildData(self, decomp, alt_format)
902 def ReadChildData(self, child, decomp=True, alt_format=None):
903 """Read the data for a particular child entry
905 This reads data from the parent and extracts the piece that relates to
909 child (Entry): Child entry to read data for (must be valid)
910 decomp (bool): True to decompress any compressed data before
911 returning it; False to return the raw, uncompressed data
912 alt_format (str): Alternative format to read in, or None
915 Data for the child (bytes)
919 def LoadData(self, decomp=True):
920 data = self.ReadData(decomp)
921 self.contents_size = len(data)
922 self.ProcessContentsUpdate(data)
923 self.Detail('Loaded data size %x' % len(data))
925 def GetAltFormat(self, data, alt_format):
926 """Read the data for an extry in an alternative format
928 Supported formats are list in the documentation for each entry. An
929 example is fdtmap which provides .
932 data (bytes): Data to convert (this should have been produced by the
934 alt_format (str): Format to use
940 """Get the image containing this entry
943 Image object containing this entry
945 return self.section.GetImage()
947 def WriteData(self, data, decomp=True):
948 """Write the data to an entry in the image
950 This is used when the image has been read in and we want to replace the
951 data for a particular entry in that image.
953 The image must be re-packed and written out afterwards.
956 data: Data to replace it with
957 decomp: True to compress the data if needed, False if data is
958 already compressed so should be used as is
961 True if the data did not result in a resize of this entry, False if
962 the entry must be resized
964 if self.size is not None:
965 self.contents_size = self.size
967 self.contents_size = self.pre_reset_size
968 ok = self.ProcessContentsUpdate(data)
969 self.Detail('WriteData: size=%x, ok=%s' % (len(data), ok))
970 section_ok = self.section.WriteChildData(self)
971 return ok and section_ok
973 def WriteChildData(self, child):
974 """Handle writing the data in a child entry
976 This should be called on the child's parent section after the child's
977 data has been updated. It should update any data structures needed to
978 validate that the update is successful.
980 This base-class implementation does nothing, since the base Entry object
981 does not have any children.
984 child: Child Entry that was written
987 True if the section could be updated successfully, False if the
988 data is such that the section could not update
992 def GetSiblingOrder(self):
993 """Get the relative order of an entry amoung its siblings
996 'start' if this entry is first among siblings, 'end' if last,
999 entries = list(self.section.GetEntries().values())
1001 if self == entries[0]:
1003 elif self == entries[-1]:
1007 def SetAllowMissing(self, allow_missing):
1008 """Set whether a section allows missing external blobs
1011 allow_missing: True if allowed, False if not allowed
1013 # This is meaningless for anything other than sections
1016 def SetAllowFakeBlob(self, allow_fake):
1017 """Set whether a section allows to create a fake blob
1020 allow_fake: True if allowed, False if not allowed
1022 self.allow_fake = allow_fake
1024 def CheckMissing(self, missing_list):
1025 """Check if any entries in this section have missing external blobs
1027 If there are missing blobs, the entries are added to the list
1030 missing_list: List of Entry objects to be added to
1033 missing_list.append(self)
1035 def check_fake_fname(self, fname, size=0):
1036 """If the file is missing and the entry allows fake blobs, fake it
1038 Sets self.faked to True if faked
1041 fname (str): Filename to check
1042 size (int): Size of fake file to create
1046 fname (str): Filename of faked file
1047 bool: True if the blob was faked, False if not
1049 if self.allow_fake and not pathlib.Path(fname).is_file():
1050 if not self.fake_fname:
1051 outfname = os.path.join(self.fake_dir, os.path.basename(fname))
1052 with open(outfname, "wb") as out:
1054 tout.info(f"Entry '{self._node.path}': Faked blob '{outfname}'")
1055 self.fake_fname = outfname
1057 return self.fake_fname, True
1060 def CheckFakedBlobs(self, faked_blobs_list):
1061 """Check if any entries in this section have faked external blobs
1063 If there are faked blobs, the entries are added to the list
1066 fake_blobs_list: List of Entry objects to be added to
1068 # This is meaningless for anything other than blobs
1071 def GetAllowMissing(self):
1072 """Get whether a section allows missing external blobs
1075 True if allowed, False if not allowed
1077 return self.allow_missing
1079 def record_missing_bintool(self, bintool):
1080 """Record a missing bintool that was needed to produce this entry
1083 bintool (Bintool): Bintool that was missing
1085 if bintool not in self.missing_bintools:
1086 self.missing_bintools.append(bintool)
1088 def check_missing_bintools(self, missing_list):
1089 """Check if any entries in this section have missing bintools
1091 If there are missing bintools, these are added to the list
1094 missing_list: List of Bintool objects to be added to
1096 for bintool in self.missing_bintools:
1097 if bintool not in missing_list:
1098 missing_list.append(bintool)
1101 def GetHelpTags(self):
1102 """Get the tags use for missing-blob help
1105 list of possible tags, most desirable first
1107 return list(filter(None, [self.missing_msg, self.name, self.etype]))
1109 def CompressData(self, indata):
1110 """Compress data according to the entry's compression method
1113 indata: Data to compress
1118 self.uncomp_data = indata
1119 if self.compress != 'none':
1120 self.uncomp_size = len(indata)
1121 if self.comp_bintool.is_present():
1122 data = self.comp_bintool.compress(indata)
1124 self.record_missing_bintool(self.comp_bintool)
1125 data = tools.get_bytes(0, 1024)
1130 def DecompressData(self, indata):
1131 """Decompress data according to the entry's compression method
1134 indata: Data to decompress
1139 if self.compress != 'none':
1140 if self.comp_bintool.is_present():
1141 data = self.comp_bintool.decompress(indata)
1142 self.uncomp_size = len(data)
1144 self.record_missing_bintool(self.comp_bintool)
1145 data = tools.get_bytes(0, 1024)
1148 self.uncomp_data = data
1152 def UseExpanded(cls, node, etype, new_etype):
1153 """Check whether to use an expanded entry type
1155 This is called by Entry.Create() when it finds an expanded version of
1156 an entry type (e.g. 'u-boot-expanded'). If this method returns True then
1157 it will be used (e.g. in place of 'u-boot'). If it returns False, it is
1161 node: Node object containing information about the entry to
1163 etype: Original entry type being used
1164 new_etype: New entry type proposed
1167 True to use this entry type, False to use the original one
1169 tout.info("Node '%s': etype '%s': %s selected" %
1170 (node.path, etype, new_etype))
1173 def CheckAltFormats(self, alt_formats):
1174 """Add any alternative formats supported by this entry type
1177 alt_formats (dict): Dict to add alt_formats to:
1178 key: Name of alt format
1183 def AddBintools(self, btools):
1184 """Add the bintools used by this entry type
1187 btools (dict of Bintool):
1190 ValueError if compression algorithm is not supported
1192 algo = self.compress
1194 algos = ['bzip2', 'lz4', 'lzma']
1195 if algo not in algos:
1196 raise ValueError("Unknown algorithm '%s'" % algo)
1197 names = {'lzma': 'lzma_alone'}
1198 name = names.get(self.compress, self.compress)
1199 self.comp_bintool = self.AddBintool(btools, name)
1202 def AddBintool(self, tools, name):
1203 """Add a new bintool to the tools used by this etype
1206 name: Name of the tool
1208 btool = bintool.Bintool.create(name)
1212 def SetUpdateHash(self, update_hash):
1213 """Set whether this entry's "hash" subnode should be updated
1216 update_hash: True if hash should be updated, False if not
1218 self.update_hash = update_hash
1220 def collect_contents_to_file(self, entries, prefix, fake_size=0):
1221 """Put the contents of a list of entries into a file
1224 entries (list of Entry): Entries to collect
1225 prefix (str): Filename prefix of file to write to
1226 fake_size (int): Size of fake file to create if needed
1228 If any entry does not have contents yet, this function returns False
1233 bytes: Concatenated data from all the entries (or None)
1234 str: Filename of file written (or None if no data)
1235 str: Unique portion of filename (or None if no data)
1238 for entry in entries:
1239 # First get the input data and put it in a file. If not available,
1241 if not entry.ObtainContents(fake_size=fake_size):
1242 return None, None, None
1243 data += entry.GetData()
1244 uniq = self.GetUniqueName()
1245 fname = tools.get_output_filename(f'{prefix}.{uniq}')
1246 tools.write_file(fname, data)
1247 return data, fname, uniq
1250 def create_fake_dir(cls):
1251 """Create the directory for fake files"""
1252 cls.fake_dir = tools.get_output_filename('binman-fake')
1253 if not os.path.exists(cls.fake_dir):
1254 os.mkdir(cls.fake_dir)
1255 tout.notice(f"Fake-blob dir is '{cls.fake_dir}'")
1257 def ensure_props(self):
1258 """Raise an exception if properties are missing
1261 prop_list (list of str): List of properties to check for
1264 ValueError: Any property is missing
1267 for prop in self.required_props:
1268 if not prop in self._node.props:
1269 not_present.append(prop)
1271 self.Raise(f"'{self.etype}' entry is missing properties: {' '.join(not_present)}")