fix: gpt_read checks every entry, and says when there are more than max (#180)

gpt_read() stopped at max used entries (16 for the identity code, 4 in the
mint): a disk whose StarshipOS partitions came after the first max read as
having none, and the entries after max were never range-checked.

Now every entry is checked and every used one counted; a good table with more
used entries than max is GPT_MANY (the first max in parts), and the identity
code says "GPT: a USB drive has more than 16 partitions -- not used". Other
callers treat anything but GPT_OK as before.

Tested first, seen to fail: qemu_session.py gpt_many -- a data stick with its
two partitions as entries 17 and 18 after 16 others, and the same with an entry
19 past the usable range: both "not this system's stick" before (silently cut,
entry 19 never checked; logs/20261112-162236). After: "more than 16
partitions" and "a damaged partition table" (logs/20261112-162921).
Regressions: gpt_bad_entries, gpt_foreign PASS. gpt_zuse FAILS, on a stale
expectation unrelated to this change -- an identity-only GPT drive with no
system stick has been held silently as a possible real Zuse since #92 Task 4;
its log is kept here. The gpt_many images are made before the boot: a first
version made them after, failed in sgdisk and left its QEMU running, stopped
by hand (logs/20261112-162003).

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 16:41:00 +00:00
parent ab63789e5b
commit 9700a7b404
16 changed files with 83 additions and 7 deletions
+47 -1
View File
@@ -87,6 +87,10 @@ 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)
gpt_many no drive at boot; a data stick whose two StarshipOS partitions are entries 17 and 18,
after 16 others: "GPT: a USB drive has more than 16 partitions -- not used", not cut
to 16 (#180); pulled; the same with an entry 19 outside the usable range:
"a damaged partition table" -- every entry checked (#180)
ident_attach_fail no drive at boot; a fresh data stick hot-plugged whose reads at its identity
partition's first sector (LBA 2048) fail, so that partition cannot be attached after
Artemis is: a message naming the identity partition, Artemis let go -- both or
@@ -137,7 +141,7 @@ ap.add_argument("--target", default="qemu") # qemu-stick: boot the PC stick
ap.add_argument("--stick-gib", type=int, default=None) # qemu-stick: the copy extended to N GiB
a = ap.parse_args()
NO_ZUSE_MODES = {"ident_attach_fail", "gpt_zuse", "gpt_hotplug", "gpt_foreign", "gpt_bad_crc", "gpt_bad_entries", "gpt_no_header",
NO_ZUSE_MODES = {"gpt_many", "ident_attach_fail", "gpt_zuse", "gpt_hotplug", "gpt_foreign", "gpt_bad_crc", "gpt_bad_entries", "gpt_no_header",
"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",
@@ -201,6 +205,32 @@ if a.mode in ("zuse_stick_first", "fallback", "replace", "mint_fail", "mint_part
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="]
if a.mode == "gpt_many":
# made before the boot, so a failure here leaves no QEMU running
import struct, zlib
many = os.path.join(work, "many.img")
subprocess.run(["make", "-f", "kernel/Makefile", f"ARCH={a.arch}", "datastick", f"DATASTICK_IMG={many}"],
cwd=ROOT, check=True, stdout=subprocess.DEVNULL)
sg = ["sgdisk", "-a", "1", "--transpose=1:17", "--transpose=2:18"]
for k in range(1, 17): # 16 fillers in the free sectors after Artemis (the
lo = 3143680 + (k - 1) * 64 # first usable is 2048: none before the identity)
sg += ["-n", f"{k}:{lo}:{lo + 63}", "-t", f"{k}:8300"]
subprocess.run(sg + [many], check=True, stdout=subprocess.DEVNULL)
bad = os.path.join(work, "many-bad.img")
subprocess.run(["cp", "--sparse=always", many, bad], check=True)
with open(bad, "r+b") as fh: # entry 19 past the last usable LBA, CRCs made good
fh.seek(512); hdr = bytearray(fh.read(92))
ent_lba, nent, esz = struct.unpack_from("<QII", hdr, 72)
last_usable = struct.unpack_from("<Q", hdr, 48)[0]
fh.seek(ent_lba * 512); ents = bytearray(fh.read(nent * esz))
o = 18 * esz
ents[o:o + 16] = bytes.fromhex("af3dc60f838472478e793d69d8477de4")
ents[o + 16:o + 32] = bytes(range(1, 17))
struct.pack_into("<QQ", ents, o + 32, last_usable - 8, last_usable + 64)
struct.pack_into("<I", hdr, 88, zlib.crc32(ents) & 0xFFFFFFFF)
struct.pack_into("<I", hdr, 16, 0); struct.pack_into("<I", hdr, 16, zlib.crc32(hdr) & 0xFFFFFFFF)
fh.seek(ent_lba * 512); fh.write(ents)
fh.seek(512); fh.write(hdr)
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)
@@ -937,6 +967,22 @@ elif m == "entropy":
check("[zuse@Hera] ok>", wait_for(PROMPT, 120))
check("no fault", count(r"EXCEPTION|PANIC|panic") == 0)
elif m == "gpt_many":
# #180: gpt_read() stopped reading entries at its max (16 here) and checked none after them
# the two sticks were made before the boot (see "gpt_many" above)
check("no ok> for 20 s with no drive", not wait_for(ANYPROMPT, 20))
add_stick(1, many, 2)
check("GPT: a USB drive has more than 16 partitions -- not used",
wait_for(r"GPT: a USB drive has more than 16 partitions -- not used", 120), near=r"GPT|USB|Artemis|Zuse")
qcmd("device_del", id="stickdev1")
time.sleep(3)
qcmd("blockdev-del", **{"node-name": "stick1"})
d = count(r"GPT: a USB drive has a damaged partition table -- not used")
add_stick(2, bad, 2)
check("an entry past the usable range, after the 16th: a damaged partition table",
wait_for(r"GPT: a USB drive has a damaged partition table -- not used", 120, d), near=r"GPT|USB|Artemis|Zuse")
check("no ok> throughout", count(ANYPROMPT) == 0)
elif m == "ident_attach_fail":
# #179: the identity partition (LBA 2048 on a data stick) cannot be read, so it cannot be
# attached -- after Artemis has been. Both partitions or neither.