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:
@@ -26,10 +26,14 @@ typedef struct {
|
|||||||
#define GPT_BAD 2 /* a protective MBR, but its header or entry array does not check out: not used */
|
#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; gpt_write_one: nothing was written */
|
#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) */
|
#define GPT_PART 4 /* gpt_write_one only: a write failed after others were made -- the disk is changed (#168) */
|
||||||
|
#define GPT_MANY 5 /* gpt_read: a valid GPT with more used entries than max -- every entry checked, none cut (#180) */
|
||||||
|
|
||||||
/* Read `disk`'s protective MBR, primary GPT header and partition-entry array,
|
/* 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,
|
* checking both CRC-32s, the revision, the sizes and the geometry, and every
|
||||||
* `*count` partitions with a non-zero type (at most `max`) are in `parts`.
|
* entry's range. On GPT_OK, `*count` partitions with a non-zero type (at most
|
||||||
|
* `max`) are in `parts`. GPT_MANY when the table is good but has more used
|
||||||
|
* entries than `max`: the first `max` are in `parts`, `*count` is `max`, and the
|
||||||
|
* caller says so rather than taking the disk as having only those (#180).
|
||||||
* Reads only. */
|
* Reads only. */
|
||||||
int gpt_read(blkio_dev_t *disk, gpt_part_t *parts, unsigned max, unsigned *count);
|
int gpt_read(blkio_dev_t *disk, gpt_part_t *parts, unsigned max, unsigned *count);
|
||||||
|
|
||||||
|
|||||||
+5
-1
@@ -124,17 +124,21 @@ int gpt_read(blkio_dev_t *disk, gpt_part_t *parts, unsigned max, unsigned *count
|
|||||||
}
|
}
|
||||||
if (gpt_crc32(g_gpt_entries, ebytes) != ecrc) return GPT_BAD;
|
if (gpt_crc32(g_gpt_entries, ebytes) != ecrc) return GPT_BAD;
|
||||||
|
|
||||||
for (i = 0; i < nent && n < max; i++) {
|
/* every entry is checked, and every used one counted -- not only the first `max` (#180) */
|
||||||
|
for (i = 0; i < nent; i++) {
|
||||||
const uint8_t *e = g_gpt_entries + (size_t)i * esize;
|
const uint8_t *e = g_gpt_entries + (size_t)i * esize;
|
||||||
static const uint8_t zero[16];
|
static const uint8_t zero[16];
|
||||||
uint64_t first = le64(e + 32), last = le64(e + 40);
|
uint64_t first = le64(e + 32), last = le64(e + 40);
|
||||||
if (memcmp(e, zero, 16) == 0) continue; /* an unused entry */
|
if (memcmp(e, zero, 16) == 0) continue; /* an unused entry */
|
||||||
if (first < first_usable || last > last_usable || first > last) return GPT_BAD;
|
if (first < first_usable || last > last_usable || first > last) return GPT_BAD;
|
||||||
|
if (n < max) {
|
||||||
memcpy(parts[n].type, e, 16);
|
memcpy(parts[n].type, e, 16);
|
||||||
parts[n].first_lba = first;
|
parts[n].first_lba = first;
|
||||||
parts[n].last_lba = last;
|
parts[n].last_lba = last;
|
||||||
|
}
|
||||||
n++;
|
n++;
|
||||||
}
|
}
|
||||||
|
if (n > max) { *count = max; return GPT_MANY; } /* more than the caller can hold: said, not cut */
|
||||||
*count = n;
|
*count = n;
|
||||||
memcpy(g_gpt_disk_guid, h + 56, 16);
|
memcpy(g_gpt_disk_guid, h + 56, 16);
|
||||||
g_gpt_alternate_lba = le64(h + 32);
|
g_gpt_alternate_lba = le64(h + 32);
|
||||||
|
|||||||
@@ -335,6 +335,10 @@ static int sk_v4_drive_arrived(sk_v4_drive_t *d)
|
|||||||
g_offered = d;
|
g_offered = d;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
if (gpt_rc == GPT_MANY) { /* SK_V4_GPT_PARTS: 16 (#180) */
|
||||||
|
console_println("GPT: a USB drive has more than 16 partitions -- not used");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
if (gpt_rc == GPT_BAD) {
|
if (gpt_rc == GPT_BAD) {
|
||||||
console_println("GPT: a USB drive has a damaged partition table -- not used");
|
console_println("GPT: a USB drive has a damaged partition table -- not used");
|
||||||
return 1;
|
return 1;
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+47
-1
@@ -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
|
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
|
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)
|
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
|
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
|
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
|
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
|
ap.add_argument("--stick-gib", type=int, default=None) # qemu-stick: the copy extended to N GiB
|
||||||
a = ap.parse_args()
|
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",
|
"gpt_read_error", "headless", "hotplug", "midpull", "waitpull", "typeahead", "emergency",
|
||||||
"alttab_after_logout", "alttab_before_pull", "emergency_pull",
|
"alttab_after_logout", "alttab_before_pull", "emergency_pull",
|
||||||
"screen_dark_until_login", "screen_console_after_login", "screen_dark_after_logout",
|
"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}"],
|
subprocess.run(["make", "-f", "kernel/Makefile", f"ARCH={a.arch}", "datastick", f"DATASTICK_IMG={dstick}"],
|
||||||
cwd=ROOT, check=True, stdout=subprocess.DEVNULL)
|
cwd=ROOT, check=True, stdout=subprocess.DEVNULL)
|
||||||
make_args += ["DATASTICK=1", f"DATASTICK_IMG={dstick}", "DATASTICK_DEP="]
|
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":
|
if a.mode == "genesis":
|
||||||
# a fresh data stick, made once into this session's directory and booted twice: the second
|
# 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)
|
# 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("[zuse@Hera] ok>", wait_for(PROMPT, 120))
|
||||||
check("no fault", count(r"EXCEPTION|PANIC|panic") == 0)
|
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":
|
elif m == "ident_attach_fail":
|
||||||
# #179: the identity partition (LBA 2048 on a data stick) cannot be read, so it cannot be
|
# #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.
|
# attached -- after Artemis has been. Both partitions or neither.
|
||||||
|
|||||||
Reference in New Issue
Block a user