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
95 lines
3.7 KiB
C
95 lines
3.7 KiB
C
/* 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;
|
|
}
|