update
This commit is contained in:
Executable
+375
@@ -0,0 +1,375 @@
|
||||
#!/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=8e85a8b64dd1a956376ff1cb31d2157bc73df8ad
|
||||
artifacts=$PRE15_RUN_DIR/artifacts
|
||||
inode=$PRE15_DUT/src/inode.c
|
||||
vnops=$PRE15_DUT/src/erofs_vnops.c
|
||||
super=$PRE15_DUT/src/super.c
|
||||
internal=$PRE15_DUT/src/internal.h
|
||||
namei=$PRE15_DUT/src/namei.c
|
||||
|
||||
for tool in git python3 sha256sum; do
|
||||
command -v "$tool" >/dev/null 2>&1 || \
|
||||
pre15_infra_blocked "missing B09 host tool: $tool"
|
||||
done
|
||||
|
||||
pre15_record_fixture b09-inode "$inode"
|
||||
pre15_record_fixture b09-vnops "$vnops"
|
||||
pre15_record_fixture b09-super "$super"
|
||||
pre15_record_fixture b09-internal "$internal"
|
||||
pre15_record_fixture b09-namei "$namei"
|
||||
pre15_record_fixture b09-case "$PRE15_DUT/tests/pre15/cases/B09-vnode.sh"
|
||||
|
||||
mkdir -p "$artifacts"
|
||||
pre15_target_reached
|
||||
|
||||
if python3 - "$PRE15_ROOT" "$PRE15_DUT" "$baseline" "$artifacts" <<'PY'
|
||||
from __future__ import annotations
|
||||
|
||||
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]
|
||||
artifacts = Path(sys.argv[4])
|
||||
src = dut / "src"
|
||||
|
||||
|
||||
def committed(path: str) -> str:
|
||||
completed = subprocess.run(
|
||||
["git", "-C", str(root), "show", f"{baseline}:repo-pre-15/src/{path}"],
|
||||
check=False,
|
||||
text=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
)
|
||||
if completed.returncode != 0:
|
||||
raise SystemExit(f"cannot read B09 baseline {path}: {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 transform source, 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:
|
||||
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 initializer(source: str, declaration: str, prefix: str) -> list[tuple[str, str]]:
|
||||
match = re.search(
|
||||
re.escape(declaration) + r"\s*=\s*\{(.*?)\n\};", source, re.DOTALL
|
||||
)
|
||||
if not match:
|
||||
raise SystemExit(f"initializer absent: {declaration}")
|
||||
return re.findall(
|
||||
rf"^\s*\.({prefix}_[A-Za-z0-9_]+)\s*=\s*([^,]+),",
|
||||
match.group(1),
|
||||
re.MULTILINE,
|
||||
)
|
||||
|
||||
|
||||
def require_order(source: str, markers: list[str], label: str) -> None:
|
||||
position = -1
|
||||
for marker in markers:
|
||||
position = source.find(marker, position + 1)
|
||||
if position < 0:
|
||||
raise SystemExit(f"{label} is missing ordered marker: {marker}")
|
||||
|
||||
|
||||
current = {
|
||||
name: (src / name).read_text(encoding="utf-8")
|
||||
for name in ("inode.c", "erofs_vnops.c", "super.c", "internal.h", "namei.c")
|
||||
}
|
||||
base = {name: committed(name) for name in current}
|
||||
|
||||
moved_marker = "static u_int\nerofs_vfs_hash(erofs_nid_t nid)"
|
||||
moved_offset = base["inode.c"].find(moved_marker)
|
||||
if moved_offset < 0:
|
||||
raise SystemExit("B09 baseline vnode adapter block is absent")
|
||||
moved_block = base["inode.c"][moved_offset:]
|
||||
expected_inode = base["inode.c"][:moved_offset].rstrip("\n") + "\n"
|
||||
if current["inode.c"] != expected_inode:
|
||||
raise SystemExit("inode.c changed outside the exact vnode adapter movement")
|
||||
if current["internal.h"] != base["internal.h"]:
|
||||
raise SystemExit("internal.h changed despite no B09 interface delta")
|
||||
if current["namei.c"] != base["namei.c"]:
|
||||
raise SystemExit("namecache/lookup code changed outside the B09 write set")
|
||||
|
||||
expected_vnops = replace_once(
|
||||
base["erofs_vnops.c"],
|
||||
"#include <sys/extattr.h>\n",
|
||||
"#include <sys/extattr.h>\n#include <sys/fnv_hash.h>\n",
|
||||
"vnode hash include",
|
||||
)
|
||||
old_open = """\tif (vp->v_type == VREG) {
|
||||
\t\tif (vnode_create_vobject(vp, vi->size, ap->a_td) != 0)
|
||||
\t\t\treturn (ENOMEM);
|
||||
\t}
|
||||
"""
|
||||
new_open = """\tif (vp->v_type == VREG)
|
||||
\t\tvnode_create_vobject(vp, vi->size, ap->a_td);
|
||||
"""
|
||||
expected_vnops = replace_once(expected_vnops, old_open, new_open, "pager dead branch")
|
||||
expected_vnops = replace_once(
|
||||
expected_vnops,
|
||||
"static int\nerofs_reclaim(struct vop_reclaim_args *ap)",
|
||||
moved_block + "\nstatic int\nerofs_reclaim(struct vop_reclaim_args *ap)",
|
||||
"vnode adapter placement",
|
||||
)
|
||||
if current["erofs_vnops.c"] != expected_vnops:
|
||||
raise SystemExit("erofs_vnops.c differs from the two declared B09 transforms")
|
||||
|
||||
expected_super = replace_once(
|
||||
base["super.c"], "static vfs_vget_t erofs_vgetf;\n", "", "vget declaration"
|
||||
)
|
||||
expected_super = replace_once(
|
||||
expected_super,
|
||||
"""static int
|
||||
erofs_vgetf(struct mount *mp, ino_t ino, int flags, struct vnode **vpp)
|
||||
{
|
||||
\treturn (erofs_vget(mp, ino, flags, vpp));
|
||||
}
|
||||
|
||||
""",
|
||||
"",
|
||||
"vget wrapper",
|
||||
)
|
||||
expected_super = replace_once(
|
||||
expected_super,
|
||||
"\t.vfs_vget = erofs_vgetf,",
|
||||
"\t.vfs_vget = erofs_vget,",
|
||||
"vfs_vget slot",
|
||||
)
|
||||
if current["super.c"] != expected_super:
|
||||
raise SystemExit("super.c differs from the exact direct-vget transform")
|
||||
|
||||
moved_functions = (
|
||||
"erofs_vfs_hash",
|
||||
"erofs_vfs_hash_cmp",
|
||||
"erofs_fill_vnode",
|
||||
"erofs_vget",
|
||||
)
|
||||
for name in moved_functions:
|
||||
if extract_function(base["inode.c"], name) != extract_function(
|
||||
current["erofs_vnops.c"], name
|
||||
):
|
||||
raise SystemExit(f"moved function changed semantics: {name}")
|
||||
definitions = {
|
||||
path: len(re.findall(r"^" + re.escape(name) + r"\s*\(", text, re.MULTILINE))
|
||||
for path, text in current.items()
|
||||
}
|
||||
if definitions["erofs_vnops.c"] != 1 or sum(definitions.values()) != 1:
|
||||
raise SystemExit(f"B09 function ownership is not unique: {name} {definitions}")
|
||||
|
||||
base_vnode_slots = initializer(
|
||||
base["erofs_vnops.c"], "struct vop_vector erofs_vnodeops", "vop"
|
||||
)
|
||||
base_fifo_slots = initializer(
|
||||
base["erofs_vnops.c"], "struct vop_vector erofs_fifoops", "vop"
|
||||
)
|
||||
vnode_slots = initializer(
|
||||
current["erofs_vnops.c"], "struct vop_vector erofs_vnodeops", "vop"
|
||||
)
|
||||
fifo_slots = initializer(
|
||||
current["erofs_vnops.c"], "struct vop_vector erofs_fifoops", "vop"
|
||||
)
|
||||
base_vfs_slots = initializer(base["super.c"], "static struct vfsops erofs_vfsops", "vfs")
|
||||
vfs_slots = initializer(current["super.c"], "static struct vfsops erofs_vfsops", "vfs")
|
||||
expected_vfs_slots = [
|
||||
(slot, "erofs_vget" if slot == "vfs_vget" else target)
|
||||
for slot, target in base_vfs_slots
|
||||
]
|
||||
if vnode_slots != base_vnode_slots or fifo_slots != base_fifo_slots:
|
||||
raise SystemExit("FreeBSD KOBJ VOP slot order or targets changed")
|
||||
if vfs_slots != expected_vfs_slots:
|
||||
raise SystemExit("VFS slots changed beyond direct erofs_vget registration")
|
||||
if ("vop_lookup", "vfs_cache_lookup") not in vnode_slots or (
|
||||
"vop_cachedlookup", "erofs_lookup"
|
||||
) not in vnode_slots:
|
||||
raise SystemExit("FreeBSD namecache slots were not preserved")
|
||||
if "erofs_vgetf" in current["super.c"]:
|
||||
raise SystemExit("trivial erofs_vgetf wrapper remains")
|
||||
|
||||
vget = extract_function(current["erofs_vnops.c"], "erofs_vget")
|
||||
require_order(
|
||||
vget,
|
||||
[
|
||||
"td = curthread;",
|
||||
"nid = (uint64_t)ino;",
|
||||
"shared = (flags & LK_TYPE_MASK) == LK_SHARED;",
|
||||
"hash = erofs_vfs_hash(nid);",
|
||||
"error = vfs_hash_get(",
|
||||
"if (error != 0 || *vpp != NULL)",
|
||||
"sbi = MTOE(mp);",
|
||||
"vi = malloc(sizeof(*vi), M_EROFS, M_WAITOK | M_ZERO);",
|
||||
"error = getnewvnode(\"erofs\", mp, &erofs_vnodeops, &vp);",
|
||||
"vp->v_data = vi;",
|
||||
"vi->nid = nid;",
|
||||
"lockmgr(vp->v_vnlock, LK_EXCLUSIVE, NULL);",
|
||||
"error = insmntque(vp, mp);",
|
||||
"error = vfs_hash_insert(",
|
||||
"if (error != 0 || *vpp != NULL)",
|
||||
"error = erofs_read_inode(sbi, nid, vi);",
|
||||
"vgone(vp);",
|
||||
"vput(vp);",
|
||||
"erofs_fill_vnode(sbi, vp, vi);",
|
||||
"vn_set_state(vp, VSTATE_CONSTRUCTED);",
|
||||
"if (shared)",
|
||||
"VOP_LOCK(vp, LK_DOWNGRADE);",
|
||||
"*vpp = vp;",
|
||||
],
|
||||
"erofs_vget lifecycle",
|
||||
)
|
||||
if vget.count("erofs_vfs_hash_cmp") != 2:
|
||||
raise SystemExit("vget no longer uses one comparator at both hash callsites")
|
||||
if vget.count("free(vi, M_EROFS);") != 2 or vget.count("*vpp = NULL;") != 3:
|
||||
raise SystemExit("vget allocation/insmntque/read failure cleanup changed")
|
||||
if vget.count("vgone(vp);") != 1 or vget.count("vput(vp);") != 1:
|
||||
raise SystemExit("vget failed-inode vnode cleanup changed")
|
||||
if re.search(r"return\s*\(\s*-", vget):
|
||||
raise SystemExit("negative errno entered the FreeBSD vget path")
|
||||
|
||||
reclaim = extract_function(current["erofs_vnops.c"], "erofs_reclaim")
|
||||
if reclaim != extract_function(base["erofs_vnops.c"], "erofs_reclaim"):
|
||||
raise SystemExit("reclaim/hash removal semantics changed")
|
||||
require_order(
|
||||
reclaim,
|
||||
["vi = VTOE(vp);", "vfs_hash_remove(vp);", "free(vi, M_EROFS);", "vp->v_data = NULL;"],
|
||||
"erofs_reclaim lifecycle",
|
||||
)
|
||||
|
||||
open_body = extract_function(current["erofs_vnops.c"], "erofs_open")
|
||||
if open_body.count("vnode_create_vobject(vp, vi->size, ap->a_td);") != 1:
|
||||
raise SystemExit("regular-vnode pager setup is not a single direct call")
|
||||
if "ENOMEM" in open_body or "vnode_create_vobject" not in open_body:
|
||||
raise SystemExit("dead pager errno folding remains")
|
||||
|
||||
slot_report = {
|
||||
"status": "PASS",
|
||||
"vnode": [{"slot": slot, "target": target} for slot, target in vnode_slots],
|
||||
"fifo": [{"slot": slot, "target": target} for slot, target in fifo_slots],
|
||||
"vfs": [{"slot": slot, "target": target} for slot, target in vfs_slots],
|
||||
}
|
||||
(artifacts / "B09-vop-slots.json").write_text(
|
||||
json.dumps(slot_report, ensure_ascii=True, indent=2, sort_keys=True) + "\n",
|
||||
encoding="ascii",
|
||||
)
|
||||
|
||||
callgraph = [
|
||||
"VFS root -> erofs_vget",
|
||||
"namecache cachedlookup -> erofs_lookup -> erofs_vget",
|
||||
"VFS vget slot -> erofs_vget",
|
||||
"erofs_vget -> vfs_hash_get[erofs_vfs_hash_cmp]",
|
||||
"erofs_vget -> getnewvnode -> LK_EXCLUSIVE -> insmntque",
|
||||
"erofs_vget -> vfs_hash_insert[erofs_vfs_hash_cmp]",
|
||||
"erofs_vget -> erofs_read_inode -> erofs_fill_vnode",
|
||||
"erofs_vget -> VSTATE_CONSTRUCTED -> optional LK_DOWNGRADE",
|
||||
"erofs_reclaim -> vfs_hash_remove -> free inode",
|
||||
]
|
||||
(artifacts / "B09-vget-callgraph.txt").write_text(
|
||||
"\n".join(callgraph) + "\n", encoding="ascii"
|
||||
)
|
||||
|
||||
result = {
|
||||
"status": "PASS",
|
||||
"final_ids": ["P15-008", "P15-050", "P15-088"],
|
||||
"baseline": baseline,
|
||||
"moved_functions": list(moved_functions),
|
||||
"moved_functions_byte_identical": True,
|
||||
"vnode_slots": len(vnode_slots),
|
||||
"fifo_slots": len(fifo_slots),
|
||||
"vfs_slots": len(vfs_slots),
|
||||
"hash_comparator_callsites": 2,
|
||||
"exclusive_construct_lock": True,
|
||||
"shared_result_downgrade": True,
|
||||
"insmntque_cleanup_preserved": True,
|
||||
"reclaim_hash_removal_preserved": True,
|
||||
"namecache_slots_preserved": True,
|
||||
"positive_errno_preserved": True,
|
||||
"b11_b15_scope": "NOT_INCLUDED",
|
||||
"qemu": "NOT_RUN",
|
||||
"full_feature_suite": "NOT_RUN",
|
||||
}
|
||||
(artifacts / "B09-result.json").write_text(
|
||||
json.dumps(result, ensure_ascii=True, indent=2, sort_keys=True) + "\n",
|
||||
encoding="ascii",
|
||||
)
|
||||
print(
|
||||
f"B09 PASS moved={len(moved_functions)} "
|
||||
f"slots={len(vnode_slots)}+{len(fifo_slots)}/{len(vfs_slots)} hash=2"
|
||||
)
|
||||
PY
|
||||
then
|
||||
:
|
||||
else
|
||||
pre15_dut_fail 'B09 vnode ownership, VOP slots, or vget graph check failed'
|
||||
fi
|
||||
|
||||
sha256sum "$artifacts/B09-vop-slots.json" \
|
||||
"$artifacts/B09-vget-callgraph.txt" "$artifacts/B09-result.json" \
|
||||
> "$artifacts/SHA256SUMS"
|
||||
printf 'B09 PASS vnode adapter ownership and FreeBSD lifecycle preserved\n'
|
||||
Reference in New Issue
Block a user