Files
erofs-freebsd-out-tree/tests/pre15/gates/P15-005.sh
T
2026-08-18 09:20:44 +02:00

1019 lines
35 KiB
Bash
Executable File

#!/bin/sh
set -eu
gate_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P)
root=$(CDPATH= cd -- "$gate_dir/../../../.." && pwd -P)
input=$gate_dir/P15-005-input.json
mode=base
base=
output=
oracle=
while test "$#" -gt 0; do
case "$1" in
--base)
test "$#" -ge 2 || { echo '--base requires a commit' >&2; exit 2; }
mode=base
base=$2
shift 2
;;
--worktree)
mode=worktree
shift
;;
--output)
test "$#" -ge 2 || { echo '--output requires a directory' >&2; exit 2; }
output=$2
shift 2
;;
--oracle)
test "$#" -ge 2 || { echo '--oracle requires a JSON file' >&2; exit 2; }
oracle=$2
shift 2
;;
*)
echo "unknown argument: $1" >&2
exit 2
;;
esac
done
test -n "$output" || { echo '--output is required' >&2; exit 2; }
case "$output" in
/*) ;;
*) output=$PWD/$output ;;
esac
test ! -e "$output" || { echo "refusing existing output: $output" >&2; exit 2; }
mkdir -p "$output"
python3 - "$root" "$input" "$mode" "$base" "$output" "$oracle" <<'PY'
from __future__ import annotations
import hashlib
import json
from pathlib import Path
import re
import struct
import subprocess
import sys
root = Path(sys.argv[1])
input_path = Path(sys.argv[2])
mode = sys.argv[3]
requested_base = sys.argv[4]
output = Path(sys.argv[5])
oracle_path = Path(sys.argv[6]) if sys.argv[6] else None
spec = json.loads(input_path.read_text(encoding="ascii"))
def run(argv: list[str], **kwargs: object) -> subprocess.CompletedProcess[str]:
return subprocess.run(argv, check=True, text=True, **kwargs)
def git(*args: str) -> str:
return subprocess.check_output(
["git", "-C", str(root), *args], text=True
).strip()
if mode == "base":
if not requested_base:
raise SystemExit("base mode requires --base")
resolved = git("rev-parse", f"{requested_base}^{{commit}}")
if resolved != spec["required_base"]:
raise SystemExit(
f"P15-005 must gate the frozen BASE {spec['required_base']}, got {resolved}"
)
else:
resolved = git("rev-parse", "HEAD")
def source(path: str) -> str:
if mode == "base":
return subprocess.check_output(
["git", "-C", str(root), "show", f"{resolved}:{path}"],
text=True,
)
return (root / path).read_text(encoding="utf-8")
sources = {path: source(path) for path in spec["source_paths"]}
source_hashes = {
path: hashlib.sha256(text.encode("utf-8")).hexdigest()
for path, text in sources.items()
}
for path, expected in spec["linux_sha256"].items():
if source_hashes[path] != expected:
raise SystemExit(f"Linux oracle identity mismatch for {path}")
for path, anchors in spec["linux_semantic_anchors"].items():
for anchor in anchors:
if anchor not in sources[path]:
raise SystemExit(f"Linux semantic anchor absent in {path}: {anchor}")
def extract_function(text: str, name: str) -> str:
match = re.search(r"^" + re.escape(name) + r"\s*\(", text, re.MULTILINE)
if not match:
raise ValueError(f"function not found: {name}")
start = text.rfind("\n\n", 0, match.start()) + 2
brace = text.find("{", match.end())
if brace < 0:
raise ValueError(f"function has no body: {name}")
depth = 0
state = "code"
index = brace
while index < len(text):
char = text[index]
following = text[index + 1] if index + 1 < len(text) 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 text[start : index + 1] + "\n"
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 ValueError(f"unterminated function: {name}")
function_bodies: dict[str, str] = {}
for key in spec["baseline_function_sha256"]:
filename, function = key.split(":", 1)
function_bodies[key] = extract_function(
sources[f"repo-pre-15/src/{filename}"], function
)
if mode == "base":
for key, expected in spec["baseline_function_sha256"].items():
actual = hashlib.sha256(function_bodies[key].encode("utf-8")).hexdigest()
if actual != expected:
raise SystemExit(f"unreviewed baseline consumer body: {key}")
def all_functions(text: str) -> dict[str, str]:
result: dict[str, str] = {}
for match in re.finditer(r"^([A-Za-z_][A-Za-z0-9_]*)\s*\(", text, re.MULTILINE):
name = match.group(1)
try:
result[name] = extract_function(text, name)
except ValueError:
continue
return result
direct_sites: set[str] = set()
for filename in (
"data.c",
"decompressor.c",
"inode.c",
"super.c",
"xattr.c",
"zdata.c",
"zmap.c",
):
funcs = all_functions(sources[f"repo-pre-15/src/{filename}"])
for name, body in funcs.items():
calls = body[body.find("{") :]
direct = "erofs_read_metadata(" in calls
if filename in {"decompressor.c", "super.c", "xattr.c"}:
direct = direct or "erofs_bread(" in calls
if direct:
direct_sites.add(f"{filename}:{name}")
expected_direct = set(spec["direct_metadata_functions"])
if direct_sites != expected_direct:
raise SystemExit(
"direct metadata consumer inventory mismatch: "
f"missing={sorted(expected_direct - direct_sites)} "
f"extra={sorted(direct_sites - expected_direct)}"
)
object_mode = "struct erofs_buf {" in sources["repo-pre-15/src/internal.h"]
ownership_failures: list[dict[str, object]] = []
candidate_contract_result: dict[str, object] = {"status": "NOT_APPLICABLE"}
if mode == "worktree":
internal = sources["repo-pre-15/src/internal.h"]
data_source = sources["repo-pre-15/src/data.c"]
required_object_markers = [
"struct erofs_buf {",
"void *data;",
"void (*release)(void *);",
"void erofs_put_metabuf(struct erofs_buf *buf);",
"erofs_read_metadata(struct erofs_sb_info *sbi, erofs_nid_t nid,",
"struct erofs_buf *buf)",
]
for marker in required_object_markers:
if marker not in internal and marker not in data_source:
raise SystemExit(f"candidate metadata object marker absent: {marker}")
if re.search(
r"erofs_read_metadata\s*\([^;]*size_t\s+len\s*,\s*void\s*\*\*bufp\s*\)\s*;",
internal,
re.DOTALL,
):
raise SystemExit("legacy metadata void-pointer prototype remains")
contract = spec["candidate_ownership_contract"]
contract_keys = set(contract["object_local_counts"])
if contract_keys != set(spec["baseline_function_sha256"]):
raise SystemExit("candidate ownership contract does not cover every audited function")
function_metrics = []
for key in sorted(contract_keys):
body = function_bodies[key]
calls = body[body.find("{") :]
initialized_objects = re.findall(
r"\bstruct\s+erofs_buf\s+([A-Za-z_][A-Za-z0-9_]*)\s*=\s*"
r"EROFS_BUF_INITIALIZER\s*;",
calls,
)
all_objects = re.findall(
r"\bstruct\s+erofs_buf\s+([A-Za-z_][A-Za-z0-9_]*)\s*(?:=|;)",
calls,
)
metrics = {
"function": key,
"object_local_count": len(initialized_objects),
"put_count": calls.count("erofs_put_metabuf("),
"legacy_release_count": calls.count("erofs_brelse("),
"raw_bread_count": calls.count("erofs_bread("),
}
function_metrics.append(metrics)
expected = {
"object_local_count": contract["object_local_counts"][key],
"put_count": contract["put_counts"][key],
"legacy_release_count": contract["legacy_release_counts"].get(key, 0),
"raw_bread_count": contract["raw_bread_counts"].get(key, 0),
}
for field, value in expected.items():
if metrics[field] != value:
ownership_failures.append(
{
"function": key,
"field": field,
"expected": value,
"actual": metrics[field],
}
)
if sorted(all_objects) != sorted(initialized_objects):
ownership_failures.append(
{"function": key, "field": "uninitialized-metadata-object"}
)
for variable in initialized_objects:
if f"&{variable}" not in calls:
ownership_failures.append(
{"function": key, "field": "object-address-unused", "variable": variable}
)
if f"erofs_put_metabuf(&{variable})" not in calls:
ownership_failures.append(
{"function": key, "field": "object-not-released", "variable": variable}
)
raw_pointer_names = re.findall(
r"\bvoid\s*\*\s*([A-Za-z_][A-Za-z0-9_]*)\s*(?:[;=,])", calls
)
for api in contract["acquire_apis"]:
for call in re.findall(
r"\b" + re.escape(api) + r"\s*\((.*?);", calls, re.DOTALL
):
for variable in raw_pointer_names:
if re.search(r"&\s*" + re.escape(variable) + r"\b", call):
ownership_failures.append(
{
"function": key,
"field": "raw-pointer-metadata-acquire",
"api": api,
"variable": variable,
}
)
candidate_contract_result = {
"status": "PASS" if not ownership_failures else "FAIL",
"function_metrics": function_metrics,
"transfer_functions": contract["transfer_functions"],
"failures": ownership_failures,
}
def compile_and_run(name: str, program: str) -> list[dict[str, int | str]]:
source_path = output / f"{name}.c"
binary_path = output / name
compile_stdout = output / f"{name}.compile.stdout"
compile_stderr = output / f"{name}.compile.stderr"
run_stdout = output / f"{name}.stdout"
run_stderr = output / f"{name}.stderr"
source_path.write_text(program, encoding="ascii")
compiler = subprocess.run(
[
"cc",
"-std=c17",
"-O0",
"-Wall",
"-Wextra",
"-Werror",
str(source_path),
"-o",
str(binary_path),
],
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
compile_stdout.write_text(compiler.stdout, encoding="utf-8")
compile_stderr.write_text(compiler.stderr, encoding="utf-8")
if compiler.returncode != 0:
raise SystemExit(f"{name} extractor compilation failed")
executed = subprocess.run(
[str(binary_path)],
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
timeout=60,
)
run_stdout.write_text(executed.stdout, encoding="utf-8")
run_stderr.write_text(executed.stderr, encoding="utf-8")
if executed.returncode != 0:
raise SystemExit(f"{name} extractor execution failed")
records = []
for line in executed.stdout.splitlines():
fields = line.split("\t")
if len(fields) != 11 or fields[0] != "tuple":
raise SystemExit(f"invalid {name} extractor output: {line!r}")
records.append(
{
"id": fields[1],
"m_la": int(fields[2]),
"m_pa": int(fields[3]),
"m_llen": int(fields[4]),
"m_plen": int(fields[5]),
"m_deviceid": int(fields[6]),
"m_flags": int(fields[7]),
"errno": int(fields[8]),
"acquire_count": int(fields[9]),
"release_count": int(fields[10]),
}
)
return records
def compile_helper(name: str, program: str) -> list[str]:
source_path = output / f"{name}.c"
binary_path = output / name
compile_stdout = output / f"{name}.compile.stdout"
compile_stderr = output / f"{name}.compile.stderr"
run_stdout = output / f"{name}.stdout"
run_stderr = output / f"{name}.stderr"
source_path.write_text(program, encoding="ascii")
compiler = subprocess.run(
[
"cc",
"-std=c17",
"-O0",
"-Wall",
"-Wextra",
"-Werror",
str(source_path),
"-o",
str(binary_path),
],
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
compile_stdout.write_text(compiler.stdout, encoding="utf-8")
compile_stderr.write_text(compiler.stderr, encoding="utf-8")
if compiler.returncode != 0:
raise SystemExit(f"{name} helper extractor compilation failed")
executed = subprocess.run(
[str(binary_path)],
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
timeout=60,
)
run_stdout.write_text(executed.stdout, encoding="utf-8")
run_stderr.write_text(executed.stderr, encoding="utf-8")
if executed.returncode != 0:
raise SystemExit(f"{name} helper extractor execution failed")
return executed.stdout.splitlines()
common = r'''
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define EIO 5
#define EINVAL 22
#define EOPNOTSUPP 45
#define EOVERFLOW 84
#define EINTEGRITY 97
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define roundup2(x, y) ((((x) + ((y) - 1)) / (y)) * (y))
#define rounddown2(x, y) ((x) - ((x) % (y)))
#define bzero(p, n) memset((p), 0, (n))
#define le16toh(x) (x)
#define le32toh(x) (x)
#define le64toh(x) (x)
typedef uint64_t erofs_nid_t;
typedef uint64_t erofs_off_t;
typedef uint64_t erofs_blk_t;
static __attribute__((unused)) uint16_t le16dec(const void *pointer) { uint16_t value; memcpy(&value, pointer, sizeof(value)); return value; }
static __attribute__((unused)) uint32_t le32dec(const void *pointer) { uint32_t value; memcpy(&value, pointer, sizeof(value)); return value; }
static __attribute__((unused)) uint64_t le64dec(const void *pointer) { uint64_t value; memcpy(&value, pointer, sizeof(value)); return value; }
'''
buffer_compat = r'''
struct erofs_buf {
void *data;
void (*release)(void *);
};
#define EROFS_BUF_INITIALIZER { .data = NULL, .release = NULL }
static unsigned int gate_acquires;
static unsigned int gate_releases;
static const unsigned char *gate_bytes;
static size_t gate_bytes_len;
static uint64_t gate_bytes_base;
static int gate_reader_mode;
static void gate_release(void *data)
{
if (data != NULL) {
++gate_releases;
free(data);
}
}
static __attribute__((unused)) void erofs_brelse(void *data) { gate_release(data); }
static __attribute__((unused)) void erofs_put_metabuf(struct erofs_buf *buf)
{
void *data;
void (*release)(void *);
if (buf == NULL || buf->data == NULL)
return;
data = buf->data;
release = buf->release;
buf->data = NULL;
buf->release = NULL;
if (release != NULL)
release(data);
}
'''
def reader_definition(objects: bool) -> str:
if objects:
return r'''
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 *data;
(void)sbi; (void)nid;
if (gate_reader_mode == 2 || off < gate_bytes_base ||
len > gate_bytes_len || off - gate_bytes_base > gate_bytes_len - len)
return (EIO);
data = malloc(len);
if (data == NULL)
return (EIO);
memcpy(data, gate_bytes + (off - gate_bytes_base), len);
buf->data = data;
buf->release = gate_release;
++gate_acquires;
return (0);
}
'''
return r'''
static int erofs_read_metadata(struct erofs_sb_info *sbi, erofs_nid_t nid,
erofs_off_t off, size_t len, void **bufp)
{
void *data;
(void)sbi; (void)nid;
if (gate_reader_mode == 2 || off < gate_bytes_base ||
len > gate_bytes_len || off - gate_bytes_base > gate_bytes_len - len)
return (EIO);
data = malloc(len);
if (data == NULL)
return (EIO);
memcpy(data, gate_bytes + (off - gate_bytes_base), len);
*bufp = data;
++gate_acquires;
return (0);
}
'''
def c_bytes(data: bytes) -> str:
if not data:
return "0"
return ", ".join(f"0x{byte:02x}" for byte in data)
data_cases = [case for case in spec["tuple_cases"] if case["engine"] == "data"]
data_case_code = []
for index, case in enumerate(data_cases):
reader = case["reader"]
raw = bytes.fromhex(reader.get("bytes_hex", ""))
sbi = case["sbi"]
inode = case["inode"]
data_case_code.append(
f'''
static const unsigned char bytes_{index}[] = {{ {c_bytes(raw)} }};
struct erofs_sb_info sbi_{index} = {{
.block_size = {sbi['block_size']}ULL,
.blkszbits = {sbi['blkszbits']},
.blocks = {sbi['blocks']}ULL,
.device_id_mask = {sbi['device_id_mask']},
}};
struct erofs_inode vi_{index} = {{
.nid = {inode['nid']}ULL,
.size = {inode['size']}ULL,
.inode_off = {inode['inode_off']}ULL,
.startblk = {inode['startblk']}ULL,
.datalayout = {inode['datalayout']},
.inode_isize = {inode['inode_isize']},
.xattr_isize = {inode['xattr_isize']},
.chunkformat = {inode['chunkformat']},
.chunkbits = {inode['chunkbits']},
}};
gate_bytes = bytes_{index};
gate_bytes_len = {len(raw)};
gate_bytes_base = {inode['inode_off'] + inode['inode_isize'] + inode['xattr_isize']}ULL;
gate_reader_mode = {2 if reader['mode'] == 'error' else 0};
gate_acquires = gate_releases = 0;
pa = 0; device = 0; run = 0; hole = false; metadata = false;
error = erofs_map_blocks(&sbi_{index}, &vi_{index}, {case['request']}ULL,
&pa, &device, &run, &hole, &metadata);
flags = (error == 0 && run != 0 && !hole ? EROFS_MAP_MAPPED : 0) |
(metadata ? EROFS_MAP_META : 0);
printf("tuple\\t{case['id']}\\t%llu\\t%llu\\t%zu\\t%zu\\t%u\\t%u\\t%d\\t%u\\t%u\\n",
(unsigned long long){case['request']}ULL, (unsigned long long)pa,
run, run, device, flags, error, gate_acquires, gate_releases);
'''
)
data_preamble = common + buffer_compat + r'''
#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_CHUNK_FORMAT_INDEXES 0x20
#define EROFS_CHUNK_FORMAT_48BIT 0x40
#define EROFS_BLOCK_MAP_ENTRY_SIZE 4
#define EROFS_DIRENT_NID_METABOX (1ULL << 63)
#define EROFS_NULL_ADDR UINT64_MAX
#define EROFS_MAP_MAPPED 0x0001
#define EROFS_MAP_META 0x0002
struct erofs_inode_chunk_index { uint16_t startblk_hi; uint16_t device_id; uint32_t startblk_lo; } __attribute__((packed));
struct erofs_inode {
erofs_nid_t nid; uint64_t size; erofs_off_t inode_off; erofs_blk_t startblk;
uint8_t datalayout; uint8_t inode_isize; uint32_t xattr_isize;
uint16_t chunkformat; uint8_t chunkbits;
};
struct erofs_sb_info {
uint64_t block_size; uint8_t blkszbits; erofs_blk_t blocks;
uint16_t device_id_mask; struct erofs_inode *metabox_en;
};
static bool erofs_nid_in_metabox(erofs_nid_t nid) { return (nid & EROFS_DIRENT_NID_METABOX) != 0; }
'''
data_program = (
data_preamble
+ reader_definition(object_mode)
+ extract_function(sources["repo-pre-15/src/data.c"], "erofs_inline_tail_start")
+ extract_function(sources["repo-pre-15/src/data.c"], "erofs_map_blocks_chunk")
+ extract_function(sources["repo-pre-15/src/data.c"], "erofs_map_blocks")
+ r'''
int main(void)
{
erofs_off_t pa; unsigned int device, flags; size_t run;
bool hole, metadata; int error;
'''
+ "".join(data_case_code)
+ "\treturn (0);\n}\n"
)
zmap_cases = [case for case in spec["tuple_cases"] if case["engine"] == "zmap"]
zmap_case_code = []
for index, case in enumerate(zmap_cases):
reader = case["reader"]
if "records" in reader:
raw = b"".join(
struct.pack(
"<IIIII",
record["plen"],
record["pstart"] & 0xFFFFFFFF,
record["pstart"] >> 32,
record["lstart"] & 0xFFFFFFFF,
record["lstart"] >> 32,
)[:16]
for record in reader["records"]
)
else:
raw = bytes.fromhex(reader.get("bytes_hex", ""))
sbi = case["sbi"]
inode = case["inode"]
zmap_case_code.append(
f'''
static const unsigned char bytes_{index}[] = {{ {c_bytes(raw)} }};
struct erofs_inode packed_{index} = {{ .nid = 999, .size = {sbi['packed_size']}ULL }};
struct erofs_sb_info sbi_{index} = {{
.block_size = {sbi['block_size']}, .blkszbits = {sbi['blkszbits']},
.available_compr_algs = {sbi['available_compr_algs']},
.packed_inode = &packed_{index},
}};
struct erofs_inode vi_{index} = {{
.nid = {inode['nid']}ULL, .size = {inode['size']}ULL,
.inode_off = {inode['inode_off']}ULL,
.inode_isize = {inode['inode_isize']},
.xattr_isize = {inode['xattr_isize']},
.datalayout = {inode['datalayout']},
.z_advise = {inode['z_advise']},
.z_lclusterbits = {inode['z_lclusterbits']},
.z_extents = {inode['z_extents']}ULL, .z_initialized = true,
}};
struct erofs_map_blocks map_{index} = {{ .m_la = {case['request']}ULL }};
gate_bytes = bytes_{index}; gate_bytes_len = {len(raw)};
gate_bytes_base = {reader.get('base', 0)}ULL;
gate_reader_mode = {2 if reader['mode'] == 'error' else 0};
gate_acquires = gate_releases = 0;
error = z_erofs_map_blocks_iter(&sbi_{index}, &vi_{index}, &map_{index}, 0);
printf("tuple\\t{case['id']}\\t%llu\\t%llu\\t%llu\\t%llu\\t%u\\t%u\\t%d\\t%u\\t%u\\n",
(unsigned long long)map_{index}.m_la,
(unsigned long long)map_{index}.m_pa,
(unsigned long long)map_{index}.m_llen,
(unsigned long long)map_{index}.m_plen,
map_{index}.m_deviceid, map_{index}.m_flags, error,
gate_acquires, gate_releases);
'''
)
zmap_preamble = common + buffer_compat + r'''
#define EROFS_INODE_COMPRESSED_FULL 1
#define EROFS_MAP_MAPPED 0x0001
#define EROFS_MAP_META 0x0002
#define EROFS_MAP_PARTIAL_MAPPED 0x0004
#define EROFS_MAP_PARTIAL_REF 0x0008
#define EROFS_MAP_FRAGMENT 0x0010
#define EROFS_MAP_FULL(f) (!((f) & (EROFS_MAP_PARTIAL_MAPPED | EROFS_MAP_PARTIAL_REF)))
#define EROFS_NULL_ADDR UINT64_MAX
#define Z_EROFS_COMPRESSION_LZ4 0
#define Z_EROFS_COMPRESSION_MAX 4
#define Z_EROFS_COMPRESSION_SHIFTED 4
#define Z_EROFS_COMPRESSION_INTERLACED 5
#define Z_EROFS_COMPRESSION_RUNTIME_MAX 6
#define Z_EROFS_PCLUSTER_MAX_SIZE (1024 * 1024)
#define Z_EROFS_PCLUSTER_MAX_DSIZE (12 * 1024 * 1024)
#define Z_EROFS_ADVISE_EXTENTS 0x0001
#define Z_EROFS_ADVISE_INTERLACED_PCLUSTER 0x0010
#define Z_EROFS_ADVISE_FRAGMENT_PCLUSTER 0x0020
#define Z_EROFS_ADVISE_EXTRECSZ_BIT 1
#define Z_EROFS_ADVISE_EXTRECSZ_MASK 0x3
#define Z_EROFS_EXTENT_PLEN_PARTIAL (1U << 27)
#define Z_EROFS_EXTENT_PLEN_FMT_BIT 28
#define Z_EROFS_EXTENT_PLEN_MASK ((Z_EROFS_PCLUSTER_MAX_SIZE << 1) - 1)
struct z_erofs_extent {
uint32_t plen, pstart_lo, pstart_hi, lstart_lo, lstart_hi;
uint8_t reserved[12];
} __attribute__((packed));
struct z_erofs_map_header { uint32_t word0; uint16_t h_advise; uint8_t h_algorithmtype; uint8_t h_clusterbits; } __attribute__((packed));
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_inode {
erofs_nid_t nid; uint64_t size; erofs_off_t inode_off;
uint8_t datalayout, inode_isize; uint32_t xattr_isize;
uint16_t z_advise; uint8_t z_algorithmtype[2], z_lclusterbits;
uint16_t z_idata_size; erofs_off_t z_fragmentoff;
uint64_t z_tailextent_headlcn, z_extents; bool z_initialized, fragment;
};
struct erofs_sb_info {
uint32_t block_size; uint8_t blkszbits; uint16_t available_compr_algs;
uint64_t packed_nid; struct erofs_inode *packed_inode;
};
'''
zmap_stubs = r'''
static int z_erofs_fill_inode(struct erofs_sb_info *sbi, struct erofs_inode *vi)
{ (void)sbi; return (vi->z_initialized ? 0 : EINTEGRITY); }
static int z_erofs_map_blocks_fo(struct erofs_sb_info *sbi, struct erofs_inode *vi,
struct erofs_map_blocks *map, int flags)
{ (void)sbi; (void)vi; (void)map; (void)flags; return (EOPNOTSUPP); }
'''
zmap_names = [
"z_erofs_extent_add",
"z_erofs_extent_roundup",
"z_erofs_extent_table_pos",
"z_erofs_extent_record_pos",
"z_erofs_read_extent",
"z_erofs_extent_lstart",
"z_erofs_map_blocks_ext",
"z_erofs_map_sanity_check",
"z_erofs_map_blocks_iter",
]
zmap_program = zmap_preamble + reader_definition(object_mode) + zmap_stubs
zmap_program += extract_function(
sources["repo-pre-15/src/erofs_fs.h"], "z_erofs_extent_recsize"
)
for name in zmap_names:
zmap_program += extract_function(sources["repo-pre-15/src/zmap.c"], name)
zmap_program += "int main(void)\n{\n\tint error;\n"
zmap_program += "".join(zmap_case_code)
zmap_program += "\treturn (0);\n}\n"
helper_result: dict[str, object] = {"status": "NOT_APPLICABLE"}
if mode == "worktree":
helper_program = common + r'''
#define EROFS_DIRENT_NID_METABOX (1ULL << 63)
typedef int64_t off_t;
struct erofs_inode { uint64_t size; };
struct erofs_sb_info { struct erofs_inode *metabox_en; };
struct erofs_buf {
void *data;
void (*release)(void *);
};
#define EROFS_BUF_INITIALIZER { .data = NULL, .release = NULL }
static int gate_error;
static unsigned int gate_acquires;
static unsigned int gate_releases;
static unsigned int gate_backend;
static bool erofs_nid_in_metabox(erofs_nid_t nid)
{ return ((nid & EROFS_DIRENT_NID_METABOX) != 0); }
static bool erofs_sb_has_metabox(struct erofs_sb_info *sbi)
{ return (sbi->metabox_en != NULL); }
static int gate_acquire(size_t len, void **bufp, unsigned int backend)
{
void *data;
if (gate_error != 0)
return (gate_error);
data = malloc(len == 0 ? 1 : len);
if (data == NULL)
return (EIO);
*bufp = data;
++gate_acquires;
gate_backend = backend;
return (0);
}
static int erofs_bread(struct erofs_sb_info *sbi, erofs_off_t off,
size_t len, void **bufp)
{ (void)sbi; (void)off; return (gate_acquire(len, bufp, 1)); }
static int erofs_read_data(struct erofs_sb_info *sbi, struct erofs_inode *vi,
erofs_off_t off, size_t len, void **bufp)
{ (void)sbi; (void)vi; (void)off; return (gate_acquire(len, bufp, 2)); }
static void erofs_brelse(void *data)
{
if (data != NULL) {
++gate_releases;
free(data);
}
}
'''
helper_program += extract_function(
sources["repo-pre-15/src/data.c"], "erofs_put_metabuf"
)
helper_program += extract_function(
sources["repo-pre-15/src/data.c"], "erofs_read_metadata"
)
helper_program += r'''
int main(void)
{
struct erofs_inode metabox = { .size = 4096 };
struct erofs_sb_info sbi = { .metabox_en = &metabox };
struct erofs_buf buf = EROFS_BUF_INITIALIZER;
int error;
int marker;
gate_error = 0; gate_acquires = gate_releases = gate_backend = 0;
error = erofs_read_metadata(&sbi, 1, 32, 8, &buf);
if (error != 0 || buf.data == NULL || buf.release == NULL ||
gate_acquires != 1 || gate_backend != 1)
return (10);
erofs_put_metabuf(&buf);
erofs_put_metabuf(&buf);
if (buf.data != NULL || buf.release != NULL || gate_releases != 1)
return (11);
printf("helper\tprimary-success\t1\t1\n");
buf = (struct erofs_buf)EROFS_BUF_INITIALIZER;
gate_error = 0; gate_acquires = gate_releases = gate_backend = 0;
error = erofs_read_metadata(&sbi, EROFS_DIRENT_NID_METABOX | 2, 64, 8,
&buf);
if (error != 0 || buf.data == NULL || buf.release == NULL ||
gate_acquires != 1 || gate_backend != 2)
return (12);
erofs_put_metabuf(&buf);
if (buf.data != NULL || buf.release != NULL || gate_releases != 1)
return (13);
printf("helper\tmetabox-success\t1\t1\n");
buf = (struct erofs_buf)EROFS_BUF_INITIALIZER;
gate_error = EIO; gate_acquires = gate_releases = gate_backend = 0;
error = erofs_read_metadata(&sbi, 1, 32, 8, &buf);
if (error != EIO || buf.data != NULL || buf.release != NULL ||
gate_acquires != 0 || gate_releases != 0)
return (14);
printf("helper\tprovider-error\t0\t0\n");
buf = (struct erofs_buf)EROFS_BUF_INITIALIZER;
gate_error = 0; gate_acquires = gate_releases = gate_backend = 0;
error = erofs_read_metadata(&sbi, 1, UINT64_MAX, 8, &buf);
if (error != EOVERFLOW || buf.data != NULL || buf.release != NULL ||
gate_acquires != 0 || gate_releases != 0)
return (15);
printf("helper\toffset-overflow\t0\t0\n");
buf.data = &marker;
buf.release = NULL;
erofs_put_metabuf(&buf);
erofs_put_metabuf(NULL);
if (buf.data != NULL || buf.release != NULL)
return (16);
printf("helper\tnull-release\t0\t0\n");
return (0);
}
'''
helper_lines = compile_helper("P15-005-buffer-helper", helper_program)
expected_helper_lines = [
"helper\tprimary-success\t1\t1",
"helper\tmetabox-success\t1\t1",
"helper\tprovider-error\t0\t0",
"helper\toffset-overflow\t0\t0",
"helper\tnull-release\t0\t0",
]
if helper_lines != expected_helper_lines:
ownership_failures.append(
{
"field": "helper-paths",
"expected": expected_helper_lines,
"actual": helper_lines,
}
)
helper_result = {
"status": "PASS" if helper_lines == expected_helper_lines else "FAIL",
"paths": helper_lines,
}
actual_records = compile_and_run("P15-005-data-extractor", data_program)
actual_records.extend(compile_and_run("P15-005-zmap-extractor", zmap_program))
actual_by_id = {record["id"]: record for record in actual_records}
expected_by_id = {case["id"]: case["expected"] for case in spec["tuple_cases"]}
if set(actual_by_id) != set(expected_by_id):
raise SystemExit("extractor case set mismatch")
tuple_failures = []
for case in spec["tuple_cases"]:
actual = actual_by_id[case["id"]]
expected = case["expected"]
for field in spec["tuple_fields"]:
if actual[field] != expected[field]:
tuple_failures.append(
{
"id": case["id"],
"field": field,
"expected": expected[field],
"actual": actual[field],
}
)
coverage = sorted(
{case["category"] for case in spec["tuple_cases"] if not case.get("supplemental")}
)
if set(coverage) != set(spec["required_categories"]):
raise SystemExit("required tuple category coverage is incomplete")
for case in spec["tuple_cases"]:
actual = actual_by_id[case["id"]]
if actual["acquire_count"] != actual["release_count"]:
tuple_failures.append({"id": case["id"], "field": "ownership"})
if (
actual["errno"] == 0
and case["request"] < case["inode"]["size"]
and actual["m_llen"] == 0
):
tuple_failures.append({"id": case["id"], "field": "H07-positive-run"})
ownership_paths = spec["ownership_paths"]
consumers = sorted({path["consumer"] for path in ownership_paths})
required_consumers = ["compressed", "inode", "map", "plain", "super", "xattr"]
if consumers != required_consumers:
raise SystemExit(f"ownership consumer coverage mismatch: {consumers}")
for path in ownership_paths:
if path["function"] not in function_bodies:
raise SystemExit(f"ownership path has no frozen function body: {path['id']}")
if path["acquire_count"] != path["release_count"]:
raise SystemExit(f"incomplete ownership oracle path: {path['id']}")
ownership_result = {
"status": "PASS" if not ownership_failures else "FAIL",
"consumers": consumers,
"direct_metadata_functions": sorted(direct_sites),
"paths": ownership_paths,
"baseline_body_sha256_verified": mode == "base",
"linux_semantic_anchors_verified": True,
"candidate_contract": candidate_contract_result,
"helper_paths": helper_result,
"failures": ownership_failures,
"short_read_rule": "a failed read before returned storage has acquire=release=0",
"success_rule": "each returned metadata allocation has exactly one release",
}
tuple_result = {
"status": "PASS" if not tuple_failures else "FAIL",
"coverage": coverage,
"tuple_fields": spec["tuple_fields"],
"records": [
{
**actual_by_id[case["id"]],
"category": case["category"],
"supplemental": bool(case.get("supplemental")),
}
for case in spec["tuple_cases"]
],
"failures": tuple_failures,
"authoritative_sources": [
"compiled function bodies extracted from the frozen/current FreeBSD source",
"independently frozen expected fields from EROFS records and Linux map semantics",
],
}
(output / "tuples.json").write_text(
json.dumps(tuple_result, indent=2, sort_keys=True) + "\n", encoding="ascii"
)
(output / "ownership.json").write_text(
json.dumps(ownership_result, indent=2, sort_keys=True) + "\n", encoding="ascii"
)
(output / "source-sha256.json").write_text(
json.dumps(source_hashes, indent=2, sort_keys=True) + "\n", encoding="ascii"
)
oracle_equal = None
if oracle_path is not None:
oracle = json.loads(oracle_path.read_text(encoding="ascii"))
oracle_records = {record["id"]: record for record in oracle["records"]}
oracle_equal = True
for case in spec["tuple_cases"]:
current = actual_by_id[case["id"]]
frozen = oracle_records.get(case["id"])
if frozen is None or any(current[field] != frozen[field] for field in spec["tuple_fields"]):
oracle_equal = False
break
if not oracle_equal:
tuple_failures.append({"field": "frozen-baseline-replay"})
status = "GO" if not tuple_failures and not ownership_failures else "STOP"
result = {
"schema": 1,
"gate": spec["gate"],
"candidate": spec["candidate"],
"status": status,
"mode": mode,
"requested_base": requested_base or None,
"resolved_head": resolved,
"object_mode": object_mode,
"tuple_status": tuple_result["status"],
"ownership_status": ownership_result["status"],
"coverage": coverage,
"case_count": len(actual_records),
"ownership_path_count": len(ownership_paths),
"oracle_equal": oracle_equal,
"qemu": "NOT_RUN",
"full_feature_suite": "NOT_RUN",
"failures": tuple_failures + ownership_failures,
}
(output / "result.json").write_text(
json.dumps(result, indent=2, sort_keys=True) + "\n", encoding="ascii"
)
hashes = []
for path in sorted(output.iterdir()):
if path.is_file() and path.name != "SHA256SUMS":
hashes.append(f"{hashlib.sha256(path.read_bytes()).hexdigest()} {path.name}")
(output / "SHA256SUMS").write_text("\n".join(hashes) + "\n", encoding="ascii")
print(json.dumps(result, sort_keys=True))
if status != "GO":
raise SystemExit(1)
PY