Files
erofs-freebsd-out-tree/tests/pre15/fixtures/B21-super-oracle.py
T
2026-08-18 09:20:44 +02:00

136 lines
4.1 KiB
Python

#!/usr/bin/env python3
"""Independent B21 superblock ordering oracle."""
from __future__ import annotations
import argparse
import json
from pathlib import Path
import struct
SUPER = 1024
PAGE_SHIFT = 12
FEATURE_COMPAT_SB_CHKSUM = 0x00000001
FEATURE_INCOMPAT_ALL = 0x000001FF
FEATURE_INCOMPAT_48BIT = 0x00000080
FEATURE_INCOMPAT_XATTR_PREFIXES = 0x00000040
CRC32C_POLY = 0x82F63B78
def crc32c(data: bytes | bytearray, seed: int = 0xFFFFFFFF) -> int:
value = seed
for byte in data:
value ^= byte
for _ in range(8):
value = (value >> 1) ^ (CRC32C_POLY if value & 1 else 0)
return value & 0xFFFFFFFF
def u16(data: bytes, offset: int) -> int:
return struct.unpack_from("<H", data, offset)[0]
def u32(data: bytes, offset: int) -> int:
return struct.unpack_from("<I", data, offset)[0]
def u64(data: bytes, offset: int) -> int:
return struct.unpack_from("<Q", data, offset)[0]
def checksum_valid(data: bytes) -> bool:
block_bits = data[SUPER + 12]
if not 9 <= block_bits <= PAGE_SHIFT:
return False
span = 1 << block_bits
if span > SUPER:
span -= SUPER
end = SUPER + span
expected = u32(data, SUPER + 4)
return expected == crc32c(data[SUPER + 8 : end], 0x5045B54A)
def classify(data: bytes) -> tuple[int, str]:
if u32(data, SUPER) != 0xE0F5E1E2:
return 22, "magic"
block_bits = data[SUPER + 12]
if not 9 <= block_bits <= PAGE_SHIFT:
return 22, "block-size"
feature_compat = u32(data, SUPER + 8)
if feature_compat & FEATURE_COMPAT_SB_CHKSUM and not checksum_valid(data):
return 97, "checksum"
if data[SUPER + 90] != 0:
return 45, "dirblkbits"
feature_incompat = u32(data, SUPER + 80)
if feature_incompat & ~FEATURE_INCOMPAT_ALL:
if u32(data, SUPER + 92) == 0xFFFFFFFF:
return 45, "feature-before-prefix"
return 45, "feature-incompat"
if 128 + data[SUPER + 13] * 16 > (1 << PAGE_SHIFT) - SUPER:
return 22, "super-extension"
blocks = u32(data, SUPER + 36)
root_nid_8b = u64(data, SUPER + 112)
if feature_incompat & FEATURE_INCOMPAT_48BIT and root_nid_8b != 0:
blocks |= u16(data, SUPER + 14) << 32
if blocks == 0:
return 97, "blocks-zero"
if blocks << block_bits > len(data):
return 6, "provider-size"
if (
feature_incompat & FEATURE_INCOMPAT_XATTR_PREFIXES
and data[SUPER + 91] != 0
and u32(data, SUPER + 92) == 0xFFFFFFFF
):
return 97, "prefix-offset"
return 0, "accepted"
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--fixtures", type=Path, required=True)
parser.add_argument("--report", type=Path, required=True)
args = parser.parse_args()
index = json.loads(
(args.fixtures / "fixture-index.json").read_text(encoding="ascii")
)
results = []
for item in index["cases"]:
path = args.fixtures / item["filename"]
actual_errno, actual_reject = classify(path.read_bytes())
passed = (
actual_errno == item["expected_errno"]
and actual_reject == item["expected_reject"]
)
results.append(
{
"actual_errno": actual_errno,
"actual_reject": actual_reject,
"expected_errno": item["expected_errno"],
"expected_reject": item["expected_reject"],
"id": item["id"],
"passed": passed,
}
)
report = {
"batch": "B21",
"case_count": len(results),
"passed_count": sum(item["passed"] for item in results),
"results": results,
"schema": 1,
"status": "PASS" if all(item["passed"] for item in results) else "FAIL",
}
args.report.write_text(
json.dumps(report, indent=2, sort_keys=True) + "\n", encoding="ascii"
)
for item in results:
print(
f"{'PASS' if item['passed'] else 'FAIL'} {item['id']} "
f"errno={item['actual_errno']} reject={item['actual_reject']}"
)
return 0 if report["status"] == "PASS" else 1
if __name__ == "__main__":
raise SystemExit(main())