初始化奇妙小屏幕控制器项目

This commit is contained in:
2026-09-08 22:56:52 +08:00
commit 8d368de3b5
491 changed files with 67678 additions and 0 deletions
@@ -0,0 +1,279 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
from dataclasses import asdict, dataclass
import gzip
import hashlib
import json
from pathlib import Path, PurePosixPath
import tarfile
from typing import BinaryIO
KERNEL_RELEASE = "6.1.31-matrix-axp313a1"
SOURCE_COMMIT = "30ff3fd5cf45417622b447a12e7a947402ffe34d"
SOURCE_ARCHIVE_NAME = "walnutpi-linux-6.1.31-30ff3fd5.tar.gz"
SOURCE_ARCHIVE_SHA256 = "8659bb3d64313c4693c3605374167a8145186e5c9d43eb771a52a7359699302f"
ARTIFACT_NAME = "axp313a-kernel-6.1.31-matrix-axp313a1.tar.gz"
MAX_REGULAR_FILES = 4_096
MAX_UNPACKED_BYTES = 512 * 1024 * 1024
REQUIRED_BOOT_FILES = {
"boot/Image-matrix-axp313a1",
"boot/sun50i-h616-walnutpi-1b-matrix-axp313a1.dtb",
"boot/sun50i-h616-walnutpi-1b-emmc-matrix-axp313a1.dtb",
f"boot/System.map-{KERNEL_RELEASE}",
f"boot/config-{KERNEL_RELEASE}",
}
class KernelArtifactError(ValueError):
pass
@dataclass(frozen=True)
class KernelArtifactInfo:
artifact: str
artifact_bytes: int
artifact_sha256: str
kernel_release: str
source_commit: str
unpacked_file_bytes: int
regular_file_count: int
module_file_count: int
def sha256_file(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as handle:
for chunk in iter(lambda: handle.read(4 * 1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def _load_json(path: Path) -> dict:
try:
document = json.loads(path.read_text(encoding="utf-8"))
except (OSError, UnicodeError, json.JSONDecodeError) as exc:
raise KernelArtifactError(f"invalid JSON metadata: {path}") from exc
if not isinstance(document, dict):
raise KernelArtifactError(f"metadata must be an object: {path}")
return document
def _verify_outer_sum(directory: Path, expected_name: str) -> tuple[Path, str]:
manifest = directory / "SHA256SUMS"
if not manifest.is_file():
raise KernelArtifactError(f"SHA256SUMS is missing: {directory}")
try:
lines = [line for line in manifest.read_text(encoding="ascii").splitlines() if line]
except (OSError, UnicodeError) as exc:
raise KernelArtifactError(f"SHA256SUMS is unreadable: {directory}") from exc
if len(lines) != 1 or " " not in lines[0]:
raise KernelArtifactError(f"SHA256SUMS must contain exactly one binary: {directory}")
expected_digest, name = lines[0].split(" ", 1)
if name != expected_name or len(expected_digest) != 64:
raise KernelArtifactError(f"SHA256SUMS entry is wrong: {directory}")
path = directory / name
if not path.is_file():
raise KernelArtifactError(f"dependency binary is missing: {path}")
actual_digest = sha256_file(path)
if actual_digest != expected_digest:
raise KernelArtifactError(f"dependency SHA-256 mismatch: {path}")
return path, actual_digest
def _normalized_member_name(raw: str) -> str:
while raw.startswith("./"):
raw = raw[2:]
pure = PurePosixPath(raw)
if not raw or raw == "." or pure.is_absolute() or ".." in pure.parts or "\\" in raw:
if raw == ".":
return ""
raise KernelArtifactError(f"unsafe archive path: {raw!r}")
return pure.as_posix()
def _member_bytes(archive: tarfile.TarFile, member: tarfile.TarInfo) -> bytes:
handle: BinaryIO | None = archive.extractfile(member)
if handle is None:
raise KernelArtifactError(f"archive member cannot be read: {member.name}")
return handle.read()
def _parse_internal_sums(body: bytes) -> dict[str, str]:
try:
lines = body.decode("ascii").splitlines()
except UnicodeError as exc:
raise KernelArtifactError("internal SHA256SUMS is not ASCII") from exc
result: dict[str, str] = {}
for line in lines:
if not line or " " not in line:
raise KernelArtifactError("internal SHA256SUMS contains an invalid line")
digest, name = line.split(" ", 1)
normalized = _normalized_member_name(name)
if len(digest) != 64 or normalized in result:
raise KernelArtifactError("internal SHA256SUMS contains an invalid entry")
result[normalized] = digest
return result
def verify_kernel_dependency(directory: Path) -> KernelArtifactInfo:
directory = Path(directory).resolve()
artifact, artifact_digest = _verify_outer_sum(directory, ARTIFACT_NAME)
metadata = _load_json(directory / "METADATA.json")
expected_keys = {
"schema_version",
"architecture",
"kernel_release",
"source_commit",
"source_archive_sha256",
"artifact",
"artifact_bytes",
"artifact_sha256",
"unpacked_file_bytes",
"regular_file_count",
"module_file_count",
"provenance",
}
if set(metadata) != expected_keys:
raise KernelArtifactError("kernel metadata fields are not exact")
fixed = {
"schema_version": 1,
"architecture": "aarch64",
"kernel_release": KERNEL_RELEASE,
"source_commit": SOURCE_COMMIT,
"source_archive_sha256": SOURCE_ARCHIVE_SHA256,
"artifact": ARTIFACT_NAME,
}
if any(metadata.get(key) != value for key, value in fixed.items()):
raise KernelArtifactError("kernel metadata identity is wrong")
if metadata["artifact_bytes"] != artifact.stat().st_size:
raise KernelArtifactError("kernel artifact size metadata is wrong")
if metadata["artifact_sha256"] != artifact_digest:
raise KernelArtifactError("kernel artifact digest metadata is wrong")
for key in ("unpacked_file_bytes", "regular_file_count", "module_file_count"):
if type(metadata[key]) is not int or metadata[key] <= 0:
raise KernelArtifactError(f"kernel metadata {key} must be a positive integer")
if not isinstance(metadata["provenance"], str) or not metadata["provenance"].strip():
raise KernelArtifactError("kernel metadata provenance is missing")
with artifact.open("rb") as raw:
header = raw.read(10)
if len(header) != 10 or header[:2] != b"\x1f\x8b" or header[4:8] != b"\0\0\0\0":
raise KernelArtifactError("kernel artifact gzip header is not deterministic")
try:
archive = tarfile.open(artifact, mode="r:gz")
except (tarfile.TarError, OSError, gzip.BadGzipFile) as exc:
raise KernelArtifactError("kernel artifact is not a readable tar.gz") from exc
with archive:
regular: dict[str, tarfile.TarInfo] = {}
seen: set[str] = set()
unpacked_bytes = 0
for member in archive.getmembers():
name = _normalized_member_name(member.name)
if not name:
if not member.isdir():
raise KernelArtifactError("archive root must be a directory")
continue
if name in seen:
raise KernelArtifactError(f"duplicate archive path: {name}")
seen.add(name)
if not (member.isfile() or member.isdir()):
raise KernelArtifactError(f"archive contains a link or special file: {name}")
if member.uid != 0 or member.gid != 0 or int(member.mtime) != 0:
raise KernelArtifactError(f"archive ownership or timestamp is not normalized: {name}")
if member.isfile():
regular[name] = member
unpacked_bytes += member.size
if len(regular) > MAX_REGULAR_FILES or unpacked_bytes > MAX_UNPACKED_BYTES:
raise KernelArtifactError("kernel artifact exceeds safety limits")
required = REQUIRED_BOOT_FILES | {"KERNEL_RELEASE", "SOURCE_COMMIT", "SHA256SUMS"}
missing = required - regular.keys()
if missing:
raise KernelArtifactError(f"kernel artifact is incomplete: {sorted(missing)}")
module_prefix = f"lib/modules/{KERNEL_RELEASE}/"
unexpected = {
name for name in regular if name not in required and not name.startswith(module_prefix)
}
if unexpected:
raise KernelArtifactError(f"kernel artifact contains an unowned file: {sorted(unexpected)}")
forbidden = {
f"lib/modules/{KERNEL_RELEASE}/build",
f"lib/modules/{KERNEL_RELEASE}/source",
}
if forbidden & seen:
raise KernelArtifactError("kernel artifact contains build/source module links")
if _member_bytes(archive, regular["KERNEL_RELEASE"]) != f"{KERNEL_RELEASE}\n".encode():
raise KernelArtifactError("kernel release marker is wrong")
if _member_bytes(archive, regular["SOURCE_COMMIT"]) != f"{SOURCE_COMMIT}\n".encode():
raise KernelArtifactError("kernel source commit marker is wrong")
image = _member_bytes(archive, regular["boot/Image-matrix-axp313a1"])
if f"Linux version {KERNEL_RELEASE}".encode() not in image:
raise KernelArtifactError("kernel Image does not contain the expected release")
internal = _parse_internal_sums(_member_bytes(archive, regular["SHA256SUMS"]))
verified_files = set(regular) - {"SHA256SUMS"}
if set(internal) != verified_files:
raise KernelArtifactError("internal SHA256SUMS does not cover exactly every payload file")
for name, digest in internal.items():
actual = hashlib.sha256(_member_bytes(archive, regular[name])).hexdigest()
if actual != digest:
raise KernelArtifactError(f"internal SHA-256 mismatch: {name}")
module_count = sum(1 for name in regular if name.startswith(module_prefix))
measurements = {
"unpacked_file_bytes": unpacked_bytes,
"regular_file_count": len(regular),
"module_file_count": module_count,
}
if any(metadata[key] != value for key, value in measurements.items()):
raise KernelArtifactError("kernel artifact measurements do not match metadata")
return KernelArtifactInfo(
artifact=ARTIFACT_NAME,
artifact_bytes=artifact.stat().st_size,
artifact_sha256=artifact_digest,
kernel_release=KERNEL_RELEASE,
source_commit=SOURCE_COMMIT,
**measurements,
)
def verify_source_dependency(directory: Path) -> dict[str, object]:
directory = Path(directory).resolve()
archive, digest = _verify_outer_sum(directory, SOURCE_ARCHIVE_NAME)
metadata = _load_json(directory / "METADATA.json")
expected = {
"schema_version": 1,
"source": "WalnutPi Linux 6.1.31",
"source_commit": SOURCE_COMMIT,
"archive": SOURCE_ARCHIVE_NAME,
"archive_bytes": archive.stat().st_size,
"archive_sha256": SOURCE_ARCHIVE_SHA256,
"purpose": "Offline reproducible build input for 6.1.31-matrix-axp313a1",
}
if metadata != expected or digest != SOURCE_ARCHIVE_SHA256:
raise KernelArtifactError("kernel source dependency metadata is wrong")
return expected
def main() -> int:
parser = argparse.ArgumentParser(description="Verify offline AXP313A kernel dependencies")
parser.add_argument("--kernel", type=Path, required=True)
parser.add_argument("--source", type=Path)
args = parser.parse_args()
result: dict[str, object] = {"kernel": asdict(verify_kernel_dependency(args.kernel))}
if args.source:
result["source"] = verify_source_dependency(args.source)
print(json.dumps(result, ensure_ascii=False, sort_keys=True, indent=2))
return 0
if __name__ == "__main__":
try:
raise SystemExit(main())
except KernelArtifactError as exc:
print(json.dumps({"ok": False, "error": str(exc)}, ensure_ascii=False))
raise SystemExit(1)