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
This commit is contained in:
+125
-2
@@ -7,8 +7,13 @@
|
||||
* 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.
|
||||
* 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"
|
||||
@@ -51,6 +56,7 @@ static uint64_t le64(const uint8_t *p) { return (uint64_t)le32(p) | (uint64_t)le
|
||||
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)
|
||||
{
|
||||
@@ -125,6 +131,7 @@ int gpt_read(blkio_dev_t *disk, gpt_part_t *parts, unsigned max, unsigned *count
|
||||
}
|
||||
*count = n;
|
||||
memcpy(g_gpt_disk_guid, h + 56, 16);
|
||||
g_gpt_alternate_lba = le64(h + 32);
|
||||
return GPT_OK;
|
||||
}
|
||||
|
||||
@@ -140,6 +147,122 @@ int gpt_find(const gpt_part_t *parts, unsigned count, const uint8_t type[16])
|
||||
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)
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
/* sk_v4_grow.c -- Artemis grows to fill the stick at its first boot
|
||||
* (docs/v4.0.0/STICK.md 5a; plan 2026-10-29-stick-5, Task 2).
|
||||
*
|
||||
* Metadata only: the volume's header, and its fence -- the top-of-volume
|
||||
* system metadata, which holds the Artemis signature and Zuse's genesis
|
||||
* marker (artemis_sig.c, blk_meta_zone_read/write) -- moved to the new top.
|
||||
* The volume's data blocks are never written, and its BAM is not resized: the
|
||||
* image is built with a BAM sized for the cap (mkartemis --bam-for), so the
|
||||
* new blocks are tracked already and read as free.
|
||||
*
|
||||
* The block subsystem places a volume's fence by its device's size, not by
|
||||
* its header (blk_format_or_load_disk), so a volume always fills its
|
||||
* partition: the partition is grown no further than the BAM can track
|
||||
* (sk_v4_grow_cap_sectors), then the volume to the partition.
|
||||
*/
|
||||
#include "starkernel/v4/sk_v4_grow.h"
|
||||
#include "block_subsystem.h"
|
||||
#include <string.h>
|
||||
|
||||
static union {
|
||||
uint8_t bytes[4096];
|
||||
blk_volume_meta_t meta;
|
||||
} g_hdr;
|
||||
static uint8_t g_move[4096];
|
||||
|
||||
static int dev_rw4k(blkio_dev_t *dev, uint64_t devblock, uint8_t *buf, int write)
|
||||
{
|
||||
uint32_t base = (uint32_t)devblock * 4u, k;
|
||||
for (k = 0; k < 4u; k++) {
|
||||
int rc = write ? blkio_write(dev, base + k, buf + k * 1024u)
|
||||
: blkio_read(dev, base + k, buf + k * 1024u);
|
||||
if (rc != BLKIO_OK) return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int read_header(blkio_dev_t *art)
|
||||
{
|
||||
if (dev_rw4k(art, 0u, g_hdr.bytes, 0) != 0) return -1;
|
||||
if (g_hdr.meta.magic != 0x53544652u || g_hdr.meta.version != 2u ||
|
||||
g_hdr.meta.bam_devblocks == 0u || g_hdr.meta.meta_fence_blocks == 0u)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* The most devblocks a volume with this header's BAM can be: no more Forth
|
||||
* blocks in its payload than the BAM tracks (compute_totals_from_B). */
|
||||
static uint64_t bam_cover_devblocks(const blk_volume_meta_t *m)
|
||||
{
|
||||
uint64_t tracked = 32768ULL * m->bam_devblocks;
|
||||
return tracked / 3u + 1u + m->bam_devblocks + m->reloc_devblocks + m->meta_fence_blocks;
|
||||
}
|
||||
|
||||
uint64_t sk_v4_grow_cap_sectors(blkio_dev_t *art, uint64_t cap_sectors)
|
||||
{
|
||||
uint64_t cover;
|
||||
if (read_header(art) != 0) return 0;
|
||||
cover = bam_cover_devblocks(&g_hdr.meta) * 8u; /* 4 KiB devblocks, in 512-byte sectors */
|
||||
return cover < cap_sectors ? cover : cap_sectors;
|
||||
}
|
||||
|
||||
int sk_v4_grow_artemis(blkio_dev_t *art)
|
||||
{
|
||||
blkio_info_t info;
|
||||
uint64_t old_t, new_t, f, k, old_total;
|
||||
|
||||
if (read_header(art) != 0) return -1;
|
||||
if (blkio_info(art, &info) != BLKIO_OK) return -1;
|
||||
old_t = g_hdr.meta.total_devblocks;
|
||||
new_t = info.total_blocks / 4u;
|
||||
if (new_t <= old_t) return 0; /* not grown: it fills its partition */
|
||||
if (new_t > bam_cover_devblocks(&g_hdr.meta)) return -1;
|
||||
f = g_hdr.meta.meta_fence_blocks;
|
||||
/* the old and new fences never overlap (a partition grows by whole MiB,
|
||||
* 256 devblocks, past a 128-devblock fence), so a copy torn by a power
|
||||
* loss is simply done again from the old top at the next boot */
|
||||
if (f >= old_t || new_t - old_t < f) return -1;
|
||||
|
||||
/* the fence to the new top, devblock by devblock */
|
||||
for (k = 0; k < f; k++) {
|
||||
if (dev_rw4k(art, old_t - 1u - k, g_move, 0) != 0) return -1;
|
||||
if (dev_rw4k(art, new_t - 1u - k, g_move, 1) != 0) return -1;
|
||||
}
|
||||
if (blkio_flush(art) != BLKIO_OK) return -1;
|
||||
|
||||
/* the header last: until it is written, the old header names the old top */
|
||||
old_total = g_hdr.meta.total_blocks;
|
||||
g_hdr.meta.total_devblocks = new_t;
|
||||
blk_volume_compute_totals(&g_hdr.meta);
|
||||
g_hdr.meta.free_blocks += g_hdr.meta.total_blocks - old_total;
|
||||
if (dev_rw4k(art, 0u, g_hdr.bytes, 1) != 0) return -1;
|
||||
if (blkio_flush(art) != BLKIO_OK) return -1;
|
||||
return 1;
|
||||
}
|
||||
@@ -22,6 +22,12 @@
|
||||
* Zuse exists -- reads her back, then writes the version 2 marker
|
||||
* (sk_v4_marker.c) naming her.
|
||||
*
|
||||
* Growth (plan 2026-10-29-stick-5, Task 2; STICK.md 5a): a stick of at least
|
||||
* 8 GB whose GPT ends short of the disk -- a stick larger than the image
|
||||
* written to it -- has its Artemis partition grown to fill it (capped at
|
||||
* 256 GiB, and at what its BAM tracks: sk_v4_grow.c), then Artemis's volume
|
||||
* grown to the partition, before anything on it is read. Metadata only.
|
||||
*
|
||||
* Not here (the plan's "not in this gate"): WIREBIND identities, EJECT,
|
||||
* ACL-ZUSE-BOOT.
|
||||
*/
|
||||
@@ -39,6 +45,7 @@
|
||||
#include "starkernel/kbd_input.h"
|
||||
#include "starkernel/gpt.h"
|
||||
#include "starkernel/v4/sk_v4_marker.h"
|
||||
#include "starkernel/v4/sk_v4_grow.h"
|
||||
#ifdef ARCH_AMD64
|
||||
#include "starkernel/ioapic.h"
|
||||
#include "starkernel/i8042.h"
|
||||
@@ -149,6 +156,45 @@ static int sk_v4_is_this_stick(unsigned nparts)
|
||||
return !g_stick_in;
|
||||
}
|
||||
|
||||
/* Growth's limits, from kernel/Makefile (-D): the stick at least this big,
|
||||
* Artemis's partition at most this big */
|
||||
#define SK_V4_GROW_MIN_BYTES STICK_GROW_MIN_BYTES
|
||||
#define SK_V4_CAP_SECTORS (STICK_ARTEMIS_CAP_GIB * 2097152ULL)
|
||||
|
||||
/* This system's stick, its GPT just read (g_gpt_parts, *nparts): when the
|
||||
* stick is larger than its GPT (its backup header is not the disk's last
|
||||
* sector) and at least 8 GB, the Artemis partition is grown to fill it, and
|
||||
* the GPT read again. Returns 0, or -1 when the GPT cannot be read again. */
|
||||
static int sk_v4_grow_stick(sk_v4_drive_t *d, unsigned *nparts)
|
||||
{
|
||||
static int said_small;
|
||||
uint64_t disk_sectors = (uint64_t)d->raw.total_blocks * (BLKIO_FORTH_BLOCK_SIZE / 512u);
|
||||
uint64_t cap, new_last, first;
|
||||
int ai = gpt_find(g_gpt_parts, *nparts, gpt_type_starship_artemis);
|
||||
|
||||
if (ai < 0 || gpt_alternate_lba() == disk_sectors - 1u) return 0; /* the GPT fills the stick */
|
||||
if (disk_sectors * 512u < SK_V4_GROW_MIN_BYTES) {
|
||||
if (!said_small) {
|
||||
console_println("Artemis: the stick is under 8 GB -- not grown");
|
||||
said_small = 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
first = g_gpt_parts[ai].first_lba;
|
||||
if (gpt_part_open(&d->art, &d->art_state, &d->raw, &g_gpt_parts[ai]) != BLKIO_OK) return 0;
|
||||
cap = sk_v4_grow_cap_sectors(&d->art, SK_V4_CAP_SECTORS);
|
||||
if (cap == 0) return 0; /* no Artemis volume: the signature check refuses it */
|
||||
if (gpt_grow_last(&d->raw, gpt_type_starship_artemis, cap, &new_last) != GPT_OK)
|
||||
console_println("GPT: the stick's table could not be grown");
|
||||
else if (new_last - first + 1u >= SK_V4_CAP_SECTORS && first + SK_V4_CAP_SECTORS + 2048u < disk_sectors)
|
||||
console_println("Artemis: past 256 GiB of the stick is left unused");
|
||||
if (gpt_read(&d->raw, g_gpt_parts, SK_V4_GPT_PARTS, nparts) != GPT_OK ||
|
||||
gpt_find(g_gpt_parts, *nparts, gpt_type_starship_identity) < 0 ||
|
||||
gpt_find(g_gpt_parts, *nparts, gpt_type_starship_artemis) < 0)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Zuse's session begins on @p dev: the console is hers, and the screen with
|
||||
* it (docs/v4.0.0/STICK.md 2). @p line says how she was found. */
|
||||
static void sk_v4_login(blkio_dev_t *dev, const uint8_t seed[32], const uint8_t pubkey[32],
|
||||
@@ -241,6 +287,10 @@ static int sk_v4_drive_arrived(sk_v4_drive_t *d)
|
||||
console_println("USB: a drive that is not this system's stick -- not used");
|
||||
return 1;
|
||||
}
|
||||
if (sk_v4_grow_stick(d, &nparts) != 0) {
|
||||
console_println("GPT: the stick's grown table does not read back");
|
||||
return 1;
|
||||
}
|
||||
if (gpt_part_open(&d->art, &d->art_state, &d->raw,
|
||||
&g_gpt_parts[gpt_find(g_gpt_parts, nparts, gpt_type_starship_artemis)]) != BLKIO_OK ||
|
||||
gpt_part_open(&d->part, &d->part_state, &d->raw,
|
||||
@@ -249,6 +299,13 @@ static int sk_v4_drive_arrived(sk_v4_drive_t *d)
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Artemis's volume fills its partition, grown with it (STICK.md 5a) */
|
||||
{
|
||||
int g = sk_v4_grow_artemis(&d->art);
|
||||
if (g > 0) console_println("Artemis: grown to fill the stick");
|
||||
else if (g < 0) console_println("Artemis: the stick's volume could not be grown");
|
||||
}
|
||||
|
||||
/* Artemis first: formatted and signed at build (STICK.md 4); a
|
||||
* signature that does not check out is not used, nor is the stick */
|
||||
if (artemis_sig_check(&d->art, &asig) != ARTEMIS_SIG_OK) {
|
||||
|
||||
Reference in New Issue
Block a user