#!/bin/sh set -eu : "${PRE15_DUT:?PRE15_DUT is required}" : "${PRE15_ROOT:?PRE15_ROOT is required}" : "${PRE15_CASE_TMP:?PRE15_CASE_TMP is required}" : "${PRE15_RUN_DIR:?PRE15_RUN_DIR is required}" : "${PRE15_LIB_DIR:?PRE15_LIB_DIR is required}" . "$PRE15_LIB_DIR/runner.sh" fixtures_only=0 if test "${1:-}" = --fixtures-only; then fixtures_only=1 shift fi test "$#" -eq 0 || pre15_runner_fail 'B17 accepts only --fixtures-only' fixture_dir=$PRE15_DUT/tests/pre15/fixtures spec=$fixture_dir/B17-xattr-spec.json generator=$fixture_dir/B17-xattr-generate.py oracle=$fixture_dir/B17-xattr-oracle.py seed=$fixture_dir/B17-xattr-seed.erofs artifacts=$PRE15_RUN_DIR/artifacts first=$PRE15_CASE_TMP/first second=$PRE15_CASE_TMP/second rebuilt=$PRE15_CASE_TMP/rebuilt-seed.erofs rebuild_work=$PRE15_CASE_TMP/rebuild-work for tool in fsck.erofs git mkfs.erofs python3 sha256sum; do command -v "$tool" >/dev/null 2>&1 || \ pre15_infra_blocked "missing B17 host tool: $tool" done pre15_record_fixture b17-spec "$spec" pre15_record_fixture b17-generator "$generator" pre15_record_fixture b17-oracle "$oracle" pre15_record_fixture b17-seed "$seed" pre15_record_fixture b17-case "$PRE15_DUT/tests/pre15/cases/B17-xattr-integrity.sh" mkdir -p "$artifacts" if python3 -B "$generator" rebuild-seed --spec "$spec" --seed "$seed" \ --output "$rebuilt" --work "$rebuild_work" \ >"$artifacts/rebuild-seed.json" 2>"$artifacts/rebuild-seed.stderr"; then : else pre15_runner_fail 'B17 tracked seed is not reproducible' fi if python3 -B "$generator" generate --spec "$spec" --seed "$seed" \ --output "$first" >"$artifacts/generate-first.json" \ 2>"$artifacts/generate-first.stderr"; then : else pre15_runner_fail 'B17 first fixture generation failed' fi if python3 -B "$generator" generate --spec "$spec" --seed "$seed" \ --output "$second" >"$artifacts/generate-second.json" \ 2>"$artifacts/generate-second.stderr"; then : else pre15_runner_fail 'B17 second fixture generation failed' fi if cmp "$first/fixture-index.json" "$second/fixture-index.json" && \ find "$first" -type f ! -name fixture-index.json -printf '%f\n' | sort | \ while IFS= read -r name; do cmp "$first/$name" "$second/$name" || exit 1; done then : else pre15_runner_fail 'B17 fixture generation is not byte reproducible' fi if python3 -B "$oracle" --spec "$spec" --fixtures "$first" \ --report "$artifacts/oracle-first.json" \ >"$artifacts/oracle-first.stdout" 2>"$artifacts/oracle-first.stderr"; then : else pre15_runner_fail 'B17 independent first oracle replay failed' fi if python3 -B "$oracle" --spec "$spec" --fixtures "$second" \ --report "$artifacts/oracle-second.json" \ >"$artifacts/oracle-second.stdout" 2>"$artifacts/oracle-second.stderr"; then : else pre15_runner_fail 'B17 independent second oracle replay failed' fi cp "$first/fixture-index.json" "$artifacts/B17-fixture-index.json" pre15_record_fixture b17-generated-index "$artifacts/B17-fixture-index.json" if python3 - "$artifacts/B17-fixture-index.json" \ "$artifacts/oracle-first.json" "$artifacts/B17-matrix.tsv" <<'PY' import json from pathlib import Path import sys index = json.loads(Path(sys.argv[1]).read_text(encoding="ascii")) report = json.loads(Path(sys.argv[2]).read_text(encoding="ascii")) if index["fixture_count"] != 25 or index["legal_count"] != 10 or index["damaged_count"] != 15: raise SystemExit("B17 fixture cardinality changed") if report["status"] != "PASS" or report["legal_passed"] != 10 or report["damaged_passed"] != 15: raise SystemExit("B17 oracle matrix is incomplete") lines = ["id\tclass\terrno\treject"] for item in report["results"]: lines.append( f"{item['id']}\t{item['class']}\t{item['actual_errno']}\t{item['actual_reject']}" ) Path(sys.argv[3]).write_text("\n".join(lines) + "\n", encoding="ascii") print( f"B17 fixtures: {index['fixture_count']} " f"({index['legal_count']} legal, {index['damaged_count']} damaged)" ) print(f"B17 fixture-set SHA256: {index['fixture_set_sha256']}") PY then : else pre15_runner_fail 'B17 fixture matrix summary failed' fi pre15_target_reached if test "$fixtures_only" -eq 1; then printf '%s\n' 'B17 fixture gate: READY' exit 0 fi if python3 - "$PRE15_ROOT" "$PRE15_DUT" "$artifacts/B17-source-check.json" <<'PY' from pathlib import Path import json import subprocess import sys root = Path(sys.argv[1]) dut = Path(sys.argv[2]) output = Path(sys.argv[3]) baseline = "98d76e4e73ed9a760b7f684d453b301c73167c22" def committed(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: raise SystemExit(f"cannot read B17 baseline {name}: {completed.stderr}") return completed.stdout def replace_once(source: str, old: str, new: str, label: str) -> str: count = source.count(old) if count != 1: raise SystemExit(f"{label}: expected one baseline transform, found {count}") return source.replace(old, new, 1) current = { name: (dut / "src" / name).read_text(encoding="utf-8") for name in ("internal.h", "super.c", "xattr.c") } base = {name: committed(name) for name in current} expected_internal = replace_once( base["internal.h"], "\tuint8_t xattr_prefix_count;\n", "\tuint8_t xattr_prefix_count;\n\tuint8_t xattr_filter_reserved;\n", "raw xattr filter state", ) expected_super = replace_once( base["super.c"], "\t/* A non-zero reserved value disables the current name-filter format. */\n" "\tif (erofs_sb_has_xattr_filter(sbi) && dsb->xattr_filter_reserved != 0)\n" "\t\tsbi->feature_compat &= ~EROFS_FEATURE_COMPAT_XATTR_FILTER;\n", "\t/* Preserve the raw feature declaration and gate its format at use sites. */\n" "\tsbi->xattr_filter_reserved = dsb->xattr_filter_reserved;\n", "raw feature preservation", ) expected_xattr = replace_once( base["xattr.c"], "\tif (entry_size > remaining)\n\t\treturn (EINTEGRITY);\n", "\tif (entry_size > remaining)\n\t\treturn (EINTEGRITY);\n" "\tif (memchr(entry->e_name, '\\0', entry->e_name_len) != NULL)\n" "\t\treturn (EINTEGRITY);\n", "inline name NUL validation", ) expected_xattr = replace_once( expected_xattr, "\terror = erofs_xattr_read_backing(sbi, backing_en, off, entry_size,\n" "\t entrybuf);\n" "\tif (error != 0)\n" "\t\treturn (error);\n" "\tif (entry_sizep != NULL)\n", "\terror = erofs_xattr_read_backing(sbi, backing_en, off, entry_size,\n" "\t entrybuf);\n" "\tif (error != 0)\n" "\t\treturn (error);\n" "\terror = erofs_xattr_validate_entry(entrybuf->data, entry_size, NULL,\n" "\t value_sizep);\n" "\tif (error != 0) {\n" "\t\terofs_put_metabuf(entrybuf);\n" "\t\treturn (error);\n" "\t}\n" "\tif (entry_sizep != NULL)\n", "shared name NUL validation", ) expected_xattr = replace_once( expected_xattr, "\tif (!erofs_sb_has_xattr_filter(sbi))\n\t\treturn (0);\n", "\tif (!erofs_sb_has_xattr_filter(sbi) ||\n" "\t sbi->xattr_filter_reserved != 0)\n" "\t\treturn (0);\n", "xattr filter use-site gate", ) expected_xattr = replace_once( expected_xattr, "\t\tprefix = buf.data;\n" "\t\tinfix_len = len - sizeof(*prefix);\n" "\t\tsbi->xattr_prefixes[i].base_index = prefix->base_index;\n", "\t\tprefix = buf.data;\n" "\t\tinfix_len = len - sizeof(*prefix);\n" "\t\tif (memchr(prefix->infix, '\\0', infix_len) != NULL) {\n" "\t\t\terror = EINTEGRITY;\n" "\t\t\tgoto fail;\n" "\t\t}\n" "\t\tsbi->xattr_prefixes[i].base_index = prefix->base_index;\n", "long-prefix NUL validation", ) expected = { "internal.h": expected_internal, "super.c": expected_super, "xattr.c": expected_xattr, } for name in expected: if current[name] != expected[name]: raise SystemExit(f"{name} differs from the exact B17 ledger transforms") diff = subprocess.run( ["git", "-C", str(root), "diff", baseline, "--", "repo-pre-15/src/internal.h", "repo-pre-15/src/super.c", "repo-pre-15/src/xattr.c"], check=True, text=True, stdout=subprocess.PIPE, ).stdout for forbidden in ("xxh32", "bloom", "cache owner", "cache waiter"): if forbidden in diff.lower(): raise SystemExit(f"B17 source diff contains out-of-scope marker: {forbidden}") if "return (-E" in "".join(current.values()): raise SystemExit("negative errno entered the FreeBSD B17 source") result = { "status": "PASS", "baseline": baseline, "changed_sources": sorted(expected), "raw_feature_preserved": True, "reserved_gated_at_use": True, "inline_name_nul": True, "shared_name_nul": True, "prefix_infix_nul": True, "acl_empty_suffix_preserved": True, } output.write_text(json.dumps(result, indent=2, sort_keys=True) + "\n", encoding="ascii") print("B17 source: exact raw-feature and name-integrity transforms present") PY then : else pre15_dut_fail 'B17 source does not match the fixture-authorized transforms' fi printf '%s\n' 'B17 source gate: PASS'