wip(kernel): the proxy Zuse -- a stick's first boot mints its own Zuse (#92, Task 1) [skip ci]
Work in progress on StarForth-v4.0.0; not yet accepted (plan 2026-10-29-stick-5, Task 1). - zuse_genesis_marker.h: the marker's version 2 -- the proxy's key and the real Zuse's -- beside version 1. - capsule_zuse_boot.c: capsule_zuse_boot_check_key() (the identity-record half of the check) and capsule_zuse_boot_genesis_mint() (the mint half of v3's genesis) split out; v3's two callers unchanged in behaviour. - sk_v4_marker.c: the v4 boot reads version 1 as a proxy with no real Zuse, and writes version 2. - sk_v4_identity.c: no marker and a blank identity partition -> genesis: mint, read back, then the marker; the proxy logs in. A marker with a real key: no session for the proxy (the fixed screen is Task 5). - qemu_session.py: boot()/shutdown(), so a mode can boot twice; modes genesis and stick_genesis; stick_boot now expects genesis and the prompt. Logs: genesis on amd64 before the change (both boots: "no genesis on this disk", FAIL) and after (minted, then recognised: PASS). The DoE CSV of 032532 is from a run whose QEMU never started (no display; its empty log directory was removed). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GE3SDvQn5EoxdGufk84zz2
This commit is contained in:
+88
-43
@@ -46,7 +46,13 @@ the stick's, plan 2026-10-26-stick-1-screen-and-keyboard.md):
|
||||
said so, not attached
|
||||
virtio_kbd aarch64/riscv64: QEMU's virtio keyboard types 7 ok
|
||||
stick_boot --target qemu-stick (amd64): the PC stick booted as a USB stick through OVMF; its
|
||||
blank identity partition attached (500 MiB, not the 2 GiB disk); headless
|
||||
blank identity partition attached (500 MiB, not the 2 GiB disk); the proxy Zuse
|
||||
minted onto it (genesis), [zuse@Hera] ok> (plan 2026-10-29-stick-5, Task 1)
|
||||
genesis a fresh data stick (DATASTICK=1, no Zuse drive), booted twice, one QEMU at a time:
|
||||
first the genesis line, [zuse@Hera] ok>, 7 ok; then the same copy again:
|
||||
identity confirmed, no genesis, the prompt (plan 2026-10-29-stick-5, Task 1)
|
||||
stick_genesis --target qemu-stick, the PC stick as built (no STICK_PAIR): its first boot mints
|
||||
the proxy Zuse and reaches [zuse@Hera] ok> with nothing set up (plug and play)
|
||||
stick_artemis ARTDISK= (no virtio disk): Artemis and Zuse from the Zuse stick alone -- the
|
||||
stick's Artemis partition attached first (785938 user blocks), then its identity
|
||||
(48724); [zuse@Hera] ok> (plan 2026-10-27-stick-4-one-disk.md, Task 4)
|
||||
@@ -88,7 +94,8 @@ NO_ZUSE_MODES = {"gpt_zuse", "gpt_hotplug", "gpt_foreign", "gpt_bad_crc", "gpt_b
|
||||
"gpt_read_error", "headless", "hotplug", "midpull", "waitpull", "typeahead", "emergency",
|
||||
"alttab_after_logout", "alttab_before_pull", "emergency_pull",
|
||||
"screen_dark_until_login", "screen_console_after_login", "screen_dark_after_logout",
|
||||
"kbd_typeahead", "stick_boot", "stick_v3", "stick_zuse", "screen_boot"}
|
||||
"kbd_typeahead", "stick_boot", "stick_v3", "stick_zuse", "screen_boot", "genesis",
|
||||
"stick_genesis"}
|
||||
label = a.label or f"{a.mode}-{a.arch}"
|
||||
work = os.path.join(ROOT, "build", "qemu-session", label)
|
||||
shutil.rmtree(work, ignore_errors=True)
|
||||
@@ -128,38 +135,18 @@ if a.mode == "screen_boot":
|
||||
make_args.append("KERNEL_ARGS=screen=boot")
|
||||
if a.mode == "stick_zuse":
|
||||
make_args.append(f"STICK_PAIR={work}") # the PC stick with this session's copy of the pair
|
||||
start = time.time()
|
||||
make_log = open(os.path.join(work, "make.txt"), "w")
|
||||
mk = subprocess.Popen(make_args, cwd=ROOT, stdout=make_log, stderr=subprocess.STDOUT)
|
||||
deadline = time.time() + 1800
|
||||
ok = True
|
||||
|
||||
|
||||
if a.mode == "genesis":
|
||||
# a fresh data stick, made once into this session's directory and booted twice: the second
|
||||
# boot is the same copy, so it finds what the first wrote (plan 2026-10-29-stick-5, Task 1)
|
||||
dstick = os.path.join(work, "data.img")
|
||||
subprocess.run(["make", "-f", "kernel/Makefile", f"ARCH={a.arch}", "datastick", f"DATASTICK_IMG={dstick}"],
|
||||
cwd=ROOT, check=True, stdout=subprocess.DEVNULL)
|
||||
make_args += ["DATASTICK=1", f"DATASTICK_IMG={dstick}", "DATASTICK_DEP="]
|
||||
def newest(pattern):
|
||||
c = [p for p in glob.glob(pattern) if os.path.getmtime(p) >= start]
|
||||
return max(c, key=os.path.getmtime) if c else None
|
||||
|
||||
|
||||
log = ser = qmp = None
|
||||
while time.time() < deadline and not (log and ser and qmp):
|
||||
if mk.poll() is not None:
|
||||
print(f"MAKE FAILED (exit {mk.returncode}); see {make_log.name}", flush=True)
|
||||
sys.exit(1)
|
||||
log = log or newest(f"{ROOT}/logs/*/{a.arch}/qemu-{a.arch}-*.log")
|
||||
ser = ser or newest(f"{ROOT}/build/{a.arch}/kernel/qemu-serial-{a.arch}-*.sock")
|
||||
qmp = qmp or newest(f"{ROOT}/build/{a.arch}/kernel/qemu-qmp-{a.arch}-*.sock")
|
||||
time.sleep(1)
|
||||
print(f"LOG: {os.path.relpath(log, ROOT)}", flush=True)
|
||||
|
||||
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
for _ in range(60):
|
||||
try:
|
||||
s.connect(ser)
|
||||
break
|
||||
except OSError:
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
def drain():
|
||||
while True:
|
||||
try:
|
||||
@@ -169,14 +156,6 @@ def drain():
|
||||
return
|
||||
|
||||
|
||||
threading.Thread(target=drain, daemon=True).start()
|
||||
|
||||
q = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
q.connect(qmp)
|
||||
qf = q.makefile("r")
|
||||
qf.readline()
|
||||
|
||||
|
||||
def qcmd(cmd, **args):
|
||||
msg = {"execute": cmd}
|
||||
if args:
|
||||
@@ -190,7 +169,48 @@ def qcmd(cmd, **args):
|
||||
return r
|
||||
|
||||
|
||||
qcmd("qmp_capabilities")
|
||||
def boot(args):
|
||||
"""One `make ... qemu` boot: QEMU in the foreground of make, its serial log, serial and QMP
|
||||
sockets found and connected (the globals every check reads)."""
|
||||
global start, mk, log, s, q, qf
|
||||
start = time.time()
|
||||
make_log = open(os.path.join(work, "make.txt"), "a")
|
||||
mk = subprocess.Popen(args, cwd=ROOT, stdout=make_log, stderr=subprocess.STDOUT)
|
||||
deadline = time.time() + 1800
|
||||
log = ser = qmp = None
|
||||
while time.time() < deadline and not (log and ser and qmp):
|
||||
if mk.poll() is not None:
|
||||
print(f"MAKE FAILED (exit {mk.returncode}); see {make_log.name}", flush=True)
|
||||
sys.exit(1)
|
||||
log = log or newest(f"{ROOT}/logs/*/{a.arch}/qemu-{a.arch}-*.log")
|
||||
ser = ser or newest(f"{ROOT}/build/{a.arch}/kernel/qemu-serial-{a.arch}-*.sock")
|
||||
qmp = qmp or newest(f"{ROOT}/build/{a.arch}/kernel/qemu-qmp-{a.arch}-*.sock")
|
||||
time.sleep(1)
|
||||
print(f"LOG: {os.path.relpath(log, ROOT)}", flush=True)
|
||||
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
for _ in range(60):
|
||||
try:
|
||||
s.connect(ser)
|
||||
break
|
||||
except OSError:
|
||||
time.sleep(1)
|
||||
threading.Thread(target=drain, daemon=True).start()
|
||||
q = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
q.connect(qmp)
|
||||
qf = q.makefile("r")
|
||||
qf.readline()
|
||||
qcmd("qmp_capabilities")
|
||||
|
||||
|
||||
def shutdown():
|
||||
"""Quit QEMU over QMP and let make finish: one QEMU at a time (.claude/CLAUDE.md)."""
|
||||
q.send(json.dumps({"execute": "quit"}).encode())
|
||||
mk.wait()
|
||||
time.sleep(2)
|
||||
|
||||
|
||||
ok = True
|
||||
boot(make_args)
|
||||
|
||||
|
||||
def text():
|
||||
@@ -371,12 +391,12 @@ elif m in ("stick_boot", "two_sticks"):
|
||||
check("System: StarForth v4", re.search(r"System: StarForth v4", text()) is not None)
|
||||
check("Artemis: the stick's partition attached",
|
||||
wait_for(r"Artemis: the stick's partition attached", 120))
|
||||
check("Zuse: no genesis on this disk", wait_for(r"Zuse: no genesis on this disk", 60))
|
||||
time.sleep(5)
|
||||
check("Zuse: genesis minted onto attached thumbdrive",
|
||||
wait_for(r"Zuse: genesis minted onto attached thumbdrive", 120))
|
||||
check("[zuse@Hera] ok>", wait_for(PROMPT, 60))
|
||||
d = disks()
|
||||
check(f"Artemis attached first, then the identity partition (user blocks {d})",
|
||||
len(d) >= 2 and d[0] > 1000000 and 300000 < d[1] < 512000)
|
||||
check("no ok> for 30 s (a blank identity: headless)", not wait_for(ANYPROMPT, 30))
|
||||
else:
|
||||
check("Zuse: identity confirmed", wait_for(r"Zuse: identity confirmed from attached thumbdrive", 120))
|
||||
check("[zuse@Hera] ok>", wait_for(PROMPT, 60))
|
||||
@@ -418,6 +438,32 @@ elif m in ("artemis_unsigned", "raw_ident"):
|
||||
check("no blk: disk line (nothing attached)", count(r"blk: disk ") == 0)
|
||||
check("no fault", count(r"EXCEPTION|PANIC|panic") == 0)
|
||||
|
||||
elif m in ("genesis", "stick_genesis"):
|
||||
if m == "stick_genesis":
|
||||
check("Boot disk: a StarshipOS stick", re.search(r"Boot disk: a StarshipOS stick", text()) is not None,
|
||||
near=r"Boot successful!")
|
||||
check("Zuse: genesis minted onto attached thumbdrive",
|
||||
wait_for(r"Zuse: genesis minted onto attached thumbdrive", 180), near=r"Artemis: |Zuse: ")
|
||||
check("[zuse@Hera] ok>", wait_for(PROMPT, 60))
|
||||
check("no identity confirmed on a first boot", count(r"Zuse: identity confirmed") == 0)
|
||||
n = count(PROMPT)
|
||||
type_line("3 4 + .")
|
||||
check("7 ok", wait_for(PROMPT, 60, n) and re.search(r"7\s+ok", text()[-400:]) is not None)
|
||||
check("no fault", count(r"EXCEPTION|PANIC|panic") == 0)
|
||||
if m == "genesis":
|
||||
shutdown()
|
||||
print("-- the second boot, the same copy of the data stick", flush=True)
|
||||
boot([x for x in make_args if x != "clean"])
|
||||
check("POST: PASSED (second boot)", wait_for(r"POST: PASSED", 900))
|
||||
check("Zuse: identity confirmed (second boot)",
|
||||
wait_for(r"Zuse: identity confirmed from attached thumbdrive", 120), near=r"Artemis: |Zuse: ")
|
||||
check("no genesis on the second boot", count(r"Zuse: genesis minted") == 0)
|
||||
check("[zuse@Hera] ok> (second boot)", wait_for(PROMPT, 60))
|
||||
n = count(PROMPT)
|
||||
type_line("3 4 + .")
|
||||
check("7 ok (second boot)", wait_for(PROMPT, 60, n) and re.search(r"7\s+ok", text()[-400:]) is not None)
|
||||
check("no fault (second boot)", count(r"EXCEPTION|PANIC|panic") == 0)
|
||||
|
||||
elif m == "stick_v3":
|
||||
t = text()
|
||||
check("Boot disk: a StarshipOS stick", re.search(r"Boot disk: a StarshipOS stick", t) is not None)
|
||||
@@ -662,8 +708,7 @@ else:
|
||||
check(f"known mode {m}", False)
|
||||
|
||||
print(f"RESULT: {'PASS' if ok else 'FAIL'}", flush=True)
|
||||
q.send(json.dumps({"execute": "quit"}).encode())
|
||||
mk.wait()
|
||||
shutdown()
|
||||
for f in ("v3/include/version.h", "capsules/BLOCK_MAP.md"):
|
||||
subprocess.run(["git", "checkout", "-q", "--", f], cwd=ROOT)
|
||||
sys.exit(0 if ok else 2)
|
||||
|
||||
Reference in New Issue
Block a user