Enable script_raw for sandbox and add a GPT disk with valid legacy and FIT scripts, as well as malformed and unsupported images. Bind the bootmeth inside the new C test so existing bootmeth lists and bootflow tests, including bootflow scan -G coverage, remain unchanged.
Introduce test/py/img/ as a package for disk-image generators. Existing C-test image setup lives in test/py/tests/test_ut.py; keep the raw-script case definitions and image generation in the new module and call it from the existing test_ut_dm_init_bootstd() initialization. Allow fs_helper.setup_image() to accept an sfdisk partition specification and reuse it for the named GPT partitions. This avoids a dependency on sgdisk or a separate initialization test. Annotate each expected C error with its Python case name to keep the two lists in sync. Signed-off-by: He Yong <[email protected]> --- Changes in v3: - Split sandbox coverage into its own patch. - Bind script_raw only inside its test and preserve existing scan -G coverage. - Annotate expected errors with Python case names. - Reuse fs_helper and sfdisk from the existing bootstd initialization test. arch/sandbox/dts/test.dts | 8 ++++ configs/sandbox_defconfig | 1 + test/boot/bootflow.c | 81 +++++++++++++++++++++++++++++++ test/py/img/__init__.py | 2 + test/py/img/script_raw.py | 98 ++++++++++++++++++++++++++++++++++++++ test/py/tests/fs_helper.py | 19 +++++--- test/py/tests/test_ut.py | 3 ++ 7 files changed, 205 insertions(+), 7 deletions(-) create mode 100644 test/py/img/__init__.py create mode 100644 test/py/img/script_raw.py diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts index 25c99ef8147..3470e4368d3 100644 --- a/arch/sandbox/dts/test.dts +++ b/arch/sandbox/dts/test.dts @@ -50,6 +50,7 @@ mmc9 = "/mmc9"; mmc10 = "/mmc10"; mmc11 = "/mmc11"; + mmc12 = "/mmc12"; pci0 = &pci0; pci1 = &pci1; pci2 = &pci2; @@ -1398,6 +1399,13 @@ filename = "mmc11.img"; }; + /* This is used for raw script bootflow tests */ + mmc12 { + status = "disabled"; + compatible = "sandbox,mmc"; + filename = "mmc12.img"; + }; + pch { compatible = "sandbox,pch"; }; diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig index a2708e6fb33..09b2980f79b 100644 --- a/configs/sandbox_defconfig +++ b/configs/sandbox_defconfig @@ -28,6 +28,7 @@ CONFIG_FIT_VERBOSE=y CONFIG_BOOTMETH_ANDROID=y CONFIG_BOOTMETH_BLS=y CONFIG_BOOTMETH_RAUC=y +CONFIG_BOOTMETH_SCRIPT_RAW=y CONFIG_UPL=y CONFIG_LEGACY_IMAGE_FORMAT=y CONFIG_MEASURED_BOOT=y diff --git a/test/boot/bootflow.c b/test/boot/bootflow.c index c4b41668a62..d7e5a51f7b5 100644 --- a/test/boot/bootflow.c +++ b/test/boot/bootflow.c @@ -35,6 +35,7 @@ extern U_BOOT_DRIVER(bootmeth_android); extern U_BOOT_DRIVER(bootmeth_cros); extern U_BOOT_DRIVER(bootmeth_rauc); extern U_BOOT_DRIVER(bootmeth_2script); +extern U_BOOT_DRIVER(bootmeth_2script_raw); /* Use this as the vendor for EFI to tell the app to exit boot services */ static u16 __efi_runtime_data test_vendor[] = u"U-Boot testing"; @@ -1908,4 +1909,84 @@ static int bootdev_hunt_fallthrough(struct unit_test_state *uts) } BOOTSTD_TEST(bootdev_hunt_fallthrough, UTF_DM | UTF_SCAN_FDT | UTF_SF_BOOTDEV | UTF_CONSOLE); + +/* Exercise valid and rejected images without restarting U-Boot per case. */ +static int check_script_raw(struct unit_test_state *uts, + struct bootflow_iter *iter) +{ + static const int errors[] = { + /* Keep in sync with CASES in test/py/img/script_raw.py. */ + 0, /* legacy */ + 0, /* fit */ + -EINVAL, /* external */ + -EINVAL, /* bad_crc */ + -E2BIG, /* too_large */ + -ENOENT, /* wrong_name */ + -ENOEXEC, /* empty */ + -EINVAL, /* bad_header */ + -E2BIG, /* partition_size */ + -EINVAL, /* bad_fit */ + }; + struct bootflow bflow; + int ret, part; + + ret = bootflow_scan_first(NULL, NULL, iter, BOOTFLOWIF_ALL, &bflow); + /* A raw script belongs to a partition, never to the whole disk. */ + ut_asserteq(-ENOENT, ret); + ut_asserteq(0, bflow.part); + bootflow_free(&bflow); + for (part = 1; part <= ARRAY_SIZE(errors); part++) { + ret = bootflow_scan_next(iter, &bflow); + ut_asserteq(part, bflow.part); + ut_asserteq(errors[part - 1], ret); + if (!ret) { + ut_asserteq(BOOTFLOWST_READY, bflow.state); + ut_asserteq_str("uboot_scr", bflow.fname); + /* A script returning successfully did not boot an OS. */ + ut_asserteq(-EFAULT, bootflow_run_boot(iter, &bflow)); + ut_assert_skip_to_line("%sraw-script-ok mmc c %d", + part == 2 ? "sha256+ " : "", part); + ut_asserteq_str("mmc", env_get("devtype")); + } else { + ut_assert(bflow.state < BOOTFLOWST_READY); + ut_assertnull(bflow.buf); + } + bootflow_free(&bflow); + } + + return 0; +} + +static int bootflow_script_raw(struct unit_test_state *uts) +{ + static const char *order[] = {"mmc12", NULL}; + struct bootflow_iter iter; + struct bootstd_priv *std; + struct udevice *bootstd; + const char **old_order; + int ret; + + if (!IS_ENABLED(CONFIG_BOOTMETH_SCRIPT_RAW)) + return -EAGAIN; + + /* Bind only for this test, leaving the default bootmeth list intact. */ + ut_assertok(uclass_first_device_err(UCLASS_BOOTSTD, &bootstd)); + ut_assertok(device_bind(bootstd, DM_DRIVER_REF(bootmeth_2script_raw), + "script_raw", 0, ofnode_null(), NULL)); + + ut_assertok(prep_mmc_bootdev(uts, "mmc12", false, &old_order)); + ut_assertok(bootstd_get_priv(&std)); + std->bootdev_order = order; + ut_assertok(bootmeth_set_order("script_raw")); + ut_assertok(env_set("verify", "yes")); + ret = check_script_raw(uts, &iter); + bootflow_iter_uninit(&iter); + std->bootdev_order = old_order; + /* Restore verify even if a scan or boot assertion failed. */ + ut_assertok(env_set("verify", NULL)); + ut_assertok(ret); + + return 0; +} +BOOTSTD_TEST(bootflow_script_raw, UTF_DM | UTF_SCAN_FDT | UTF_CONSOLE); #endif /* CONFIG_SANDBOX */ diff --git a/test/py/img/__init__.py b/test/py/img/__init__.py new file mode 100644 index 00000000000..526c5dda958 --- /dev/null +++ b/test/py/img/__init__.py @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0+ +"""Disk-image generators for U-Boot C tests.""" diff --git a/test/py/img/script_raw.py b/test/py/img/script_raw.py new file mode 100644 index 00000000000..671c5917d6d --- /dev/null +++ b/test/py/img/script_raw.py @@ -0,0 +1,98 @@ +# SPDX-License-Identifier: GPL-2.0+ +# Copyright 2026 He Yong <[email protected]> + +"""Create a GPT disk containing valid and malformed raw script images.""" + +import os +import struct +import zlib + +import utils +from tests import fs_helper + + +CASES = ('legacy', 'fit', 'external', 'bad_crc', + 'too_large', 'wrong_name', 'empty', 'bad_header', 'partition_size', + 'bad_fit') + + +def script_image(ubman, case): + """Build one script image, optionally corrupting its header or data.""" + workdir = ubman.config.build_dir + script = os.path.join(workdir, 'raw-script.cmd') + image = os.path.join(workdir, 'raw-script.scr') + mkimage = os.path.join(ubman.config.build_dir, 'tools', 'mkimage') + with open(script, 'w', encoding='utf-8') as outf: + outf.write('echo raw-script-ok ${devtype} ${devnum} ' + '${distro_bootpart}\n') + if case in ('fit', 'external', 'bad_fit'): + its = os.path.join(workdir, 'raw-script.its') + with open(its, 'w', encoding='utf-8') as outf: + outf.write('''/dts-v1/; +/ { + description = "Raw script test"; + #address-cells = <1>; + images { + script { + description = "Boot script"; + data = /incbin/("raw-script.cmd"); + type = "script"; + arch = "sandbox"; + compression = "none"; + hash { algo = "sha256"; }; + }; + }; + configurations { + default = "conf"; + conf { + script = "script"; + }; + }; +}; +''') + args = [mkimage, '-f', its] + if case == 'external': + args.append('-E') + utils.run_and_log(ubman, args + [image]) + else: + utils.run_and_log(ubman, [mkimage, '-A', 'sandbox', '-T', 'script', + '-C', 'none', '-d', script, image]) + with open(image, 'rb') as inf: + data = bytearray(inf.read()) + if case == 'bad_crc': + data[-1] ^= 1 + elif case == 'too_large': + struct.pack_into('>I', data, 12, 2 * 1024 * 1024) + elif case == 'empty': + data = bytearray(512) + if case == 'too_large': + struct.pack_into('>I', data, 4, 0) + struct.pack_into('>I', data, 4, zlib.crc32(data[:64])) + return data + + +def setup_script_raw_image(ubman): + """Create one disk for all cases, with 512 KiB non-bootable partitions.""" + spec = 'label: gpt\n' + for part, case in enumerate(CASES, 1): + start = part * 2048 + label = 'other' if case == 'wrong_name' else 'uboot_scr' + spec += f'start={start}, size=1024, name="{label}"\n' + disk, _ = fs_helper.setup_image(ubman, 12, None, img_size=16, + part_spec=spec) + with open(disk, 'r+b') as outf: + for part, case in enumerate(CASES, 1): + data = script_image(ubman, case) + if case == 'bad_header': + data[4] ^= 1 + elif case == 'partition_size': + struct.pack_into('>I', data, 12, 512 * 1024) + elif case == 'bad_fit': + # Keep the header valid but corrupt the structure block. + offset = struct.unpack_from('>I', data, 8)[0] + struct.pack_into('>I', data, offset, 0xffffffff) + if case == 'partition_size': + struct.pack_into('>I', data, 4, 0) + struct.pack_into('>I', data, 4, zlib.crc32(data[:64])) + outf.seek(part * 1024 * 1024) + outf.write(data) diff --git a/test/py/tests/fs_helper.py b/test/py/tests/fs_helper.py index e3824b2c1fd..7c9577e28b1 100644 --- a/test/py/tests/fs_helper.py +++ b/test/py/tests/fs_helper.py @@ -8,7 +8,7 @@ import re import os import shutil -from subprocess import call, check_call, check_output, CalledProcessError +from subprocess import call, check_call, check_output, CalledProcessError, run from subprocess import DEVNULL import tempfile @@ -262,8 +262,8 @@ def mk_fs(config, fs_type, size, prefix, src_dir=None, fs_img=None, quiet=False) raise def setup_image(ubman, devnum, part_type, img_size=20, second_part=False, - basename='mmc'): - """Create a disk image with one or two partitions + basename='mmc', part_spec=None): + """Create a disk image with default or caller-specified partitions Args: ubman (ConsoleBase): Console to use @@ -272,6 +272,8 @@ def setup_image(ubman, devnum, part_type, img_size=20, second_part=False, img_size (int): Image size in MiB second_part (bool): True to contain a small second partition basename (str): Base name to use in the filename, e.g. 'mmc' + part_spec (str): Optional sfdisk partition specification, overriding + part_type and second_part Returns: tuple: @@ -281,14 +283,17 @@ def setup_image(ubman, devnum, part_type, img_size=20, second_part=False, fname = os.path.join(ubman.config.source_dir, f'{basename}{devnum}.img') mnt = os.path.join(ubman.config.persistent_data_dir, 'scratch') - spec = f'type={part_type:x}, size={img_size - 2}M, start=1M, bootable' - if second_part: - spec += '\ntype=c' + spec = part_spec + if spec is None: + spec = f'type={part_type:x}, size={img_size - 2}M, start=1M, bootable' + if second_part: + spec += '\ntype=c' try: check_call(f'mkdir -p {mnt}', shell=True) check_call(f'qemu-img create {fname} {img_size}M', shell=True) - check_call(f'printf "{spec}" | sfdisk {fname}', shell=True) + # Pass the specification directly, including quoted GPT names. + run(['sfdisk', fname], input=spec.encode(), check=True) except CalledProcessError: call(f'rm -f {fname}', shell=True) raise diff --git a/test/py/tests/test_ut.py b/test/py/tests/test_ut.py index 2a5acc18c61..c52d7f46ac2 100644 --- a/test/py/tests/test_ut.py +++ b/test/py/tests/test_ut.py @@ -16,6 +16,7 @@ import re import pytest import utils +from img.script_raw import setup_script_raw_image # pylint: disable=E0611 from tests import fs_helper from fs_helper import DiskHelper, FsHelper @@ -755,6 +756,8 @@ def test_ut_dm_init_bootstd(ubman): setup_efi_image(ubman) setup_rauc_image(ubman) setup_bls_prefix_image(ubman) + if ubman.config.buildconfig.get('config_bootmeth_script_raw') == 'y': + setup_script_raw_image(ubman) # Restart so that the new mmc1.img is picked up ubman.restart_uboot() -- 2.43.0
