patman: Drop references to __future__
[oweals/u-boot.git] / tools / binman / cbfs_util.py
1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright 2019 Google LLC
3 # Written by Simon Glass <sjg@chromium.org>
4
5 """Support for coreboot's CBFS format
6
7 CBFS supports a header followed by a number of files, generally targeted at SPI
8 flash.
9
10 The format is somewhat defined by documentation in the coreboot tree although
11 it is necessary to rely on the C structures and source code (mostly cbfstool)
12 to fully understand it.
13
14 Currently supported: raw and stage types with compression, padding empty areas
15     with empty files, fixed-offset files
16 """
17
18 from collections import OrderedDict
19 import io
20 import struct
21 import sys
22
23 import command
24 import elf
25 import tools
26
27 # Set to True to enable printing output while working
28 DEBUG = False
29
30 # Set to True to enable output from running cbfstool for debugging
31 VERBOSE = False
32
33 # The master header, at the start of the CBFS
34 HEADER_FORMAT      = '>IIIIIIII'
35 HEADER_LEN         = 0x20
36 HEADER_MAGIC       = 0x4f524243
37 HEADER_VERSION1    = 0x31313131
38 HEADER_VERSION2    = 0x31313132
39
40 # The file header, at the start of each file in the CBFS
41 FILE_HEADER_FORMAT = b'>8sIIII'
42 FILE_HEADER_LEN    = 0x18
43 FILE_MAGIC         = b'LARCHIVE'
44 FILENAME_ALIGN     = 16  # Filename lengths are aligned to this
45
46 # A stage header containing information about 'stage' files
47 # Yes this is correct: this header is in litte-endian format
48 STAGE_FORMAT       = '<IQQII'
49 STAGE_LEN          = 0x1c
50
51 # An attribute describring the compression used in a file
52 ATTR_COMPRESSION_FORMAT = '>IIII'
53 ATTR_COMPRESSION_LEN = 0x10
54
55 # Attribute tags
56 # Depending on how the header was initialised, it may be backed with 0x00 or
57 # 0xff. Support both.
58 FILE_ATTR_TAG_UNUSED        = 0
59 FILE_ATTR_TAG_UNUSED2       = 0xffffffff
60 FILE_ATTR_TAG_COMPRESSION   = 0x42435a4c
61 FILE_ATTR_TAG_HASH          = 0x68736148
62 FILE_ATTR_TAG_POSITION      = 0x42435350  # PSCB
63 FILE_ATTR_TAG_ALIGNMENT     = 0x42434c41  # ALCB
64 FILE_ATTR_TAG_PADDING       = 0x47444150  # PDNG
65
66 # This is 'the size of bootblock reserved in firmware image (cbfs.txt)'
67 # Not much more info is available, but we set it to 4, due to this comment in
68 # cbfstool.c:
69 # This causes 4 bytes to be left out at the end of the image, for two reasons:
70 # 1. The cbfs master header pointer resides there
71 # 2. Ssme cbfs implementations assume that an image that resides below 4GB has
72 #    a bootblock and get confused when the end of the image is at 4GB == 0.
73 MIN_BOOTBLOCK_SIZE     = 4
74
75 # Files start aligned to this boundary in the CBFS
76 ENTRY_ALIGN    = 0x40
77
78 # CBFSs must declare an architecture since much of the logic is designed with
79 # x86 in mind. The effect of setting this value is not well documented, but in
80 # general x86 is used and this makes use of a boot block and an image that ends
81 # at the end of 32-bit address space.
82 ARCHITECTURE_UNKNOWN  = 0xffffffff
83 ARCHITECTURE_X86      = 0x00000001
84 ARCHITECTURE_ARM      = 0x00000010
85 ARCHITECTURE_AARCH64  = 0x0000aa64
86 ARCHITECTURE_MIPS     = 0x00000100
87 ARCHITECTURE_RISCV    = 0xc001d0de
88 ARCHITECTURE_PPC64    = 0x407570ff
89
90 ARCH_NAMES = {
91     ARCHITECTURE_UNKNOWN  : 'unknown',
92     ARCHITECTURE_X86      : 'x86',
93     ARCHITECTURE_ARM      : 'arm',
94     ARCHITECTURE_AARCH64  : 'arm64',
95     ARCHITECTURE_MIPS     : 'mips',
96     ARCHITECTURE_RISCV    : 'riscv',
97     ARCHITECTURE_PPC64    : 'ppc64',
98     }
99
100 # File types. Only supported ones are included here
101 TYPE_CBFSHEADER     = 0x02   # Master header, HEADER_FORMAT
102 TYPE_STAGE          = 0x10   # Stage, holding an executable, see STAGE_FORMAT
103 TYPE_RAW            = 0x50   # Raw file, possibly compressed
104 TYPE_EMPTY          = 0xffffffff     # Empty data
105
106 # Compression types
107 COMPRESS_NONE, COMPRESS_LZMA, COMPRESS_LZ4 = range(3)
108
109 COMPRESS_NAMES = {
110     COMPRESS_NONE : 'none',
111     COMPRESS_LZMA : 'lzma',
112     COMPRESS_LZ4  : 'lz4',
113     }
114
115 def find_arch(find_name):
116     """Look up an architecture name
117
118     Args:
119         find_name: Architecture name to find
120
121     Returns:
122         ARCHITECTURE_... value or None if not found
123     """
124     for arch, name in ARCH_NAMES.items():
125         if name == find_name:
126             return arch
127     return None
128
129 def find_compress(find_name):
130     """Look up a compression algorithm name
131
132     Args:
133         find_name: Compression algorithm name to find
134
135     Returns:
136         COMPRESS_... value or None if not found
137     """
138     for compress, name in COMPRESS_NAMES.items():
139         if name == find_name:
140             return compress
141     return None
142
143 def compress_name(compress):
144     """Look up the name of a compression algorithm
145
146     Args:
147         compress: Compression algorithm number to find (COMPRESS_...)
148
149     Returns:
150         Compression algorithm name (string)
151
152     Raises:
153         KeyError if the algorithm number is invalid
154     """
155     return COMPRESS_NAMES[compress]
156
157 def align_int(val, align):
158     """Align a value up to the given alignment
159
160     Args:
161         val: Integer value to align
162         align: Integer alignment value (e.g. 4 to align to 4-byte boundary)
163
164     Returns:
165         integer value aligned to the required boundary, rounding up if necessary
166     """
167     return int((val + align - 1) / align) * align
168
169 def align_int_down(val, align):
170     """Align a value down to the given alignment
171
172     Args:
173         val: Integer value to align
174         align: Integer alignment value (e.g. 4 to align to 4-byte boundary)
175
176     Returns:
177         integer value aligned to the required boundary, rounding down if
178             necessary
179     """
180     return int(val / align) * align
181
182 def _pack_string(instr):
183     """Pack a string to the required aligned size by adding padding
184
185     Args:
186         instr: String to process
187
188     Returns:
189         String with required padding (at least one 0x00 byte) at the end
190     """
191     val = tools.ToBytes(instr)
192     pad_len = align_int(len(val) + 1, FILENAME_ALIGN)
193     return val + tools.GetBytes(0, pad_len - len(val))
194
195
196 class CbfsFile(object):
197     """Class to represent a single CBFS file
198
199     This is used to hold the information about a file, including its contents.
200     Use the get_data_and_offset() method to obtain the raw output for writing to
201     CBFS.
202
203     Properties:
204         name: Name of file
205         offset: Offset of file data from start of file header
206         cbfs_offset: Offset of file data in bytes from start of CBFS, or None to
207             place this file anyway
208         data: Contents of file, uncompressed
209         orig_data: Original data added to the file, possibly compressed
210         data_len: Length of (possibly compressed) data in bytes
211         ftype: File type (TYPE_...)
212         compression: Compression type (COMPRESS_...)
213         memlen: Length of data in memory, i.e. the uncompressed length, None if
214             no compression algortihm is selected
215         load: Load address in memory if known, else None
216         entry: Entry address in memory if known, else None. This is where
217             execution starts after the file is loaded
218         base_address: Base address to use for 'stage' files
219         erase_byte: Erase byte to use for padding between the file header and
220             contents (used for empty files)
221         size: Size of the file in bytes (used for empty files)
222     """
223     def __init__(self, name, ftype, data, cbfs_offset, compress=COMPRESS_NONE):
224         self.name = name
225         self.offset = None
226         self.cbfs_offset = cbfs_offset
227         self.data = data
228         self.orig_data = data
229         self.ftype = ftype
230         self.compress = compress
231         self.memlen = None
232         self.load = None
233         self.entry = None
234         self.base_address = None
235         self.data_len = len(data)
236         self.erase_byte = None
237         self.size = None
238
239     def decompress(self):
240         """Handle decompressing data if necessary"""
241         indata = self.data
242         if self.compress == COMPRESS_LZ4:
243             data = tools.Decompress(indata, 'lz4', with_header=False)
244         elif self.compress == COMPRESS_LZMA:
245             data = tools.Decompress(indata, 'lzma', with_header=False)
246         else:
247             data = indata
248         self.memlen = len(data)
249         self.data = data
250         self.data_len = len(indata)
251
252     @classmethod
253     def stage(cls, base_address, name, data, cbfs_offset):
254         """Create a new stage file
255
256         Args:
257             base_address: Int base address for memory-mapping of ELF file
258             name: String file name to put in CBFS (does not need to correspond
259                 to the name that the file originally came from)
260             data: Contents of file
261             cbfs_offset: Offset of file data in bytes from start of CBFS, or
262                 None to place this file anyway
263
264         Returns:
265             CbfsFile object containing the file information
266         """
267         cfile = CbfsFile(name, TYPE_STAGE, data, cbfs_offset)
268         cfile.base_address = base_address
269         return cfile
270
271     @classmethod
272     def raw(cls, name, data, cbfs_offset, compress):
273         """Create a new raw file
274
275         Args:
276             name: String file name to put in CBFS (does not need to correspond
277                 to the name that the file originally came from)
278             data: Contents of file
279             cbfs_offset: Offset of file data in bytes from start of CBFS, or
280                 None to place this file anyway
281             compress: Compression algorithm to use (COMPRESS_...)
282
283         Returns:
284             CbfsFile object containing the file information
285         """
286         return CbfsFile(name, TYPE_RAW, data, cbfs_offset, compress)
287
288     @classmethod
289     def empty(cls, space_to_use, erase_byte):
290         """Create a new empty file of a given size
291
292         Args:
293             space_to_use:: Size of available space, which must be at least as
294                 large as the alignment size for this CBFS
295             erase_byte: Byte to use for contents of file (repeated through the
296                 whole file)
297
298         Returns:
299             CbfsFile object containing the file information
300         """
301         cfile = CbfsFile('', TYPE_EMPTY, b'', None)
302         cfile.size = space_to_use - FILE_HEADER_LEN - FILENAME_ALIGN
303         cfile.erase_byte = erase_byte
304         return cfile
305
306     def calc_start_offset(self):
307         """Check if this file needs to start at a particular offset in CBFS
308
309         Returns:
310             None if the file can be placed anywhere, or
311             the largest offset where the file could start (integer)
312         """
313         if self.cbfs_offset is None:
314             return None
315         return self.cbfs_offset - self.get_header_len()
316
317     def get_header_len(self):
318         """Get the length of headers required for a file
319
320         This is the minimum length required before the actual data for this file
321         could start. It might start later if there is padding.
322
323         Returns:
324             Total length of all non-data fields, in bytes
325         """
326         name = _pack_string(self.name)
327         hdr_len = len(name) + FILE_HEADER_LEN
328         if self.ftype == TYPE_STAGE:
329             pass
330         elif self.ftype == TYPE_RAW:
331             hdr_len += ATTR_COMPRESSION_LEN
332         elif self.ftype == TYPE_EMPTY:
333             pass
334         else:
335             raise ValueError('Unknown file type %#x\n' % self.ftype)
336         return hdr_len
337
338     def get_data_and_offset(self, offset=None, pad_byte=None):
339         """Obtain the contents of the file, in CBFS format and the offset of
340         the data within the file
341
342         Returns:
343             tuple:
344                 bytes representing the contents of this file, packed and aligned
345                     for directly inserting into the final CBFS output
346                 offset to the file data from the start of the returned data.
347         """
348         name = _pack_string(self.name)
349         hdr_len = len(name) + FILE_HEADER_LEN
350         attr_pos = 0
351         content = b''
352         attr = b''
353         pad = b''
354         data = self.data
355         if self.ftype == TYPE_STAGE:
356             elf_data = elf.DecodeElf(data, self.base_address)
357             content = struct.pack(STAGE_FORMAT, self.compress,
358                                   elf_data.entry, elf_data.load,
359                                   len(elf_data.data), elf_data.memsize)
360             data = elf_data.data
361         elif self.ftype == TYPE_RAW:
362             orig_data = data
363             if self.compress == COMPRESS_LZ4:
364                 data = tools.Compress(orig_data, 'lz4', with_header=False)
365             elif self.compress == COMPRESS_LZMA:
366                 data = tools.Compress(orig_data, 'lzma', with_header=False)
367             self.memlen = len(orig_data)
368             self.data_len = len(data)
369             attr = struct.pack(ATTR_COMPRESSION_FORMAT,
370                                FILE_ATTR_TAG_COMPRESSION, ATTR_COMPRESSION_LEN,
371                                self.compress, self.memlen)
372         elif self.ftype == TYPE_EMPTY:
373             data = tools.GetBytes(self.erase_byte, self.size)
374         else:
375             raise ValueError('Unknown type %#x when writing\n' % self.ftype)
376         if attr:
377             attr_pos = hdr_len
378             hdr_len += len(attr)
379         if self.cbfs_offset is not None:
380             pad_len = self.cbfs_offset - offset - hdr_len
381             if pad_len < 0:  # pragma: no cover
382                 # Test coverage of this is not available since this should never
383                 # happen. It indicates that get_header_len() provided an
384                 # incorrect value (too small) so that we decided that we could
385                 # put this file at the requested place, but in fact a previous
386                 # file extends far enough into the CBFS that this is not
387                 # possible.
388                 raise ValueError("Internal error: CBFS file '%s': Requested offset %#x but current output position is %#x" %
389                                  (self.name, self.cbfs_offset, offset))
390             pad = tools.GetBytes(pad_byte, pad_len)
391             hdr_len += pad_len
392
393         # This is the offset of the start of the file's data,
394         size = len(content) + len(data)
395         hdr = struct.pack(FILE_HEADER_FORMAT, FILE_MAGIC, size,
396                           self.ftype, attr_pos, hdr_len)
397
398         # Do a sanity check of the get_header_len() function, to ensure that it
399         # stays in lockstep with this function
400         expected_len = self.get_header_len()
401         actual_len = len(hdr + name + attr)
402         if expected_len != actual_len:  # pragma: no cover
403             # Test coverage of this is not available since this should never
404             # happen. It probably indicates that get_header_len() is broken.
405             raise ValueError("Internal error: CBFS file '%s': Expected headers of %#x bytes, got %#d" %
406                              (self.name, expected_len, actual_len))
407         return hdr + name + attr + pad + content + data, hdr_len
408
409
410 class CbfsWriter(object):
411     """Class to handle writing a Coreboot File System (CBFS)
412
413     Usage is something like:
414
415         cbw = CbfsWriter(size)
416         cbw.add_file_raw('u-boot', tools.ReadFile('u-boot.bin'))
417         ...
418         data, cbfs_offset = cbw.get_data_and_offset()
419
420     Attributes:
421         _master_name: Name of the file containing the master header
422         _size: Size of the filesystem, in bytes
423         _files: Ordered list of files in the CBFS, each a CbfsFile
424         _arch: Architecture of the CBFS (ARCHITECTURE_...)
425         _bootblock_size: Size of the bootblock, typically at the end of the CBFS
426         _erase_byte: Byte to use for empty space in the CBFS
427         _align: Alignment to use for files, typically ENTRY_ALIGN
428         _base_address: Boot block offset in bytes from the start of CBFS.
429             Typically this is located at top of the CBFS. It is 0 when there is
430             no boot block
431         _header_offset: Offset of master header in bytes from start of CBFS
432         _contents_offset: Offset of first file header
433         _hdr_at_start: True if the master header is at the start of the CBFS,
434             instead of the end as normal for x86
435         _add_fileheader: True to add a fileheader around the master header
436     """
437     def __init__(self, size, arch=ARCHITECTURE_X86):
438         """Set up a new CBFS
439
440         This sets up all properties to default values. Files can be added using
441         add_file_raw(), etc.
442
443         Args:
444             size: Size of CBFS in bytes
445             arch: Architecture to declare for CBFS
446         """
447         self._master_name = 'cbfs master header'
448         self._size = size
449         self._files = OrderedDict()
450         self._arch = arch
451         self._bootblock_size = 0
452         self._erase_byte = 0xff
453         self._align = ENTRY_ALIGN
454         self._add_fileheader = False
455         if self._arch == ARCHITECTURE_X86:
456             # Allow 4 bytes for the header pointer. That holds the
457             # twos-compliment negative offset of the master header in bytes
458             # measured from one byte past the end of the CBFS
459             self._base_address = self._size - max(self._bootblock_size,
460                                                   MIN_BOOTBLOCK_SIZE)
461             self._header_offset = self._base_address - HEADER_LEN
462             self._contents_offset = 0
463             self._hdr_at_start = False
464         else:
465             # For non-x86, different rules apply
466             self._base_address = 0
467             self._header_offset = align_int(self._base_address +
468                                             self._bootblock_size, 4)
469             self._contents_offset = align_int(self._header_offset +
470                                               FILE_HEADER_LEN +
471                                               self._bootblock_size, self._align)
472             self._hdr_at_start = True
473
474     def _skip_to(self, fd, offset):
475         """Write out pad bytes until a given offset
476
477         Args:
478             fd: File objext to write to
479             offset: Offset to write to
480         """
481         if fd.tell() > offset:
482             raise ValueError('No space for data before offset %#x (current offset %#x)' %
483                              (offset, fd.tell()))
484         fd.write(tools.GetBytes(self._erase_byte, offset - fd.tell()))
485
486     def _pad_to(self, fd, offset):
487         """Write out pad bytes and/or an empty file until a given offset
488
489         Args:
490             fd: File objext to write to
491             offset: Offset to write to
492         """
493         self._align_to(fd, self._align)
494         upto = fd.tell()
495         if upto > offset:
496             raise ValueError('No space for data before pad offset %#x (current offset %#x)' %
497                              (offset, upto))
498         todo = align_int_down(offset - upto, self._align)
499         if todo:
500             cbf = CbfsFile.empty(todo, self._erase_byte)
501             fd.write(cbf.get_data_and_offset()[0])
502         self._skip_to(fd, offset)
503
504     def _align_to(self, fd, align):
505         """Write out pad bytes until a given alignment is reached
506
507         This only aligns if the resulting output would not reach the end of the
508         CBFS, since we want to leave the last 4 bytes for the master-header
509         pointer.
510
511         Args:
512             fd: File objext to write to
513             align: Alignment to require (e.g. 4 means pad to next 4-byte
514                 boundary)
515         """
516         offset = align_int(fd.tell(), align)
517         if offset < self._size:
518             self._skip_to(fd, offset)
519
520     def add_file_stage(self, name, data, cbfs_offset=None):
521         """Add a new stage file to the CBFS
522
523         Args:
524             name: String file name to put in CBFS (does not need to correspond
525                 to the name that the file originally came from)
526             data: Contents of file
527             cbfs_offset: Offset of this file's data within the CBFS, in bytes,
528                 or None to place this file anywhere
529
530         Returns:
531             CbfsFile object created
532         """
533         cfile = CbfsFile.stage(self._base_address, name, data, cbfs_offset)
534         self._files[name] = cfile
535         return cfile
536
537     def add_file_raw(self, name, data, cbfs_offset=None,
538                      compress=COMPRESS_NONE):
539         """Create a new raw file
540
541         Args:
542             name: String file name to put in CBFS (does not need to correspond
543                 to the name that the file originally came from)
544             data: Contents of file
545             cbfs_offset: Offset of this file's data within the CBFS, in bytes,
546                 or None to place this file anywhere
547             compress: Compression algorithm to use (COMPRESS_...)
548
549         Returns:
550             CbfsFile object created
551         """
552         cfile = CbfsFile.raw(name, data, cbfs_offset, compress)
553         self._files[name] = cfile
554         return cfile
555
556     def _write_header(self, fd, add_fileheader):
557         """Write out the master header to a CBFS
558
559         Args:
560             fd: File object
561             add_fileheader: True to place the master header in a file header
562                 record
563         """
564         if fd.tell() > self._header_offset:
565             raise ValueError('No space for header at offset %#x (current offset %#x)' %
566                              (self._header_offset, fd.tell()))
567         if not add_fileheader:
568             self._pad_to(fd, self._header_offset)
569         hdr = struct.pack(HEADER_FORMAT, HEADER_MAGIC, HEADER_VERSION2,
570                           self._size, self._bootblock_size, self._align,
571                           self._contents_offset, self._arch, 0xffffffff)
572         if add_fileheader:
573             name = _pack_string(self._master_name)
574             fd.write(struct.pack(FILE_HEADER_FORMAT, FILE_MAGIC, len(hdr),
575                                  TYPE_CBFSHEADER, 0,
576                                  FILE_HEADER_LEN + len(name)))
577             fd.write(name)
578             self._header_offset = fd.tell()
579             fd.write(hdr)
580             self._align_to(fd, self._align)
581         else:
582             fd.write(hdr)
583
584     def get_data(self):
585         """Obtain the full contents of the CBFS
586
587         Thhis builds the CBFS with headers and all required files.
588
589         Returns:
590             'bytes' type containing the data
591         """
592         fd = io.BytesIO()
593
594         # THe header can go at the start in some cases
595         if self._hdr_at_start:
596             self._write_header(fd, add_fileheader=self._add_fileheader)
597         self._skip_to(fd, self._contents_offset)
598
599         # Write out each file
600         for cbf in self._files.values():
601             # Place the file at its requested place, if any
602             offset = cbf.calc_start_offset()
603             if offset is not None:
604                 self._pad_to(fd, align_int_down(offset, self._align))
605             pos = fd.tell()
606             data, data_offset = cbf.get_data_and_offset(pos, self._erase_byte)
607             fd.write(data)
608             self._align_to(fd, self._align)
609             cbf.calced_cbfs_offset = pos + data_offset
610         if not self._hdr_at_start:
611             self._write_header(fd, add_fileheader=self._add_fileheader)
612
613         # Pad to the end and write a pointer to the CBFS master header
614         self._pad_to(fd, self._base_address or self._size - 4)
615         rel_offset = self._header_offset - self._size
616         fd.write(struct.pack('<I', rel_offset & 0xffffffff))
617
618         return fd.getvalue()
619
620
621 class CbfsReader(object):
622     """Class to handle reading a Coreboot File System (CBFS)
623
624     Usage is something like:
625         cbfs = cbfs_util.CbfsReader(data)
626         cfile = cbfs.files['u-boot']
627         self.WriteFile('u-boot.bin', cfile.data)
628
629     Attributes:
630         files: Ordered list of CbfsFile objects
631         align: Alignment to use for files, typically ENTRT_ALIGN
632         stage_base_address: Base address to use when mapping ELF files into the
633             CBFS for TYPE_STAGE files. If this is larger than the code address
634             of the ELF file, then data at the start of the ELF file will not
635             appear in the CBFS. Currently there are no tests for behaviour as
636             documentation is sparse
637         magic: Integer magic number from master header (HEADER_MAGIC)
638         version: Version number of CBFS (HEADER_VERSION2)
639         rom_size: Size of CBFS
640         boot_block_size: Size of boot block
641         cbfs_offset: Offset of the first file in bytes from start of CBFS
642         arch: Architecture of CBFS file (ARCHITECTURE_...)
643     """
644     def __init__(self, data, read=True):
645         self.align = ENTRY_ALIGN
646         self.arch = None
647         self.boot_block_size = None
648         self.cbfs_offset = None
649         self.files = OrderedDict()
650         self.magic = None
651         self.rom_size = None
652         self.stage_base_address = 0
653         self.version = None
654         self.data = data
655         if read:
656             self.read()
657
658     def read(self):
659         """Read all the files in the CBFS and add them to self.files"""
660         with io.BytesIO(self.data) as fd:
661             # First, get the master header
662             if not self._find_and_read_header(fd, len(self.data)):
663                 raise ValueError('Cannot find master header')
664             fd.seek(self.cbfs_offset)
665
666             # Now read in the files one at a time
667             while True:
668                 cfile = self._read_next_file(fd)
669                 if cfile:
670                     self.files[cfile.name] = cfile
671                 elif cfile is False:
672                     break
673
674     def _find_and_read_header(self, fd, size):
675         """Find and read the master header in the CBFS
676
677         This looks at the pointer word at the very end of the CBFS. This is an
678         offset to the header relative to the size of the CBFS, which is assumed
679         to be known. Note that the offset is in *little endian* format.
680
681         Args:
682             fd: File to read from
683             size: Size of file
684
685         Returns:
686             True if header was found, False if not
687         """
688         orig_pos = fd.tell()
689         fd.seek(size - 4)
690         rel_offset, = struct.unpack('<I', fd.read(4))
691         pos = (size + rel_offset) & 0xffffffff
692         fd.seek(pos)
693         found = self._read_header(fd)
694         if not found:
695             print('Relative offset seems wrong, scanning whole image')
696             for pos in range(0, size - HEADER_LEN, 4):
697                 fd.seek(pos)
698                 found = self._read_header(fd)
699                 if found:
700                     break
701         fd.seek(orig_pos)
702         return found
703
704     def _read_next_file(self, fd):
705         """Read the next file from a CBFS
706
707         Args:
708             fd: File to read from
709
710         Returns:
711             CbfsFile object, if found
712             None if no object found, but data was parsed (e.g. TYPE_CBFSHEADER)
713             False if at end of CBFS and reading should stop
714         """
715         file_pos = fd.tell()
716         data = fd.read(FILE_HEADER_LEN)
717         if len(data) < FILE_HEADER_LEN:
718             print('File header at %#x ran out of data' % file_pos)
719             return False
720         magic, size, ftype, attr, offset = struct.unpack(FILE_HEADER_FORMAT,
721                                                          data)
722         if magic != FILE_MAGIC:
723             return False
724         pos = fd.tell()
725         name = self._read_string(fd)
726         if name is None:
727             print('String at %#x ran out of data' % pos)
728             return False
729
730         if DEBUG:
731             print('name', name)
732
733         # If there are attribute headers present, read those
734         compress = self._read_attr(fd, file_pos, attr, offset)
735         if compress is None:
736             return False
737
738         # Create the correct CbfsFile object depending on the type
739         cfile = None
740         cbfs_offset = file_pos + offset
741         fd.seek(cbfs_offset, io.SEEK_SET)
742         if ftype == TYPE_CBFSHEADER:
743             self._read_header(fd)
744         elif ftype == TYPE_STAGE:
745             data = fd.read(STAGE_LEN)
746             cfile = CbfsFile.stage(self.stage_base_address, name, b'',
747                                    cbfs_offset)
748             (cfile.compress, cfile.entry, cfile.load, cfile.data_len,
749              cfile.memlen) = struct.unpack(STAGE_FORMAT, data)
750             cfile.data = fd.read(cfile.data_len)
751         elif ftype == TYPE_RAW:
752             data = fd.read(size)
753             cfile = CbfsFile.raw(name, data, cbfs_offset, compress)
754             cfile.decompress()
755             if DEBUG:
756                 print('data', data)
757         elif ftype == TYPE_EMPTY:
758             # Just read the data and discard it, since it is only padding
759             fd.read(size)
760             cfile = CbfsFile('', TYPE_EMPTY, b'', cbfs_offset)
761         else:
762             raise ValueError('Unknown type %#x when reading\n' % ftype)
763         if cfile:
764             cfile.offset = offset
765
766         # Move past the padding to the start of a possible next file. If we are
767         # already at an alignment boundary, then there is no padding.
768         pad = (self.align - fd.tell() % self.align) % self.align
769         fd.seek(pad, io.SEEK_CUR)
770         return cfile
771
772     @classmethod
773     def _read_attr(cls, fd, file_pos, attr, offset):
774         """Read attributes from the file
775
776         CBFS files can have attributes which are things that cannot fit into the
777         header. The only attributes currently supported are compression and the
778         unused tag.
779
780         Args:
781             fd: File to read from
782             file_pos: Position of file in fd
783             attr: Offset of attributes, 0 if none
784             offset: Offset of file data (used to indicate the end of the
785                                          attributes)
786
787         Returns:
788             Compression to use for the file (COMPRESS_...)
789         """
790         compress = COMPRESS_NONE
791         if not attr:
792             return compress
793         attr_size = offset - attr
794         fd.seek(file_pos + attr, io.SEEK_SET)
795         while attr_size:
796             pos = fd.tell()
797             hdr = fd.read(8)
798             if len(hdr) < 8:
799                 print('Attribute tag at %x ran out of data' % pos)
800                 return None
801             atag, alen = struct.unpack(">II", hdr)
802             data = hdr + fd.read(alen - 8)
803             if atag == FILE_ATTR_TAG_COMPRESSION:
804                 # We don't currently use this information
805                 atag, alen, compress, _decomp_size = struct.unpack(
806                     ATTR_COMPRESSION_FORMAT, data)
807             elif atag == FILE_ATTR_TAG_UNUSED2:
808                 break
809             else:
810                 print('Unknown attribute tag %x' % atag)
811             attr_size -= len(data)
812         return compress
813
814     def _read_header(self, fd):
815         """Read the master header
816
817         Reads the header and stores the information obtained into the member
818         variables.
819
820         Args:
821             fd: File to read from
822
823         Returns:
824             True if header was read OK, False if it is truncated or has the
825                 wrong magic or version
826         """
827         pos = fd.tell()
828         data = fd.read(HEADER_LEN)
829         if len(data) < HEADER_LEN:
830             print('Header at %x ran out of data' % pos)
831             return False
832         (self.magic, self.version, self.rom_size, self.boot_block_size,
833          self.align, self.cbfs_offset, self.arch, _) = struct.unpack(
834              HEADER_FORMAT, data)
835         return self.magic == HEADER_MAGIC and (
836             self.version == HEADER_VERSION1 or
837             self.version == HEADER_VERSION2)
838
839     @classmethod
840     def _read_string(cls, fd):
841         """Read a string from a file
842
843         This reads a string and aligns the data to the next alignment boundary
844
845         Args:
846             fd: File to read from
847
848         Returns:
849             string read ('str' type) encoded to UTF-8, or None if we ran out of
850                 data
851         """
852         val = b''
853         while True:
854             data = fd.read(FILENAME_ALIGN)
855             if len(data) < FILENAME_ALIGN:
856                 return None
857             pos = data.find(b'\0')
858             if pos == -1:
859                 val += data
860             else:
861                 val += data[:pos]
862                 break
863         return val.decode('utf-8')
864
865
866 def cbfstool(fname, *cbfs_args, **kwargs):
867     """Run cbfstool with provided arguments
868
869     If the tool fails then this function raises an exception and prints out the
870     output and stderr.
871
872     Args:
873         fname: Filename of CBFS
874         *cbfs_args: List of arguments to pass to cbfstool
875
876     Returns:
877         CommandResult object containing the results
878     """
879     args = ['cbfstool', fname] + list(cbfs_args)
880     if kwargs.get('base') is not None:
881         args += ['-b', '%#x' % kwargs['base']]
882     result = command.RunPipe([args], capture=not VERBOSE,
883                              capture_stderr=not VERBOSE, raise_on_error=False)
884     if result.return_code:
885         print(result.stderr, file=sys.stderr)
886         raise Exception("Failed to run (error %d): '%s'" %
887                         (result.return_code, ' '.join(args)))