Files
UniXNU Docs 5c92d4048f docs: add the VitePress documentation site
Add English and Simplified Chinese documentation under docs/, covering the boot chain, build, boot, debugging, porting, drivers, XNU 4 KiB pages, macOS and device notes. Add the Pages workflow and CNAME. Rename the boot manager from CrabApple to UniXNU across the package, symbols, paths, configuration file and logs. Update README with the documentation badge and CONTRIBUTING link.
2026-10-19 22:10:43 +08:00

145 lines
5.4 KiB
Python

#!/usr/bin/env python3
"""Build a GPT test disk whose named partition starts with a FAT32 volume.
Layout (all sizes in MiB):
GPT
├─ "misc" --misc MiB, empty
└─ <--name> the rest of the disk:
[0, --fat) FAT32 volume populated from --tree
[--fat, ...) raw; its first bytes are --marker
This models a device partition whose head carries the UniXNU payload
volume and whose tail is handed to the payload as a separate disk through
[[block_slice]]. Needs mkfs.vfat or mformat; files are copied with mcopy
when available, otherwise with the pyfatfs module (run this script with an
interpreter that has it).
"""
import argparse
import shutil
import struct
import subprocess
import sys
import tempfile
import uuid
import zlib
from pathlib import Path
MIB = 1024 * 1024
def write_gpt(path: Path, size: int, misc: int, name: str) -> int:
sector = 512
last = size * MIB // sector - 1
start = 2048 + misc * MIB // sector
entries = bytearray(128 * 128)
linux_data = uuid.UUID("0fc63daf-8483-4772-8e79-3d69d8477de4").bytes_le
for index, (first, end, label) in enumerate([
(2048, start - 1, "misc"), (start, last - 33, name)
]):
encoded = label.encode("utf-16-le")
if len(encoded) > 72:
raise ValueError("GPT partition name exceeds 36 UTF-16 units")
struct.pack_into("<16s16sQQQ72s", entries, index * 128,
linux_data, uuid.uuid4().bytes_le, first, end, 0, encoded)
disk_guid = uuid.uuid4().bytes_le
def header(current, alternate, table):
data = bytearray(sector)
struct.pack_into("<8sIIIIQQQQ16sQIII", data, 0,
b"EFI PART", 0x10000, 92, 0, 0, current, alternate,
34, last - 33, disk_guid, table, 128, 128,
zlib.crc32(entries) & 0xFFFFFFFF)
struct.pack_into("<I", data, 16, zlib.crc32(data[:92]) & 0xFFFFFFFF)
return data
mbr = bytearray(sector)
struct.pack_into("<B3sB3sII", mbr, 446, 0, b"\x00\x02\x00", 0xEE,
b"\xff\xff\xff", 1, min(last, 0xFFFFFFFF))
mbr[510:512] = b"\x55\xaa"
with path.open("r+b") as disk:
for lba, data in [(0, mbr), (1, header(1, last, 2)), (2, entries),
(last - 32, entries),
(last, header(last, 1, last - 32))]:
disk.seek(lba * sector)
disk.write(data)
return start * sector
def populate_mtools(image: Path, tree: Path) -> None:
for path in sorted(tree.rglob("*")):
rel = "::/" + path.relative_to(tree).as_posix()
if path.is_dir():
subprocess.run(["mmd", "-i", str(image), rel], check=True)
else:
subprocess.run(["mcopy", "-i", str(image), str(path), rel], check=True)
def populate_pyfatfs(image: Path, tree: Path) -> None:
import warnings
warnings.filterwarnings("ignore")
from pyfatfs.PyFatFS import PyFatFS
fat = PyFatFS(str(image))
try:
for path in sorted(tree.rglob("*")):
rel = "/" + path.relative_to(tree).as_posix()
if path.is_dir():
fat.makedirs(rel, recreate=True)
else:
with fat.openbin(rel, "wb") as out:
out.write(path.read_bytes())
finally:
fat.close()
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("--out", type=Path, required=True)
parser.add_argument("--tree", type=Path, required=True, help="files for the FAT volume")
parser.add_argument("--size", type=int, default=256, help="disk size, MiB")
parser.add_argument("--misc", type=int, default=8, help="first partition size, MiB")
parser.add_argument("--fat", type=int, default=64, help="FAT volume size, MiB (>= 33)")
parser.add_argument("--name", default="userdata", help="GPT name of the second partition")
parser.add_argument("--marker", default="UNIXNU-TAIL-MARKER", help="text at the start of the tail")
args = parser.parse_args()
if args.fat < 33 or args.misc + args.fat + 2 >= args.size:
sys.exit("error: sizes do not fit (FAT32 needs >= 33 MiB)")
args.out.parent.mkdir(parents=True, exist_ok=True)
with open(args.out, "wb") as f:
f.truncate(args.size * MIB)
start = write_gpt(args.out, args.size, args.misc, args.name)
with tempfile.TemporaryDirectory() as tmp:
fat = Path(tmp) / "fat.img"
if shutil.which("mkfs.vfat"):
subprocess.run(["mkfs.vfat", "-F", "32", "-n", "PAYLOAD", "-C", str(fat), str(args.fat * 1024)],
check=True, capture_output=True)
else:
with fat.open("wb") as image:
image.truncate(args.fat * MIB)
subprocess.run(["mformat", "-i", str(fat), "-F", "-v", "PAYLOAD", "::"],
check=True, capture_output=True)
if shutil.which("mcopy") and shutil.which("mmd"):
populate_mtools(fat, args.tree)
else:
populate_pyfatfs(fat, args.tree)
data = fat.read_bytes()
with open(args.out, "r+b") as disk:
disk.seek(start)
disk.write(data)
disk.seek(start + args.fat * MIB)
disk.write(args.marker.encode())
print(f"{args.out}: partition '{args.name}' at {start} bytes, FAT {args.fat} MiB, tail marker at +{args.fat} MiB")
return 0
if __name__ == "__main__":
sys.exit(main())