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