feat(gpt): gpt_write_one -- a new disk of one partition, for the real Zuse's stick (#92, 4.3) [skip ci]

The old MBR zeroed first, the backup header and entries, the primary, the protective MBR last: a write torn anywhere leaves no table. Builds clean on amd64, aarch64, riscv64; checked on the host against blank 600 MiB and 32 MiB images and a used Zuse stick (sfdisk --verify clean, gpt_read reads it back). Not yet called.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GE3SDvQn5EoxdGufk84zz2
This commit is contained in:
Claude
2026-11-08 18:12:04 +00:00
parent 1074520ec9
commit e94cd3245d
3 changed files with 109 additions and 2 deletions
+98 -1
View File
@@ -13,9 +13,12 @@
* 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.
* rewritten with their CRCs, the backup before the primary. And a new disk
* of one partition (gpt_write_one, plan 2026-10-29-stick-5, Task 4): the
* real Zuse's stick, written by MINT-ZUSE.
*/
#include "starkernel/gpt.h"
#include "starkernel/rng.h"
#include "console.h"
#include <stddef.h>
#include <string.h>
@@ -263,6 +266,100 @@ int gpt_grow_last(blkio_dev_t *disk, const uint8_t type[16], uint64_t max_part_s
return GPT_OK;
}
/* ---- the writer: a new disk with one partition (MINT-ZUSE) -------------- */
#define GPT_NENT 128u
#define GPT_ESIZE 128u
#define GPT_ENT_SECTORS ((GPT_NENT * GPT_ESIZE) / GPT_SECTOR) /* 32 */
static uint8_t g_gpt_new_entries[GPT_NENT * GPT_ESIZE];
/* A random GUID, version 4, as on disk (the RNG; the caller has entropy). */
static int gpt_new_guid(uint8_t g[16])
{
if (rng_get_bytes(g, 16) != 0) return -1;
g[7] = (uint8_t)((g[7] & 0x0Fu) | 0x40u); /* version 4, in the third field's high byte */
g[8] = (uint8_t)((g[8] & 0x3Fu) | 0x80u); /* the RFC 4122 variant */
return 0;
}
int gpt_write_one(blkio_dev_t *disk, const uint8_t type[16], uint64_t part_sectors, uint8_t disk_guid[16])
{
static uint8_t hdr[GPT_SECTOR], bak[GPT_SECTOR], mbr[GPT_SECTOR];
static const char name[] = "StarshipOS identity";
blkio_info_t info;
uint64_t disk_sectors, alt, last_usable, first = 2048u, last, i;
uint8_t *e = g_gpt_new_entries, part_guid[16];
if (!disk || disk->forth_block_size != BLKIO_FORTH_BLOCK_SIZE) return GPT_BAD;
if (disk->vt->info(disk, &info) == BLKIO_OK && info.phys_sector_size != 0u &&
info.phys_sector_size != GPT_SECTOR)
return GPT_BAD; /* sectors that are not 512 bytes */
disk_sectors = (uint64_t)disk->total_blocks * (BLKIO_FORTH_BLOCK_SIZE / GPT_SECTOR);
alt = disk_sectors - 1u;
last_usable = alt - GPT_ENT_SECTORS - 1u;
if (disk_sectors < first + 2048u + GPT_ENT_SECTORS + 2u) return GPT_BAD;
if (part_sectors > last_usable - first + 1u) part_sectors = last_usable - first + 1u;
part_sectors -= part_sectors % 2048u; /* whole MiB, so whole 1 KiB blocks */
if (part_sectors == 0u) return GPT_BAD;
last = first + part_sectors - 1u;
if (gpt_new_guid(disk_guid) != 0 || gpt_new_guid(part_guid) != 0) return GPT_EIO;
/* the one entry: type, its own GUID, first and last LBA, its name in UTF-16LE */
memset(g_gpt_new_entries, 0, sizeof(g_gpt_new_entries));
memcpy(e, type, 16);
memcpy(e + 16, part_guid, 16);
put_le64(e + 32, first);
put_le64(e + 40, last);
for (i = 0; name[i]; i++) e[56u + 2u * i] = (uint8_t)name[i];
/* the primary header; the backup is it with its LBAs swapped */
memset(hdr, 0, sizeof(hdr));
memcpy(hdr, "EFI PART", 8);
put_le32(hdr + 8, 0x00010000u);
put_le32(hdr + 12, 92u);
put_le64(hdr + 24, 1u);
put_le64(hdr + 32, alt);
put_le64(hdr + 40, 2u + GPT_ENT_SECTORS);
put_le64(hdr + 48, last_usable);
memcpy(hdr + 56, disk_guid, 16);
put_le64(hdr + 72, 2u);
put_le32(hdr + 80, GPT_NENT);
put_le32(hdr + 84, GPT_ESIZE);
put_le32(hdr + 88, gpt_crc32(g_gpt_new_entries, sizeof(g_gpt_new_entries)));
memcpy(bak, hdr, sizeof(bak));
put_le64(bak + 24, alt);
put_le64(bak + 32, 1u);
put_le64(bak + 72, alt - GPT_ENT_SECTORS);
put_le32(hdr + 16, gpt_crc32(hdr, 92u));
put_le32(bak + 16, gpt_crc32(bak, 92u));
/* the protective MBR: one partition of type 0xEE over the disk */
memset(mbr, 0, sizeof(mbr));
mbr[446u + 1u] = 0x00u; mbr[446u + 2u] = 0x02u; mbr[446u + 3u] = 0x00u; /* CHS start 0/0/2 */
mbr[446u + 4u] = 0xEEu;
mbr[446u + 5u] = 0xFFu; mbr[446u + 6u] = 0xFFu; mbr[446u + 7u] = 0xFFu; /* CHS end: past it */
put_le32(mbr + 446u + 8u, 1u);
put_le32(mbr + 446u + 12u, alt > 0xFFFFFFFFu ? 0xFFFFFFFFu : (uint32_t)alt);
mbr[510] = 0x55u; mbr[511] = 0xAAu;
/* the old MBR zeroed first, then the backup, the primary, and the new MBR
* last: until it is written the disk is not a GPT disk at all (gpt_read:
* no protective MBR), so a write torn anywhere leaves no table naming a
* partition not yet written -- a used stick's old table included */
{
static const uint8_t zero[GPT_SECTOR];
if (put_sectors(disk, 0u, zero, 1u) != 0 || disk->vt->flush(disk) != BLKIO_OK) return GPT_EIO;
}
if (put_sectors(disk, alt - GPT_ENT_SECTORS, g_gpt_new_entries, GPT_ENT_SECTORS) != 0 ||
put_sectors(disk, alt, bak, 1u) != 0 ||
put_sectors(disk, 2u, g_gpt_new_entries, GPT_ENT_SECTORS) != 0 ||
put_sectors(disk, 1u, hdr, 1u) != 0 || disk->vt->flush(disk) != BLKIO_OK ||
put_sectors(disk, 0u, mbr, 1u) != 0 || disk->vt->flush(disk) != BLKIO_OK)
return GPT_EIO;
return GPT_OK;
}
/* ---- a partition as a blkio device ------------------------------------- */
static int part_open(blkio_dev_t *dev, const blkio_params_t *p)