This commit is contained in:
2026-08-18 09:20:44 +02:00
commit b826cd721a
522 changed files with 93730 additions and 0 deletions
+276
View File
@@ -0,0 +1,276 @@
#!/usr/bin/env python3
"""Generate deterministic B21 superblock trust-order fixtures."""
from __future__ import annotations
import argparse
import hashlib
import json
import os
from pathlib import Path
import shutil
import struct
import subprocess
SUPER = 1024
FEATURE_COMPAT_SB_CHKSUM = 0x00000001
FEATURE_INCOMPAT_XATTR_PREFIXES = 0x00000040
UNKNOWN_INCOMPAT = 0x80000000
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 sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as stream:
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def write_json(path: Path, value: object) -> None:
path.write_text(
json.dumps(value, indent=2, sort_keys=True) + "\n", encoding="ascii"
)
class Image:
def __init__(self, data: bytes | bytearray):
self.data = bytearray(data)
if len(self.data) < SUPER + 144:
raise ValueError("image is shorter than the extended superblock")
if self.u32(SUPER) != 0xE0F5E1E2:
raise ValueError("image has the wrong EROFS magic")
if not self.u32(SUPER + 8) & FEATURE_COMPAT_SB_CHKSUM:
raise ValueError("image does not declare a superblock checksum")
@classmethod
def load(cls, path: Path) -> "Image":
return cls(path.read_bytes())
def clone(self) -> "Image":
return Image(self.data)
def u16(self, offset: int) -> int:
return struct.unpack_from("<H", self.data, offset)[0]
def u32(self, offset: int) -> int:
return struct.unpack_from("<I", self.data, offset)[0]
def u64(self, offset: int) -> int:
return struct.unpack_from("<Q", self.data, offset)[0]
def put_u16(self, offset: int, value: int) -> None:
struct.pack_into("<H", self.data, offset, value)
def put_u32(self, offset: int, value: int) -> None:
struct.pack_into("<I", self.data, offset, value)
@property
def block_bits(self) -> int:
return self.data[SUPER + 12]
@property
def block_size(self) -> int:
return 1 << self.block_bits
@property
def checksum_end(self) -> int:
span = self.block_size
if span > SUPER:
span -= SUPER
return SUPER + span
def checksum_valid(self) -> bool:
expected = self.u32(SUPER + 4)
canonical = bytearray(self.data[SUPER : self.checksum_end])
struct.pack_into("<I", canonical, 4, 0)
kernel = crc32c(self.data[SUPER + 8 : self.checksum_end], 0x5045B54A)
return expected == crc32c(canonical) == kernel
def update_checksum(self) -> None:
self.put_u32(SUPER + 4, 0)
checksum = crc32c(self.data[SUPER : self.checksum_end])
self.put_u32(SUPER + 4, checksum)
if not self.checksum_valid():
raise AssertionError("updated B21 checksum does not verify")
def inode_offset(self, nid: int) -> int:
return (self.u32(SUPER + 40) << self.block_bits) + (nid << 5)
def fix_packed_inode_mode(self) -> None:
packed_nid = self.u64(SUPER + 96)
if packed_nid == 0:
raise ValueError("B21 seed did not materialize a packed prefix carrier")
self.put_u16(self.inode_offset(packed_nid) + 4, 0o100644)
def load_spec(path: Path) -> dict:
spec = json.loads(path.read_text(encoding="ascii"))
if spec.get("schema") != 1 or spec.get("batch") != "B21":
raise ValueError("invalid B21 fixture spec identity")
if not isinstance(spec.get("cases"), list) or not spec["cases"]:
raise ValueError("B21 fixture spec has no cases")
return spec
def create_source(root: Path) -> None:
root.mkdir(parents=True)
control = root / "control.txt"
prefix = root / "prefix.bin"
control.write_bytes(b"B21 superblock control\n")
prefix.write_bytes(b"B21 prefix control\n")
control.chmod(0o644)
prefix.chmod(0o644)
os.setxattr(prefix, b"user.company.branch.leaf", b"B21-prefix-value")
for path in (control, prefix, root):
os.utime(path, (0, 0), follow_symlinks=False)
def build_seed(spec: dict, output: Path, work: Path) -> dict[str, object]:
mkfs = shutil.which("mkfs.erofs")
if mkfs is None:
raise SystemExit("mkfs.erofs is required for B21 fixtures")
version_run = subprocess.run(
[mkfs, "-V"], check=False, text=True,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
)
version = version_run.stdout.splitlines()[0] if version_run.stdout else ""
if version != spec["seed"]["mkfs_version"]:
raise SystemExit(
f"mkfs.erofs version differs: expected {spec['seed']['mkfs_version']!r}, "
f"got {version!r}"
)
source = work / "source"
create_source(source)
command = [
mkfs,
"-d0",
"-T0",
"--all-time",
"--all-root",
"--workers=1",
"--sort=path",
f"-U{spec['seed']['uuid']}",
"-x2",
"-Eforce-inode-extended",
"--xattr-prefix=user.company.",
str(output),
str(source),
]
completed = subprocess.run(
command, check=False, text=True,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
)
if completed.returncode != 0:
raise SystemExit(
f"mkfs.erofs failed with {completed.returncode}:\n{completed.stdout}"
)
image = Image.load(output)
image.fix_packed_inode_mode()
image.update_checksum()
output.write_bytes(image.data)
if not image.u32(SUPER + 80) & FEATURE_INCOMPAT_XATTR_PREFIXES:
raise ValueError("B21 seed does not declare xattr prefixes")
return {"version": version}
def mutate(image: Image, mutation: str) -> list[dict[str, int | str]]:
changes: list[dict[str, int | str]] = []
def record(field: str, offset: int, size: int) -> None:
changes.append({"field": field, "offset": offset, "size": size})
if mutation == "none":
return changes
if mutation in {"unknown-feature", "feature-prefix"}:
offset = SUPER + 80
image.put_u32(offset, image.u32(offset) | UNKNOWN_INCOMPAT)
record("feature_incompat", offset, 4)
if mutation == "blocks":
offset = SUPER + 36
image.put_u32(offset, 0xFFFFFFFF)
record("blocks_lo", offset, 4)
elif mutation == "extslots":
offset = SUPER + 13
image.data[offset] = 0xFF
record("sb_extslots", offset, 1)
elif mutation == "dirblk":
offset = SUPER + 90
image.data[offset] = 1
record("dirblkbits", offset, 1)
elif mutation in {"prefix-offset", "feature-prefix"}:
offset = SUPER + 92
image.put_u32(offset, 0xFFFFFFFF)
record("xattr_prefix_start", offset, 4)
elif mutation != "unknown-feature":
raise ValueError(f"unknown B21 mutation: {mutation}")
return changes
def generate(spec_path: Path, output: Path, work: Path) -> int:
spec = load_spec(spec_path)
if output.exists() or work.exists():
raise SystemExit("B21 output and work paths must not already exist")
output.mkdir(parents=True)
work.mkdir(parents=True)
seed_path = work / "seed.erofs"
build = build_seed(spec, seed_path, work / "seed-work")
seed = Image.load(seed_path)
seed_hash = sha256(seed_path)
cases = []
for item in spec["cases"]:
image = seed.clone()
changes = mutate(image, item["mutation"])
if item["authenticated"] and item["mutation"] != "none":
image.update_checksum()
path = output / f"{item['id']}.erofs"
path.write_bytes(image.data)
checksum_valid = image.checksum_valid()
if checksum_valid != item["authenticated"]:
raise AssertionError(f"checksum state mismatch for {item['id']}")
cases.append(
{
**item,
"changes": changes,
"checksum_valid": checksum_valid,
"filename": path.name,
"sha256": sha256(path),
"size": path.stat().st_size,
}
)
index = {
"batch": "B21",
"build": build,
"case_count": len(cases),
"cases": cases,
"damaged_count": sum(item["class"] == "damaged" for item in cases),
"legal_count": sum(item["class"] == "legal" for item in cases),
"schema": 1,
"seed_sha256": seed_hash,
}
write_json(output / "fixture-index.json", index)
print(json.dumps({"status": "PASS", **index}, sort_keys=True))
return 0
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--spec", type=Path, required=True)
parser.add_argument("--output", type=Path, required=True)
parser.add_argument("--work", type=Path, required=True)
args = parser.parse_args()
return generate(args.spec, args.output, args.work)
if __name__ == "__main__":
raise SystemExit(main())