/* SPDX-License-Identifier: LicenseRef-Starship-1.0 * Copyright (c) 2023-2026 Robert A. James */ /* test_gpt.c -- kernel/src/gpt.c on the host: gpt_read and gpt_write_one against a RAM disk whose * writes can fail at chosen blocks (docs/v4.0.0/plans/2026-11-12-kernel-host-tests.md, increment 1). * #168: gpt_write_one tells "nothing written" (GPT_EIO) from "the disk is changed" (GPT_PART). * #180: gpt_read checks every entry and says when there are more than max (GPT_MANY). */ #include "harness.h" #include "fakes.h" #include "starkernel/gpt.h" #include #define DISK_BLOCKS 8192u /* 8 MiB: 16384 sectors */ #define SECTORS (DISK_BLOCKS * 2u) static void put_le32(uint8_t *p, uint32_t v) { p[0] = (uint8_t)v; p[1] = (uint8_t)(v >> 8); p[2] = (uint8_t)(v >> 16); p[3] = (uint8_t)(v >> 24); } static void put_le64(uint8_t *p, uint64_t v) { put_le32(p, (uint32_t)v); put_le32(p + 4, (uint32_t)(v >> 32)); } /* A GPT written by hand: `used` entries of 64 sectors each from LBA 2048, the first two of them * the StarshipOS identity and Artemis types when `ours_at` is 0, or at entries ours_at and * ours_at+1; entry `bad` (if not -1) given a range past the last usable LBA. Both CRCs good. */ static void make_table(fake_disk *d, unsigned used, unsigned ours_at, int bad) { static const uint8_t linux_t[16] = { 0xAF, 0x3D, 0xC6, 0x0F, 0x83, 0x84, 0x72, 0x47, 0x8E, 0x79, 0x3D, 0x69, 0xD8, 0x47, 0x7D, 0xE4 }; uint8_t *mbr = fake_disk_sector(d, 0), *h = fake_disk_sector(d, 1), *e = fake_disk_sector(d, 2); uint64_t last_usable = SECTORS - 34u; unsigned i; memset(d->bytes, 0, (size_t)34 * 512u); mbr[446 + 4] = 0xEE; put_le32(mbr + 446 + 8, 1u); put_le32(mbr + 446 + 12, SECTORS - 1u); mbr[510] = 0x55; mbr[511] = 0xAA; for (i = 0; i < used; i++) { uint8_t *x = e + (size_t)i * 128u; const uint8_t *t = (i == ours_at) ? gpt_type_starship_identity : (i == ours_at + 1u) ? gpt_type_starship_artemis : linux_t; memcpy(x, t, 16); x[16] = (uint8_t)(i + 1); /* a partition GUID */ put_le64(x + 32, 2048u + (uint64_t)i * 64u); put_le64(x + 40, 2048u + (uint64_t)i * 64u + 63u); if ((int)i == bad) { put_le64(x + 32, last_usable - 8u); put_le64(x + 40, last_usable + 64u); } } memcpy(h, "EFI PART", 8); put_le32(h + 8, 0x00010000u); put_le32(h + 12, 92u); put_le64(h + 24, 1u); put_le64(h + 32, SECTORS - 1u); put_le64(h + 40, 34u); put_le64(h + 48, last_usable); h[56] = 0x5A; /* a disk GUID */ put_le64(h + 72, 2u); put_le32(h + 80, 128u); put_le32(h + 84, 128u); put_le32(h + 88, gpt_crc32(e, 128u * 128u)); put_le32(h + 16, 0u); put_le32(h + 16, gpt_crc32(h, 92u)); } int main(void) { blkio_dev_t dev; fake_disk d; gpt_part_t parts[16]; unsigned n = 0; uint8_t guid[16]; int rc; fake_rng_reset(); fake_console_reset(); /* gpt_write_one, then gpt_read: the table it writes reads back */ CHECK(fake_disk_open(&dev, &d, DISK_BLOCKS) == BLKIO_OK, "a RAM disk"); rc = gpt_write_one(&dev, gpt_type_starship_identity, 4096u, guid); CHECK(rc == GPT_OK, "gpt_write_one on a blank disk: GPT_OK (%d)", rc); rc = gpt_read(&dev, parts, 16u, &n); CHECK(rc == GPT_OK && n == 1u && memcmp(parts[0].type, gpt_type_starship_identity, 16) == 0 && parts[0].first_lba == 2048u, "and reads back: one identity partition at 2048 (rc %d, n %u)", rc, n); fake_disk_free(&d); /* #180: every entry checked, more than max said */ fake_disk_open(&dev, &d, DISK_BLOCKS); make_table(&d, 16u, 0u, -1); rc = gpt_read(&dev, parts, 16u, &n); CHECK(rc == GPT_OK && n == 16u, "16 used entries, max 16: GPT_OK, 16 (rc %d, n %u)", rc, n); make_table(&d, 18u, 16u, -1); rc = gpt_read(&dev, parts, 16u, &n); CHECK(rc == GPT_MANY && n == 16u, "#180: 18 used entries, ours the 17th and 18th, max 16: GPT_MANY, not cut to 16 as if they were all (rc %d, n %u)", rc, n); make_table(&d, 17u, 0u, 16); rc = gpt_read(&dev, parts, 16u, &n); CHECK(rc == GPT_BAD, "#180: the 17th entry out of range, max 16: GPT_BAD -- it is checked (rc %d)", rc); make_table(&d, 4u, 0u, 2); rc = gpt_read(&dev, parts, 16u, &n); CHECK(rc == GPT_BAD, "an entry out of range within max: GPT_BAD (rc %d)", rc); fake_disk_free(&d); /* #168: gpt_write_one says whether it changed the disk */ fake_disk_open(&dev, &d, DISK_BLOCKS); fake_rng_fail(1); rc = gpt_write_one(&dev, gpt_type_starship_identity, 4096u, guid); CHECK(rc == GPT_EIO && d.writes == 0u, "no RNG: GPT_EIO before any write (rc %d, %u writes)", rc, d.writes); fake_rng_fail(0); fake_disk_free(&d); fake_disk_open(&dev, &d, DISK_BLOCKS); make_table(&d, 2u, 0u, -1); fake_disk_fail_write(&d, 0u); /* sector 0: its first write */ rc = gpt_write_one(&dev, gpt_type_starship_identity, 4096u, guid); CHECK(rc == GPT_EIO && d.writes == 0u, "#168: its first write fails: GPT_EIO, nothing written (rc %d, %u writes)", rc, d.writes); rc = gpt_read(&dev, parts, 16u, &n); CHECK(rc == GPT_OK && n == 2u, "and the old table is still there (rc %d, n %u)", rc, n); fake_disk_free(&d); fake_disk_open(&dev, &d, DISK_BLOCKS); make_table(&d, 2u, 0u, -1); fake_disk_fail_write(&d, 1u); /* the primary entries, after the MBR is zeroed */ rc = gpt_write_one(&dev, gpt_type_starship_identity, 4096u, guid); CHECK(rc == GPT_PART && d.writes > 0u, "#168: a later write fails: GPT_PART, the disk changed (rc %d, %u writes)", rc, d.writes); fake_disk_free(&d); fake_disk_open(&dev, &d, DISK_BLOCKS); d.fail_flush = 1; rc = gpt_write_one(&dev, gpt_type_starship_identity, 4096u, guid); CHECK(rc == GPT_PART && d.writes == 1u, "#168: the first write made, its flush failing: GPT_PART (rc %d, %u writes)", rc, d.writes); fake_disk_free(&d); return DONE(); }