binman: Add DecompressData function to entry class
[platform/kernel/u-boot.git] / tools / binman / etype / section.py
1 # SPDX-License-Identifier:      GPL-2.0+
2 # Copyright (c) 2018 Google, Inc
3 # Written by Simon Glass <sjg@chromium.org>
4
5 """Entry-type module for sections (groups of entries)
6
7 Sections are entries which can contain other entries. This allows hierarchical
8 images to be created.
9 """
10
11 from collections import OrderedDict
12 import concurrent.futures
13 import re
14 import sys
15
16 from binman.entry import Entry
17 from binman import state
18 from dtoc import fdt_util
19 from patman import tools
20 from patman import tout
21 from patman.tools import to_hex_size
22
23
24 class Entry_section(Entry):
25     """Entry that contains other entries
26
27     A section is an entry which can contain other entries, thus allowing
28     hierarchical images to be created. See 'Sections and hierarchical images'
29     in the binman README for more information.
30
31     The base implementation simply joins the various entries together, using
32     various rules about alignment, etc.
33
34     Subclassing
35     ~~~~~~~~~~~
36
37     This class can be subclassed to support other file formats which hold
38     multiple entries, such as CBFS. To do this, override the following
39     functions. The documentation here describes what your function should do.
40     For example code, see etypes which subclass `Entry_section`, or `cbfs.py`
41     for a more involved example::
42
43        $ grep -l \(Entry_section tools/binman/etype/*.py
44
45     ReadNode()
46         Call `super().ReadNode()`, then read any special properties for the
47         section. Then call `self.ReadEntries()` to read the entries.
48
49         Binman calls this at the start when reading the image description.
50
51     ReadEntries()
52         Read in the subnodes of the section. This may involve creating entries
53         of a particular etype automatically, as well as reading any special
54         properties in the entries. For each entry, entry.ReadNode() should be
55         called, to read the basic entry properties. The properties should be
56         added to `self._entries[]`, in the correct order, with a suitable name.
57
58         Binman calls this at the start when reading the image description.
59
60     BuildSectionData(required)
61         Create the custom file format that you want and return it as bytes.
62         This likely sets up a file header, then loops through the entries,
63         adding them to the file. For each entry, call `entry.GetData()` to
64         obtain the data. If that returns None, and `required` is False, then
65         this method must give up and return None. But if `required` is True then
66         it should assume that all data is valid.
67
68         Binman calls this when packing the image, to find out the size of
69         everything. It is called again at the end when building the final image.
70
71     SetImagePos(image_pos):
72         Call `super().SetImagePos(image_pos)`, then set the `image_pos` values
73         for each of the entries. This should use the custom file format to find
74         the `start offset` (and `image_pos`) of each entry. If the file format
75         uses compression in such a way that there is no offset available (other
76         than reading the whole file and decompressing it), then the offsets for
77         affected entries can remain unset (`None`). The size should also be set
78         if possible.
79
80         Binman calls this after the image has been packed, to update the
81         location that all the entries ended up at.
82
83     ReadChildData(child, decomp, alt_format):
84         The default version of this may be good enough, if you are able to
85         implement SetImagePos() correctly. But that is a bit of a bypass, so
86         you can override this method to read from your custom file format. It
87         should read the entire entry containing the custom file using
88         `super().ReadData(True)`, then parse the file to get the data for the
89         given child, then return that data.
90
91         If your file format supports compression, the `decomp` argument tells
92         you whether to return the compressed data (`decomp` is False) or to
93         uncompress it first, then return the uncompressed data (`decomp` is
94         True). This is used by the `binman extract -U` option.
95
96         If your entry supports alternative formats, the alt_format provides the
97         alternative format that the user has selected. Your function should
98         return data in that format. This is used by the 'binman extract -l'
99         option.
100
101         Binman calls this when reading in an image, in order to populate all the
102         entries with the data from that image (`binman ls`).
103
104     WriteChildData(child):
105         Binman calls this after `child.data` is updated, to inform the custom
106         file format about this, in case it needs to do updates.
107
108         The default version of this does nothing and probably needs to be
109         overridden for the 'binman replace' command to work. Your version should
110         use `child.data` to update the data for that child in the custom file
111         format.
112
113         Binman calls this when updating an image that has been read in and in
114         particular to update the data for a particular entry (`binman replace`)
115
116     Properties / Entry arguments
117     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
118
119     See :ref:`develop/package/binman:Image description format` for more
120     information.
121
122     align-default
123         Default alignment for this section, if no alignment is given in the
124         entry
125
126     pad-byte
127         Pad byte to use when padding
128
129     sort-by-offset
130         True if entries should be sorted by offset, False if they must be
131         in-order in the device tree description
132
133     end-at-4gb
134         Used to build an x86 ROM which ends at 4GB (2^32)
135
136     name-prefix
137         Adds a prefix to the name of every entry in the section when writing out
138         the map
139
140     skip-at-start
141         Number of bytes before the first entry starts. These effectively adjust
142         the starting offset of entries. For example, if this is 16, then the
143         first entry would start at 16. An entry with offset = 20 would in fact
144         be written at offset 4 in the image file, since the first 16 bytes are
145         skipped when writing.
146
147     Since a section is also an entry, it inherits all the properies of entries
148     too.
149
150     Note that the `allow_missing` member controls whether this section permits
151     external blobs to be missing their contents. The option will produce an
152     image but of course it will not work. It is useful to make sure that
153     Continuous Integration systems can build without the binaries being
154     available. This is set by the `SetAllowMissing()` method, if
155     `--allow-missing` is passed to binman.
156     """
157     def __init__(self, section, etype, node, test=False):
158         if not test:
159             super().__init__(section, etype, node)
160         self._entries = OrderedDict()
161         self._pad_byte = 0
162         self._sort = False
163         self._skip_at_start = None
164         self._end_4gb = False
165         self._ignore_missing = False
166
167     def ReadNode(self):
168         """Read properties from the section node"""
169         super().ReadNode()
170         self._pad_byte = fdt_util.GetInt(self._node, 'pad-byte', 0)
171         self._sort = fdt_util.GetBool(self._node, 'sort-by-offset')
172         self._end_4gb = fdt_util.GetBool(self._node, 'end-at-4gb')
173         self._skip_at_start = fdt_util.GetInt(self._node, 'skip-at-start')
174         if self._end_4gb:
175             if not self.size:
176                 self.Raise("Section size must be provided when using end-at-4gb")
177             if self._skip_at_start is not None:
178                 self.Raise("Provide either 'end-at-4gb' or 'skip-at-start'")
179             else:
180                 self._skip_at_start = 0x100000000 - self.size
181         else:
182             if self._skip_at_start is None:
183                 self._skip_at_start = 0
184         self._name_prefix = fdt_util.GetString(self._node, 'name-prefix')
185         self.align_default = fdt_util.GetInt(self._node, 'align-default', 0)
186
187         self.ReadEntries()
188
189     def ReadEntries(self):
190         for node in self._node.subnodes:
191             if node.name.startswith('hash') or node.name.startswith('signature'):
192                 continue
193             entry = Entry.Create(self, node,
194                                  expanded=self.GetImage().use_expanded,
195                                  missing_etype=self.GetImage().missing_etype)
196             entry.ReadNode()
197             entry.SetPrefix(self._name_prefix)
198             self._entries[node.name] = entry
199
200     def _Raise(self, msg):
201         """Raises an error for this section
202
203         Args:
204             msg (str): Error message to use in the raise string
205         Raises:
206             ValueError: always
207         """
208         raise ValueError("Section '%s': %s" % (self._node.path, msg))
209
210     def GetFdts(self):
211         fdts = {}
212         for entry in self._entries.values():
213             fdts.update(entry.GetFdts())
214         return fdts
215
216     def ProcessFdt(self, fdt):
217         """Allow entries to adjust the device tree
218
219         Some entries need to adjust the device tree for their purposes. This
220         may involve adding or deleting properties.
221         """
222         todo = self._entries.values()
223         for passnum in range(3):
224             next_todo = []
225             for entry in todo:
226                 if not entry.ProcessFdt(fdt):
227                     next_todo.append(entry)
228             todo = next_todo
229             if not todo:
230                 break
231         if todo:
232             self.Raise('Internal error: Could not complete processing of Fdt: remaining %s' %
233                        todo)
234         return True
235
236     def gen_entries(self):
237         super().gen_entries()
238         for entry in self._entries.values():
239             entry.gen_entries()
240
241     def AddMissingProperties(self, have_image_pos):
242         """Add new properties to the device tree as needed for this entry"""
243         super().AddMissingProperties(have_image_pos)
244         if self.compress != 'none':
245             have_image_pos = False
246         for entry in self._entries.values():
247             entry.AddMissingProperties(have_image_pos)
248
249     def ObtainContents(self, fake_size=0, skip_entry=None):
250         return self.GetEntryContents(skip_entry=skip_entry)
251
252     def GetPaddedDataForEntry(self, entry, entry_data):
253         """Get the data for an entry including any padding
254
255         Gets the entry data and uses the section pad-byte value to add padding
256         before and after as defined by the pad-before and pad-after properties.
257         This does not consider alignment.
258
259         Args:
260             entry: Entry to check
261
262         Returns:
263             Contents of the entry along with any pad bytes before and
264             after it (bytes)
265         """
266         pad_byte = (entry._pad_byte if isinstance(entry, Entry_section)
267                     else self._pad_byte)
268
269         data = bytearray()
270         # Handle padding before the entry
271         if entry.pad_before:
272             data += tools.get_bytes(self._pad_byte, entry.pad_before)
273
274         # Add in the actual entry data
275         data += entry_data
276
277         # Handle padding after the entry
278         if entry.pad_after:
279             data += tools.get_bytes(self._pad_byte, entry.pad_after)
280
281         if entry.size:
282             data += tools.get_bytes(pad_byte, entry.size - len(data))
283
284         self.Detail('GetPaddedDataForEntry: size %s' % to_hex_size(self.data))
285
286         return data
287
288     def BuildSectionData(self, required):
289         """Build the contents of a section
290
291         This places all entries at the right place, dealing with padding before
292         and after entries. It does not do padding for the section itself (the
293         pad-before and pad-after properties in the section items) since that is
294         handled by the parent section.
295
296         This should be overridden by subclasses which want to build their own
297         data structure for the section.
298
299         Args:
300             required: True if the data must be present, False if it is OK to
301                 return None
302
303         Returns:
304             Contents of the section (bytes)
305         """
306         section_data = bytearray()
307
308         for entry in self._entries.values():
309             entry_data = entry.GetData(required)
310
311             # This can happen when this section is referenced from a collection
312             # earlier in the image description. See testCollectionSection().
313             if not required and entry_data is None:
314                 return None
315             data = self.GetPaddedDataForEntry(entry, entry_data)
316             # Handle empty space before the entry
317             pad = (entry.offset or 0) - self._skip_at_start - len(section_data)
318             if pad > 0:
319                 section_data += tools.get_bytes(self._pad_byte, pad)
320
321             # Add in the actual entry data
322             section_data += data
323
324         self.Detail('GetData: %d entries, total size %#x' %
325                     (len(self._entries), len(section_data)))
326         return self.CompressData(section_data)
327
328     def GetPaddedData(self, data=None):
329         """Get the data for a section including any padding
330
331         Gets the section data and uses the parent section's pad-byte value to
332         add padding before and after as defined by the pad-before and pad-after
333         properties. If this is a top-level section (i.e. an image), this is the
334         same as GetData(), since padding is not supported.
335
336         This does not consider alignment.
337
338         Returns:
339             Contents of the section along with any pad bytes before and
340             after it (bytes)
341         """
342         section = self.section or self
343         if data is None:
344             data = self.GetData()
345         return section.GetPaddedDataForEntry(self, data)
346
347     def GetData(self, required=True):
348         """Get the contents of an entry
349
350         This builds the contents of the section, stores this as the contents of
351         the section and returns it
352
353         Args:
354             required: True if the data must be present, False if it is OK to
355                 return None
356
357         Returns:
358             bytes content of the section, made up for all all of its subentries.
359             This excludes any padding. If the section is compressed, the
360             compressed data is returned
361         """
362         data = self.BuildSectionData(required)
363         if data is None:
364             return None
365         self.SetContents(data)
366         return data
367
368     def GetOffsets(self):
369         """Handle entries that want to set the offset/size of other entries
370
371         This calls each entry's GetOffsets() method. If it returns a list
372         of entries to update, it updates them.
373         """
374         self.GetEntryOffsets()
375         return {}
376
377     def ResetForPack(self):
378         """Reset offset/size fields so that packing can be done again"""
379         super().ResetForPack()
380         for entry in self._entries.values():
381             entry.ResetForPack()
382
383     def Pack(self, offset):
384         """Pack all entries into the section"""
385         self._PackEntries()
386         if self._sort:
387             self._SortEntries()
388         self._extend_entries()
389
390         data = self.BuildSectionData(True)
391         self.SetContents(data)
392
393         self.CheckSize()
394
395         offset = super().Pack(offset)
396         self.CheckEntries()
397         return offset
398
399     def _PackEntries(self):
400         """Pack all entries into the section"""
401         offset = self._skip_at_start
402         for entry in self._entries.values():
403             offset = entry.Pack(offset)
404         return offset
405
406     def _extend_entries(self):
407         """Extend any entries that are permitted to"""
408         exp_entry = None
409         for entry in self._entries.values():
410             if exp_entry:
411                 exp_entry.extend_to_limit(entry.offset)
412                 exp_entry = None
413             if entry.extend_size:
414                 exp_entry = entry
415         if exp_entry:
416             exp_entry.extend_to_limit(self.size)
417
418     def _SortEntries(self):
419         """Sort entries by offset"""
420         entries = sorted(self._entries.values(), key=lambda entry: entry.offset)
421         self._entries.clear()
422         for entry in entries:
423             self._entries[entry._node.name] = entry
424
425     def CheckEntries(self):
426         """Check that entries do not overlap or extend outside the section"""
427         max_size = self.size if self.uncomp_size is None else self.uncomp_size
428
429         offset = 0
430         prev_name = 'None'
431         for entry in self._entries.values():
432             entry.CheckEntries()
433             if (entry.offset < self._skip_at_start or
434                     entry.offset + entry.size > self._skip_at_start +
435                     max_size):
436                 entry.Raise('Offset %#x (%d) size %#x (%d) is outside the '
437                             "section '%s' starting at %#x (%d) "
438                             'of size %#x (%d)' %
439                             (entry.offset, entry.offset, entry.size, entry.size,
440                              self._node.path, self._skip_at_start,
441                              self._skip_at_start, max_size, max_size))
442             if entry.offset < offset and entry.size:
443                 entry.Raise("Offset %#x (%d) overlaps with previous entry '%s' "
444                             "ending at %#x (%d)" %
445                             (entry.offset, entry.offset, prev_name, offset, offset))
446             offset = entry.offset + entry.size
447             prev_name = entry.GetPath()
448
449     def WriteSymbols(self, section):
450         """Write symbol values into binary files for access at run time"""
451         for entry in self._entries.values():
452             entry.WriteSymbols(self)
453
454     def SetCalculatedProperties(self):
455         super().SetCalculatedProperties()
456         for entry in self._entries.values():
457             entry.SetCalculatedProperties()
458
459     def SetImagePos(self, image_pos):
460         super().SetImagePos(image_pos)
461         if self.compress == 'none':
462             for entry in self._entries.values():
463                 entry.SetImagePos(image_pos + self.offset)
464
465     def ProcessContents(self):
466         sizes_ok_base = super(Entry_section, self).ProcessContents()
467         sizes_ok = True
468         for entry in self._entries.values():
469             if not entry.ProcessContents():
470                 sizes_ok = False
471         return sizes_ok and sizes_ok_base
472
473     def WriteMap(self, fd, indent):
474         """Write a map of the section to a .map file
475
476         Args:
477             fd: File to write the map to
478         """
479         Entry.WriteMapLine(fd, indent, self.name, self.offset or 0,
480                            self.size, self.image_pos)
481         for entry in self._entries.values():
482             entry.WriteMap(fd, indent + 1)
483
484     def GetEntries(self):
485         return self._entries
486
487     def GetContentsByPhandle(self, phandle, source_entry, required):
488         """Get the data contents of an entry specified by a phandle
489
490         This uses a phandle to look up a node and and find the entry
491         associated with it. Then it returns the contents of that entry.
492
493         The node must be a direct subnode of this section.
494
495         Args:
496             phandle: Phandle to look up (integer)
497             source_entry: Entry containing that phandle (used for error
498                 reporting)
499             required: True if the data must be present, False if it is OK to
500                 return None
501
502         Returns:
503             data from associated entry (as a string), or None if not found
504         """
505         node = self._node.GetFdt().LookupPhandle(phandle)
506         if not node:
507             source_entry.Raise("Cannot find node for phandle %d" % phandle)
508         entry = self.FindEntryByNode(node)
509         if not entry:
510             source_entry.Raise("Cannot find entry for node '%s'" % node.name)
511         return entry.GetData(required)
512
513     def LookupSymbol(self, sym_name, optional, msg, base_addr, entries=None):
514         """Look up a symbol in an ELF file
515
516         Looks up a symbol in an ELF file. Only entry types which come from an
517         ELF image can be used by this function.
518
519         At present the only entry properties supported are:
520             offset
521             image_pos - 'base_addr' is added if this is not an end-at-4gb image
522             size
523
524         Args:
525             sym_name: Symbol name in the ELF file to look up in the format
526                 _binman_<entry>_prop_<property> where <entry> is the name of
527                 the entry and <property> is the property to find (e.g.
528                 _binman_u_boot_prop_offset). As a special case, you can append
529                 _any to <entry> to have it search for any matching entry. E.g.
530                 _binman_u_boot_any_prop_offset will match entries called u-boot,
531                 u-boot-img and u-boot-nodtb)
532             optional: True if the symbol is optional. If False this function
533                 will raise if the symbol is not found
534             msg: Message to display if an error occurs
535             base_addr: Base address of image. This is added to the returned
536                 image_pos in most cases so that the returned position indicates
537                 where the targetted entry/binary has actually been loaded. But
538                 if end-at-4gb is used, this is not done, since the binary is
539                 already assumed to be linked to the ROM position and using
540                 execute-in-place (XIP).
541
542         Returns:
543             Value that should be assigned to that symbol, or None if it was
544                 optional and not found
545
546         Raises:
547             ValueError if the symbol is invalid or not found, or references a
548                 property which is not supported
549         """
550         m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name)
551         if not m:
552             raise ValueError("%s: Symbol '%s' has invalid format" %
553                              (msg, sym_name))
554         entry_name, prop_name = m.groups()
555         entry_name = entry_name.replace('_', '-')
556         if not entries:
557             entries = self._entries
558         entry = entries.get(entry_name)
559         if not entry:
560             if entry_name.endswith('-any'):
561                 root = entry_name[:-4]
562                 for name in entries:
563                     if name.startswith(root):
564                         rest = name[len(root):]
565                         if rest in ['', '-img', '-nodtb']:
566                             entry = entries[name]
567         if not entry:
568             err = ("%s: Entry '%s' not found in list (%s)" %
569                    (msg, entry_name, ','.join(entries.keys())))
570             if optional:
571                 print('Warning: %s' % err, file=sys.stderr)
572                 return None
573             raise ValueError(err)
574         if prop_name == 'offset':
575             return entry.offset
576         elif prop_name == 'image_pos':
577             value = entry.image_pos
578             if not self.GetImage()._end_4gb:
579                 value += base_addr
580             return value
581         if prop_name == 'size':
582             return entry.size
583         else:
584             raise ValueError("%s: No such property '%s'" % (msg, prop_name))
585
586     def GetRootSkipAtStart(self):
587         """Get the skip-at-start value for the top-level section
588
589         This is used to find out the starting offset for root section that
590         contains this section. If this is a top-level section then it returns
591         the skip-at-start offset for this section.
592
593         This is used to get the absolute position of section within the image.
594
595         Returns:
596             Integer skip-at-start value for the root section containing this
597                 section
598         """
599         if self.section:
600             return self.section.GetRootSkipAtStart()
601         return self._skip_at_start
602
603     def GetStartOffset(self):
604         """Get the start offset for this section
605
606         Returns:
607             The first available offset in this section (typically 0)
608         """
609         return self._skip_at_start
610
611     def GetImageSize(self):
612         """Get the size of the image containing this section
613
614         Returns:
615             Image size as an integer number of bytes, which may be None if the
616                 image size is dynamic and its sections have not yet been packed
617         """
618         return self.GetImage().size
619
620     def FindEntryType(self, etype):
621         """Find an entry type in the section
622
623         Args:
624             etype: Entry type to find
625         Returns:
626             entry matching that type, or None if not found
627         """
628         for entry in self._entries.values():
629             if entry.etype == etype:
630                 return entry
631         return None
632
633     def GetEntryContents(self, skip_entry=None):
634         """Call ObtainContents() for each entry in the section
635         """
636         def _CheckDone(entry):
637             if entry != skip_entry:
638                 if not entry.ObtainContents():
639                     next_todo.append(entry)
640             return entry
641
642         todo = self._entries.values()
643         for passnum in range(3):
644             threads = state.GetThreads()
645             next_todo = []
646
647             if threads == 0:
648                 for entry in todo:
649                     _CheckDone(entry)
650             else:
651                 with concurrent.futures.ThreadPoolExecutor(
652                         max_workers=threads) as executor:
653                     future_to_data = {
654                         entry: executor.submit(_CheckDone, entry)
655                         for entry in todo}
656                     timeout = 60
657                     if self.GetImage().test_section_timeout:
658                         timeout = 0
659                     done, not_done = concurrent.futures.wait(
660                         future_to_data.values(), timeout=timeout)
661                     # Make sure we check the result, so any exceptions are
662                     # generated. Check the results in entry order, since tests
663                     # may expect earlier entries to fail first.
664                     for entry in todo:
665                         job = future_to_data[entry]
666                         job.result()
667                     if not_done:
668                         self.Raise('Timed out obtaining contents')
669
670             todo = next_todo
671             if not todo:
672                 break
673
674         if todo:
675             self.Raise('Internal error: Could not complete processing of contents: remaining %s' %
676                        todo)
677         return True
678
679     def _SetEntryOffsetSize(self, name, offset, size):
680         """Set the offset and size of an entry
681
682         Args:
683             name: Entry name to update
684             offset: New offset, or None to leave alone
685             size: New size, or None to leave alone
686         """
687         entry = self._entries.get(name)
688         if not entry:
689             self._Raise("Unable to set offset/size for unknown entry '%s'" %
690                         name)
691         entry.SetOffsetSize(self._skip_at_start + offset if offset is not None
692                             else None, size)
693
694     def GetEntryOffsets(self):
695         """Handle entries that want to set the offset/size of other entries
696
697         This calls each entry's GetOffsets() method. If it returns a list
698         of entries to update, it updates them.
699         """
700         for entry in self._entries.values():
701             offset_dict = entry.GetOffsets()
702             for name, info in offset_dict.items():
703                 self._SetEntryOffsetSize(name, *info)
704
705     def CheckSize(self):
706         contents_size = len(self.data)
707
708         size = self.size
709         if not size:
710             data = self.GetPaddedData(self.data)
711             size = len(data)
712             size = tools.align(size, self.align_size)
713
714         if self.size and contents_size > self.size:
715             self._Raise("contents size %#x (%d) exceeds section size %#x (%d)" %
716                         (contents_size, contents_size, self.size, self.size))
717         if not self.size:
718             self.size = size
719         if self.size != tools.align(self.size, self.align_size):
720             self._Raise("Size %#x (%d) does not match align-size %#x (%d)" %
721                         (self.size, self.size, self.align_size,
722                          self.align_size))
723         return size
724
725     def ListEntries(self, entries, indent):
726         """List the files in the section"""
727         Entry.AddEntryInfo(entries, indent, self.name, self.etype, self.size,
728                            self.image_pos, None, self.offset, self)
729         for entry in self._entries.values():
730             entry.ListEntries(entries, indent + 1)
731
732     def LoadData(self, decomp=True):
733         for entry in self._entries.values():
734             entry.LoadData(decomp)
735         self.Detail('Loaded data')
736
737     def GetImage(self):
738         """Get the image containing this section
739
740         Note that a top-level section is actually an Image, so this function may
741         return self.
742
743         Returns:
744             Image object containing this section
745         """
746         if not self.section:
747             return self
748         return self.section.GetImage()
749
750     def GetSort(self):
751         """Check if the entries in this section will be sorted
752
753         Returns:
754             True if to be sorted, False if entries will be left in the order
755                 they appear in the device tree
756         """
757         return self._sort
758
759     def ReadData(self, decomp=True, alt_format=None):
760         tout.info("ReadData path='%s'" % self.GetPath())
761         parent_data = self.section.ReadData(True, alt_format)
762         offset = self.offset - self.section._skip_at_start
763         data = parent_data[offset:offset + self.size]
764         tout.info(
765             '%s: Reading data from offset %#x-%#x (real %#x), size %#x, got %#x' %
766                   (self.GetPath(), self.offset, self.offset + self.size, offset,
767                    self.size, len(data)))
768         return data
769
770     def ReadChildData(self, child, decomp=True, alt_format=None):
771         tout.debug(f"ReadChildData for child '{child.GetPath()}'")
772         parent_data = self.ReadData(True, alt_format)
773         offset = child.offset - self._skip_at_start
774         tout.debug("Extract for child '%s': offset %#x, skip_at_start %#x, result %#x" %
775                    (child.GetPath(), child.offset, self._skip_at_start, offset))
776         data = parent_data[offset:offset + child.size]
777         if decomp:
778             indata = data
779             data = child.DecompressData(indata)
780             if child.uncomp_size:
781                 tout.info("%s: Decompressing data size %#x with algo '%s' to data size %#x" %
782                             (child.GetPath(), len(indata), child.compress,
783                             len(data)))
784         if alt_format:
785             new_data = child.GetAltFormat(data, alt_format)
786             if new_data is not None:
787                 data = new_data
788         return data
789
790     def WriteData(self, data, decomp=True):
791         self.Raise("Replacing sections is not implemented yet")
792
793     def WriteChildData(self, child):
794         return True
795
796     def SetAllowMissing(self, allow_missing):
797         """Set whether a section allows missing external blobs
798
799         Args:
800             allow_missing: True if allowed, False if not allowed
801         """
802         self.allow_missing = allow_missing
803         for entry in self._entries.values():
804             entry.SetAllowMissing(allow_missing)
805
806     def SetAllowFakeBlob(self, allow_fake):
807         """Set whether a section allows to create a fake blob
808
809         Args:
810             allow_fake_blob: True if allowed, False if not allowed
811         """
812         super().SetAllowFakeBlob(allow_fake)
813         for entry in self._entries.values():
814             entry.SetAllowFakeBlob(allow_fake)
815
816     def CheckMissing(self, missing_list):
817         """Check if any entries in this section have missing external blobs
818
819         If there are missing blobs, the entries are added to the list
820
821         Args:
822             missing_list: List of Entry objects to be added to
823         """
824         for entry in self._entries.values():
825             entry.CheckMissing(missing_list)
826
827     def CheckFakedBlobs(self, faked_blobs_list):
828         """Check if any entries in this section have faked external blobs
829
830         If there are faked blobs, the entries are added to the list
831
832         Args:
833             fake_blobs_list: List of Entry objects to be added to
834         """
835         for entry in self._entries.values():
836             entry.CheckFakedBlobs(faked_blobs_list)
837
838     def check_missing_bintools(self, missing_list):
839         """Check if any entries in this section have missing bintools
840
841         If there are missing bintools, these are added to the list
842
843         Args:
844             missing_list: List of Bintool objects to be added to
845         """
846         super().check_missing_bintools(missing_list)
847         for entry in self._entries.values():
848             entry.check_missing_bintools(missing_list)
849
850     def _CollectEntries(self, entries, entries_by_name, add_entry):
851         """Collect all the entries in an section
852
853         This builds up a dict of entries in this section and all subsections.
854         Entries are indexed by path and by name.
855
856         Since all paths are unique, entries will not have any conflicts. However
857         entries_by_name make have conflicts if two entries have the same name
858         (e.g. with different parent sections). In this case, an entry at a
859         higher level in the hierarchy will win over a lower-level entry.
860
861         Args:
862             entries: dict to put entries:
863                 key: entry path
864                 value: Entry object
865             entries_by_name: dict to put entries
866                 key: entry name
867                 value: Entry object
868             add_entry: Entry to add
869         """
870         entries[add_entry.GetPath()] = add_entry
871         to_add = add_entry.GetEntries()
872         if to_add:
873             for entry in to_add.values():
874                 entries[entry.GetPath()] = entry
875             for entry in to_add.values():
876                 self._CollectEntries(entries, entries_by_name, entry)
877         entries_by_name[add_entry.name] = add_entry
878
879     def MissingArgs(self, entry, missing):
880         """Report a missing argument, if enabled
881
882         For entries which require arguments, this reports an error if some are
883         missing. If missing entries are being ignored (e.g. because we read the
884         entry from an image rather than creating it), this function does
885         nothing.
886
887         Args:
888             entry (Entry): Entry to raise the error on
889             missing (list of str): List of missing properties / entry args, each
890             a string
891         """
892         if not self._ignore_missing:
893             missing = ', '.join(missing)
894             entry.Raise(f'Missing required properties/entry args: {missing}')
895
896     def CheckAltFormats(self, alt_formats):
897         for entry in self._entries.values():
898             entry.CheckAltFormats(alt_formats)
899
900     def AddBintools(self, btools):
901         super().AddBintools(btools)
902         for entry in self._entries.values():
903             entry.AddBintools(btools)