#!/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(" int: return struct.unpack_from(" int: return struct.unpack_from(" None: struct.pack_into(" None: struct.pack_into(" 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(" 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())