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