#!/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'