#!/usr/bin/env python3 from __future__ import annotations import argparse import json from pathlib import Path import re import subprocess SOURCE_NAMES = ( "compress.h", "decompressor.c", "decompressor_lz4.c", "decompressor_lzma.c", "decompressor_deflate.c", "decompressor_zstd.c", "zdata.c", ) def fail(message: str) -> None: raise SystemExit(message) def replace_once(source: str, old: str, new: str, label: str) -> str: count = source.count(old) if count != 1: fail(f"{label}: expected one source occurrence, found {count}") return source.replace(old, new, 1) def extract_function(source: str, name: str) -> str: match = re.search(r"^" + re.escape(name) + r"\s*\(", source, re.MULTILINE) if not match: fail(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()) if brace < 0: fail(f"missing function body: {name}") 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 fail(f"unterminated function: {name}") def committed(root: Path, baseline: str, name: str) -> str: completed = subprocess.run( ["git", "-C", str(root), "show", f"{baseline}:repo-pre-15/src/{name}"], check=False, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) if completed.returncode != 0: fail(f"cannot read B25 baseline {name}: {completed.stderr.strip()}") return completed.stdout def require_once(source: str, needle: str, label: str) -> None: count = source.count(needle) if count != 1: fail(f"{label}: expected one occurrence, found {count}") def verify_exact_small_transforms(current: dict[str, str], base: dict[str, str]) -> None: expected = replace_once( base["compress.h"], "\tconst char *name;\n", "\tconst char *name;\n" "\t/* Callbacks return zero or a positive FreeBSD errno. */\n", "callback errno contract", ) if current["compress.h"] != expected: fail("compress.h differs from the declared callback contract annotation") expected = base["decompressor.c"] expected = replace_once( expected, "\t\tif (size < sizeof(*lz4))\n\t\t\treturn (EINVAL);", "\t\tif (size < sizeof(*lz4))\n\t\t\treturn (EINTEGRITY);", "short LZ4 config", ) expected = replace_once( expected, "\t\t (Z_EROFS_PCLUSTER_MAX_SIZE >> sbi->blkszbits))\n" "\t\t\treturn (EINVAL);", "\t\t (Z_EROFS_PCLUSTER_MAX_SIZE >> sbi->blkszbits))\n" "\t\t\treturn (EOPNOTSUPP);", "unsupported LZ4 pcluster size", ) if expected.count("\t\treturn (EOVERFLOW);\n") != 2: fail("baseline config arithmetic errno count changed") expected = expected.replace( "\t\treturn (EOVERFLOW);\n", "\t\treturn (EINTEGRITY);\n" ) expected = replace_once(expected, "\tint ret;\n", "", "folding temporary") expected = replace_once( expected, "\tret = decompressor->decompress(&rq);\n" "\treturn (ret == 0 ? 0 : EIO);", "\treturn (decompressor->decompress(&rq));", "dispatch preservation", ) if current["decompressor.c"] != expected: fail("decompressor.c differs from the declared B25 dispatch/config transforms") expected = base["decompressor_lz4.c"] if expected.count("return (-1);") != 12 or expected.count(": -1);") != 1: fail("baseline LZ4 private-status count changed") expected = expected.replace("return (-1);", "return (EINTEGRITY);") expected = expected.replace(": -1);", ": EINTEGRITY);") if current["decompressor_lz4.c"] != expected: fail("LZ4 differs from the exact private-status conversion") if current["zdata.c"] != base["zdata.c"]: fail("B25 changed the already-correct zdata I/O or cleanup path") def verify_source_contracts( source: dict[str, str], fixture: dict[str, object] ) -> dict[str, object]: joined = "\n".join(source[name] for name in SOURCE_NAMES) if re.search(r"return\s*\(\s*-E[A-Z0-9_]+", joined): fail("negative Linux errno entered the FreeBSD codec path") for name in SOURCE_NAMES: if "return (-1)" in source[name] or re.search(r":\s*-1\s*\)", source[name]): fail(f"private -1 codec status remains in {name}") dispatch = extract_function(source["decompressor.c"], "z_erofs_decompress") if dispatch.count("return (decompressor->decompress(&rq));") != 2: fail("typed backend dispatch does not have two direct callback exits") if "ret == 0 ? 0 : EIO" in dispatch: fail("dispatcher still folds typed backend errors") require_once(dispatch, "return (EOPNOTSUPP);", "unsupported algorithm") if dispatch.count("return (EINTEGRITY);") != 2: fail("dispatch-local corruption checks changed") for case in fixture["dispatch"]: if case.get("backend_errno") != case.get("expected"): fail("dispatch fixture does not require exact errno preservation") config_expectations = { "z_erofs_load_lz4_config": ("decompressor.c", "EINTEGRITY", "EOPNOTSUPP"), "z_erofs_load_lzma_config": ( "decompressor_lzma.c", "EINTEGRITY", "EOPNOTSUPP", ), "z_erofs_load_deflate_config": ( "decompressor_deflate.c", "EINTEGRITY", "EOPNOTSUPP", ), "z_erofs_load_zstd_config": ( "decompressor_zstd.c", "EINTEGRITY", "EOPNOTSUPP", ), } for function, (name, malformed, unsupported) in config_expectations.items(): body = extract_function(source[name], function) if f"return ({malformed});" not in body: fail(f"{function} lacks malformed-config errno") if f"return ({unsupported});" not in body: fail(f"{function} lacks unsupported-config errno") if fixture["config"] != [ {"class": "malformed", "errno": "EINTEGRITY"}, {"class": "unsupported", "errno": "EOPNOTSUPP"}, ]: fail("config fixture does not encode the B25 typed classes") lzma = extract_function(source["decompressor_lzma.c"], "z_erofs_lzma_decompress") require_once(lzma, "return (EOVERFLOW);", "LZMA ABI overflow") require_once(lzma, "return (ENOMEM);", "LZMA allocation failure") if not ( lzma.index("xz_dec_microlzma_run") < lzma.index("xz_dec_microlzma_end") < lzma.index("return (error);") ): fail("LZMA state is not released before status publication") deflate = extract_function( source["decompressor_deflate.c"], "z_erofs_deflate_decompress" ) require_once(deflate, "inflateEnd(&strm)", "Deflate cleanup") require_once( deflate, "if (error == 0 && endret != Z_OK)", "Deflate primary-error preservation", ) if deflate.index("inflateEnd(&strm)") > deflate.index("return (error);"): fail("Deflate returns before releasing initialized state") zstd_source = source["decompressor_zstd.c"] enabled = extract_function(zstd_source, "z_erofs_zstd_decompress") require_once(enabled, "return (ENOMEM);", "Zstd allocation failure") require_once( enabled, "if (error == 0 && ZSTD_isError(ret))", "Zstd primary-error preservation", ) require_once(enabled, "error = EOPNOTSUPP;", "Zstd parameter mapping") if enabled.count("error = EINTEGRITY;") != 4: fail("Zstd stream corruption/completion mappings changed") require_once(enabled, "error = EIO;", "Zstd release mapping") if "ZSTD_getErrorCode" in zstd_source: fail("B25 added a Zstd provider symbol outside the existing ABI") if zstd_source.count("return (EOPNOTSUPP);") < 3: fail("Zstd config/runtime disabled paths are not typed unsupported") expected_zstd = { "create_context": "ENOMEM", "set_window_limit": "EOPNOTSUPP", "decompress_stream": "EINTEGRITY", "free_context": "EIO", } actual_zstd = { case.get("operation"): case.get("expected") for case in fixture["zstd"] } if actual_zstd != expected_zstd or len(fixture["zstd"]) != len(expected_zstd): fail("Zstd phase fixture does not match the source contract") read_extent = extract_function(source["zdata.c"], "z_erofs_read_extent") decode_at = read_extent.index("error = z_erofs_decompress") meta_release_at = read_extent.index("erofs_put_metabuf(&buf)", decode_at) physical_release_at = read_extent.index("erofs_brelse(compressed)", decode_at) error_at = read_extent.index("if (error != 0)", decode_at) free_at = read_extent.index("free(decoded, M_EROFS)", error_at) if not ( decode_at < meta_release_at < error_at < free_at and decode_at < physical_release_at < error_at < free_at ): fail("zdata input/output cleanup ordering changed") return { "callbacks": 2, "config_loaders": len(config_expectations), "dispatch_direct": True, "dispatch_fixture_cases": len(fixture["dispatch"]), "negative_errno": False, "zdata_unchanged": True, } def compile_status_harness( source: dict[str, str], fixture: dict[str, object], artifacts: Path ) -> dict[str, int]: xz_helper = extract_function(source["decompressor_lzma.c"], "z_erofs_lzma_error") zlib_helper = extract_function( source["decompressor_deflate.c"], "z_erofs_deflate_error" ) checks: list[str] = [] for group, function in ( ("xz", "z_erofs_lzma_error"), ("zlib", "z_erofs_deflate_error"), ): for case in fixture[group]: checks.append( f'\tcheck("{group}:{case["status"]}", ' f'{function}({case["status"]}), {case["expected"]});' ) program = f'''#include #include #define EIO 5 #define ENOMEM 12 #define EOVERFLOW 75 #define EOPNOTSUPP 95 #define EINTEGRITY 97 enum xz_ret {{ \tXZ_OK, \tXZ_STREAM_END, \tXZ_UNSUPPORTED_CHECK, \tXZ_MEM_ERROR, \tXZ_MEMLIMIT_ERROR, \tXZ_FORMAT_ERROR, \tXZ_OPTIONS_ERROR, \tXZ_DATA_ERROR, \tXZ_BUF_ERROR }}; #define Z_OK 0 #define Z_STREAM_END 1 #define Z_NEED_DICT 2 #define Z_ERRNO (-1) #define Z_STREAM_ERROR (-2) #define Z_DATA_ERROR (-3) #define Z_MEM_ERROR (-4) #define Z_BUF_ERROR (-5) #define Z_VERSION_ERROR (-6) {xz_helper} {zlib_helper} static int failures; static void check(const char *name, int actual, int expected) {{ \tif (actual != expected) {{ \t\tfprintf(stderr, "%s: %d != %d\\n", name, actual, expected); \t\t++failures; \t}} }} int main(void) {{ {chr(10).join(checks)} \tif (failures != 0) \t\treturn (1); \tprintf("status-map cases={len(checks)}\\n"); \treturn (0); }} ''' harness = artifacts / "B25-status-map.c" binary = artifacts / "B25-status-map" harness.write_text(program, encoding="ascii") compile_run = subprocess.run( ["cc", "-std=c11", "-Wall", "-Wextra", "-Werror", str(harness), "-o", str(binary)], check=False, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) (artifacts / "B25-status-map-build.stdout").write_text( compile_run.stdout, encoding="utf-8" ) (artifacts / "B25-status-map-build.stderr").write_text( compile_run.stderr, encoding="utf-8" ) if compile_run.returncode != 0: fail("B25 status-map harness did not compile") execute = subprocess.run( [str(binary)], check=False, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) (artifacts / "B25-status-map.stdout").write_text(execute.stdout, encoding="utf-8") (artifacts / "B25-status-map.stderr").write_text(execute.stderr, encoding="utf-8") if execute.returncode != 0: fail("B25 status-map harness failed") binary.unlink() return { "xz": len(fixture["xz"]), "zlib": len(fixture["zlib"]), "zstd": len(fixture["zstd"]), } def main() -> None: parser = argparse.ArgumentParser() parser.add_argument("--artifacts", required=True, type=Path) parser.add_argument("--baseline", required=True) parser.add_argument("--dut", required=True, type=Path) parser.add_argument("--fixture", required=True, type=Path) parser.add_argument("--root", required=True, type=Path) args = parser.parse_args() fixture = json.loads(args.fixture.read_text(encoding="ascii")) if fixture.get("schema") != 1 or fixture.get("candidate") != "P15-023": fail("invalid B25 fixture identity") for key in ("config", "dispatch", "xz", "zlib", "zstd"): if not isinstance(fixture.get(key), list) or not fixture[key]: fail(f"empty B25 fixture group: {key}") source_dir = args.dut / "src" current = { name: (source_dir / name).read_text(encoding="utf-8") for name in SOURCE_NAMES } base = {name: committed(args.root, args.baseline, name) for name in SOURCE_NAMES} verify_exact_small_transforms(current, base) contracts = verify_source_contracts(current, fixture) mappings = compile_status_harness(current, fixture, args.artifacts) report = { "candidate": "P15-023", "contracts": contracts, "mapping_cases": mappings, "status": "PASS", } (args.artifacts / "B25-codec-errors.json").write_text( json.dumps(report, ensure_ascii=True, indent=2, sort_keys=True) + "\n", encoding="ascii", ) print( "B25 codec errno contract: " f"xz={mappings['xz']} zlib={mappings['zlib']} zstd={mappings['zstd']}" ) if __name__ == "__main__": main()