The increment 2 final review's minors 1-4 (Captain Bob, 2026-10-27: "fix 1-4"): 1. A protective MBR with no "EFI PART" header is GPT_BAD, not GPT_NONE: the MBR says it is a GPT disk, and a GPT disk is never attached whole. 2. A disk whose block 0 cannot be read is GPT_EIO: "GPT: a USB drive could not be read -- not used", nothing attached (it was read raw, attached whole, and a raw Zuse drive behind it logged her in). 3. The entry array's LBA is checked against the disk's size directly. 4. gpt_bad_entries: the entry array changed, the header valid -- refused. Harness modes gpt_no_header, gpt_read_error (blkdebug EIO on sector 0), gpt_bad_entries. The plan carries the amendment. Red (amd64): gpt_no_header 070335, gpt_read_error 070717 FAIL; gpt_bad_entries 070048 PASS (existing check, now pinned). Green: every GPT mode on amd64 071113-072933; gpt_zuse, gpt_read_error, gpt_no_header on aarch64 073222-074047 and riscv64 074526-075535; the identity gate's and increment 1's sessions 080036-082345, emergency 083130 (082635 was run without EMERGENCY_CONSOLE_ENABLED=1 -- a harness invocation error). Accepted: suite EXIT 0; 093121, 093352, 093750, POST 538/538, dict_hash 0xfec8c47cfc6f437f; test-32, 0 failures. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
198 lines
7.7 KiB
C
198 lines
7.7 KiB
C
/* gpt.c -- the GPT reader, and a block device of one partition.
|
|
*
|
|
* GPT for boot media and installed disks (Captain Bob, 2026-10-26;
|
|
* docs/v4.0.0/STICK.md 3 and 4, overturning FABRIC-2.md F.8 for those two
|
|
* kinds of media). The kernel reads a disk's GPT -- the protective MBR, the
|
|
* primary header and its partition-entry array, both CRC-checked -- and makes
|
|
* a block device of a StarshipOS partition: its block 0 is the partition's
|
|
* first 1 KiB, and nothing past its end can be reached through it. The
|
|
* identity and Artemis formats inside a partition are exactly those of a raw
|
|
* drive today. Reads only. 512-byte logical sectors only (the stick and its
|
|
* data stick); docs/v4.0.0/plans/2026-10-26-stick-2-gpt-reader.md.
|
|
*/
|
|
#include "starkernel/gpt.h"
|
|
#include "console.h"
|
|
#include <stddef.h>
|
|
#include <string.h>
|
|
|
|
/* The two StarshipOS partition type GUIDs, as they are on disk: the first
|
|
* three fields little-endian, the last eight bytes as written.
|
|
* Identity 23BEBFE1-4817-4CF3-A537-9069BF159788,
|
|
* Artemis BCD7B586-1C8E-4EEF-86A3-273A8D8EE624. */
|
|
const uint8_t gpt_type_starship_identity[16] = {
|
|
0xE1, 0xBF, 0xBE, 0x23, 0x17, 0x48, 0xF3, 0x4C,
|
|
0xA5, 0x37, 0x90, 0x69, 0xBF, 0x15, 0x97, 0x88
|
|
};
|
|
const uint8_t gpt_type_starship_artemis[16] = {
|
|
0x86, 0xB5, 0xD7, 0xBC, 0x8E, 0x1C, 0xEF, 0x4E,
|
|
0x86, 0xA3, 0x27, 0x3A, 0x8D, 0x8E, 0xE6, 0x24
|
|
};
|
|
|
|
uint32_t gpt_crc32(const void *buf, size_t len)
|
|
{
|
|
const uint8_t *p = (const uint8_t *)buf;
|
|
uint32_t crc = 0xFFFFFFFFu;
|
|
size_t i;
|
|
int k;
|
|
|
|
for (i = 0; i < len; i++) {
|
|
crc ^= p[i];
|
|
for (k = 0; k < 8; k++) crc = (crc >> 1) ^ (0xEDB88320u & (0u - (crc & 1u)));
|
|
}
|
|
return crc ^ 0xFFFFFFFFu;
|
|
}
|
|
|
|
static uint32_t le32(const uint8_t *p) { return (uint32_t)p[0] | (uint32_t)p[1] << 8 | (uint32_t)p[2] << 16 | (uint32_t)p[3] << 24; }
|
|
static uint64_t le64(const uint8_t *p) { return (uint64_t)le32(p) | (uint64_t)le32(p + 4) << 32; }
|
|
|
|
#define GPT_SECTOR 512u
|
|
#define GPT_ENTRIES_MAX (32u * 1024u) /* the entry array read, at most (128 entries of 128 bytes is 16 KiB) */
|
|
|
|
static uint8_t g_gpt_hdr_block[BLKIO_FORTH_BLOCK_SIZE];
|
|
static uint8_t g_gpt_entries[GPT_ENTRIES_MAX];
|
|
|
|
int gpt_read(blkio_dev_t *disk, gpt_part_t *parts, unsigned max, unsigned *count)
|
|
{
|
|
const uint8_t *mbr = g_gpt_hdr_block, *h = g_gpt_hdr_block + GPT_SECTOR;
|
|
uint32_t hsize, hcrc, nent, esize, ecrc, i;
|
|
uint64_t my_lba, first_usable, last_usable, ent_lba, disk_sectors;
|
|
uint8_t hdr[GPT_SECTOR];
|
|
size_t ebytes;
|
|
unsigned n = 0;
|
|
|
|
*count = 0;
|
|
if (!disk || disk->forth_block_size != BLKIO_FORTH_BLOCK_SIZE) return GPT_NONE;
|
|
{
|
|
static int said;
|
|
blkio_info_t info;
|
|
if (disk->vt->info(disk, &info) == BLKIO_OK && info.phys_sector_size != 0u &&
|
|
info.phys_sector_size != GPT_SECTOR) {
|
|
if (!said) { console_println("GPT: a drive with sectors other than 512 bytes is read without its partition table"); said = 1; }
|
|
return GPT_NONE;
|
|
}
|
|
}
|
|
/* a disk whose first block cannot be read may be a GPT disk: not used */
|
|
if (blkio_read(disk, 0u, g_gpt_hdr_block) != BLKIO_OK) return GPT_EIO;
|
|
|
|
/* the protective MBR: 0x55AA, and a partition of type 0xEE */
|
|
if (mbr[510] != 0x55u || mbr[511] != 0xAAu) return GPT_NONE;
|
|
for (i = 0; i < 4u; i++) if (mbr[446u + 16u * i + 4u] == 0xEEu) break;
|
|
if (i == 4u) return GPT_NONE;
|
|
|
|
/* from here it is a GPT disk -- the protective MBR says so: anything
|
|
* wrong, the header's signature first, is GPT_BAD */
|
|
if (memcmp(h, "EFI PART", 8) != 0) return GPT_BAD;
|
|
if (le32(h + 8) != 0x00010000u) return GPT_BAD;
|
|
hsize = le32(h + 12);
|
|
if (hsize < 92u || hsize > GPT_SECTOR) return GPT_BAD;
|
|
memcpy(hdr, h, GPT_SECTOR);
|
|
hcrc = le32(hdr + 16);
|
|
memset(hdr + 16, 0, 4);
|
|
if (gpt_crc32(hdr, hsize) != hcrc) return GPT_BAD;
|
|
|
|
my_lba = le64(h + 24);
|
|
first_usable = le64(h + 40);
|
|
last_usable = le64(h + 48);
|
|
ent_lba = le64(h + 72);
|
|
nent = le32(h + 80);
|
|
esize = le32(h + 84);
|
|
ecrc = le32(h + 88);
|
|
disk_sectors = (uint64_t)disk->total_blocks * (BLKIO_FORTH_BLOCK_SIZE / GPT_SECTOR);
|
|
if (my_lba != 1u || esize < 128u || esize % 128u != 0u || nent == 0u) return GPT_BAD;
|
|
if (first_usable > last_usable || last_usable >= disk_sectors) return GPT_BAD;
|
|
ebytes = (size_t)nent * esize;
|
|
if (ebytes > GPT_ENTRIES_MAX || ent_lba < 2u || ent_lba >= disk_sectors || (ent_lba % 2u) != 0u) return GPT_BAD;
|
|
if (ent_lba + (ebytes + GPT_SECTOR - 1u) / GPT_SECTOR > first_usable) return GPT_BAD;
|
|
|
|
/* the entry array, a 1 KiB block at a time */
|
|
for (i = 0; (size_t)i * BLKIO_FORTH_BLOCK_SIZE < ebytes; i++) {
|
|
if (blkio_read(disk, (uint32_t)(ent_lba / 2u) + i, g_gpt_entries + (size_t)i * BLKIO_FORTH_BLOCK_SIZE) != BLKIO_OK)
|
|
return GPT_BAD;
|
|
}
|
|
if (gpt_crc32(g_gpt_entries, ebytes) != ecrc) return GPT_BAD;
|
|
|
|
for (i = 0; i < nent && n < max; i++) {
|
|
const uint8_t *e = g_gpt_entries + (size_t)i * esize;
|
|
static const uint8_t zero[16];
|
|
uint64_t first = le64(e + 32), last = le64(e + 40);
|
|
if (memcmp(e, zero, 16) == 0) continue; /* an unused entry */
|
|
if (first < first_usable || last > last_usable || first > last) return GPT_BAD;
|
|
memcpy(parts[n].type, e, 16);
|
|
parts[n].first_lba = first;
|
|
parts[n].last_lba = last;
|
|
n++;
|
|
}
|
|
*count = n;
|
|
return GPT_OK;
|
|
}
|
|
|
|
int gpt_find(const gpt_part_t *parts, unsigned count, const uint8_t type[16])
|
|
{
|
|
unsigned i;
|
|
for (i = 0; i < count; i++) if (memcmp(parts[i].type, type, 16) == 0) return (int)i;
|
|
return -1;
|
|
}
|
|
|
|
/* ---- a partition as a blkio device ------------------------------------- */
|
|
|
|
static int part_open(blkio_dev_t *dev, const blkio_params_t *p)
|
|
{
|
|
gpt_part_dev_t *st = (gpt_part_dev_t *)p->opaque;
|
|
dev->state = st;
|
|
dev->forth_block_size = BLKIO_FORTH_BLOCK_SIZE;
|
|
dev->total_blocks = st->fblocks;
|
|
return BLKIO_OK;
|
|
}
|
|
|
|
static int part_close(blkio_dev_t *dev) { (void)dev; return BLKIO_OK; }
|
|
|
|
static int part_read(blkio_dev_t *dev, uint32_t fblock, void *dst)
|
|
{
|
|
gpt_part_dev_t *st = (gpt_part_dev_t *)dev->state;
|
|
if (fblock >= st->fblocks) return BLKIO_EINVAL;
|
|
return blkio_read(st->disk, st->first_fblock + fblock, dst);
|
|
}
|
|
|
|
static int part_write(blkio_dev_t *dev, uint32_t fblock, const void *src)
|
|
{
|
|
gpt_part_dev_t *st = (gpt_part_dev_t *)dev->state;
|
|
if (fblock >= st->fblocks) return BLKIO_EINVAL;
|
|
return blkio_write(st->disk, st->first_fblock + fblock, src);
|
|
}
|
|
|
|
static int part_flush(blkio_dev_t *dev)
|
|
{
|
|
gpt_part_dev_t *st = (gpt_part_dev_t *)dev->state;
|
|
return st->disk->vt->flush(st->disk);
|
|
}
|
|
|
|
static int part_info(blkio_dev_t *dev, blkio_info_t *out)
|
|
{
|
|
gpt_part_dev_t *st = (gpt_part_dev_t *)dev->state;
|
|
int rc = st->disk->vt->info(st->disk, out);
|
|
if (rc != BLKIO_OK) return rc;
|
|
out->total_blocks = st->fblocks;
|
|
out->phys_size_bytes = (uint64_t)st->fblocks * BLKIO_FORTH_BLOCK_SIZE;
|
|
return BLKIO_OK;
|
|
}
|
|
|
|
static const blkio_vtable_t g_part_vt = { part_open, part_close, part_read, part_write, part_flush, part_info };
|
|
|
|
int gpt_part_open(blkio_dev_t *dev, gpt_part_dev_t *st, blkio_dev_t *disk, const gpt_part_t *p)
|
|
{
|
|
blkio_params_t params;
|
|
uint64_t sectors;
|
|
|
|
if (!dev || !st || !disk || !p) return BLKIO_EINVAL;
|
|
sectors = p->last_lba - p->first_lba + 1u;
|
|
if ((p->first_lba % 2u) != 0u || (sectors % 2u) != 0u) return BLKIO_EINVAL;
|
|
if (p->first_lba / 2u + sectors / 2u > disk->total_blocks) return BLKIO_EINVAL;
|
|
st->disk = disk;
|
|
st->first_fblock = (uint32_t)(p->first_lba / 2u);
|
|
st->fblocks = (uint32_t)(sectors / 2u);
|
|
params.forth_block_size = BLKIO_FORTH_BLOCK_SIZE;
|
|
params.total_blocks = st->fblocks;
|
|
params.opaque = st;
|
|
return blkio_open(dev, &g_part_vt, ¶ms);
|
|
}
|