Files
erofs-freebsd-out-tree/tests/pre15/cases/B02-layout.sh
T
2026-08-18 09:20:44 +02:00

320 lines
11 KiB
Bash
Executable File

#!/bin/sh
set -eu
: "${PRE15_DUT:?PRE15_DUT is required}"
: "${PRE15_ROOT:?PRE15_ROOT is required}"
: "${PRE15_CASE_TMP:?PRE15_CASE_TMP is required}"
: "${PRE15_RUN_DIR:?PRE15_RUN_DIR is required}"
: "${PRE15_FREEBSD_SRC:?PRE15_FREEBSD_SRC is required}"
: "${PRE15_LIB_DIR:?PRE15_LIB_DIR is required}"
. "$PRE15_LIB_DIR/runner.sh"
probe=$PRE15_DUT/tests/pre15/probes/ondisk_layout.c
freebsd_header=$PRE15_DUT/src/erofs_fs.h
internal_header=$PRE15_DUT/src/internal.h
linux_header=$PRE15_ROOT/src-linux/erofs_fs.h
sys=$PRE15_FREEBSD_SRC/sys
target=x86_64-unknown-freebsd15.0
artifacts=$PRE15_RUN_DIR/artifacts
include_root=$PRE15_CASE_TMP/include
for tool in clang python3 sha256sum; do
command -v "$tool" >/dev/null 2>&1 || \
pre15_infra_blocked "missing B02 host tool: $tool"
done
test -d "$sys/amd64/include" || \
pre15_infra_blocked "FreeBSD amd64 headers are absent: $sys"
pre15_record_fixture b02-layout-probe "$probe"
pre15_record_fixture b02-freebsd-ondisk-header "$freebsd_header"
pre15_record_fixture b02-freebsd-internal-header "$internal_header"
pre15_record_fixture b02-linux-ondisk-header "$linux_header"
mkdir -p "$artifacts" "$include_root"
ln -s "$sys/amd64/include" "$include_root/machine"
ln -s "$sys/x86/include" "$include_root/x86"
resource_include=$(clang -print-resource-dir)/include
pre15_target_reached
if clang --target="$target" -fsyntax-only -std=gnu17 -Wall -Wextra -Werror \
-nostdinc -isystem "$resource_include" \
-isystem "$PRE15_FREEBSD_SRC/include" -isystem "$include_root" \
-isystem "$sys" -Xclang -fdump-record-layouts "$probe" \
> "$artifacts/freebsd-record-layouts.txt" \
2> "$artifacts/freebsd-layout.stderr"; then
:
else
pre15_dut_fail 'FreeBSD ondisk layout/type/macro oracle failed'
fi
if clang -DB02_LINUX_REFERENCE -fsyntax-only -std=gnu17 \
-Wall -Wextra -Werror -Xclang -fdump-record-layouts "$probe" \
> "$artifacts/linux-record-layouts.txt" \
2> "$artifacts/linux-layout.stderr"; then
:
else
pre15_dut_fail 'Linux reference layout/type/macro oracle failed'
fi
clang --target="$target" -E -dM -std=gnu17 -nostdinc \
-isystem "$resource_include" -isystem "$PRE15_FREEBSD_SRC/include" \
-isystem "$include_root" -isystem "$sys" "$probe" | \
awk '$2 ~ /^(EROFS_|Z_EROFS_)/ { print }' | LC_ALL=C sort \
> "$artifacts/freebsd-macros.txt"
clang -DB02_LINUX_REFERENCE -E -dM -std=gnu17 "$probe" | \
awk '$2 ~ /^(EROFS_|Z_EROFS_)/ { print }' | LC_ALL=C sort \
> "$artifacts/linux-macros.txt"
if python3 - "$PRE15_ROOT" "$artifacts/static-check.json" <<'PY'
from __future__ import annotations
import json
from pathlib import Path
import re
import sys
root = Path(sys.argv[1])
output = Path(sys.argv[2])
dut = root / "repo-pre-15"
src = dut / "src"
ondisk = (src / "erofs_fs.h").read_text(encoding="utf-8")
internal = (src / "internal.h").read_text(encoding="utf-8")
linux = (root / "src-linux/erofs_fs.h").read_text(encoding="utf-8")
def require_once(source: str, token: str) -> None:
count = source.count(token)
if count != 1:
raise SystemExit(f"expected one occurrence of {token!r}, found {count}")
def require_order(source: str, tokens: tuple[str, ...], label: str) -> None:
positions = []
for token in tokens:
require_once(source, token)
positions.append(source.index(token))
if positions != sorted(positions):
raise SystemExit(f"{label} section order mismatch: {tokens!r}")
ondisk_markers = (
"struct erofs_deviceslot {",
"struct erofs_super_block {",
"EROFS_INODE_CHUNK_BASED",
"#define EROFS_CHUNK_FORMAT_BLKBITS_MASK",
"#define EROFS_INODE_LAYOUT_COMPACT",
"struct erofs_inode_chunk_info {",
"struct erofs_inode_compact {",
"struct erofs_inode_extended {",
"struct erofs_xattr_ibody_header {",
"struct erofs_xattr_entry {",
"struct erofs_xattr_long_prefix {",
"#define EROFS_NULL_ADDR",
"struct erofs_inode_chunk_index {",
"#define EROFS_DIRENT_NID_METABOX_BIT",
"struct erofs_dirent {",
"#define EROFS_NAME_LEN",
"struct z_erofs_lz4_cfgs {",
"struct z_erofs_map_header {",
"Z_EROFS_LCLUSTER_TYPE_PLAIN",
"struct z_erofs_lcluster_index {",
"struct z_erofs_extent {",
)
require_order(ondisk, ondisk_markers, "FreeBSD ondisk")
require_order(linux, ondisk_markers, "Linux ondisk")
aliases = {
"EROFS_INODE_LAYOUT_COMPACT": "0",
"EROFS_INODE_LAYOUT_EXTENDED": "1",
"EROFS_INODE_LAYOUT_PLAIN": "EROFS_INODE_FLAT_PLAIN",
}
for name, value in aliases.items():
pattern = rf"^#define[ \t]+{name}[ \t]+{value}$"
if len(re.findall(pattern, ondisk, re.MULTILINE)) != 1:
raise SystemExit(f"layout alias mismatch: {name}")
if not re.search(
r"^#define[ \t]+EROFS_DEVT_SLOT_SIZE[ \t]+"
r"sizeof\(struct erofs_deviceslot\)$",
ondisk,
re.MULTILINE,
):
raise SystemExit("EROFS_DEVT_SLOT_SIZE is not derived from its record")
if re.search(r"^#if.*EROFS_DEVT_SLOT_SIZE", ondisk, re.MULTILINE):
raise SystemExit("sizeof-based EROFS_DEVT_SLOT_SIZE is used in #if")
internal_markers = (
"struct erofs_mount_opts {",
"struct erofs_sb_lz4_info {",
"struct erofs_device_info {",
"struct erofs_xattr_prefix_item {",
"struct erofs_zextent_cache {",
"struct erofs_mount {",
"#define EROFS_FEATURE_FUNCS",
"EROFS_FEATURE_FUNCS(lz4_0padding",
"struct erofs_node {",
"struct erofs_fid {",
"erofs_inode_version(unsigned int ifmt)",
"#define EROFS_MAP_MAPPED",
"struct erofs_map_blocks {",
"struct erofs_map_dev {",
"/* Buffer and device I/O. */",
"/* Logical mapping and file data. */",
"/* Inode and vnode lifecycle. */",
"/* Directory operations. */",
"/* Compressed mapping and data. */",
"/* Compression configuration. */",
"/* VOP vectors. */",
)
require_order(internal, internal_markers, "FreeBSD internal")
if re.search(r"return[ \t]+-E[A-Z0-9_]+", internal):
raise SystemExit("Linux negative-errno convention entered FreeBSD internal.h")
vnops = (src / "erofs_vnops.c").read_text(encoding="utf-8")
super_source = (src / "super.c").read_text(encoding="utf-8")
inode = (src / "inode.c").read_text(encoding="utf-8")
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,
)
vnode = initializer(vnops, "struct vop_vector erofs_vnodeops", "vop")
fifo = initializer(vnops, "struct vop_vector erofs_fifoops", "vop")
vfs = initializer(super_source, "static struct vfsops erofs_vfsops", "vfs")
expected_vnode = [
("vop_default", "&default_vnodeops"),
("vop_inactive", "erofs_inactive"),
("vop_reclaim", "erofs_reclaim"),
("vop_lookup", "vfs_cache_lookup"),
("vop_cachedlookup", "erofs_lookup"),
("vop_readdir", "erofs_readdir"),
("vop_readlink", "erofs_readlink"),
("vop_open", "erofs_open"),
("vop_read", "erofs_read"),
("vop_bmap", "erofs_bmap"),
("vop_getpages", "vnode_pager_local_getpages"),
("vop_getpages_async", "vnode_pager_local_getpages_async"),
("vop_getattr", "erofs_getattr"),
("vop_setattr", "erofs_setattr"),
("vop_access", "erofs_access"),
("vop_pathconf", "erofs_pathconf"),
("vop_getextattr", "erofs_getextattr"),
("vop_listextattr", "erofs_listextattr"),
("vop_deleteextattr", "erofs_deleteextattr"),
("vop_setextattr", "erofs_setextattr"),
("vop_getacl", "erofs_vop_getacl"),
("vop_aclcheck", "erofs_aclcheck"),
("vop_setacl", "erofs_setacl"),
("vop_vptofh", "erofs_vptofh"),
]
expected_fifo = [
("vop_default", "&fifo_specops"),
("vop_access", "erofs_access"),
("vop_aclcheck", "erofs_aclcheck"),
("vop_deleteextattr", "erofs_deleteextattr"),
("vop_getacl", "erofs_vop_getacl"),
("vop_getextattr", "erofs_getextattr"),
("vop_getattr", "erofs_getattr"),
("vop_listextattr", "erofs_listextattr"),
("vop_pathconf", "erofs_pathconf"),
("vop_reclaim", "erofs_reclaim"),
("vop_setacl", "erofs_setacl"),
("vop_setattr", "erofs_setattr"),
("vop_setextattr", "erofs_setextattr"),
("vop_vptofh", "erofs_vptofh"),
]
expected_vfs = [
("vfs_fhtovp", "erofs_fhtovp"),
("vfs_mount", "erofs_mount"),
("vfs_root", "erofs_root"),
("vfs_statfs", "erofs_statfs"),
("vfs_unmount", "erofs_unmount"),
("vfs_vget", "erofs_vgetf"),
]
if vnode != expected_vnode or fifo != expected_fifo or vfs != expected_vfs:
raise SystemExit("VOP/VFS callback targets changed")
hash_calls = re.findall(
r"vfs_hash_(?:get|insert)\s*\([^;]*?erofs_vfs_hash_cmp", inode, re.DOTALL
)
if len(hash_calls) != 2:
raise SystemExit(f"hash comparator callsites changed: {len(hash_calls)}")
decompressors = []
for path in sorted(src.glob("decompressor*.c")):
for line in path.read_text(encoding="utf-8").splitlines():
match = re.match(r"\s*\.(config|decompress)\s*=\s*([^,]+),", line)
if match:
decompressors.append((path.name, match.group(1), match.group(2)))
expected_decompressors = [
("decompressor.c", "decompress", "z_erofs_transform_plain"),
("decompressor.c", "decompress", "z_erofs_transform_plain"),
("decompressor.c", "config", "z_erofs_load_lz4_config"),
("decompressor.c", "decompress", "z_erofs_lz4_decompress"),
("decompressor_deflate.c", "config", "z_erofs_load_deflate_config"),
("decompressor_deflate.c", "decompress", "z_erofs_deflate_decompress"),
("decompressor_lzma.c", "config", "z_erofs_load_lzma_config"),
("decompressor_lzma.c", "decompress", "z_erofs_lzma_decompress"),
("decompressor_zstd.c", "config", "z_erofs_load_zstd_config"),
("decompressor_zstd.c", "decompress", "z_erofs_zstd_decompress"),
]
if decompressors != expected_decompressors:
raise SystemExit("decompressor callback targets changed")
result = {
"status": "PASS",
"source_symbol_denominator": {
"baseline_physical": 464,
"baseline_unique": 389,
"b02_macro_delta": 3,
"expected_physical": 467,
"expected_unique": 392,
"added_names": sorted(aliases),
"kld_symbol_delta": 0,
},
"callbacks": {
"vnode_slots": len(vnode),
"fifo_slots": len(fifo),
"vfs_slots": len(vfs),
"hash_comparator_callsites": len(hash_calls),
"decompressor_fields": len(decompressors),
},
"freebsd_adaptations": [
"packed ondisk records",
"little-endian typedefs",
"explicit supported incompat mask",
"FreeBSD VOP/VFS types",
"positive errno convention",
],
}
with output.open("x", encoding="ascii") as stream:
json.dump(result, stream, ensure_ascii=True, indent=2, sort_keys=True)
stream.write("\n")
print(
"static PASS source-symbols=464+3 callbacks=24+14/6 hash=2 decompress=10"
)
PY
then
:
else
pre15_dut_fail 'header order, symbol denominator, or callback oracle failed'
fi
sha256sum \
"$artifacts/freebsd-record-layouts.txt" \
"$artifacts/linux-record-layouts.txt" \
"$artifacts/freebsd-macros.txt" \
"$artifacts/linux-macros.txt" \
"$artifacts/static-check.json" \
> "$artifacts/SHA256SUMS"
printf 'layout PASS FreeBSD and Linux size/offset/alignment/type/macro oracles\n'