Files
LithosAnanke/kernel/src/gpt.c
T
Claude 5bb247d678 wip(kernel): Artemis grows to fill the stick at its first boot -- metadata only, capped at 256 GiB (#92, Task 2) [skip ci]
Not yet accepted (plan 2026-10-29-stick-5, Task 2, step 3).
- gpt.c: the writer, gpt_grow_last() -- the backup entry array and header to
  the disk's end, the last partition to the last usable sector (whole MiB,
  capped), both headers' CRCs, the backup before the primary, the
  protective MBR's size; gpt_alternate_lba().
- sk_v4_grow.c: the volume grown to its partition -- its fence (the Artemis
  signature, Zuse's genesis marker) moved to the new top, the header last;
  the partition grown no further than the BAM tracks (the block subsystem
  places the fence by the device's size, so a volume must fill it).
- sk_v4_identity.c: a stick of 8 GB or more whose GPT ends short of the disk
  is grown before its partitions are opened; "Artemis: grown to fill the
  stick" / "under 8 GB -- not grown" / "past 256 GiB ... left unused".
- block_subsystem: blk_volume_compute_totals() (compute_totals_from_B,
  public) and blk_subsys_set_format_bam_for(); v3's behaviour unchanged.
- mkartemis --bam-for, mkdiskimage.sh --bam-for, kernel/Makefile
  STICK_GROW_MIN_BYTES / STICK_ARTEMIS_CAP_GIB: the stick's and data stick's
  Artemis BAM sized for 256 GiB (6144 devblocks). STICK.md 5a: 256 GiB (R1).

After: stick_grow PASS (16 GiB: 12129370 user blocks, 8 s POST to prompt;
second boot not grown, recognised; sfdisk --verify: no errors), stick_small
PASS, check_images.sh PASS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GE3SDvQn5EoxdGufk84zz2
2026-11-08 12:03:46 +00:00

328 lines
13 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. 512-byte logical sectors only (the stick and its data stick);
* docs/v4.0.0/plans/2026-10-26-stick-2-gpt-reader.md.
*
* The writer (plan 2026-10-29-stick-5, Task 2; STICK.md 5a): the stick's last
* partition grown to the end of a disk larger than the image written to it --
* the backup header and entry array moved to the disk's end, both headers
* rewritten with their CRCs, the backup before the primary.
*/
#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];
static uint8_t g_gpt_disk_guid[16]; /* the DiskGUID of the last gpt_read that gave GPT_OK */
static uint64_t g_gpt_alternate_lba; /* and its backup header's LBA */
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;
memcpy(g_gpt_disk_guid, h + 56, 16);
g_gpt_alternate_lba = le64(h + 32);
return GPT_OK;
}
void gpt_disk_guid(uint8_t guid[16])
{
memcpy(guid, g_gpt_disk_guid, 16);
}
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;
}
/* ---- the writer: growing the last partition to the disk's end ---------- */
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)); }
static uint8_t g_gpt_rmw[BLKIO_FORTH_BLOCK_SIZE];
/* Write `n` 512-byte sectors from `src` at sector `lba`: each through a
* read-modify-write of the 1 KiB block that holds it, so a sector at an odd
* LBA (the backup header, the backup entry array) is written alone. */
static int put_sectors(blkio_dev_t *disk, uint64_t lba, const uint8_t *src, uint32_t n)
{
uint32_t i;
for (i = 0; i < n; i++) {
uint64_t s = lba + i;
uint32_t blk = (uint32_t)(s / 2u);
if (blkio_read(disk, blk, g_gpt_rmw) != BLKIO_OK) return -1;
memcpy(g_gpt_rmw + (s % 2u) * GPT_SECTOR, src + (size_t)i * GPT_SECTOR, GPT_SECTOR);
if (blkio_write(disk, blk, g_gpt_rmw) != BLKIO_OK) return -1;
}
return 0;
}
uint64_t gpt_alternate_lba(void)
{
return g_gpt_alternate_lba;
}
int gpt_grow_last(blkio_dev_t *disk, const uint8_t type[16], uint64_t max_part_sectors, uint64_t *new_last_lba)
{
static gpt_part_t parts[16];
static uint8_t hdr[GPT_SECTOR], bak[GPT_SECTOR];
const uint8_t *h = g_gpt_hdr_block + GPT_SECTOR;
uint8_t *mbr = g_gpt_hdr_block, *e = (uint8_t *)0;
uint64_t disk_sectors, alt, ent_lba, new_alt, new_ent, new_last_usable, first, last, want, top = 0;
uint32_t nent, esize, ent_sectors, i;
unsigned count;
size_t ebytes;
int rc;
rc = gpt_read(disk, parts, 16u, &count);
if (rc != GPT_OK) return rc == GPT_NONE ? GPT_BAD : rc;
nent = le32(h + 80);
esize = le32(h + 84);
ebytes = (size_t)nent * esize;
ent_sectors = (uint32_t)((ebytes + GPT_SECTOR - 1u) / GPT_SECTOR);
ent_lba = le64(h + 72);
alt = le64(h + 32);
disk_sectors = (uint64_t)disk->total_blocks * (BLKIO_FORTH_BLOCK_SIZE / GPT_SECTOR);
/* the partition of `type`, which must be the last on the disk */
for (i = 0; i < nent; i++) {
uint8_t *x = g_gpt_entries + (size_t)i * esize;
static const uint8_t zero[16];
if (memcmp(x, zero, 16) == 0) continue;
if (le64(x + 40) > top) top = le64(x + 40);
if (!e && memcmp(x, type, 16) == 0) e = x;
}
if (!e) return GPT_BAD;
first = le64(e + 32);
last = le64(e + 40);
*new_last_lba = last;
if (alt == disk_sectors - 1u) return GPT_OK; /* already ends at the disk's end */
if (alt > disk_sectors - 1u || last != top) return GPT_BAD;
/* the backup entry array and header at the disk's end; the partition to
* the last usable sector before them, on whole MiB, at most max sectors */
new_alt = disk_sectors - 1u;
new_ent = new_alt - ent_sectors;
new_last_usable = new_ent - 1u;
if (new_last_usable < last) return GPT_BAD;
want = new_last_usable - first + 1u;
if (want > max_part_sectors) want = max_part_sectors;
want -= want % 2048u;
if (first + want - 1u > last) {
last = first + want - 1u;
put_le64(e + 40, last);
}
/* the primary header, then the backup: each with its own LBAs and CRC */
memcpy(hdr, h, GPT_SECTOR);
put_le64(hdr + 32, new_alt);
put_le64(hdr + 48, new_last_usable);
put_le32(hdr + 88, gpt_crc32(g_gpt_entries, ebytes));
memcpy(bak, hdr, GPT_SECTOR);
put_le64(bak + 24, new_alt);
put_le64(bak + 32, 1u);
put_le64(bak + 72, new_ent);
put_le32(hdr + 16, 0u);
put_le32(hdr + 16, gpt_crc32(hdr, le32(hdr + 12)));
put_le32(bak + 16, 0u);
put_le32(bak + 16, gpt_crc32(bak, le32(bak + 12)));
/* the backup before the primary: a write torn between them leaves the old
* primary, which still checks out, and the next boot grows again */
if (put_sectors(disk, new_ent, g_gpt_entries, ent_sectors) != 0 ||
put_sectors(disk, new_alt, bak, 1u) != 0 || disk->vt->flush(disk) != BLKIO_OK)
return GPT_EIO;
if (put_sectors(disk, ent_lba, g_gpt_entries, ent_sectors) != 0 ||
put_sectors(disk, 1u, hdr, 1u) != 0 || disk->vt->flush(disk) != BLKIO_OK)
return GPT_EIO;
/* the protective MBR's partition covers the disk (as many sectors as fit) */
for (i = 0; i < 4u; i++) {
uint8_t *p = mbr + 446u + 16u * i;
if (p[4] == 0xEEu) {
put_le32(p + 12, disk_sectors - 1u > 0xFFFFFFFFu ? 0xFFFFFFFFu : (uint32_t)(disk_sectors - 1u));
break;
}
}
if (put_sectors(disk, 0u, mbr, 1u) != 0 || disk->vt->flush(disk) != BLKIO_OK) return GPT_EIO;
*new_last_lba = last;
return GPT_OK;
}
/* ---- 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, &params);
}