blob: ec4a2e5a8c6d6c41811af50a514defcfb6c3984f [file] [log] [blame]
Simon Glass96a62962019-07-08 13:18:52 -06001# 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
Simon Glassa61e6fe2019-07-08 13:18:55 -060014Currently supported: raw and stage types with compression, padding empty areas
15 with empty files
Simon Glass96a62962019-07-08 13:18:52 -060016"""
17
18from __future__ import print_function
19
20from collections import OrderedDict
21import io
22import struct
23import sys
24
25import command
26import elf
27import tools
28
29# Set to True to enable printing output while working
30DEBUG = False
31
32# Set to True to enable output from running cbfstool for debugging
33VERBOSE = False
34
35# The master header, at the start of the CBFS
36HEADER_FORMAT = '>IIIIIIII'
37HEADER_LEN = 0x20
38HEADER_MAGIC = 0x4f524243
39HEADER_VERSION1 = 0x31313131
40HEADER_VERSION2 = 0x31313132
41
42# The file header, at the start of each file in the CBFS
43FILE_HEADER_FORMAT = b'>8sIIII'
44FILE_HEADER_LEN = 0x18
45FILE_MAGIC = b'LARCHIVE'
46FILENAME_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
50STAGE_FORMAT = '<IQQII'
51STAGE_LEN = 0x1c
52
53# An attribute describring the compression used in a file
54ATTR_COMPRESSION_FORMAT = '>IIII'
55ATTR_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.
60FILE_ATTR_TAG_UNUSED = 0
61FILE_ATTR_TAG_UNUSED2 = 0xffffffff
62FILE_ATTR_TAG_COMPRESSION = 0x42435a4c
63FILE_ATTR_TAG_HASH = 0x68736148
64FILE_ATTR_TAG_POSITION = 0x42435350 # PSCB
65FILE_ATTR_TAG_ALIGNMENT = 0x42434c41 # ALCB
66FILE_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.
75MIN_BOOTBLOCK_SIZE = 4
76
77# Files start aligned to this boundary in the CBFS
78ENTRY_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.
84ARCHITECTURE_UNKNOWN = 0xffffffff
85ARCHITECTURE_X86 = 0x00000001
86ARCHITECTURE_ARM = 0x00000010
87ARCHITECTURE_AARCH64 = 0x0000aa64
88ARCHITECTURE_MIPS = 0x00000100
89ARCHITECTURE_RISCV = 0xc001d0de
90ARCHITECTURE_PPC64 = 0x407570ff
91
92ARCH_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
103TYPE_CBFSHEADER = 0x02 # Master header, HEADER_FORMAT
104TYPE_STAGE = 0x10 # Stage, holding an executable, see STAGE_FORMAT
105TYPE_RAW = 0x50 # Raw file, possibly compressed
Simon Glassa61e6fe2019-07-08 13:18:55 -0600106TYPE_EMPTY = 0xffffffff # Empty data
Simon Glass96a62962019-07-08 13:18:52 -0600107
108# Compression types
109COMPRESS_NONE, COMPRESS_LZMA, COMPRESS_LZ4 = range(3)
110
111COMPRESS_NAMES = {
112 COMPRESS_NONE : 'none',
113 COMPRESS_LZMA : 'lzma',
114 COMPRESS_LZ4 : 'lz4',
115 }
116
117def 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
131def 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
145def align_int(val, align):
146 """Align a value up to the given alignment
147
148 Args:
149 val: Integer value to align
150 align: Integer alignment value (e.g. 4 to align to 4-byte boundary)
151
152 Returns:
153 integer value aligned to the required boundary, rounding up if necessary
154 """
155 return int((val + align - 1) / align) * align
156
Simon Glassa61e6fe2019-07-08 13:18:55 -0600157def align_int_down(val, align):
158 """Align a value down 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 down if
166 necessary
167 """
168 return int(val / align) * align
169
Simon Glass96a62962019-07-08 13:18:52 -0600170def _pack_string(instr):
171 """Pack a string to the required aligned size by adding padding
172
173 Args:
174 instr: String to process
175
176 Returns:
177 String with required padding (at least one 0x00 byte) at the end
178 """
179 val = tools.ToBytes(instr)
180 pad_len = align_int(len(val) + 1, FILENAME_ALIGN)
181 return val + tools.GetBytes(0, pad_len - len(val))
182
183
184class CbfsFile(object):
185 """Class to represent a single CBFS file
186
187 This is used to hold the information about a file, including its contents.
188 Use the get_data() method to obtain the raw output for writing to CBFS.
189
190 Properties:
191 name: Name of file
192 offset: Offset of file data from start of file header
193 data: Contents of file, uncompressed
194 data_len: Length of (possibly compressed) data in bytes
195 ftype: File type (TYPE_...)
196 compression: Compression type (COMPRESS_...)
197 memlen: Length of data in memory (typically the uncompressed length)
198 load: Load address in memory if known, else None
199 entry: Entry address in memory if known, else None. This is where
200 execution starts after the file is loaded
201 base_address: Base address to use for 'stage' files
Simon Glassa61e6fe2019-07-08 13:18:55 -0600202 erase_byte: Erase byte to use for padding between the file header and
203 contents (used for empty files)
204 size: Size of the file in bytes (used for empty files)
Simon Glass96a62962019-07-08 13:18:52 -0600205 """
206 def __init__(self, name, ftype, data, compress=COMPRESS_NONE):
207 self.name = name
208 self.offset = None
209 self.data = data
210 self.ftype = ftype
211 self.compress = compress
212 self.memlen = len(data)
213 self.load = None
214 self.entry = None
215 self.base_address = None
216 self.data_len = 0
Simon Glassa61e6fe2019-07-08 13:18:55 -0600217 self.erase_byte = None
218 self.size = None
Simon Glass96a62962019-07-08 13:18:52 -0600219
220 def decompress(self):
221 """Handle decompressing data if necessary"""
222 indata = self.data
223 if self.compress == COMPRESS_LZ4:
224 data = tools.Decompress(indata, 'lz4')
225 elif self.compress == COMPRESS_LZMA:
226 data = tools.Decompress(indata, 'lzma')
227 else:
228 data = indata
229 self.memlen = len(data)
230 self.data = data
231 self.data_len = len(indata)
232
233 @classmethod
234 def stage(cls, base_address, name, data):
235 """Create a new stage file
236
237 Args:
238 base_address: Int base address for memory-mapping of ELF file
239 name: String file name to put in CBFS (does not need to correspond
240 to the name that the file originally came from)
241 data: Contents of file
242
243 Returns:
244 CbfsFile object containing the file information
245 """
246 cfile = CbfsFile(name, TYPE_STAGE, data)
247 cfile.base_address = base_address
248 return cfile
249
250 @classmethod
251 def raw(cls, name, data, compress):
252 """Create a new raw file
253
254 Args:
255 name: String file name to put in CBFS (does not need to correspond
256 to the name that the file originally came from)
257 data: Contents of file
258 compress: Compression algorithm to use (COMPRESS_...)
259
260 Returns:
261 CbfsFile object containing the file information
262 """
263 return CbfsFile(name, TYPE_RAW, data, compress)
264
Simon Glassa61e6fe2019-07-08 13:18:55 -0600265 @classmethod
266 def empty(cls, space_to_use, erase_byte):
267 """Create a new empty file of a given size
268
269 Args:
270 space_to_use:: Size of available space, which must be at least as
271 large as the alignment size for this CBFS
272 erase_byte: Byte to use for contents of file (repeated through the
273 whole file)
274
275 Returns:
276 CbfsFile object containing the file information
277 """
278 cfile = CbfsFile('', TYPE_EMPTY, b'')
279 cfile.size = space_to_use - FILE_HEADER_LEN - FILENAME_ALIGN
280 cfile.erase_byte = erase_byte
281 return cfile
282
Simon Glass96a62962019-07-08 13:18:52 -0600283 def get_data(self):
284 """Obtain the contents of the file, in CBFS format
285
286 Returns:
287 bytes representing the contents of this file, packed and aligned
288 for directly inserting into the final CBFS output
289 """
290 name = _pack_string(self.name)
291 hdr_len = len(name) + FILE_HEADER_LEN
292 attr_pos = 0
293 content = b''
294 attr = b''
295 data = self.data
296 if self.ftype == TYPE_STAGE:
297 elf_data = elf.DecodeElf(data, self.base_address)
298 content = struct.pack(STAGE_FORMAT, self.compress,
299 elf_data.entry, elf_data.load,
300 len(elf_data.data), elf_data.memsize)
301 data = elf_data.data
302 elif self.ftype == TYPE_RAW:
303 orig_data = data
304 if self.compress == COMPRESS_LZ4:
305 data = tools.Compress(orig_data, 'lz4')
306 elif self.compress == COMPRESS_LZMA:
307 data = tools.Compress(orig_data, 'lzma')
308 attr = struct.pack(ATTR_COMPRESSION_FORMAT,
309 FILE_ATTR_TAG_COMPRESSION, ATTR_COMPRESSION_LEN,
310 self.compress, len(orig_data))
Simon Glassa61e6fe2019-07-08 13:18:55 -0600311 elif self.ftype == TYPE_EMPTY:
312 data = tools.GetBytes(self.erase_byte, self.size)
Simon Glass96a62962019-07-08 13:18:52 -0600313 else:
314 raise ValueError('Unknown type %#x when writing\n' % self.ftype)
315 if attr:
316 attr_pos = hdr_len
317 hdr_len += len(attr)
318 hdr = struct.pack(FILE_HEADER_FORMAT, FILE_MAGIC,
319 len(content) + len(data),
320 self.ftype, attr_pos, hdr_len)
321 return hdr + name + attr + content + data
322
323
324class CbfsWriter(object):
325 """Class to handle writing a Coreboot File System (CBFS)
326
327 Usage is something like:
328
329 cbw = CbfsWriter(size)
330 cbw.add_file_raw('u-boot', tools.ReadFile('u-boot.bin'))
331 ...
332 data = cbw.get_data()
333
334 Attributes:
335 _master_name: Name of the file containing the master header
336 _size: Size of the filesystem, in bytes
337 _files: Ordered list of files in the CBFS, each a CbfsFile
338 _arch: Architecture of the CBFS (ARCHITECTURE_...)
339 _bootblock_size: Size of the bootblock, typically at the end of the CBFS
340 _erase_byte: Byte to use for empty space in the CBFS
341 _align: Alignment to use for files, typically ENTRY_ALIGN
342 _base_address: Boot block offset in bytes from the start of CBFS.
343 Typically this is located at top of the CBFS. It is 0 when there is
344 no boot block
345 _header_offset: Offset of master header in bytes from start of CBFS
346 _contents_offset: Offset of first file header
347 _hdr_at_start: True if the master header is at the start of the CBFS,
348 instead of the end as normal for x86
349 _add_fileheader: True to add a fileheader around the master header
350 """
351 def __init__(self, size, arch=ARCHITECTURE_X86):
352 """Set up a new CBFS
353
354 This sets up all properties to default values. Files can be added using
355 add_file_raw(), etc.
356
357 Args:
358 size: Size of CBFS in bytes
359 arch: Architecture to declare for CBFS
360 """
361 self._master_name = 'cbfs master header'
362 self._size = size
363 self._files = OrderedDict()
364 self._arch = arch
365 self._bootblock_size = 0
366 self._erase_byte = 0xff
367 self._align = ENTRY_ALIGN
368 self._add_fileheader = False
369 if self._arch == ARCHITECTURE_X86:
370 # Allow 4 bytes for the header pointer. That holds the
371 # twos-compliment negative offset of the master header in bytes
372 # measured from one byte past the end of the CBFS
373 self._base_address = self._size - max(self._bootblock_size,
374 MIN_BOOTBLOCK_SIZE)
375 self._header_offset = self._base_address - HEADER_LEN
376 self._contents_offset = 0
377 self._hdr_at_start = False
378 else:
379 # For non-x86, different rules apply
380 self._base_address = 0
381 self._header_offset = align_int(self._base_address +
382 self._bootblock_size, 4)
383 self._contents_offset = align_int(self._header_offset +
384 FILE_HEADER_LEN +
385 self._bootblock_size, self._align)
386 self._hdr_at_start = True
387
388 def _skip_to(self, fd, offset):
389 """Write out pad bytes until a given offset
390
391 Args:
392 fd: File objext to write to
393 offset: Offset to write to
394 """
395 if fd.tell() > offset:
396 raise ValueError('No space for data before offset %#x (current offset %#x)' %
397 (offset, fd.tell()))
398 fd.write(tools.GetBytes(self._erase_byte, offset - fd.tell()))
399
Simon Glassa61e6fe2019-07-08 13:18:55 -0600400 def _pad_to(self, fd, offset):
401 """Write out pad bytes and/or an empty file until a given offset
402
403 Args:
404 fd: File objext to write to
405 offset: Offset to write to
406 """
407 self._align_to(fd, self._align)
408 upto = fd.tell()
409 if upto > offset:
410 raise ValueError('No space for data before pad offset %#x (current offset %#x)' %
411 (offset, upto))
412 todo = align_int_down(offset - upto, self._align)
413 if todo:
414 cbf = CbfsFile.empty(todo, self._erase_byte)
415 fd.write(cbf.get_data())
416 self._skip_to(fd, offset)
417
Simon Glass96a62962019-07-08 13:18:52 -0600418 def _align_to(self, fd, align):
419 """Write out pad bytes until a given alignment is reached
420
421 This only aligns if the resulting output would not reach the end of the
422 CBFS, since we want to leave the last 4 bytes for the master-header
423 pointer.
424
425 Args:
426 fd: File objext to write to
427 align: Alignment to require (e.g. 4 means pad to next 4-byte
428 boundary)
429 """
430 offset = align_int(fd.tell(), align)
431 if offset < self._size:
432 self._skip_to(fd, offset)
433
434 def add_file_stage(self, name, data):
435 """Add a new stage file to the CBFS
436
437 Args:
438 name: String file name to put in CBFS (does not need to correspond
439 to the name that the file originally came from)
440 data: Contents of file
441
442 Returns:
443 CbfsFile object created
444 """
445 cfile = CbfsFile.stage(self._base_address, name, data)
446 self._files[name] = cfile
447 return cfile
448
449 def add_file_raw(self, name, data, compress=COMPRESS_NONE):
450 """Create a new raw file
451
452 Args:
453 name: String file name to put in CBFS (does not need to correspond
454 to the name that the file originally came from)
455 data: Contents of file
456 compress: Compression algorithm to use (COMPRESS_...)
457
458 Returns:
459 CbfsFile object created
460 """
461 cfile = CbfsFile.raw(name, data, compress)
462 self._files[name] = cfile
463 return cfile
464
465 def _write_header(self, fd, add_fileheader):
466 """Write out the master header to a CBFS
467
468 Args:
469 fd: File object
470 add_fileheader: True to place the master header in a file header
471 record
472 """
473 if fd.tell() > self._header_offset:
474 raise ValueError('No space for header at offset %#x (current offset %#x)' %
475 (self._header_offset, fd.tell()))
476 if not add_fileheader:
Simon Glassa61e6fe2019-07-08 13:18:55 -0600477 self._pad_to(fd, self._header_offset)
Simon Glass96a62962019-07-08 13:18:52 -0600478 hdr = struct.pack(HEADER_FORMAT, HEADER_MAGIC, HEADER_VERSION2,
479 self._size, self._bootblock_size, self._align,
480 self._contents_offset, self._arch, 0xffffffff)
481 if add_fileheader:
482 name = _pack_string(self._master_name)
483 fd.write(struct.pack(FILE_HEADER_FORMAT, FILE_MAGIC, len(hdr),
484 TYPE_CBFSHEADER, 0,
485 FILE_HEADER_LEN + len(name)))
486 fd.write(name)
487 self._header_offset = fd.tell()
488 fd.write(hdr)
489 self._align_to(fd, self._align)
490 else:
491 fd.write(hdr)
492
493 def get_data(self):
494 """Obtain the full contents of the CBFS
495
496 Thhis builds the CBFS with headers and all required files.
497
498 Returns:
499 'bytes' type containing the data
500 """
501 fd = io.BytesIO()
502
503 # THe header can go at the start in some cases
504 if self._hdr_at_start:
505 self._write_header(fd, add_fileheader=self._add_fileheader)
506 self._skip_to(fd, self._contents_offset)
507
508 # Write out each file
509 for cbf in self._files.values():
510 fd.write(cbf.get_data())
511 self._align_to(fd, self._align)
512 if not self._hdr_at_start:
513 self._write_header(fd, add_fileheader=self._add_fileheader)
514
515 # Pad to the end and write a pointer to the CBFS master header
Simon Glassa61e6fe2019-07-08 13:18:55 -0600516 self._pad_to(fd, self._base_address or self._size - 4)
Simon Glass96a62962019-07-08 13:18:52 -0600517 rel_offset = self._header_offset - self._size
518 fd.write(struct.pack('<I', rel_offset & 0xffffffff))
519
520 return fd.getvalue()
521
522
523class CbfsReader(object):
524 """Class to handle reading a Coreboot File System (CBFS)
525
526 Usage is something like:
527 cbfs = cbfs_util.CbfsReader(data)
528 cfile = cbfs.files['u-boot']
529 self.WriteFile('u-boot.bin', cfile.data)
530
531 Attributes:
532 files: Ordered list of CbfsFile objects
533 align: Alignment to use for files, typically ENTRT_ALIGN
534 stage_base_address: Base address to use when mapping ELF files into the
535 CBFS for TYPE_STAGE files. If this is larger than the code address
536 of the ELF file, then data at the start of the ELF file will not
537 appear in the CBFS. Currently there are no tests for behaviour as
538 documentation is sparse
539 magic: Integer magic number from master header (HEADER_MAGIC)
540 version: Version number of CBFS (HEADER_VERSION2)
541 rom_size: Size of CBFS
542 boot_block_size: Size of boot block
543 cbfs_offset: Offset of the first file in bytes from start of CBFS
544 arch: Architecture of CBFS file (ARCHITECTURE_...)
545 """
546 def __init__(self, data, read=True):
547 self.align = ENTRY_ALIGN
548 self.arch = None
549 self.boot_block_size = None
550 self.cbfs_offset = None
551 self.files = OrderedDict()
552 self.magic = None
553 self.rom_size = None
554 self.stage_base_address = 0
555 self.version = None
556 self.data = data
557 if read:
558 self.read()
559
560 def read(self):
561 """Read all the files in the CBFS and add them to self.files"""
562 with io.BytesIO(self.data) as fd:
563 # First, get the master header
564 if not self._find_and_read_header(fd, len(self.data)):
565 raise ValueError('Cannot find master header')
566 fd.seek(self.cbfs_offset)
567
568 # Now read in the files one at a time
569 while True:
570 cfile = self._read_next_file(fd)
571 if cfile:
572 self.files[cfile.name] = cfile
573 elif cfile is False:
574 break
575
576 def _find_and_read_header(self, fd, size):
577 """Find and read the master header in the CBFS
578
579 This looks at the pointer word at the very end of the CBFS. This is an
580 offset to the header relative to the size of the CBFS, which is assumed
581 to be known. Note that the offset is in *little endian* format.
582
583 Args:
584 fd: File to read from
585 size: Size of file
586
587 Returns:
588 True if header was found, False if not
589 """
590 orig_pos = fd.tell()
591 fd.seek(size - 4)
592 rel_offset, = struct.unpack('<I', fd.read(4))
593 pos = (size + rel_offset) & 0xffffffff
594 fd.seek(pos)
595 found = self._read_header(fd)
596 if not found:
597 print('Relative offset seems wrong, scanning whole image')
598 for pos in range(0, size - HEADER_LEN, 4):
599 fd.seek(pos)
600 found = self._read_header(fd)
601 if found:
602 break
603 fd.seek(orig_pos)
604 return found
605
606 def _read_next_file(self, fd):
607 """Read the next file from a CBFS
608
609 Args:
610 fd: File to read from
611
612 Returns:
613 CbfsFile object, if found
614 None if no object found, but data was parsed (e.g. TYPE_CBFSHEADER)
615 False if at end of CBFS and reading should stop
616 """
617 file_pos = fd.tell()
618 data = fd.read(FILE_HEADER_LEN)
619 if len(data) < FILE_HEADER_LEN:
620 print('File header at %x ran out of data' % file_pos)
621 return False
622 magic, size, ftype, attr, offset = struct.unpack(FILE_HEADER_FORMAT,
623 data)
624 if magic != FILE_MAGIC:
625 return False
626 pos = fd.tell()
627 name = self._read_string(fd)
628 if name is None:
629 print('String at %x ran out of data' % pos)
630 return False
631
632 if DEBUG:
633 print('name', name)
634
635 # If there are attribute headers present, read those
636 compress = self._read_attr(fd, file_pos, attr, offset)
637 if compress is None:
638 return False
639
640 # Create the correct CbfsFile object depending on the type
641 cfile = None
642 fd.seek(file_pos + offset, io.SEEK_SET)
643 if ftype == TYPE_CBFSHEADER:
644 self._read_header(fd)
645 elif ftype == TYPE_STAGE:
646 data = fd.read(STAGE_LEN)
647 cfile = CbfsFile.stage(self.stage_base_address, name, b'')
648 (cfile.compress, cfile.entry, cfile.load, cfile.data_len,
649 cfile.memlen) = struct.unpack(STAGE_FORMAT, data)
650 cfile.data = fd.read(cfile.data_len)
651 elif ftype == TYPE_RAW:
652 data = fd.read(size)
653 cfile = CbfsFile.raw(name, data, compress)
654 cfile.decompress()
655 if DEBUG:
656 print('data', data)
Simon Glassa61e6fe2019-07-08 13:18:55 -0600657 elif ftype == TYPE_EMPTY:
658 # Just read the data and discard it, since it is only padding
659 fd.read(size)
660 cfile = CbfsFile('', TYPE_EMPTY, b'')
Simon Glass96a62962019-07-08 13:18:52 -0600661 else:
662 raise ValueError('Unknown type %#x when reading\n' % ftype)
663 if cfile:
664 cfile.offset = offset
665
666 # Move past the padding to the start of a possible next file. If we are
667 # already at an alignment boundary, then there is no padding.
668 pad = (self.align - fd.tell() % self.align) % self.align
669 fd.seek(pad, io.SEEK_CUR)
670 return cfile
671
672 @classmethod
673 def _read_attr(cls, fd, file_pos, attr, offset):
674 """Read attributes from the file
675
676 CBFS files can have attributes which are things that cannot fit into the
677 header. The only attribute currently supported is compression.
678
679 Args:
680 fd: File to read from
681 file_pos: Position of file in fd
682 attr: Offset of attributes, 0 if none
683 offset: Offset of file data (used to indicate the end of the
684 attributes)
685
686 Returns:
687 Compression to use for the file (COMPRESS_...)
688 """
689 compress = COMPRESS_NONE
690 if not attr:
691 return compress
692 attr_size = offset - attr
693 fd.seek(file_pos + attr, io.SEEK_SET)
694 while attr_size:
695 pos = fd.tell()
696 hdr = fd.read(8)
697 if len(hdr) < 8:
698 print('Attribute tag at %x ran out of data' % pos)
699 return None
700 atag, alen = struct.unpack(">II", hdr)
701 data = hdr + fd.read(alen - 8)
702 if atag == FILE_ATTR_TAG_COMPRESSION:
703 # We don't currently use this information
704 atag, alen, compress, _decomp_size = struct.unpack(
705 ATTR_COMPRESSION_FORMAT, data)
706 else:
707 print('Unknown attribute tag %x' % atag)
708 attr_size -= len(data)
709 return compress
710
711 def _read_header(self, fd):
712 """Read the master header
713
714 Reads the header and stores the information obtained into the member
715 variables.
716
717 Args:
718 fd: File to read from
719
720 Returns:
721 True if header was read OK, False if it is truncated or has the
722 wrong magic or version
723 """
724 pos = fd.tell()
725 data = fd.read(HEADER_LEN)
726 if len(data) < HEADER_LEN:
727 print('Header at %x ran out of data' % pos)
728 return False
729 (self.magic, self.version, self.rom_size, self.boot_block_size,
730 self.align, self.cbfs_offset, self.arch, _) = struct.unpack(
731 HEADER_FORMAT, data)
732 return self.magic == HEADER_MAGIC and (
733 self.version == HEADER_VERSION1 or
734 self.version == HEADER_VERSION2)
735
736 @classmethod
737 def _read_string(cls, fd):
738 """Read a string from a file
739
740 This reads a string and aligns the data to the next alignment boundary
741
742 Args:
743 fd: File to read from
744
745 Returns:
746 string read ('str' type) encoded to UTF-8, or None if we ran out of
747 data
748 """
749 val = b''
750 while True:
751 data = fd.read(FILENAME_ALIGN)
752 if len(data) < FILENAME_ALIGN:
753 return None
754 pos = data.find(b'\0')
755 if pos == -1:
756 val += data
757 else:
758 val += data[:pos]
759 break
760 return val.decode('utf-8')
761
762
763def cbfstool(fname, *cbfs_args):
764 """Run cbfstool with provided arguments
765
766 If the tool fails then this function raises an exception and prints out the
767 output and stderr.
768
769 Args:
770 fname: Filename of CBFS
771 *cbfs_args: List of arguments to pass to cbfstool
772
773 Returns:
774 CommandResult object containing the results
775 """
776 args = ('cbfstool', fname) + cbfs_args
777 result = command.RunPipe([args], capture=not VERBOSE,
778 capture_stderr=not VERBOSE, raise_on_error=False)
779 if result.return_code:
780 print(result.stderr, file=sys.stderr)
781 raise Exception("Failed to run (error %d): '%s'" %
782 (result.return_code, ' '.join(args)))