126 lines
3.8 KiB
Python
Executable File
126 lines
3.8 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import shutil
|
|
import subprocess
|
|
|
|
|
|
def sha256_bytes(data: bytes) -> str:
|
|
return hashlib.sha256(data).hexdigest()
|
|
|
|
|
|
def sha256_path(path: Path) -> str:
|
|
return sha256_bytes(path.read_bytes())
|
|
|
|
|
|
def run(argv: list[str], cwd: Path) -> None:
|
|
completed = subprocess.run(
|
|
argv,
|
|
cwd=cwd,
|
|
check=False,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT,
|
|
text=True,
|
|
timeout=120,
|
|
)
|
|
if completed.returncode != 0:
|
|
raise SystemExit(
|
|
f"command failed ({completed.returncode}): {' '.join(argv)}\n"
|
|
f"{completed.stdout}"
|
|
)
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--spec", type=Path, required=True)
|
|
parser.add_argument("--output", type=Path, required=True)
|
|
args = parser.parse_args()
|
|
spec = json.loads(args.spec.read_text(encoding="ascii"))
|
|
if (
|
|
spec.get("schema") != 1
|
|
or spec.get("batch") != "B32"
|
|
or spec.get("test") != "TC168-cache-inflight"
|
|
):
|
|
raise SystemExit("invalid B32 cache fixture spec")
|
|
if args.output.exists():
|
|
raise SystemExit(f"refusing existing B32 output: {args.output}")
|
|
if shutil.which("mkfs.erofs") is None:
|
|
raise SystemExit("mkfs.erofs is required")
|
|
|
|
source = (b"Pre15-B32-cache-state:0123456789abcdef\n" * 2000)[
|
|
: spec["source"]["size"]
|
|
]
|
|
if sha256_bytes(source) != spec["source"]["sha256"]:
|
|
raise SystemExit("B32 deterministic source identity changed")
|
|
|
|
source_dir = args.output / "source"
|
|
images = args.output / "images"
|
|
source_dir.mkdir(parents=True)
|
|
images.mkdir()
|
|
payload = source_dir / "payload.bin"
|
|
payload.write_bytes(source)
|
|
os.utime(payload, (0, 0))
|
|
os.utime(source_dir, (0, 0))
|
|
|
|
valid = images / "lzma-valid.erofs"
|
|
run(["mkfs.erofs", *spec["mkfs_args"], str(valid), str(source_dir)], images)
|
|
valid_data = valid.read_bytes()
|
|
if len(valid_data) != 8192:
|
|
raise SystemExit(f"unexpected B32 image size: {len(valid_data)}")
|
|
pcluster_start = 4096
|
|
pcluster_end = 8192
|
|
pcluster = valid_data[pcluster_start:pcluster_end]
|
|
leading = next(
|
|
(index for index, value in enumerate(pcluster) if value), len(pcluster)
|
|
)
|
|
stream = pcluster[leading:]
|
|
if leading == len(pcluster) or len(stream) < 2:
|
|
raise SystemExit("B32 LZMA stream boundary is empty")
|
|
|
|
truncated_data = bytearray(valid_data)
|
|
truncated_data[pcluster_start + leading : pcluster_end] = b"\0" + stream[:-1]
|
|
if truncated_data == valid_data:
|
|
raise SystemExit("B32 truncated mutation changed no bytes")
|
|
truncated = images / "lzma-truncated.erofs"
|
|
truncated.write_bytes(truncated_data)
|
|
|
|
records = []
|
|
for image_class, path, expected_errno in (
|
|
("valid", valid, 0),
|
|
("truncated", truncated, 97),
|
|
):
|
|
records.append(
|
|
{
|
|
"class": image_class,
|
|
"expected_errno": expected_errno,
|
|
"path": path.name,
|
|
"sha256": sha256_path(path),
|
|
}
|
|
)
|
|
index = {
|
|
"batch": "B32",
|
|
"decoded_size": len(source),
|
|
"fixture_count": len(records),
|
|
"fixtures": records,
|
|
"leading_zero_bytes": leading,
|
|
"physical_length": len(pcluster),
|
|
"physical_offset": pcluster_start,
|
|
"schema": 1,
|
|
"source_sha256": sha256_path(payload),
|
|
"status": "READY",
|
|
"test": "TC168-cache-inflight",
|
|
}
|
|
(args.output / "fixture-index.json").write_text(
|
|
json.dumps(index, indent=2, sort_keys=True) + "\n", encoding="ascii"
|
|
)
|
|
print(json.dumps(index, indent=2, sort_keys=True))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|