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:
Claude
2026-11-08 12:03:46 +00:00
parent 4e935e4825
commit 5bb247d678
17 changed files with 889 additions and 22 deletions
+94
View File
@@ -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;
}
+57
View File
@@ -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) {