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
7CBFS supports a header followed by a number of files, generally targeted at SPI
8flash.
9
10The format is somewhat defined by documentation in the coreboot tree although
11it is necessary to rely on the C structures and source code (mostly cbfstool)
12to fully understand it.
13
14Currently supported: raw and stage types with compression, padding empty areas
15    with empty files, fixed-offset files
16"""
17
18from collections import OrderedDict
19import io
20import struct
21import sys
22
23from binman import elf
24from patman import command
25from patman import tools
26
27# Set to True to enable printing output while working
28DEBUG = False
29
30# Set to True to enable output from running cbfstool for debugging
31VERBOSE = False
32
33# The master header, at the start of the CBFS
34HEADER_FORMAT      = '>IIIIIIII'
35HEADER_LEN         = 0x20
36HEADER_MAGIC       = 0x4f524243
37HEADER_VERSION1    = 0x31313131
38HEADER_VERSION2    = 0x31313132
39
40# The file header, at the start of each file in the CBFS
41FILE_HEADER_FORMAT = b'>8sIIII'
42FILE_HEADER_LEN    = 0x18
43FILE_MAGIC         = b'LARCHIVE'
44FILENAME_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
48STAGE_FORMAT       = '<IQQII'
49STAGE_LEN          = 0x1c
50
51# An attribute describring the compression used in a file
52ATTR_COMPRESSION_FORMAT = '>IIII'
53ATTR_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.
58FILE_ATTR_TAG_UNUSED        = 0
59FILE_ATTR_TAG_UNUSED2       = 0xffffffff
60FILE_ATTR_TAG_COMPRESSION   = 0x42435a4c
61FILE_ATTR_TAG_HASH          = 0x68736148
62FILE_ATTR_TAG_POSITION      = 0x42435350  # PSCB
63FILE_ATTR_TAG_ALIGNMENT     = 0x42434c41  # ALCB
64FILE_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.
73MIN_BOOTBLOCK_SIZE     = 4
74
75# Files start aligned to this boundary in the CBFS
76ENTRY_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.
82ARCHITECTURE_UNKNOWN  = 0xffffffff
83ARCHITECTURE_X86      = 0x00000001
84ARCHITECTURE_ARM      = 0x00000010
85ARCHITECTURE_AARCH64  = 0x0000aa64
86ARCHITECTURE_MIPS     = 0x00000100
87ARCHITECTURE_RISCV    = 0xc001d0de
88ARCHITECTURE_PPC64    = 0x407570ff
89
90ARCH_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
101TYPE_CBFSHEADER     = 0x02   # Master header, HEADER_FORMAT
102TYPE_STAGE          = 0x10   # Stage, holding an executable, see STAGE_FORMAT
103TYPE_RAW            = 0x50   # Raw file, possibly compressed
104TYPE_EMPTY          = 0xffffffff     # Empty data
105
106# Compression types
107COMPRESS_NONE, COMPRESS_LZMA, COMPRESS_LZ4 = range(3)
108
109COMPRESS_NAMES = {
110    COMPRESS_NONE : 'none',
111    COMPRESS_LZMA : 'lzma',
112    COMPRESS_LZ4  : 'lz4',
113    }
114
115def 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
129def 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
143def 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
157def 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
169def 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
182def _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
196class 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
410class 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
621class 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
866def 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)))
888