#!/bin/sh set -eu : "${PRE15_DUT:?PRE15_DUT is required}" : "${PRE15_ROOT:?PRE15_ROOT is required}" : "${PRE15_RUN_DIR:?PRE15_RUN_DIR is required}" : "${PRE15_LIB_DIR:?PRE15_LIB_DIR is required}" . "$PRE15_LIB_DIR/runner.sh" gate=$PRE15_DUT/tests/pre15/gates/P15-006.sh input=$PRE15_DUT/tests/pre15/gates/P15-006-input.json oracle=$PRE15_DUT/tests/pre15/fixtures/B07-map-oracle.json map_fixture=$PRE15_DUT/tests/pre15/fixtures/B08-map-runs.json io_fixture=$PRE15_DUT/tests/pre15/fixtures/B08-io-caps.json b07c_case=$PRE15_DUT/tests/pre15/cases/B07c-map-consumers.sh artifacts=$PRE15_RUN_DIR/artifacts baseline=6673f51152a5195a8a8903aa801f820abce7936e b07c=55c609db13fc9b0f21a3c7c9ec5cb9574fd18276 for tool in cc git python3 sha256sum; do command -v "$tool" >/dev/null 2>&1 || \ pre15_infra_blocked "missing B08 host tool: $tool" done pre15_record_fixture b08-gate "$gate" pre15_record_fixture b08-input "$input" pre15_record_fixture b08-b07-oracle "$oracle" pre15_record_fixture b08-map-runs "$map_fixture" pre15_record_fixture b08-io-caps "$io_fixture" pre15_record_fixture b08-b07c-case "$b07c_case" pre15_record_fixture b08-data "$PRE15_DUT/src/data.c" mkdir -p "$artifacts" pre15_target_reached if "$gate" --base "$baseline" --oracle "$oracle" \ --output "$artifacts/baseline" >"$artifacts/baseline.stdout" \ 2>"$artifacts/baseline.stderr"; then : else pre15_dut_fail 'B08 frozen P15-006 baseline replay failed' fi if python3 - "$b07c_case" "$artifacts/derive-B07c.py" <<'PY' from pathlib import Path import sys source = Path(sys.argv[1]).read_text(encoding="utf-8") marker = 'if python3 - "$gate" "$artifacts/P15-006-B07c.py" <<\'PY\'\n' start = source.index(marker) + len(marker) end = source.index("\nPY\nthen", start) Path(sys.argv[2]).write_text(source[start:end] + "\n", encoding="ascii") PY then : else pre15_dut_fail 'B08 could not recover the proven B07c replay adapter' fi if python3 "$artifacts/derive-B07c.py" "$gate" \ "$artifacts/P15-006-B08.py" >"$artifacts/derive.stdout" \ 2>"$artifacts/derive.stderr"; then : else pre15_dut_fail 'B08 could not derive the current map replay' fi candidate_rc=0 python3 "$artifacts/P15-006-B08.py" "$PRE15_ROOT" "$input" \ worktree '' "$artifacts/candidate" "$oracle" \ >"$artifacts/candidate.stdout" 2>"$artifacts/candidate.stderr" || \ candidate_rc=$? if test "$candidate_rc" -ne 1; then pre15_dut_fail 'B08 old-run oracle did not reject exactly the intended change' fi if python3 - "$PRE15_ROOT" "$PRE15_DUT" "$oracle" "$map_fixture" \ "$io_fixture" "$artifacts/baseline" "$artifacts/candidate" \ "$artifacts/B08-result.json" "$b07c" "$artifacts" <<'PY' from __future__ import annotations from collections import Counter import hashlib import json from pathlib import Path import re import struct import subprocess import sys root = Path(sys.argv[1]) dut = Path(sys.argv[2]) oracle_path = Path(sys.argv[3]) map_fixture_path = Path(sys.argv[4]) io_fixture_path = Path(sys.argv[5]) baseline = Path(sys.argv[6]) candidate = Path(sys.argv[7]) output = Path(sys.argv[8]) b07c = sys.argv[9] artifacts = Path(sys.argv[10]) def load(path: Path) -> dict: return json.loads(path.read_text(encoding="ascii")) def sha256_bytes(data: bytes) -> str: return hashlib.sha256(data).hexdigest() def extract_function(source: str, name: str) -> str: match = re.search(r"^" + re.escape(name) + r"\s*\(", source, re.MULTILINE) if not match: raise SystemExit(f"missing function: {name}") name_line = source.rfind("\n", 0, match.start()) + 1 start = source.rfind("\n", 0, name_line - 1) + 1 brace = source.find("{", match.end()) depth = 0 state = "code" index = brace while index < len(source): char = source[index] following = source[index + 1] if index + 1 < len(source) else "" if state == "code": if char == "/" and following == "*": state = "block" index += 2 continue if char == "/" and following == "/": state = "line" index += 2 continue if char == '"': state = "string" elif char == "'": state = "character" elif char == "{": depth += 1 elif char == "}": depth -= 1 if depth == 0: return source[start : index + 1] elif state == "block" and char == "*" and following == "/": state = "code" index += 2 continue elif state == "line" and char == "\n": state = "code" elif state in {"string", "character"}: if char == "\\": index += 2 continue if (state == "string" and char == '"') or ( state == "character" and char == "'" ): state = "code" index += 1 raise SystemExit(f"unterminated function: {name}") def function_names(source: str) -> set[str]: return set(re.findall( r"^([A-Za-z_][A-Za-z0-9_]*)\s*\(", source, re.MULTILINE )) def committed_data() -> str: completed = subprocess.run( ["git", "-C", str(root), "show", f"{b07c}:repo-pre-15/src/data.c"], check=False, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) if completed.returncode != 0: raise SystemExit(f"cannot read B07c data.c: {completed.stderr}") return completed.stdout def require_order(source: str, markers: list[str], label: str) -> None: position = -1 for marker in markers: next_position = source.find(marker, position + 1) if next_position < 0: raise SystemExit(f"{label} is missing ordered marker: {marker}") position = next_position def compile_and_run(name: str, program: str) -> list[str]: source_path = artifacts / f"{name}.c" binary_path = artifacts / name source_path.write_text(program, encoding="ascii") compiled = subprocess.run( [ "cc", "-std=c11", "-O2", "-Wall", "-Wextra", "-Werror", str(source_path), "-o", str(binary_path), ], check=False, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) (artifacts / f"{name}.compile.stdout").write_text( compiled.stdout, encoding="utf-8" ) (artifacts / f"{name}.compile.stderr").write_text( compiled.stderr, encoding="utf-8" ) if compiled.returncode != 0: raise SystemExit(f"{name} compilation failed") executed = subprocess.run( [str(binary_path)], check=False, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) (artifacts / f"{name}.stdout").write_text( executed.stdout, encoding="utf-8" ) (artifacts / f"{name}.stderr").write_text( executed.stderr, encoding="utf-8" ) if executed.returncode != 0: raise SystemExit(f"{name} execution failed") return executed.stdout.splitlines() oracle = load(oracle_path) map_fixture = load(map_fixture_path) io_fixture = load(io_fixture_path) base_result = load(baseline / "result.json") base_tuples = load(baseline / "tuples.json") base_devices = load(baseline / "devices.json") result = load(candidate / "result.json") tuples = load(candidate / "tuples.json") devices = load(candidate / "devices.json") if sha256_bytes(oracle_path.read_bytes()) != map_fixture["b07_oracle_sha256"]: raise SystemExit("B08 map fixture is not anchored to the frozen B07 oracle") if map_fixture["schema"] != 1 or map_fixture["candidate"] != "P15-074": raise SystemExit("invalid B08 map fixture identity") if io_fixture["schema"] != 1 or io_fixture["candidate"] != "P15-074": raise SystemExit("invalid B08 I/O fixture identity") if ( base_result["status"] != "GO" or base_result["oracle_equal"] is not True or base_result["map_case_count"] != 80 or base_result["device_case_count"] != 13 ): raise SystemExit("frozen B07 control replay did not GO") if result["status"] != "STOP" or result["map_case_count"] != 80: raise SystemExit("B08 candidate did not expose the intended old-run delta") if result["device_case_count"] != 13 or result["model_sha256"] != oracle["model_sha256"]: raise SystemExit("B08 candidate changed the independent corpus denominator") if result["adapter_probe"]["status"] != "PASS": raise SystemExit("B08 common map adapter probe failed") if devices["status"] != "PASS" or devices["records"] != base_devices["records"]: raise SystemExit("B08 changed one of the 13 frozen device records") tuple_fields = ( "m_la", "m_pa", "m_llen", "m_plen", "m_deviceid", "m_flags", "m_algorithmformat", "errno", "acquire_count", "release_count", ) tuple_layout = map_fixture["b07_tuple_layout"] if tuple_layout != oracle["tuple_byte_layout"]: raise SystemExit("B08 tuple byte layout drifted") oracle_records = {record["id"]: record for record in oracle["records"]} actual_records = {record["id"]: record for record in tuples["records"]} base_records = {record["id"]: record for record in base_tuples["records"]} transforms = {record["id"]: record for record in map_fixture["b07_transforms"]} if len(oracle_records) != 80 or set(actual_records) != set(oracle_records): raise SystemExit("B08 map tuple ID set changed") if set(base_records) != set(oracle_records): raise SystemExit("B08 baseline tuple ID set changed") expected_bytes = bytearray() actual_bytes = bytearray() changed_ids = [] for record in oracle["records"]: case_id = record["id"] if base_records[case_id]["tuple_hex"] != record["tuple_hex"]: raise SystemExit(f"frozen B07 baseline drifted: {case_id}") expected = dict(zip( tuple_fields, struct.unpack(tuple_layout, bytes.fromhex(record["tuple_hex"])), strict=True, )) if case_id in transforms: expected["m_llen"] = transforms[case_id]["m_llen"] expected["m_plen"] = transforms[case_id]["m_plen"] expected_tuple = struct.pack( tuple_layout, *(expected[field] for field in tuple_fields) ) actual_tuple = bytes.fromhex(actual_records[case_id]["tuple_hex"]) if actual_tuple != expected_tuple: raise SystemExit(f"B08 transformed tuple mismatch: {case_id}") if actual_tuple != bytes.fromhex(record["tuple_hex"]): changed_ids.append(case_id) expected_bytes.extend(expected_tuple) actual_bytes.extend(actual_tuple) if set(changed_ids) != set(transforms): raise SystemExit(f"B08 changed unexpected B07 tuples: {changed_ids!r}") transformed_hash = sha256_bytes(bytes(expected_bytes)) if bytes(actual_bytes) != bytes(expected_bytes): raise SystemExit("B08 full transformed tuple stream mismatch") if transformed_hash != map_fixture["expected_transformed_tuple_sha256"]: raise SystemExit("B08 transformed tuple fixture digest mismatch") allowed_failures = [] for failure in result["failures"]: if failure.get("field") == "frozen-byte-oracle" and "id" not in failure: continue if failure.get("id") in transforms and failure.get("field") in { "m_llen", "m_plen" }: continue allowed_failures.append(failure) if allowed_failures: raise SystemExit(f"B08 old oracle reported unrelated failures: {allowed_failures!r}") data_source = (dut / "src/data.c").read_text(encoding="utf-8") old_data = committed_data() expected_preamble = old_data[:old_data.index("static erofs_off_t\n")].replace( "#include \n", "#include \n#include \n", ) if data_source[:data_source.index("static erofs_off_t\n")] != expected_preamble: raise SystemExit("B08 data.c preamble changed beyond the MAXPHYS header") old_functions = function_names(old_data) data_functions = function_names(data_source) if data_functions != old_functions: raise SystemExit("B08 added or removed a data.c function") changed_functions = { "erofs_map_blocks_flatmode", "erofs_read_data", "erofs_read_uio" } for function in sorted(data_functions - changed_functions): if extract_function(data_source, function) != extract_function( old_data, function ): raise SystemExit(f"B08 changed out-of-scope data.c function: {function}") for function in changed_functions: if extract_function(data_source, function) == extract_function( old_data, function ): raise SystemExit(f"B08 did not change required function: {function}") flatmode = extract_function(data_source, "erofs_map_blocks_flatmode") read_data = extract_function(data_source, "erofs_read_data") read_uio = extract_function(data_source, "erofs_read_uio") if flatmode.count("map->m_llen = remain;") != 1: raise SystemExit("plain mapping does not expose the EOF run") if flatmode.count("map->m_llen = MIN(remain, tail_start - loff);") != 1: raise SystemExit("pre-inline mapping does not stop at the inline tail") if "MAXPHYS" in flatmode: raise SystemExit("B08 incorrectly capped the map contract") if read_data.count("(uint64_t)MAXPHYS") != 1: raise SystemExit("erofs_read_data lacks one MAXPHYS cap") if read_uio.count("(uint64_t)MAXPHYS") != 1: raise SystemExit("erofs_read_uio lacks one MAXPHYS cap") require_order( read_data, [ "if (erofs_inode_is_data_compressed(vi->datalayout))", "return (z_erofs_read_data(sbi, vi, loff, len, bufp));", "error = erofs_map_blocks(sbi, vi, &map);", "MIN(map.m_llen, (uint64_t)MAXPHYS)", "erofs_read_physical(sbi, map.m_deviceid, map.m_pa", "erofs_brelse(blk);", ], "erofs_read_data", ) require_order( read_uio, [ "if (erofs_inode_is_data_compressed(vi->datalayout))", "return (z_erofs_read_uio(sbi, vi, uio));", "error = erofs_map_blocks(sbi, vi, &map);", "MIN(map.m_llen, (uint64_t)MAXPHYS)", "erofs_read_physical(sbi, map.m_deviceid, map.m_pa", "error = uiomove(blk, want, uio);", "erofs_brelse(blk);", ], "erofs_read_uio", ) map_common = r''' #include #include #include #include #define EOPNOTSUPP 45 #define EOVERFLOW 84 #define EINTEGRITY 97 #define EROFS_NULL_ADDR UINT32_MAX #define EROFS_INODE_FLAT_PLAIN 0 #define EROFS_INODE_COMPRESSED_FULL 1 #define EROFS_INODE_FLAT_INLINE 2 #define EROFS_INODE_COMPRESSED_COMPACT 3 #define EROFS_INODE_CHUNK_BASED 4 #define EROFS_MAP_MAPPED 0x0001 #define EROFS_MAP_META 0x0002 #define MIN(a, b) ((a) < (b) ? (a) : (b)) #define roundup2(x, y) (((x) + ((y) - 1)) & ~((y) - 1)) typedef uint64_t erofs_off_t; typedef uint64_t erofs_blk_t; struct erofs_sb_info { uint64_t block_size; unsigned int blkszbits; }; struct erofs_inode { uint64_t size; erofs_off_t inode_off; erofs_blk_t startblk; uint8_t datalayout; uint8_t inode_isize; uint32_t xattr_isize; }; struct erofs_map_blocks { erofs_off_t m_pa, m_la; uint64_t m_plen, m_llen; unsigned short m_deviceid; char m_algorithmformat; unsigned int m_flags; }; static bool erofs_inode_is_data_compressed(unsigned int layout) { return (layout == EROFS_INODE_COMPRESSED_FULL || layout == EROFS_INODE_COMPRESSED_COMPACT); } static int erofs_map_blocks_chunk(struct erofs_sb_info *sbi, struct erofs_inode *vi, struct erofs_map_blocks *map) { (void)sbi; (void)vi; (void)map; return (EOPNOTSUPP); } static int z_erofs_map_blocks(struct erofs_sb_info *sbi, struct erofs_inode *vi, struct erofs_map_blocks *map) { (void)sbi; (void)vi; (void)map; return (EOPNOTSUPP); } ''' map_program = map_common map_program += extract_function(data_source, "erofs_inline_tail_start") + "\n" map_program += flatmode + "\n" map_program += extract_function(data_source, "erofs_map_blocks") + "\n" map_program += "\nint\nmain(void)\n{\n\tint error;\n" for case in map_fixture["map_cases"]: inode = case["inode"] map_program += f''' {{ struct erofs_sb_info sbi = {{ .block_size = 4096, .blkszbits = 12 }}; struct erofs_inode vi = {{ .size = UINT64_C({inode['size']}), .inode_off = UINT64_C({inode.get('inode_off', 0)}), .startblk = UINT64_C({inode['startblk']}), .datalayout = {inode['layout']}, .inode_isize = {inode.get('inode_isize', 0)}, .xattr_isize = {inode.get('xattr_isize', 0)}, }}; struct erofs_map_blocks map = {{ .m_la = UINT64_C({case['request']}) }}; error = erofs_map_blocks(&sbi, &vi, &map); printf("map\\t{case['id']}\\t%llu\\t%llu\\t%llu\\t%llu\\t%u\\t%u\\t%d\\t%d\\n", (unsigned long long)map.m_la, (unsigned long long)map.m_pa, (unsigned long long)map.m_llen, (unsigned long long)map.m_plen, map.m_deviceid, map.m_flags, (int)map.m_algorithmformat, error); }} ''' map_program += "\treturn (0);\n}\n" map_lines = compile_and_run("B08-map-extractor", map_program) map_actual = {} for line in map_lines: fields = line.split("\t") if len(fields) != 10 or fields[0] != "map": raise SystemExit(f"invalid B08 map extractor line: {line!r}") map_actual[fields[1]] = dict(zip( ( "m_la", "m_pa", "m_llen", "m_plen", "m_deviceid", "m_flags", "m_algorithmformat", "errno", ), [int(value) for value in fields[2:]], strict=True, )) if set(map_actual) != {case["id"] for case in map_fixture["map_cases"]}: raise SystemExit("B08 custom map case set changed") positive_runs = 0 for case in map_fixture["map_cases"]: actual = map_actual[case["id"]] expected = {**case["expected"], "m_algorithmformat": 0} if actual != expected: raise SystemExit( f"B08 custom map mismatch {case['id']}: {actual!r} != {expected!r}" ) if ( actual["errno"] == 0 and case["request"] < case["inode"]["size"] ): if actual["m_llen"] == 0: raise SystemExit(f"B08 non-EOF zero run: {case['id']}") positive_runs += 1 maxphys = io_fixture["harness_maxphys"] consumer_common = f''' #include #include #include #include #include #include #define EIO 5 #define ENOMEM 12 #define EINVAL 22 #define EOPNOTSUPP 45 #define EOVERFLOW 84 #define EINTEGRITY 97 #define PAGE_SIZE 4096 #define MAXPHYS {maxphys} #define M_EROFS 0 #define M_WAITOK 0 #define EROFS_INODE_FLAT_PLAIN 0 #define EROFS_MAP_MAPPED 0x0001 #define EROFS_MAP_META 0x0002 #define EROFS_BUF_INITIALIZER {{ .data = NULL, .release = NULL }} #define MIN(a, b) ((a) < (b) ? (a) : (b)) #define bzero(ptr, len) memset((ptr), 0, (len)) typedef uint64_t erofs_off_t; typedef uint64_t erofs_nid_t; struct erofs_sb_info {{ int unused; }}; struct erofs_inode {{ erofs_nid_t nid; uint64_t size; uint8_t datalayout; }}; struct erofs_map_blocks {{ erofs_off_t m_pa, m_la; uint64_t m_plen, m_llen; unsigned short m_deviceid; char m_algorithmformat; unsigned int m_flags; }}; struct erofs_buf {{ void *data; void (*release)(void *); }}; struct uio {{ int64_t uio_offset; size_t uio_resid; unsigned char *buffer; size_t moved; }}; static size_t gate_allocations; static void * gate_malloc(size_t size) {{ void *buffer = malloc(size); if (buffer != NULL) ++gate_allocations; return (buffer); }} static void gate_free(void *buffer) {{ if (buffer != NULL) {{ --gate_allocations; free(buffer); }} }} #define malloc(size, type, flags) gate_malloc(size) #define free(buffer, type) gate_free(buffer) #define GATE_MAX_CALLS 32 #define GATE_PHYSICAL_BASE UINT64_C(1048576) static bool gate_hole; static unsigned int gate_map_calls; static unsigned int gate_physical_calls; static erofs_off_t gate_physical_offsets[GATE_MAX_CALLS]; static size_t gate_physical_lengths[GATE_MAX_CALLS]; static void gate_reset(bool hole) {{ gate_hole = hole; gate_map_calls = 0; gate_physical_calls = 0; memset(gate_physical_offsets, 0, sizeof(gate_physical_offsets)); memset(gate_physical_lengths, 0, sizeof(gate_physical_lengths)); }} static bool erofs_inode_is_data_compressed(unsigned int layout) {{ (void)layout; return (false); }} static int erofs_map_blocks(struct erofs_sb_info *sbi, struct erofs_inode *vi, struct erofs_map_blocks *map) {{ struct erofs_map_blocks next = {{ .m_la = map->m_la }}; (void)sbi; ++gate_map_calls; if (next.m_la < vi->size) {{ next.m_pa = GATE_PHYSICAL_BASE + next.m_la; next.m_llen = vi->size - next.m_la; next.m_plen = next.m_llen; if (!gate_hole) next.m_flags = EROFS_MAP_MAPPED; }} *map = next; return (0); }} static int erofs_read_physical(struct erofs_sb_info *sbi, unsigned int device_id, erofs_off_t off, size_t len, void **bufp) {{ unsigned char *buffer; size_t index; (void)sbi; (void)device_id; if (len > MAXPHYS || gate_physical_calls == GATE_MAX_CALLS) return (EINVAL); gate_physical_offsets[gate_physical_calls] = off; gate_physical_lengths[gate_physical_calls] = len; ++gate_physical_calls; buffer = gate_malloc(len); if (buffer == NULL) return (ENOMEM); for (index = 0; index < len; ++index) buffer[index] = (unsigned char)(off - GATE_PHYSICAL_BASE + index); *bufp = buffer; return (0); }} static int erofs_read_metadata(struct erofs_sb_info *sbi, erofs_nid_t nid, erofs_off_t off, size_t len, struct erofs_buf *buf) {{ (void)sbi; (void)nid; (void)off; (void)len; (void)buf; return (EIO); }} static void erofs_put_metabuf(struct erofs_buf *buf) {{ (void)buf; }} static void erofs_brelse(void *buffer) {{ gate_free(buffer); }} static int uiomove(const void *source, size_t len, struct uio *uio) {{ if (len > uio->uio_resid) return (EINVAL); memcpy(uio->buffer + uio->moved, source, len); uio->moved += len; uio->uio_resid -= len; uio->uio_offset += (int64_t)len; return (0); }} static int z_erofs_read_data(struct erofs_sb_info *sbi, struct erofs_inode *vi, erofs_off_t loff, size_t len, void **bufp) {{ (void)sbi; (void)vi; (void)loff; (void)len; (void)bufp; return (EOPNOTSUPP); }} static int z_erofs_read_uio(struct erofs_sb_info *sbi, struct erofs_inode *vi, struct uio *uio) {{ (void)sbi; (void)vi; (void)uio; return (EOPNOTSUPP); }} static int gate_check_data(const unsigned char *buffer, size_t length, uint64_t offset, bool hole) {{ size_t index; for (index = 0; index < length; ++index) {{ unsigned char expected = hole ? 0 : (unsigned char)(offset + index); if (buffer[index] != expected) return (1); }} return (0); }} static int gate_fail(const char *id, const char *reason) {{ fprintf(stderr, "%s: %s\\n", id, reason); return (1); }} ''' consumer_program = consumer_common consumer_program += read_data + "\n" consumer_program += read_uio + "\n" consumer_program += "\nint\nmain(void)\n{\n" for scenario in io_fixture["scenarios"]: scenario_id = scenario["id"] hole = scenario["mapping"] == "hole" expected_offsets = scenario["expected_physical_offsets"] expected_lengths = scenario["expected_physical_lengths"] checks = [] for index, (offset, length) in enumerate(zip( expected_offsets, expected_lengths, strict=True )): checks.append( f"\t\tif (gate_physical_offsets[{index}] != UINT64_C({offset}) || " f"gate_physical_lengths[{index}] != {length})\n" f"\t\t\treturn (gate_fail(\"{scenario_id}\", \"physical call tuple\"));\n" ) call_checks = "".join(checks) if scenario["api"] == "read_data": success_check = "" if scenario["expected_errno"] == 0: success_check = f''' if (buffer == NULL || gate_check_data(buffer, {scenario['length']}, UINT64_C({scenario['offset']}), {'true' if hole else 'false'}) != 0) return (gate_fail("{scenario_id}", "returned data")); ''' else: success_check = f''' if (buffer != NULL) return (gate_fail("{scenario_id}", "error buffer ownership")); ''' consumer_program += f''' {{ struct erofs_sb_info sbi = {{ 0 }}; struct erofs_inode vi = {{ .nid = 1, .size = UINT64_C({scenario['inode_size']}), .datalayout = EROFS_INODE_FLAT_PLAIN, }}; void *buffer = NULL; int error; gate_reset({'true' if hole else 'false'}); error = erofs_read_data(&sbi, &vi, UINT64_C({scenario['offset']}), {scenario['length']}, &buffer); if (error != {scenario['expected_errno']}) return (gate_fail("{scenario_id}", "errno")); if (gate_map_calls != {scenario['expected_map_calls']} || gate_physical_calls != {len(expected_lengths)}) return (gate_fail("{scenario_id}", "call count")); {call_checks}{success_check} gate_free(buffer); if (gate_allocations != 0) return (gate_fail("{scenario_id}", "allocation balance")); printf("io\\t{scenario_id}\\tPASS\\t%u\\t%u\\n", gate_map_calls, gate_physical_calls); }} ''' elif scenario["api"] == "read_uio": consumer_program += f''' {{ struct erofs_sb_info sbi = {{ 0 }}; struct erofs_inode vi = {{ .nid = 1, .size = UINT64_C({scenario['inode_size']}), .datalayout = EROFS_INODE_FLAT_PLAIN, }}; unsigned char *buffer = gate_malloc({scenario['length']}); struct uio uio = {{ .uio_offset = {scenario['offset']}, .uio_resid = {scenario['length']}, .buffer = buffer, .moved = 0, }}; int error; if (buffer == NULL) return (gate_fail("{scenario_id}", "test allocation")); memset(buffer, 0xa5, {scenario['length']}); gate_reset({'true' if hole else 'false'}); error = erofs_read_uio(&sbi, &vi, &uio); if (error != {scenario['expected_errno']} || uio.uio_offset != {scenario['expected_final_offset']} || uio.uio_resid != {scenario['expected_resid']}) return (gate_fail("{scenario_id}", "uio result")); if (gate_map_calls != {scenario['expected_map_calls']} || gate_physical_calls != {len(expected_lengths)}) return (gate_fail("{scenario_id}", "call count")); {call_checks} if (uio.moved != {scenario['length'] - scenario['expected_resid']} || gate_check_data(buffer, uio.moved, UINT64_C({scenario['offset']}), {'true' if hole else 'false'}) != 0) return (gate_fail("{scenario_id}", "moved data")); gate_free(buffer); if (gate_allocations != 0) return (gate_fail("{scenario_id}", "allocation balance")); printf("io\\t{scenario_id}\\tPASS\\t%u\\t%u\\n", gate_map_calls, gate_physical_calls); }} ''' else: raise SystemExit(f"unknown B08 I/O API: {scenario['api']}") consumer_program += "\treturn (0);\n}\n" io_lines = compile_and_run("B08-io-extractor", consumer_program) expected_io_ids = [scenario["id"] for scenario in io_fixture["scenarios"]] actual_io_ids = [] for line in io_lines: fields = line.split("\t") if len(fields) != 5 or fields[0] != "io" or fields[2] != "PASS": raise SystemExit(f"invalid B08 I/O extractor line: {line!r}") actual_io_ids.append(fields[1]) if actual_io_ids != expected_io_ids: raise SystemExit("B08 I/O scenario order or denominator changed") cleanup = Counter( (record["actual"]["acquire_count"], record["actual"]["release_count"]) for record in tuples["records"] ) if any(acquire != release for acquire, release in cleanup): raise SystemExit(f"B08 metadata cleanup became unbalanced: {cleanup!r}") if any(record["actual"]["errno"] < 0 for record in tuples["records"]): raise SystemExit("B08 observed a negative errno") summary = { "status": "PASS", "final_id": "P15-074", "b07_map_cases": 80, "b07_unchanged_tuples": 80 - len(changed_ids), "b07_intended_run_tuples": len(changed_ids), "b07_unexpected_tuple_differences": 0, "b07_device_cases": 13, "b07_device_differences": 0, "b07_model_sha256": result["model_sha256"], "transformed_tuple_bytes": len(actual_bytes), "transformed_tuple_sha256": sha256_bytes(bytes(actual_bytes)), "custom_map_cases": len(map_fixture["map_cases"]), "custom_positive_runs": positive_runs, "io_scenarios": len(io_fixture["scenarios"]), "harness_maxphys": maxphys, "max_observed_physical_io": max( length for scenario in io_fixture["scenarios"] for length in scenario["expected_physical_lengths"] ), "cleanup_distribution": { f"{acquire}:{release}": count for (acquire, release), count in sorted(cleanup.items()) }, "compressed_scope": "UNCHANGED", "vnode_backed_scope": "NOT_ADDED", "qemu": "NOT_RUN", "full_feature_suite": "NOT_RUN", } with output.open("x", encoding="ascii") as stream: json.dump(summary, stream, ensure_ascii=True, indent=2, sort_keys=True) stream.write("\n") print( "B08 PASS map=80 unchanged=73 intended=7 device=13 " f"custom={len(map_fixture['map_cases'])} io={len(io_fixture['scenarios'])}" ) PY then : else pre15_dut_fail 'B08 plain-run/MAXPHYS boundary check failed' fi sha256sum "$artifacts/baseline/result.json" \ "$artifacts/baseline/tuples.json" "$artifacts/baseline/devices.json" \ "$artifacts/candidate/result.json" "$artifacts/candidate/tuples.json" \ "$artifacts/candidate/devices.json" "$artifacts/B08-result.json" \ "$artifacts/B08-map-extractor" "$artifacts/B08-io-extractor" \ >"$artifacts/SHA256SUMS" printf 'B08 PASS contiguous plain/pre-inline runs with MAXPHYS-capped I/O\n'