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