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