Simon Glass | 4997a7e | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | # SPDX-License-Identifier: GPL-2.0+ |
| 3 | # Copyright 2019 Google LLC |
| 4 | # Written by Simon Glass <sjg@chromium.org> |
| 5 | |
| 6 | """Tests for cbfs_util |
| 7 | |
| 8 | These create and read various CBFSs and compare the results with expected |
| 9 | values and with cbfstool |
| 10 | """ |
| 11 | |
| 12 | from __future__ import print_function |
| 13 | |
| 14 | import io |
| 15 | import os |
| 16 | import shutil |
| 17 | import struct |
| 18 | import tempfile |
| 19 | import unittest |
| 20 | |
| 21 | import cbfs_util |
| 22 | from cbfs_util import CbfsWriter |
| 23 | import elf |
| 24 | import test_util |
| 25 | import tools |
| 26 | |
| 27 | U_BOOT_DATA = b'1234' |
| 28 | U_BOOT_DTB_DATA = b'udtb' |
| 29 | COMPRESS_DATA = b'compress xxxxxxxxxxxxxxxxxxxxxx data' |
| 30 | |
| 31 | |
| 32 | class TestCbfs(unittest.TestCase): |
| 33 | """Test of cbfs_util classes""" |
| 34 | #pylint: disable=W0212 |
| 35 | @classmethod |
| 36 | def setUpClass(cls): |
| 37 | # Create a temporary directory for test files |
| 38 | cls._indir = tempfile.mkdtemp(prefix='cbfs_util.') |
| 39 | tools.SetInputDirs([cls._indir]) |
| 40 | |
| 41 | # Set up some useful data files |
| 42 | TestCbfs._make_input_file('u-boot.bin', U_BOOT_DATA) |
| 43 | TestCbfs._make_input_file('u-boot.dtb', U_BOOT_DTB_DATA) |
| 44 | TestCbfs._make_input_file('compress', COMPRESS_DATA) |
| 45 | |
| 46 | # Set up a temporary output directory, used by the tools library when |
| 47 | # compressing files |
| 48 | tools.PrepareOutputDir(None) |
| 49 | |
| 50 | cls.have_cbfstool = True |
| 51 | try: |
| 52 | tools.Run('which', 'cbfstool') |
| 53 | except: |
| 54 | cls.have_cbfstool = False |
| 55 | |
| 56 | cls.have_lz4 = True |
| 57 | try: |
| 58 | tools.Run('lz4', '--no-frame-crc', '-c', |
| 59 | tools.GetInputFilename('u-boot.bin')) |
| 60 | except: |
| 61 | cls.have_lz4 = False |
| 62 | |
| 63 | @classmethod |
| 64 | def tearDownClass(cls): |
| 65 | """Remove the temporary input directory and its contents""" |
| 66 | if cls._indir: |
| 67 | shutil.rmtree(cls._indir) |
| 68 | cls._indir = None |
| 69 | tools.FinaliseOutputDir() |
| 70 | |
| 71 | @classmethod |
| 72 | def _make_input_file(cls, fname, contents): |
| 73 | """Create a new test input file, creating directories as needed |
| 74 | |
| 75 | Args: |
| 76 | fname: Filename to create |
| 77 | contents: File contents to write in to the file |
| 78 | Returns: |
| 79 | Full pathname of file created |
| 80 | """ |
| 81 | pathname = os.path.join(cls._indir, fname) |
| 82 | tools.WriteFile(pathname, contents) |
| 83 | return pathname |
| 84 | |
| 85 | def _check_hdr(self, data, size, offset=0, arch=cbfs_util.ARCHITECTURE_X86): |
| 86 | """Check that the CBFS has the expected header |
| 87 | |
| 88 | Args: |
| 89 | data: Data to check |
| 90 | size: Expected ROM size |
| 91 | offset: Expected offset to first CBFS file |
| 92 | arch: Expected architecture |
| 93 | |
| 94 | Returns: |
| 95 | CbfsReader object containing the CBFS |
| 96 | """ |
| 97 | cbfs = cbfs_util.CbfsReader(data) |
| 98 | self.assertEqual(cbfs_util.HEADER_MAGIC, cbfs.magic) |
| 99 | self.assertEqual(cbfs_util.HEADER_VERSION2, cbfs.version) |
| 100 | self.assertEqual(size, cbfs.rom_size) |
| 101 | self.assertEqual(0, cbfs.boot_block_size) |
| 102 | self.assertEqual(cbfs_util.ENTRY_ALIGN, cbfs.align) |
| 103 | self.assertEqual(offset, cbfs.cbfs_offset) |
| 104 | self.assertEqual(arch, cbfs.arch) |
| 105 | return cbfs |
| 106 | |
| 107 | def _check_uboot(self, cbfs, ftype=cbfs_util.TYPE_RAW, offset=0x38, |
Simon Glass | e073d4e | 2019-07-08 13:18:56 -0600 | [diff] [blame] | 108 | data=U_BOOT_DATA, cbfs_offset=None): |
Simon Glass | 4997a7e | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 109 | """Check that the U-Boot file is as expected |
| 110 | |
| 111 | Args: |
| 112 | cbfs: CbfsReader object to check |
| 113 | ftype: Expected file type |
| 114 | offset: Expected offset of file |
| 115 | data: Expected data in file |
Simon Glass | e073d4e | 2019-07-08 13:18:56 -0600 | [diff] [blame] | 116 | cbfs_offset: Expected CBFS offset for file's data |
Simon Glass | 4997a7e | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 117 | |
| 118 | Returns: |
| 119 | CbfsFile object containing the file |
| 120 | """ |
| 121 | self.assertIn('u-boot', cbfs.files) |
| 122 | cfile = cbfs.files['u-boot'] |
| 123 | self.assertEqual('u-boot', cfile.name) |
| 124 | self.assertEqual(offset, cfile.offset) |
Simon Glass | e073d4e | 2019-07-08 13:18:56 -0600 | [diff] [blame] | 125 | if cbfs_offset is not None: |
| 126 | self.assertEqual(cbfs_offset, cfile.cbfs_offset) |
Simon Glass | 4997a7e | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 127 | self.assertEqual(data, cfile.data) |
| 128 | self.assertEqual(ftype, cfile.ftype) |
| 129 | self.assertEqual(cbfs_util.COMPRESS_NONE, cfile.compress) |
| 130 | self.assertEqual(len(data), cfile.memlen) |
| 131 | return cfile |
| 132 | |
Simon Glass | e073d4e | 2019-07-08 13:18:56 -0600 | [diff] [blame] | 133 | def _check_dtb(self, cbfs, offset=0x38, data=U_BOOT_DTB_DATA, |
| 134 | cbfs_offset=None): |
Simon Glass | 4997a7e | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 135 | """Check that the U-Boot dtb file is as expected |
| 136 | |
| 137 | Args: |
| 138 | cbfs: CbfsReader object to check |
| 139 | offset: Expected offset of file |
| 140 | data: Expected data in file |
Simon Glass | e073d4e | 2019-07-08 13:18:56 -0600 | [diff] [blame] | 141 | cbfs_offset: Expected CBFS offset for file's data |
Simon Glass | 4997a7e | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 142 | """ |
| 143 | self.assertIn('u-boot-dtb', cbfs.files) |
| 144 | cfile = cbfs.files['u-boot-dtb'] |
| 145 | self.assertEqual('u-boot-dtb', cfile.name) |
| 146 | self.assertEqual(offset, cfile.offset) |
Simon Glass | e073d4e | 2019-07-08 13:18:56 -0600 | [diff] [blame] | 147 | if cbfs_offset is not None: |
| 148 | self.assertEqual(cbfs_offset, cfile.cbfs_offset) |
Simon Glass | 4997a7e | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 149 | self.assertEqual(U_BOOT_DTB_DATA, cfile.data) |
| 150 | self.assertEqual(cbfs_util.TYPE_RAW, cfile.ftype) |
| 151 | self.assertEqual(cbfs_util.COMPRESS_NONE, cfile.compress) |
| 152 | self.assertEqual(len(U_BOOT_DTB_DATA), cfile.memlen) |
| 153 | |
| 154 | def _check_raw(self, data, size, offset=0, arch=cbfs_util.ARCHITECTURE_X86): |
| 155 | """Check that two raw files are added as expected |
| 156 | |
| 157 | Args: |
| 158 | data: Data to check |
| 159 | size: Expected ROM size |
| 160 | offset: Expected offset to first CBFS file |
| 161 | arch: Expected architecture |
| 162 | """ |
| 163 | cbfs = self._check_hdr(data, size, offset=offset, arch=arch) |
| 164 | self._check_uboot(cbfs) |
| 165 | self._check_dtb(cbfs) |
| 166 | |
Simon Glass | e073d4e | 2019-07-08 13:18:56 -0600 | [diff] [blame] | 167 | def _get_expected_cbfs(self, size, arch='x86', compress=None, base=None): |
Simon Glass | 4997a7e | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 168 | """Get the file created by cbfstool for a particular scenario |
| 169 | |
| 170 | Args: |
| 171 | size: Size of the CBFS in bytes |
| 172 | arch: Architecture of the CBFS, as a string |
| 173 | compress: Compression to use, e.g. cbfs_util.COMPRESS_LZMA |
Simon Glass | e073d4e | 2019-07-08 13:18:56 -0600 | [diff] [blame] | 174 | base: Base address of file, or None to put it anywhere |
Simon Glass | 4997a7e | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 175 | |
| 176 | Returns: |
| 177 | Resulting CBFS file, or None if cbfstool is not available |
| 178 | """ |
| 179 | if not self.have_cbfstool or not self.have_lz4: |
| 180 | return None |
| 181 | cbfs_fname = os.path.join(self._indir, 'test.cbfs') |
| 182 | cbfs_util.cbfstool(cbfs_fname, 'create', '-m', arch, '-s', '%#x' % size) |
Simon Glass | e073d4e | 2019-07-08 13:18:56 -0600 | [diff] [blame] | 183 | if base: |
| 184 | base = [(1 << 32) - size + b for b in base] |
Simon Glass | 4997a7e | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 185 | cbfs_util.cbfstool(cbfs_fname, 'add', '-n', 'u-boot', '-t', 'raw', |
| 186 | '-c', compress and compress[0] or 'none', |
| 187 | '-f', tools.GetInputFilename( |
Simon Glass | e073d4e | 2019-07-08 13:18:56 -0600 | [diff] [blame] | 188 | compress and 'compress' or 'u-boot.bin'), |
| 189 | base=base[0] if base else None) |
Simon Glass | 4997a7e | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 190 | cbfs_util.cbfstool(cbfs_fname, 'add', '-n', 'u-boot-dtb', '-t', 'raw', |
| 191 | '-c', compress and compress[1] or 'none', |
| 192 | '-f', tools.GetInputFilename( |
Simon Glass | e073d4e | 2019-07-08 13:18:56 -0600 | [diff] [blame] | 193 | compress and 'compress' or 'u-boot.dtb'), |
| 194 | base=base[1] if base else None) |
Simon Glass | 4997a7e | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 195 | return cbfs_fname |
| 196 | |
| 197 | def _compare_expected_cbfs(self, data, cbfstool_fname): |
| 198 | """Compare against what cbfstool creates |
| 199 | |
| 200 | This compares what binman creates with what cbfstool creates for what |
| 201 | is proportedly the same thing. |
| 202 | |
| 203 | Args: |
| 204 | data: CBFS created by binman |
| 205 | cbfstool_fname: CBFS created by cbfstool |
| 206 | """ |
| 207 | if not self.have_cbfstool or not self.have_lz4: |
| 208 | return |
| 209 | expect = tools.ReadFile(cbfstool_fname) |
| 210 | if expect != data: |
| 211 | tools.WriteFile('/tmp/expect', expect) |
| 212 | tools.WriteFile('/tmp/actual', data) |
| 213 | print('diff -y <(xxd -g1 /tmp/expect) <(xxd -g1 /tmp/actual) | colordiff') |
| 214 | self.fail('cbfstool produced a different result') |
| 215 | |
| 216 | def test_cbfs_functions(self): |
| 217 | """Test global functions of cbfs_util""" |
| 218 | self.assertEqual(cbfs_util.ARCHITECTURE_X86, cbfs_util.find_arch('x86')) |
| 219 | self.assertIsNone(cbfs_util.find_arch('bad-arch')) |
| 220 | |
| 221 | self.assertEqual(cbfs_util.COMPRESS_LZMA, cbfs_util.find_compress('lzma')) |
| 222 | self.assertIsNone(cbfs_util.find_compress('bad-comp')) |
| 223 | |
| 224 | def test_cbfstool_failure(self): |
| 225 | """Test failure to run cbfstool""" |
| 226 | if not self.have_cbfstool: |
| 227 | self.skipTest('No cbfstool available') |
| 228 | try: |
| 229 | # In verbose mode this test fails since stderr is not captured. Fix |
| 230 | # this by turning off verbosity. |
| 231 | old_verbose = cbfs_util.VERBOSE |
| 232 | cbfs_util.VERBOSE = False |
| 233 | with test_util.capture_sys_output() as (_stdout, stderr): |
| 234 | with self.assertRaises(Exception) as e: |
| 235 | cbfs_util.cbfstool('missing-file', 'bad-command') |
| 236 | finally: |
| 237 | cbfs_util.VERBOSE = old_verbose |
| 238 | self.assertIn('Unknown command', stderr.getvalue()) |
| 239 | self.assertIn('Failed to run', str(e.exception)) |
| 240 | |
| 241 | def test_cbfs_raw(self): |
| 242 | """Test base handling of a Coreboot Filesystem (CBFS)""" |
| 243 | size = 0xb0 |
| 244 | cbw = CbfsWriter(size) |
| 245 | cbw.add_file_raw('u-boot', U_BOOT_DATA) |
| 246 | cbw.add_file_raw('u-boot-dtb', U_BOOT_DTB_DATA) |
| 247 | data = cbw.get_data() |
| 248 | self._check_raw(data, size) |
| 249 | cbfs_fname = self._get_expected_cbfs(size=size) |
| 250 | self._compare_expected_cbfs(data, cbfs_fname) |
| 251 | |
| 252 | def test_cbfs_invalid_file_type(self): |
| 253 | """Check handling of an invalid file type when outputiing a CBFS""" |
| 254 | size = 0xb0 |
| 255 | cbw = CbfsWriter(size) |
| 256 | cfile = cbw.add_file_raw('u-boot', U_BOOT_DATA) |
| 257 | |
| 258 | # Change the type manually before generating the CBFS, and make sure |
| 259 | # that the generator complains |
| 260 | cfile.ftype = 0xff |
| 261 | with self.assertRaises(ValueError) as e: |
| 262 | cbw.get_data() |
| 263 | self.assertIn('Unknown type 0xff when writing', str(e.exception)) |
| 264 | |
| 265 | def test_cbfs_invalid_file_type_on_read(self): |
| 266 | """Check handling of an invalid file type when reading the CBFS""" |
| 267 | size = 0xb0 |
| 268 | cbw = CbfsWriter(size) |
| 269 | cbw.add_file_raw('u-boot', U_BOOT_DATA) |
| 270 | |
| 271 | data = cbw.get_data() |
| 272 | |
| 273 | # Read in the first file header |
| 274 | cbr = cbfs_util.CbfsReader(data, read=False) |
| 275 | with io.BytesIO(data) as fd: |
| 276 | self.assertTrue(cbr._find_and_read_header(fd, len(data))) |
| 277 | pos = fd.tell() |
| 278 | hdr_data = fd.read(cbfs_util.FILE_HEADER_LEN) |
| 279 | magic, size, ftype, attr, offset = struct.unpack( |
| 280 | cbfs_util.FILE_HEADER_FORMAT, hdr_data) |
| 281 | |
| 282 | # Create a new CBFS with a change to the file type |
| 283 | ftype = 0xff |
| 284 | newdata = data[:pos] |
| 285 | newdata += struct.pack(cbfs_util.FILE_HEADER_FORMAT, magic, size, ftype, |
| 286 | attr, offset) |
| 287 | newdata += data[pos + cbfs_util.FILE_HEADER_LEN:] |
| 288 | |
| 289 | # Read in this CBFS and make sure that the reader complains |
| 290 | with self.assertRaises(ValueError) as e: |
| 291 | cbfs_util.CbfsReader(newdata) |
| 292 | self.assertIn('Unknown type 0xff when reading', str(e.exception)) |
| 293 | |
| 294 | def test_cbfs_no_space(self): |
| 295 | """Check handling of running out of space in the CBFS""" |
| 296 | size = 0x60 |
| 297 | cbw = CbfsWriter(size) |
| 298 | cbw.add_file_raw('u-boot', U_BOOT_DATA) |
| 299 | with self.assertRaises(ValueError) as e: |
| 300 | cbw.get_data() |
| 301 | self.assertIn('No space for header', str(e.exception)) |
| 302 | |
| 303 | def test_cbfs_no_space_skip(self): |
| 304 | """Check handling of running out of space in CBFS with file header""" |
Simon Glass | 7c173ce | 2019-07-08 13:18:55 -0600 | [diff] [blame] | 305 | size = 0x5c |
| 306 | cbw = CbfsWriter(size, arch=cbfs_util.ARCHITECTURE_PPC64) |
| 307 | cbw._add_fileheader = True |
| 308 | cbw.add_file_raw('u-boot', U_BOOT_DATA) |
| 309 | with self.assertRaises(ValueError) as e: |
| 310 | cbw.get_data() |
| 311 | self.assertIn('No space for data before offset', str(e.exception)) |
| 312 | |
| 313 | def test_cbfs_no_space_pad(self): |
| 314 | """Check handling of running out of space in CBFS with file header""" |
Simon Glass | 4997a7e | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 315 | size = 0x70 |
| 316 | cbw = CbfsWriter(size) |
| 317 | cbw._add_fileheader = True |
| 318 | cbw.add_file_raw('u-boot', U_BOOT_DATA) |
| 319 | with self.assertRaises(ValueError) as e: |
| 320 | cbw.get_data() |
Simon Glass | 7c173ce | 2019-07-08 13:18:55 -0600 | [diff] [blame] | 321 | self.assertIn('No space for data before pad offset', str(e.exception)) |
Simon Glass | 4997a7e | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 322 | |
| 323 | def test_cbfs_bad_header_ptr(self): |
| 324 | """Check handling of a bad master-header pointer""" |
| 325 | size = 0x70 |
| 326 | cbw = CbfsWriter(size) |
| 327 | cbw.add_file_raw('u-boot', U_BOOT_DATA) |
| 328 | data = cbw.get_data() |
| 329 | |
| 330 | # Add one to the pointer to make it invalid |
| 331 | newdata = data[:-4] + struct.pack('<I', cbw._header_offset + 1) |
| 332 | |
| 333 | # We should still be able to find the master header by searching |
| 334 | with test_util.capture_sys_output() as (stdout, _stderr): |
| 335 | cbfs = cbfs_util.CbfsReader(newdata) |
| 336 | self.assertIn('Relative offset seems wrong', stdout.getvalue()) |
| 337 | self.assertIn('u-boot', cbfs.files) |
| 338 | self.assertEqual(size, cbfs.rom_size) |
| 339 | |
| 340 | def test_cbfs_bad_header(self): |
| 341 | """Check handling of a bad master header""" |
| 342 | size = 0x70 |
| 343 | cbw = CbfsWriter(size) |
| 344 | cbw.add_file_raw('u-boot', U_BOOT_DATA) |
| 345 | data = cbw.get_data() |
| 346 | |
| 347 | # Drop most of the header and try reading the modified CBFS |
| 348 | newdata = data[:cbw._header_offset + 4] |
| 349 | |
| 350 | with test_util.capture_sys_output() as (stdout, _stderr): |
| 351 | with self.assertRaises(ValueError) as e: |
| 352 | cbfs_util.CbfsReader(newdata) |
| 353 | self.assertIn('Relative offset seems wrong', stdout.getvalue()) |
| 354 | self.assertIn('Cannot find master header', str(e.exception)) |
| 355 | |
| 356 | def test_cbfs_bad_file_header(self): |
| 357 | """Check handling of a bad file header""" |
| 358 | size = 0x70 |
| 359 | cbw = CbfsWriter(size) |
| 360 | cbw.add_file_raw('u-boot', U_BOOT_DATA) |
| 361 | data = cbw.get_data() |
| 362 | |
| 363 | # Read in the CBFS master header (only), then stop |
| 364 | cbr = cbfs_util.CbfsReader(data, read=False) |
| 365 | with io.BytesIO(data) as fd: |
| 366 | self.assertTrue(cbr._find_and_read_header(fd, len(data))) |
| 367 | pos = fd.tell() |
| 368 | |
| 369 | # Remove all but 4 bytes of the file headerm and try to read the file |
| 370 | newdata = data[:pos + 4] |
| 371 | with test_util.capture_sys_output() as (stdout, _stderr): |
| 372 | with io.BytesIO(newdata) as fd: |
| 373 | fd.seek(pos) |
| 374 | self.assertEqual(False, cbr._read_next_file(fd)) |
| 375 | self.assertIn('File header at 0 ran out of data', stdout.getvalue()) |
| 376 | |
| 377 | def test_cbfs_bad_file_string(self): |
| 378 | """Check handling of an incomplete filename string""" |
| 379 | size = 0x70 |
| 380 | cbw = CbfsWriter(size) |
| 381 | cbw.add_file_raw('16-characters xx', U_BOOT_DATA) |
| 382 | data = cbw.get_data() |
| 383 | |
| 384 | # Read in the CBFS master header (only), then stop |
| 385 | cbr = cbfs_util.CbfsReader(data, read=False) |
| 386 | with io.BytesIO(data) as fd: |
| 387 | self.assertTrue(cbr._find_and_read_header(fd, len(data))) |
| 388 | pos = fd.tell() |
| 389 | |
| 390 | # Create a new CBFS with only the first 16 bytes of the file name, then |
| 391 | # try to read the file |
| 392 | newdata = data[:pos + cbfs_util.FILE_HEADER_LEN + 16] |
| 393 | with test_util.capture_sys_output() as (stdout, _stderr): |
| 394 | with io.BytesIO(newdata) as fd: |
| 395 | fd.seek(pos) |
| 396 | self.assertEqual(False, cbr._read_next_file(fd)) |
| 397 | self.assertIn('String at %x ran out of data' % |
| 398 | cbfs_util.FILE_HEADER_LEN, stdout.getvalue()) |
| 399 | |
| 400 | def test_cbfs_debug(self): |
| 401 | """Check debug output""" |
| 402 | size = 0x70 |
| 403 | cbw = CbfsWriter(size) |
| 404 | cbw.add_file_raw('u-boot', U_BOOT_DATA) |
| 405 | data = cbw.get_data() |
| 406 | |
| 407 | try: |
| 408 | cbfs_util.DEBUG = True |
| 409 | with test_util.capture_sys_output() as (stdout, _stderr): |
| 410 | cbfs_util.CbfsReader(data) |
| 411 | self.assertEqual('name u-boot\ndata %s\n' % U_BOOT_DATA, |
| 412 | stdout.getvalue()) |
| 413 | finally: |
| 414 | cbfs_util.DEBUG = False |
| 415 | |
| 416 | def test_cbfs_bad_attribute(self): |
| 417 | """Check handling of bad attribute tag""" |
| 418 | if not self.have_lz4: |
| 419 | self.skipTest('lz4 --no-frame-crc not available') |
| 420 | size = 0x140 |
| 421 | cbw = CbfsWriter(size) |
Simon Glass | e073d4e | 2019-07-08 13:18:56 -0600 | [diff] [blame] | 422 | cbw.add_file_raw('u-boot', COMPRESS_DATA, None, |
Simon Glass | 4997a7e | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 423 | compress=cbfs_util.COMPRESS_LZ4) |
| 424 | data = cbw.get_data() |
| 425 | |
| 426 | # Search the CBFS for the expected compression tag |
| 427 | with io.BytesIO(data) as fd: |
| 428 | while True: |
| 429 | pos = fd.tell() |
| 430 | tag, = struct.unpack('>I', fd.read(4)) |
| 431 | if tag == cbfs_util.FILE_ATTR_TAG_COMPRESSION: |
| 432 | break |
| 433 | |
| 434 | # Create a new CBFS with the tag changed to something invalid |
| 435 | newdata = data[:pos] + struct.pack('>I', 0x123) + data[pos + 4:] |
| 436 | with test_util.capture_sys_output() as (stdout, _stderr): |
| 437 | cbfs_util.CbfsReader(newdata) |
| 438 | self.assertEqual('Unknown attribute tag 123\n', stdout.getvalue()) |
| 439 | |
| 440 | def test_cbfs_missing_attribute(self): |
| 441 | """Check handling of an incomplete attribute tag""" |
| 442 | if not self.have_lz4: |
| 443 | self.skipTest('lz4 --no-frame-crc not available') |
| 444 | size = 0x140 |
| 445 | cbw = CbfsWriter(size) |
Simon Glass | e073d4e | 2019-07-08 13:18:56 -0600 | [diff] [blame] | 446 | cbw.add_file_raw('u-boot', COMPRESS_DATA, None, |
Simon Glass | 4997a7e | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 447 | compress=cbfs_util.COMPRESS_LZ4) |
| 448 | data = cbw.get_data() |
| 449 | |
| 450 | # Read in the CBFS master header (only), then stop |
| 451 | cbr = cbfs_util.CbfsReader(data, read=False) |
| 452 | with io.BytesIO(data) as fd: |
| 453 | self.assertTrue(cbr._find_and_read_header(fd, len(data))) |
| 454 | pos = fd.tell() |
| 455 | |
| 456 | # Create a new CBFS with only the first 4 bytes of the compression tag, |
| 457 | # then try to read the file |
| 458 | tag_pos = pos + cbfs_util.FILE_HEADER_LEN + cbfs_util.FILENAME_ALIGN |
| 459 | newdata = data[:tag_pos + 4] |
| 460 | with test_util.capture_sys_output() as (stdout, _stderr): |
| 461 | with io.BytesIO(newdata) as fd: |
| 462 | fd.seek(pos) |
| 463 | self.assertEqual(False, cbr._read_next_file(fd)) |
| 464 | self.assertIn('Attribute tag at %x ran out of data' % tag_pos, |
| 465 | stdout.getvalue()) |
| 466 | |
| 467 | def test_cbfs_file_master_header(self): |
| 468 | """Check handling of a file containing a master header""" |
| 469 | size = 0x100 |
| 470 | cbw = CbfsWriter(size) |
| 471 | cbw._add_fileheader = True |
| 472 | cbw.add_file_raw('u-boot', U_BOOT_DATA) |
| 473 | data = cbw.get_data() |
| 474 | |
| 475 | cbr = cbfs_util.CbfsReader(data) |
| 476 | self.assertIn('u-boot', cbr.files) |
| 477 | self.assertEqual(size, cbr.rom_size) |
| 478 | |
| 479 | def test_cbfs_arch(self): |
| 480 | """Test on non-x86 architecture""" |
| 481 | size = 0x100 |
| 482 | cbw = CbfsWriter(size, arch=cbfs_util.ARCHITECTURE_PPC64) |
| 483 | cbw.add_file_raw('u-boot', U_BOOT_DATA) |
| 484 | cbw.add_file_raw('u-boot-dtb', U_BOOT_DTB_DATA) |
| 485 | data = cbw.get_data() |
| 486 | self._check_raw(data, size, offset=0x40, |
| 487 | arch=cbfs_util.ARCHITECTURE_PPC64) |
| 488 | |
| 489 | # Compare against what cbfstool creates |
| 490 | cbfs_fname = self._get_expected_cbfs(size=size, arch='ppc64') |
| 491 | self._compare_expected_cbfs(data, cbfs_fname) |
| 492 | |
| 493 | def test_cbfs_stage(self): |
| 494 | """Tests handling of a Coreboot Filesystem (CBFS)""" |
| 495 | if not elf.ELF_TOOLS: |
| 496 | self.skipTest('Python elftools not available') |
| 497 | elf_fname = os.path.join(self._indir, 'cbfs-stage.elf') |
| 498 | elf.MakeElf(elf_fname, U_BOOT_DATA, U_BOOT_DTB_DATA) |
| 499 | |
| 500 | size = 0xb0 |
| 501 | cbw = CbfsWriter(size) |
| 502 | cbw.add_file_stage('u-boot', tools.ReadFile(elf_fname)) |
| 503 | |
| 504 | data = cbw.get_data() |
| 505 | cbfs = self._check_hdr(data, size) |
| 506 | load = 0xfef20000 |
| 507 | entry = load + 2 |
| 508 | |
| 509 | cfile = self._check_uboot(cbfs, cbfs_util.TYPE_STAGE, offset=0x28, |
| 510 | data=U_BOOT_DATA + U_BOOT_DTB_DATA) |
| 511 | |
| 512 | self.assertEqual(entry, cfile.entry) |
| 513 | self.assertEqual(load, cfile.load) |
| 514 | self.assertEqual(len(U_BOOT_DATA) + len(U_BOOT_DTB_DATA), |
| 515 | cfile.data_len) |
| 516 | |
| 517 | # Compare against what cbfstool creates |
| 518 | if self.have_cbfstool: |
| 519 | cbfs_fname = os.path.join(self._indir, 'test.cbfs') |
| 520 | cbfs_util.cbfstool(cbfs_fname, 'create', '-m', 'x86', '-s', |
| 521 | '%#x' % size) |
| 522 | cbfs_util.cbfstool(cbfs_fname, 'add-stage', '-n', 'u-boot', |
| 523 | '-f', elf_fname) |
| 524 | self._compare_expected_cbfs(data, cbfs_fname) |
| 525 | |
| 526 | def test_cbfs_raw_compress(self): |
| 527 | """Test base handling of compressing raw files""" |
| 528 | if not self.have_lz4: |
| 529 | self.skipTest('lz4 --no-frame-crc not available') |
| 530 | size = 0x140 |
| 531 | cbw = CbfsWriter(size) |
Simon Glass | e073d4e | 2019-07-08 13:18:56 -0600 | [diff] [blame] | 532 | cbw.add_file_raw('u-boot', COMPRESS_DATA, None, |
Simon Glass | 4997a7e | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 533 | compress=cbfs_util.COMPRESS_LZ4) |
Simon Glass | e073d4e | 2019-07-08 13:18:56 -0600 | [diff] [blame] | 534 | cbw.add_file_raw('u-boot-dtb', COMPRESS_DATA, None, |
Simon Glass | 4997a7e | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 535 | compress=cbfs_util.COMPRESS_LZMA) |
| 536 | data = cbw.get_data() |
| 537 | |
| 538 | cbfs = self._check_hdr(data, size) |
| 539 | self.assertIn('u-boot', cbfs.files) |
| 540 | cfile = cbfs.files['u-boot'] |
| 541 | self.assertEqual(cfile.name, 'u-boot') |
| 542 | self.assertEqual(cfile.offset, 56) |
| 543 | self.assertEqual(cfile.data, COMPRESS_DATA) |
| 544 | self.assertEqual(cfile.ftype, cbfs_util.TYPE_RAW) |
| 545 | self.assertEqual(cfile.compress, cbfs_util.COMPRESS_LZ4) |
| 546 | self.assertEqual(cfile.memlen, len(COMPRESS_DATA)) |
| 547 | |
| 548 | self.assertIn('u-boot-dtb', cbfs.files) |
| 549 | cfile = cbfs.files['u-boot-dtb'] |
| 550 | self.assertEqual(cfile.name, 'u-boot-dtb') |
| 551 | self.assertEqual(cfile.offset, 56) |
| 552 | self.assertEqual(cfile.data, COMPRESS_DATA) |
| 553 | self.assertEqual(cfile.ftype, cbfs_util.TYPE_RAW) |
| 554 | self.assertEqual(cfile.compress, cbfs_util.COMPRESS_LZMA) |
| 555 | self.assertEqual(cfile.memlen, len(COMPRESS_DATA)) |
| 556 | |
| 557 | cbfs_fname = self._get_expected_cbfs(size=size, compress=['lz4', 'lzma']) |
| 558 | self._compare_expected_cbfs(data, cbfs_fname) |
| 559 | |
Simon Glass | 7c173ce | 2019-07-08 13:18:55 -0600 | [diff] [blame] | 560 | def test_cbfs_raw_space(self): |
| 561 | """Test files with unused space in the CBFS""" |
| 562 | size = 0xf0 |
| 563 | cbw = CbfsWriter(size) |
| 564 | cbw.add_file_raw('u-boot', U_BOOT_DATA) |
| 565 | cbw.add_file_raw('u-boot-dtb', U_BOOT_DTB_DATA) |
| 566 | data = cbw.get_data() |
| 567 | self._check_raw(data, size) |
| 568 | cbfs_fname = self._get_expected_cbfs(size=size) |
| 569 | self._compare_expected_cbfs(data, cbfs_fname) |
| 570 | |
Simon Glass | e073d4e | 2019-07-08 13:18:56 -0600 | [diff] [blame] | 571 | def test_cbfs_offset(self): |
| 572 | """Test a CBFS with files at particular offsets""" |
| 573 | size = 0x200 |
| 574 | cbw = CbfsWriter(size) |
| 575 | cbw.add_file_raw('u-boot', U_BOOT_DATA, 0x40) |
| 576 | cbw.add_file_raw('u-boot-dtb', U_BOOT_DTB_DATA, 0x140) |
| 577 | |
| 578 | data = cbw.get_data() |
| 579 | cbfs = self._check_hdr(data, size) |
| 580 | self._check_uboot(cbfs, ftype=cbfs_util.TYPE_RAW, offset=0x40, |
| 581 | cbfs_offset=0x40) |
| 582 | self._check_dtb(cbfs, offset=0x40, cbfs_offset=0x140) |
| 583 | |
| 584 | cbfs_fname = self._get_expected_cbfs(size=size, base=(0x40, 0x140)) |
| 585 | self._compare_expected_cbfs(data, cbfs_fname) |
| 586 | |
| 587 | def test_cbfs_invalid_file_type_header(self): |
| 588 | """Check handling of an invalid file type when outputting a header""" |
| 589 | size = 0xb0 |
| 590 | cbw = CbfsWriter(size) |
| 591 | cfile = cbw.add_file_raw('u-boot', U_BOOT_DATA, 0) |
| 592 | |
| 593 | # Change the type manually before generating the CBFS, and make sure |
| 594 | # that the generator complains |
| 595 | cfile.ftype = 0xff |
| 596 | with self.assertRaises(ValueError) as e: |
| 597 | cbw.get_data() |
| 598 | self.assertIn('Unknown file type 0xff', str(e.exception)) |
| 599 | |
| 600 | def test_cbfs_offset_conflict(self): |
| 601 | """Test a CBFS with files that want to overlap""" |
| 602 | size = 0x200 |
| 603 | cbw = CbfsWriter(size) |
| 604 | cbw.add_file_raw('u-boot', U_BOOT_DATA, 0x40) |
| 605 | cbw.add_file_raw('u-boot-dtb', U_BOOT_DTB_DATA, 0x80) |
| 606 | |
| 607 | with self.assertRaises(ValueError) as e: |
| 608 | cbw.get_data() |
| 609 | self.assertIn('No space for data before pad offset', str(e.exception)) |
| 610 | |
| 611 | def test_cbfs_check_offset(self): |
| 612 | """Test that we can discover the offset of a file after writing it""" |
| 613 | size = 0xb0 |
| 614 | cbw = CbfsWriter(size) |
| 615 | cbw.add_file_raw('u-boot', U_BOOT_DATA) |
| 616 | cbw.add_file_raw('u-boot-dtb', U_BOOT_DTB_DATA) |
| 617 | data = cbw.get_data() |
| 618 | |
| 619 | cbfs = cbfs_util.CbfsReader(data) |
| 620 | self.assertEqual(0x38, cbfs.files['u-boot'].cbfs_offset) |
| 621 | self.assertEqual(0x78, cbfs.files['u-boot-dtb'].cbfs_offset) |
| 622 | |
Simon Glass | 4997a7e | 2019-07-08 13:18:52 -0600 | [diff] [blame] | 623 | |
| 624 | if __name__ == '__main__': |
| 625 | unittest.main() |