update
This commit is contained in:
Executable
+150
@@ -0,0 +1,150 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
|
||||
EXTENT_RE = re.compile(
|
||||
r"^\s*0:\s*(\d+)\.\.\s*(\d+)\s*\|\s*(\d+)\s*:\s*"
|
||||
r"(\d+)\.\.\s*(\d+)\s*\|\s*(\d+)\s*$",
|
||||
re.MULTILINE,
|
||||
)
|
||||
|
||||
|
||||
def sha256_path(path: Path) -> str:
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def run(argv: list[str], cwd: Path) -> str:
|
||||
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}"
|
||||
)
|
||||
return 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") != "B33"
|
||||
or spec.get("test") != "TC168-cache-inflight"
|
||||
):
|
||||
raise SystemExit("invalid B33 cache fixture spec")
|
||||
if args.output.exists():
|
||||
raise SystemExit(f"refusing existing B33 output: {args.output}")
|
||||
for tool in ("dump.erofs", "mkfs.erofs"):
|
||||
if shutil.which(tool) is None:
|
||||
raise SystemExit(f"{tool} is required")
|
||||
|
||||
pattern = b"Pre15-B33-cache-policy:0123456789abcdef\n"
|
||||
expected = spec["source"]
|
||||
source = (pattern * ((expected["size"] + len(pattern) - 1) // len(pattern)))[
|
||||
: expected["size"]
|
||||
]
|
||||
if hashlib.sha256(source).hexdigest() != expected["sha256"]:
|
||||
raise SystemExit("B33 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 / "lz4-valid.erofs"
|
||||
run(["mkfs.erofs", *spec["mkfs_args"], str(valid), str(source_dir)], images)
|
||||
extent_text = run(
|
||||
["dump.erofs", "-e", "--path=/payload.bin", str(valid)], images
|
||||
)
|
||||
match = EXTENT_RE.search(extent_text)
|
||||
if match is None:
|
||||
raise SystemExit("B33 single extent could not be parsed")
|
||||
logical, logical_end, logical_length, physical, physical_end, physical_length = (
|
||||
map(int, match.groups())
|
||||
)
|
||||
if (
|
||||
logical != 0
|
||||
or logical_end != expected["size"]
|
||||
or logical_length != expected["size"]
|
||||
or physical_end - physical != physical_length
|
||||
or physical_length != 4096
|
||||
):
|
||||
raise SystemExit("B33 fixture is not one 64 KiB logical pcluster")
|
||||
|
||||
valid_data = valid.read_bytes()
|
||||
if physical + physical_length > len(valid_data):
|
||||
raise SystemExit("B33 physical extent exceeds image")
|
||||
pcluster = valid_data[physical : physical + physical_length]
|
||||
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("B33 LZ4 stream boundary is empty")
|
||||
truncated_data = bytearray(valid_data)
|
||||
truncated_data[physical + leading : physical + physical_length] = (
|
||||
b"\0" + stream[:-1]
|
||||
)
|
||||
if truncated_data == valid_data:
|
||||
raise SystemExit("B33 truncated mutation changed no bytes")
|
||||
truncated = images / "lz4-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": "B33",
|
||||
"decoded_size": len(source),
|
||||
"fixture_count": len(records),
|
||||
"fixtures": records,
|
||||
"leading_zero_bytes": leading,
|
||||
"physical_length": physical_length,
|
||||
"physical_offset": physical,
|
||||
"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()
|
||||
Reference in New Issue
Block a user