update
This commit is contained in:
Executable
+359
@@ -0,0 +1,359 @@
|
||||
#!/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"
|
||||
|
||||
baseline=145d892afc8a425294029d4349f03f1b17cf8f92
|
||||
artifacts=$PRE15_RUN_DIR/artifacts
|
||||
xattr=$PRE15_DUT/src/xattr.c
|
||||
linux_xattr=$PRE15_ROOT/src-linux/xattr.c
|
||||
xattr_header=$PRE15_DUT/src/xattr.h
|
||||
vnops=$PRE15_DUT/src/erofs_vnops.c
|
||||
fixture_dir=$PRE15_DUT/tests/pre15/fixtures
|
||||
fixture_spec=$fixture_dir/B17-xattr-spec.json
|
||||
fixture_generator=$fixture_dir/B17-xattr-generate.py
|
||||
fixture_oracle=$fixture_dir/B17-xattr-oracle.py
|
||||
fixture_seed=$fixture_dir/B17-xattr-seed.erofs
|
||||
|
||||
for tool in git python3 sha256sum; do
|
||||
command -v "$tool" >/dev/null 2>&1 || \
|
||||
pre15_infra_blocked "missing B18 host tool: $tool"
|
||||
done
|
||||
|
||||
pre15_record_fixture b18-freebsd-xattr "$xattr"
|
||||
pre15_record_fixture b18-linux-xattr "$linux_xattr"
|
||||
pre15_record_fixture b18-xattr-header "$xattr_header"
|
||||
pre15_record_fixture b18-vnops "$vnops"
|
||||
pre15_record_fixture b18-b17-spec "$fixture_spec"
|
||||
pre15_record_fixture b18-b17-generator "$fixture_generator"
|
||||
pre15_record_fixture b18-b17-oracle "$fixture_oracle"
|
||||
pre15_record_fixture b18-b17-seed "$fixture_seed"
|
||||
pre15_record_fixture b18-case \
|
||||
"$PRE15_DUT/tests/pre15/cases/B18-xattr-order.sh"
|
||||
|
||||
mkdir -p "$artifacts"
|
||||
pre15_target_reached
|
||||
|
||||
if python3 - "$PRE15_ROOT" "$PRE15_DUT" "$baseline" \
|
||||
"$artifacts/B18-xattr-order.json" <<'PY'
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import Counter
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
root = Path(sys.argv[1])
|
||||
dut = Path(sys.argv[2])
|
||||
baseline = sys.argv[3]
|
||||
output = Path(sys.argv[4])
|
||||
xattr_path = dut / "src/xattr.c"
|
||||
current = xattr_path.read_text(encoding="utf-8")
|
||||
linux = (root / "src-linux/xattr.c").read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def committed_bytes(path: str) -> bytes:
|
||||
completed = subprocess.run(
|
||||
["git", "-C", str(root), "show", f"{baseline}:repo-pre-15/{path}"],
|
||||
check=False,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
)
|
||||
if completed.returncode != 0:
|
||||
raise SystemExit(
|
||||
f"cannot read B18 baseline {path}: "
|
||||
f"{completed.stderr.decode(errors='replace')}"
|
||||
)
|
||||
return completed.stdout
|
||||
|
||||
|
||||
def require_once(source: str, marker: str, label: str) -> int:
|
||||
count = source.count(marker)
|
||||
if count != 1:
|
||||
raise SystemExit(f"{label}: expected one marker, found {count}: {marker!r}")
|
||||
return source.index(marker)
|
||||
|
||||
|
||||
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())
|
||||
if brace < 0:
|
||||
raise SystemExit(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
|
||||
raise SystemExit(f"unterminated function: {name}")
|
||||
|
||||
|
||||
def require_order(source: str, markers: tuple[str, ...], label: str) -> None:
|
||||
position = -1
|
||||
for marker in markers:
|
||||
position = source.find(marker, position + 1)
|
||||
if position < 0:
|
||||
raise SystemExit(f"{label}: missing ordered marker: {marker}")
|
||||
|
||||
|
||||
base = committed_bytes("src/xattr.c").decode("utf-8")
|
||||
iterator_start = require_once(base, "struct erofs_xattr_iter {", "iterator")
|
||||
iterator_end_marker = "\n};\n\n"
|
||||
iterator_end = base.find(iterator_end_marker, iterator_start)
|
||||
if iterator_end < 0:
|
||||
raise SystemExit("B18 baseline iterator terminator is absent")
|
||||
iterator_end += len(iterator_end_marker)
|
||||
iterator_block = base[iterator_start:iterator_end]
|
||||
|
||||
without_iterator = base[:iterator_start] + base[iterator_end:]
|
||||
acl_start = require_once(
|
||||
without_iterator,
|
||||
"static int\nerofs_inode_has_noacl(",
|
||||
"ACL/filter block",
|
||||
)
|
||||
acl_end = require_once(
|
||||
without_iterator,
|
||||
"static int\nerofs_listxattr_foreach(",
|
||||
"common iterator block",
|
||||
)
|
||||
if acl_end <= acl_start:
|
||||
raise SystemExit("B18 baseline ACL/filter block is not before the iterator core")
|
||||
acl_block = without_iterator[acl_start:acl_end]
|
||||
|
||||
expected = without_iterator[:acl_start] + without_iterator[acl_end:]
|
||||
core_start = require_once(
|
||||
expected,
|
||||
"static int\nerofs_xattr_backing_size(",
|
||||
"backing core",
|
||||
)
|
||||
expected = expected[:core_start] + iterator_block + expected[core_start:]
|
||||
acl_adapter = require_once(expected, "int\nerofs_get_acl(", "ACL adapter")
|
||||
expected = expected[:acl_adapter] + acl_block + expected[acl_adapter:]
|
||||
if current != expected:
|
||||
raise SystemExit("xattr.c differs from the two exact B18 definition moves")
|
||||
|
||||
base_names = re.findall(r"^(erofs_[A-Za-z0-9_]+)\s*\(", base, re.MULTILINE)
|
||||
current_names = re.findall(
|
||||
r"^(erofs_[A-Za-z0-9_]+)\s*\(", current, re.MULTILINE
|
||||
)
|
||||
if Counter(base_names) != Counter(current_names):
|
||||
raise SystemExit("B18 changed the xattr function definition multiset")
|
||||
|
||||
base_functions = {name: extract_function(base, name) for name in base_names}
|
||||
current_functions = {name: extract_function(current, name) for name in current_names}
|
||||
for name in base_functions:
|
||||
if current_functions[name] != base_functions[name]:
|
||||
raise SystemExit(f"B18 changed function text instead of moving it: {name}")
|
||||
if current_functions[name].startswith("static ") != base_functions[name].startswith(
|
||||
"static "
|
||||
):
|
||||
raise SystemExit(f"B18 changed symbol visibility: {name}")
|
||||
|
||||
|
||||
def direct_calls(functions: dict[str, str]) -> Counter[tuple[str, str]]:
|
||||
calls: Counter[tuple[str, str]] = Counter()
|
||||
for caller, function in functions.items():
|
||||
brace = function.find("{")
|
||||
for callee in re.findall(
|
||||
r"\b(erofs_[A-Za-z0-9_]+)\s*\(", function[brace + 1 :]
|
||||
):
|
||||
calls[(caller, callee)] += 1
|
||||
return calls
|
||||
|
||||
|
||||
base_calls = direct_calls(base_functions)
|
||||
current_calls = direct_calls(current_functions)
|
||||
if current_calls != base_calls:
|
||||
raise SystemExit("B18 changed the direct-call multiset")
|
||||
|
||||
expected_order = [
|
||||
"erofs_xattr_backing_size",
|
||||
"erofs_xattr_read_backing",
|
||||
"erofs_xattr_read_metadata",
|
||||
"erofs_xattr_move",
|
||||
"erofs_xattr_load_body",
|
||||
"erofs_xattr_validate_entry",
|
||||
"erofs_xattr_prefix",
|
||||
"erofs_xattr_namespace_prefix",
|
||||
"erofs_xattr_list_move",
|
||||
"erofs_xattr_resolve_name",
|
||||
"erofs_xattr_name_match",
|
||||
"erofs_xattr_shared_entry_offset",
|
||||
"erofs_xattr_load_shared_entry",
|
||||
"erofs_listxattr_foreach",
|
||||
"erofs_getxattr_foreach",
|
||||
"erofs_xattr_iter_inline",
|
||||
"erofs_xattr_iter_shared",
|
||||
"erofs_getxattr",
|
||||
"erofs_listxattr",
|
||||
"erofs_xattr_prefixes_cleanup",
|
||||
"erofs_xattr_prefixes_init",
|
||||
"erofs_inode_has_noacl",
|
||||
"erofs_acl_from_mode",
|
||||
"erofs_posix_acl_from_xattr",
|
||||
"erofs_get_acl",
|
||||
]
|
||||
if current_names != expected_order:
|
||||
raise SystemExit(f"B18 xattr definition order mismatch: {current_names}")
|
||||
if current.index("struct erofs_xattr_iter {") > current.index(
|
||||
"erofs_xattr_backing_size("
|
||||
):
|
||||
raise SystemExit("xattr iterator contract is not before the backing/core helpers")
|
||||
|
||||
linux_aligned_core = (
|
||||
"erofs_listxattr_foreach(",
|
||||
"erofs_getxattr_foreach(",
|
||||
"erofs_xattr_iter_inline(",
|
||||
"erofs_xattr_iter_shared(",
|
||||
"erofs_getxattr(",
|
||||
"erofs_listxattr(",
|
||||
"erofs_xattr_prefixes_cleanup(",
|
||||
"erofs_xattr_prefixes_init(",
|
||||
)
|
||||
require_order(current, linux_aligned_core, "FreeBSD common xattr core")
|
||||
require_order(linux, linux_aligned_core, "Linux common xattr core")
|
||||
|
||||
readonly_paths = (
|
||||
"src/xattr.h",
|
||||
"src/erofs_vnops.c",
|
||||
"tests/pre15/fixtures/B17-xattr-spec.json",
|
||||
"tests/pre15/fixtures/B17-xattr-generate.py",
|
||||
"tests/pre15/fixtures/B17-xattr-oracle.py",
|
||||
"tests/pre15/fixtures/B17-xattr-seed.erofs",
|
||||
)
|
||||
for path in readonly_paths:
|
||||
if (dut / path).read_bytes() != committed_bytes(path):
|
||||
raise SystemExit(f"B18 changed a read-only xattr/VOP/fixture contract: {path}")
|
||||
|
||||
vnops = (dut / "src/erofs_vnops.c").read_text(encoding="utf-8")
|
||||
getextattr = extract_function(vnops, "erofs_getextattr")
|
||||
listextattr = extract_function(vnops, "erofs_listextattr")
|
||||
require_order(
|
||||
getextattr,
|
||||
("extattr_check_cred(", "switch (ap->a_attrnamespace)", "erofs_getxattr("),
|
||||
"FreeBSD getextattr adapter",
|
||||
)
|
||||
require_order(
|
||||
listextattr,
|
||||
("extattr_check_cred(", "switch (ap->a_attrnamespace)", "erofs_listxattr("),
|
||||
"FreeBSD listextattr adapter",
|
||||
)
|
||||
if vnops.count("extattr_check_cred(") != 2:
|
||||
raise SystemExit("FreeBSD extattr credential boundary changed")
|
||||
if "ERANGE" in current:
|
||||
raise SystemExit("Linux all-or-nothing xattr buffer semantics entered FreeBSD")
|
||||
if "uiomove(value, value_size, uio)" not in current:
|
||||
raise SystemExit("FreeBSD partial extattr transfer path is absent")
|
||||
if re.search(r"return\s*(?:\(\s*)?-E[A-Z0-9_]+", current):
|
||||
raise SystemExit("Linux negative errno entered FreeBSD xattr.c")
|
||||
|
||||
load_body = extract_function(current, "erofs_xattr_load_body")
|
||||
require_order(
|
||||
load_body,
|
||||
(
|
||||
"vi->xattr_isize < sizeof(*ih)",
|
||||
"vi->xattr_isize == sizeof(*ih)",
|
||||
"error = EOPNOTSUPP",
|
||||
"header_size = sizeof(*ih)",
|
||||
),
|
||||
"exact-header compatibility",
|
||||
)
|
||||
|
||||
spec_path = dut / "tests/pre15/fixtures/B17-xattr-spec.json"
|
||||
spec = json.loads(spec_path.read_text(encoding="ascii"))
|
||||
cases = spec.get("cases", [])
|
||||
legal = sum(item.get("class") == "legal" for item in cases)
|
||||
damaged = sum(item.get("class") == "damaged" for item in cases)
|
||||
if len(cases) != 25 or legal != 10 or damaged != 15:
|
||||
raise SystemExit("B17 xattr fixture order/cardinality changed")
|
||||
seed_path = dut / "tests/pre15/fixtures" / spec["seed"]["path"]
|
||||
seed_hash = hashlib.sha256(seed_path.read_bytes()).hexdigest()
|
||||
if seed_hash != spec["seed"]["sha256"]:
|
||||
raise SystemExit("B17 xattr seed no longer matches its frozen specification")
|
||||
|
||||
result = {
|
||||
"status": "PASS",
|
||||
"baseline": baseline,
|
||||
"source_sha256": hashlib.sha256(current.encode("utf-8")).hexdigest(),
|
||||
"definition_count": len(current_names),
|
||||
"direct_call_edges": len(current_calls),
|
||||
"direct_call_sites": sum(current_calls.values()),
|
||||
"static_visibility_unchanged": True,
|
||||
"function_text_unchanged": True,
|
||||
"linux_core_order": list(linux_aligned_core),
|
||||
"freebsd_contracts": [
|
||||
"positive errno",
|
||||
"partial uiomove",
|
||||
"extattr_check_cred",
|
||||
"USER and SYSTEM namespaces",
|
||||
"exact-header EOPNOTSUPP",
|
||||
"uncached xattr lookup",
|
||||
],
|
||||
"b17_fixture_cases": len(cases),
|
||||
"b17_fixture_legal": legal,
|
||||
"b17_fixture_damaged": damaged,
|
||||
"b17_seed_sha256": seed_hash,
|
||||
}
|
||||
output.write_text(
|
||||
json.dumps(result, indent=2, sort_keys=True) + "\n", encoding="ascii"
|
||||
)
|
||||
print(
|
||||
"B18 xattr order: exact moves, "
|
||||
f"{len(current_names)} definitions, "
|
||||
f"{sum(current_calls.values())} direct calls"
|
||||
)
|
||||
print(f"B18 B17 fixture contract: {len(cases)} cases, seed {seed_hash}")
|
||||
PY
|
||||
then
|
||||
:
|
||||
else
|
||||
pre15_dut_fail 'B18 xattr ordering or FreeBSD contract equivalence failed'
|
||||
fi
|
||||
|
||||
printf '%s\n' 'B18 xattr order: PASS'
|
||||
Reference in New Issue
Block a user