1019 lines
35 KiB
Bash
Executable File
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 = ▮
|
|
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
|