fix: MINT-ZUSE says the stick was erased when it was (#168)

failed() printed "MINT-ZUSE: failed -- nothing changed" on every failure
after YES, though by then gpt_write_one() had rewritten the stick's partition
table and the identity partition's first blocks had been zeroed.

gpt_write_one() now tells the two apart: GPT_EIO when its first write failed
(nothing written), GPT_PART when a later write or flush failed (the disk is
changed). MINT-ZUSE keeps "failed -- nothing changed" for GPT_EIO only, and
says "failed -- the stick was erased; Zuse not minted" for every failure
after the table is written. The marker is still written last.

Tested first, seen to fail: qemu_session.py mint_part_fail (writes fail only
at the identity partition's first sector, LBA 2048) -- "nothing changed",
logs/20261112-122132. After: mint_part_fail PASS; mint_fail (every write
fails) still "nothing changed", PASS; replace (the success path) PASS. Their
logs here. FABRIC-3 XLVIII.20.

Captain Bob, 2026-11-12: "start #168".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RzZgrf7V7Zk2iczBXDRbm
This commit is contained in:
Claude
2026-11-12 12:41:36 +00:00
parent 7e30b4525e
commit 54311be18a
21 changed files with 102 additions and 19 deletions
+16
View File
@@ -6565,3 +6565,19 @@ and memorize frequently so we can pick up where we left off").
removed, it can never take a job if it comes back. #140's "Done when" is met by this entry and the
workflows' comments once it reaches `master`.
### 20. #168: MINT-ZUSE says what happened to the stick (2026-11-12)
- **Found** (the code review of 2026-11-11): `failed()` printed "MINT-ZUSE: failed -- nothing changed"
on every failure after YES, though `gpt_write_one()` had by then rewritten the stick's partition
table and the identity partition's first blocks had been zeroed.
- **Tested first, seen to fail:** `qemu_session.py mint_part_fail` -- as `mint_fail`, but only writes
touching the identity partition's first sector (LBA 2048) fail, so the GPT is written and the mint
fails after it. Before: "nothing changed" (`logs/20261112-122132`).
- **Built:** `gpt_write_one()` tells the two apart -- `GPT_EIO` when its first write failed (nothing
written), a new `GPT_PART` when a later write or flush failed (the disk is changed). MINT-ZUSE says
"failed -- nothing changed" only then, and "failed -- the stick was erased; Zuse not minted" (58
characters) for every failure after the table is written. The marker is still written last: the
old Zuse stick works either way.
- **Seen to pass:** `mint_part_fail` (the new message, never "nothing changed", the old stick logs
in); `mint_fail`, every write failing, still "nothing changed"; `replace`, the success path.
+6 -2
View File
@@ -24,7 +24,8 @@ typedef struct {
#define GPT_OK 0 /* a valid GPT */
#define GPT_NONE 1 /* not a GPT disk -- no protective MBR -- (or one whose sectors are not 512 bytes): read it raw */
#define GPT_BAD 2 /* a protective MBR, but its header or entry array does not check out: not used */
#define GPT_EIO 3 /* its first block could not be read: not used */
#define GPT_EIO 3 /* its first block could not be read: not used; gpt_write_one: nothing was written */
#define GPT_PART 4 /* gpt_write_one only: a write failed after others were made -- the disk is changed (#168) */
/* Read `disk`'s protective MBR, primary GPT header and partition-entry array,
* checking both CRC-32s, the revision, the sizes and the geometry. On GPT_OK,
@@ -59,7 +60,10 @@ int gpt_grow_last(blkio_dev_t *disk, const uint8_t type[16], uint64_t max_part_s
* entries, the backup header and entries at the disk's end, the protective
* MBR written last (the old one zeroed first, so a write torn anywhere leaves
* no table). Nothing inside the partition is written. GPT_OK; GPT_BAD for a
* disk whose sectors are not 512 bytes or that is too small; GPT_EIO. */
* disk whose sectors are not 512 bytes or that is too small (nothing written);
* GPT_EIO when its first write failed (nothing written, the disk as it was);
* GPT_PART when a later write or a flush failed (the disk is changed: its old
* table is gone). The caller says which (#168). */
int gpt_write_one(blkio_dev_t *disk, const uint8_t type[16], uint64_t part_sectors, uint8_t disk_guid[16]);
/* The index of the first partition of `type`, or -1. */
+4 -2
View File
@@ -352,14 +352,16 @@ int gpt_write_one(blkio_dev_t *disk, const uint8_t type[16], uint64_t part_secto
* partition not yet written -- a used stick's old table included */
{
static const uint8_t zero[GPT_SECTOR];
if (put_sectors(disk, 0u, zero, 1u) != 0 || disk->vt->flush(disk) != BLKIO_OK) return GPT_EIO;
if (put_sectors(disk, 0u, zero, 1u) != 0) return GPT_EIO; /* nothing written yet */
if (disk->vt->flush(disk) != BLKIO_OK) return GPT_PART; /* the MBR may be gone */
}
/* from here the disk is changed: any failure says so (#168) */
if (put_sectors(disk, alt - GPT_ENT_SECTORS, g_gpt_new_entries, GPT_ENT_SECTORS) != 0 ||
put_sectors(disk, alt, bak, 1u) != 0 ||
put_sectors(disk, 2u, g_gpt_new_entries, GPT_ENT_SECTORS) != 0 ||
put_sectors(disk, 1u, hdr, 1u) != 0 || disk->vt->flush(disk) != BLKIO_OK ||
put_sectors(disk, 0u, mbr, 1u) != 0 || disk->vt->flush(disk) != BLKIO_OK)
return GPT_EIO;
return GPT_PART;
return GPT_OK;
}
+20 -9
View File
@@ -11,7 +11,9 @@
* fresh Zuse minted into it, read back -- and only then the real key written
* into the genesis marker on the system stick's Artemis. Any failure before
* the marker leaves the marker as it was: the old Zuse stick, if there is
* one, still works.
* one, still works. A failure says what happened to the stick (#168): "nothing
* changed" only while nothing has been written to it; once its partition table
* is written, "the stick was erased".
*/
#include "starkernel/v4/sk_v4_mint.h"
#include "starkernel/v4/sk_v4_identity.h"
@@ -65,13 +67,20 @@ static int read_answer(char *buf, int max)
}
}
static int failed(void)
static int failed(void) /* nothing has been written to the stick */
{
console_println("MINT-ZUSE: failed -- nothing changed");
sk_v4_identity_release_offered();
return -1;
}
static int erased(void) /* its old partition table is gone (#168) */
{
console_println("MINT-ZUSE: failed -- the stick was erased; Zuse not minted");
sk_v4_identity_release_offered();
return -1;
}
int sk_v4_mint_zuse(void)
{
static blkio_dev_t part;
@@ -115,27 +124,29 @@ int sk_v4_mint_zuse(void)
sk_v4_identity_release_offered();
return -1;
}
if (rc != GPT_OK || gpt_read(raw, parts, 4u, &n) != GPT_OK) return failed();
if (rc == GPT_EIO) return failed(); /* its first write failed: nothing written */
if (rc != GPT_OK) return erased();
if (gpt_read(raw, parts, 4u, &n) != GPT_OK) return erased();
pi = gpt_find(parts, n, gpt_type_starship_identity);
if (pi < 0 || gpt_part_open(&part, &part_state, raw, &parts[pi]) != BLKIO_OK) return failed();
if (pi < 0 || gpt_part_open(&part, &part_state, raw, &parts[pi]) != BLKIO_OK) return erased();
/* nothing of the stick's old bytes may read as an identity */
for (i = 0; i < MINT_ZERO_FBLOCKS; i++)
if (blkio_write(&part, i, zero) != BLKIO_OK) return failed();
if (blkio_write(&part, i, zero) != BLKIO_OK) return erased();
/* Zuse, minted, then read back from the stick */
if (capsule_zuse_boot_genesis_mint(&part, seed, pub) != 0) return failed();
if (capsule_zuse_boot_genesis_mint(&part, seed, pub) != 0) return erased();
sig_rc = homeblocks_sig_check(&part, HOMEBLOCKS_SIG_START_FBLOCK, &sig);
if (capsule_zuse_boot_check_key(&part, sig_rc, &sig, pub, rseed, rpub) != ZUSE_DRIVE_HERS ||
memcmp(rseed, seed, 32) != 0)
return failed();
return erased();
/* only now the marker: the proxy kept, the real Zuse named */
m = *sk_v4_identity_marker();
if (!m.present) return failed();
if (!m.present) return erased();
memcpy(m.real, pub, 32);
m.has_real = 1;
if (sk_v4_marker_write(&m) != 0) return failed();
if (sk_v4_marker_write(&m) != 0) return erased();
sk_v4_identity_set_marker(&m);
memset(seed, 0, sizeof seed);
memset(rseed, 0, sizeof rseed);
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+32 -6
View File
@@ -87,6 +87,9 @@ the stick's, plan 2026-10-26-stick-1-screen-and-keyboard.md):
no ok> for 30 s; the new one added: the prompt
mint_fail the fixed screen; R, a stick whose writes all fail (blkdebug): "failed -- nothing
changed", the screen again; the old stick added: the prompt (Review Focus 4)
mint_part_fail as mint_fail, but only writes to the identity partition's first sector (LBA 2048)
fail: the GPT is written, then the mint fails -- "failed -- the stick was erased; Zuse
not minted", never "nothing changed" (#168); no replacement; the old stick: the prompt
system_pull_real logged in on her stick; the data stick pulled: hers let go first (no detach
refusal), session ended; the data stick replugged, hers still in: she is logged in
bam_flush --target qemu-stick --stick-gib 16: an Artemis block changed and SAVE-BUFFERS'd three
@@ -136,7 +139,7 @@ NO_ZUSE_MODES = {"gpt_zuse", "gpt_hotplug", "gpt_foreign", "gpt_bad_crc", "gpt_b
"screen_dark_until_login", "screen_console_after_login", "screen_dark_after_logout",
"kbd_typeahead", "stick_boot", "stick_v3", "stick_zuse", "screen_boot", "genesis",
"stick_genesis", "stick_grow", "stick_small", "bam_flush",
"zuse_stick_first", "fallback", "replace", "mint_fail", "system_pull_real"}
"zuse_stick_first", "fallback", "replace", "mint_fail", "mint_part_fail", "system_pull_real"}
label = a.label or f"{a.mode}-{a.arch}"
work = os.path.join(ROOT, "build", "qemu-session", label)
shutil.rmtree(work, ignore_errors=True)
@@ -188,7 +191,7 @@ if a.stick_gib:
make_args.append(f"STICK_GIB={a.stick_gib}")
if a.mode in ("stick_grow", "stick_small", "bam_flush"):
make_args.append("KERNEL_ARGS=--log-level=info") # the blk: disk lines the sizes are read from
if a.mode in ("zuse_stick_first", "fallback", "replace", "mint_fail", "system_pull_real"):
if a.mode in ("zuse_stick_first", "fallback", "replace", "mint_fail", "mint_part_fail", "system_pull_real"):
# a data stick made once into the session's directory: minted on, then booted again
dstick = os.path.join(work, "data.img")
subprocess.run(["make", "-f", "kernel/Makefile", f"ARCH={a.arch}", "datastick", f"DATASTICK_IMG={dstick}"],
@@ -417,12 +420,16 @@ def add_zuse(n, img=None, fail_sector0=False):
drive=f"zuse{n}", id=f"zusedev{n}")
def add_stick(n, img, port, fail_writes=False):
def add_stick(n, img, port, fail_writes=False, fail_sector=None):
"""A USB stick added over QMP on the xHCI controller's `port` (plan 2026-10-29-stick-5, Task 4);
with fail_writes, every write to it fails (blkdebug, EIO -- Task 5's mint_fail)."""
with fail_writes, every write to it fails (blkdebug, EIO -- Task 5's mint_fail); with fail_sector,
only writes touching that sector fail (#168's mint_part_fail)."""
f = {"driver": "file", "filename": img}
if fail_writes:
f = {"driver": "blkdebug", "image": f, "inject-error": [{"event": "none", "iotype": "write", "errno": 5}]}
elif fail_sector is not None:
f = {"driver": "blkdebug", "image": f,
"inject-error": [{"event": "none", "iotype": "write", "sector": fail_sector, "errno": 5}]}
qcmd("blockdev-add", driver="raw", **{"node-name": f"stick{n}"}, file=f)
qcmd("device_add", driver="usb-storage", bus="xhci0.0", port=str(port), drive=f"stick{n}", id=f"stickdev{n}")
@@ -728,7 +735,7 @@ elif m == "fallback":
check("no ok> on the screen", "ok> " not in text()[text().rfind("Zuse: session ended"):])
check("no fault", count(r"EXCEPTION|PANIC|panic") == 0)
elif m in ("replace", "mint_fail", "system_pull_real"):
elif m in ("replace", "mint_fail", "mint_part_fail", "system_pull_real"):
SCREEN0 = r"Zuse's stick is not here\."
old_img = os.path.join(work, "zuse-real.img")
subprocess.run(["truncate", "-s", "600M", old_img], check=True)
@@ -743,7 +750,7 @@ elif m in ("replace", "mint_fail", "system_pull_real"):
print("-- the second boot: the data stick alone", flush=True)
boot([x for x in make_args if x != "clean"])
check("POST: PASSED (second boot)", wait_for(r"POST: PASSED", 900))
if m in ("replace", "mint_fail"):
if m in ("replace", "mint_fail", "mint_part_fail"):
check("the fixed screen", wait_for(SCREEN0, 120), near=r"Artemis: |Zuse")
time.sleep(3)
p0 = count(r"MINT-ZUSE: plug in the stick for the real Zuse")
@@ -776,6 +783,25 @@ elif m in ("replace", "mint_fail", "system_pull_real"):
add_stick(4, new_img, 3)
check("the new one again: identity confirmed", wait_for(r"Zuse: identity confirmed from attached thumbdrive", 120, c))
check("the prompt", wait_for(PROMPT, 60))
elif m == "mint_part_fail":
# #168: the GPT is written (sectors 0-33 and the disk's end), the identity partition's
# first sector is not -- the stick has been changed when the mint fails
shots = count(SCREEN0)
add_stick(2, new_img, 3, fail_sector=2048)
wait_for(r"MINT-ZUSE: type YES to write it", 120)
type_line("YES")
check("MINT-ZUSE: failed -- the stick was erased; Zuse not minted",
wait_for(r"MINT-ZUSE: failed -- the stick was erased; Zuse not minted", 300), near=r"MINT-ZUSE")
check("never \"nothing changed\" for a stick that was written", count(r"failed -- nothing changed") == 0)
check("no replacement minted (none in this boot's log)", count(r"real Zuse minted") == 0)
check("the fixed screen again", wait_for(SCREEN0, 60, shots))
qcmd("device_del", id="stickdev2")
time.sleep(3)
qcmd("blockdev-del", **{"node-name": "stick2"})
c = count(r"Zuse: identity confirmed from attached thumbdrive")
add_stick(3, old_img, 3)
check("the old stick still logs in", wait_for(r"Zuse: identity confirmed from attached thumbdrive", 120, c))
check("the prompt", wait_for(PROMPT, 60))
else:
shots = count(SCREEN0)
add_stick(2, new_img, 3, fail_writes=True)