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
247 lines
9.5 KiB
C
247 lines
9.5 KiB
C
/* mkartemis.c -- format, or check, an Artemis partition inside a disk image.
|
|
*
|
|
* The stick's Artemis partition is formatted when the image is built, not on
|
|
* first boot (docs/v4.0.0/STICK.md 4): its STFR v2 volume header, BAM and
|
|
* relocation table, and its artemis_sig_t genesis signature. This tool does
|
|
* it with the kernel's own code -- v3's block_subsystem.c
|
|
* (blk_subsys_confirm_format) and kernel/src/artemis_sig.c
|
|
* (artemis_sig_genesis_stamp, artemis_sig_check) -- through a blkio device
|
|
* over a byte range of the image file, so the bytes are exactly those the
|
|
* kernel writes. Zuse's genesis marker is not written: the first boot does
|
|
* that. docs/v4.0.0/plans/2026-10-27-stick-3-images.md.
|
|
*
|
|
* mkartemis --format IMG --first-lba L --sectors N [--bam-for M]
|
|
* mkartemis --check IMG --first-lba L --sectors N
|
|
*
|
|
* L, N and M count 512-byte sectors; L and N must be even (1 KiB blocks).
|
|
* --bam-for M sizes the volume's BAM as for a volume of M sectors, so it can
|
|
* grow that far on a larger stick with no BAM to move (docs/v4.0.0/STICK.md
|
|
* 5a; plan 2026-10-29-stick-5, R1 and Task 2). --check reports the BAM's
|
|
* size in 4 KiB devblocks.
|
|
*/
|
|
#define _GNU_SOURCE
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/random.h>
|
|
#include <unistd.h>
|
|
|
|
#include "blkio.h"
|
|
#include "block_subsystem.h"
|
|
#include "starkernel/artemis_sig.h"
|
|
#include "starkernel/rng.h"
|
|
|
|
/* The kernel draws disk_uuid from its entropy backend; on the build machine
|
|
* that is the host's own, getrandom(2). */
|
|
int rng_get_bytes(uint8_t *buf, size_t n)
|
|
{
|
|
while (n > 0) {
|
|
ssize_t got = getrandom(buf, n, 0);
|
|
if (got < 0) {
|
|
if (errno == EINTR) continue;
|
|
return -1;
|
|
}
|
|
buf += got;
|
|
n -= (size_t)got;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* ---- a blkio device over [first_lba, first_lba + sectors) of a file ---- */
|
|
|
|
typedef struct {
|
|
int fd;
|
|
off_t base; /* first_lba * 512 */
|
|
uint32_t blocks; /* sectors / 2 */
|
|
int read_only;
|
|
} range_t;
|
|
|
|
static int range_open(blkio_dev_t *dev, const blkio_params_t *p)
|
|
{
|
|
range_t *r = (range_t *)p->opaque;
|
|
dev->state = r;
|
|
dev->forth_block_size = BLKIO_FORTH_BLOCK_SIZE;
|
|
dev->total_blocks = r->blocks;
|
|
return BLKIO_OK;
|
|
}
|
|
|
|
static int range_close(blkio_dev_t *dev) { (void)dev; return BLKIO_OK; }
|
|
|
|
static int range_read(blkio_dev_t *dev, uint32_t fblock, void *dst)
|
|
{
|
|
range_t *r = (range_t *)dev->state;
|
|
if (fblock >= r->blocks) return BLKIO_EINVAL;
|
|
if (pread(r->fd, dst, BLKIO_FORTH_BLOCK_SIZE, r->base + (off_t)fblock * BLKIO_FORTH_BLOCK_SIZE)
|
|
!= (ssize_t)BLKIO_FORTH_BLOCK_SIZE) return BLKIO_EIO;
|
|
return BLKIO_OK;
|
|
}
|
|
|
|
static int range_write(blkio_dev_t *dev, uint32_t fblock, const void *src)
|
|
{
|
|
range_t *r = (range_t *)dev->state;
|
|
if (fblock >= r->blocks) return BLKIO_EINVAL;
|
|
if (r->read_only) return BLKIO_EIO;
|
|
if (pwrite(r->fd, src, BLKIO_FORTH_BLOCK_SIZE, r->base + (off_t)fblock * BLKIO_FORTH_BLOCK_SIZE)
|
|
!= (ssize_t)BLKIO_FORTH_BLOCK_SIZE) return BLKIO_EIO;
|
|
return BLKIO_OK;
|
|
}
|
|
|
|
static int range_flush(blkio_dev_t *dev)
|
|
{
|
|
range_t *r = (range_t *)dev->state;
|
|
if (r->read_only) return BLKIO_OK;
|
|
return fsync(r->fd) == 0 ? BLKIO_OK : BLKIO_EIO;
|
|
}
|
|
|
|
static int range_info(blkio_dev_t *dev, blkio_info_t *out)
|
|
{
|
|
range_t *r = (range_t *)dev->state;
|
|
out->forth_block_size = BLKIO_FORTH_BLOCK_SIZE;
|
|
out->total_blocks = r->blocks;
|
|
out->phys_sector_size = 512u;
|
|
out->phys_size_bytes = (uint64_t)r->blocks * BLKIO_FORTH_BLOCK_SIZE;
|
|
out->read_only = (uint8_t)r->read_only;
|
|
return BLKIO_OK;
|
|
}
|
|
|
|
static const blkio_vtable_t g_range_vt = { range_open, range_close, range_read, range_write, range_flush, range_info };
|
|
|
|
extern void sf_time_init(void); /* v3/src/platform/platform_init.c: the clock the volume header's times come from */
|
|
|
|
static uint8_t g_ram[(size_t)BLK_RAM_BLOCKS * BLK_FORTH_SIZE];
|
|
|
|
static int usage(void)
|
|
{
|
|
fprintf(stderr, "usage: mkartemis --format|--check IMG --first-lba L --sectors N [--bam-for M]\n");
|
|
return 2;
|
|
}
|
|
|
|
/* A sector count: decimal digits only, the whole string, no overflow. */
|
|
static int parse_count(const char *s, unsigned long long *out)
|
|
{
|
|
char *end;
|
|
if (*s < '0' || *s > '9') return -1;
|
|
errno = 0;
|
|
*out = strtoull(s, &end, 10);
|
|
return (errno != 0 || *end != '\0') ? -1 : 0;
|
|
}
|
|
|
|
/* Put the partition into the block subsystem: its first LBN and user blocks. */
|
|
static int attach(blkio_dev_t *dev, uint32_t *start_lbn, uint32_t *count)
|
|
{
|
|
if (blk_subsys_init(g_ram, sizeof(g_ram)) != BLK_OK || blk_subsys_attach_device(dev) != BLK_OK ||
|
|
blk_get_device_range(dev, start_lbn, count) != BLK_OK) {
|
|
fprintf(stderr, "mkartemis: the block subsystem would not take the partition\n");
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* The partition holds an STFR v2 volume and a good Artemis signature. The
|
|
* header is read from the disk itself and judged as attach judges it
|
|
* (block_subsystem.c blk_format_or_load_disk): blk_get_volume_meta would give
|
|
* the geometry computed in memory for a blank disk, magic and all. */
|
|
static uint32_t g_bam_devblocks; /* the BAM's size, as check_volume read it */
|
|
|
|
static int check_volume(blkio_dev_t *dev, uint32_t *count, artemis_sig_t *sig)
|
|
{
|
|
static uint8_t hdr[BLK_DEVICE_SECTOR];
|
|
blk_volume_meta_t meta;
|
|
artemis_sig_result_t src;
|
|
uint32_t i;
|
|
uint32_t start_lbn;
|
|
int rc = -1;
|
|
|
|
if (attach(dev, &start_lbn, count) != 0) return -1;
|
|
for (i = 0; i < BLK_DEVICE_SECTOR / BLKIO_FORTH_BLOCK_SIZE; i++)
|
|
if (blkio_read(dev, i, hdr + i * BLKIO_FORTH_BLOCK_SIZE) != BLKIO_OK) memset(hdr, 0, sizeof(hdr));
|
|
memset(&meta, 0, sizeof(meta));
|
|
memcpy(&meta, hdr, sizeof(meta) < sizeof(hdr) ? sizeof(meta) : sizeof(hdr));
|
|
if (meta.magic != 0x53544652u || meta.version != 2u || meta.bam_devblocks == 0u)
|
|
fprintf(stderr, "mkartemis: no STFR v2 volume header\n");
|
|
else if ((src = artemis_sig_check(dev, sig)) != ARTEMIS_SIG_OK)
|
|
fprintf(stderr, "mkartemis: the Artemis signature does not check out (%d)\n", (int)src);
|
|
else
|
|
rc = 0;
|
|
g_bam_devblocks = meta.bam_devblocks;
|
|
blk_subsys_shutdown();
|
|
return rc;
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
const char *mode, *img;
|
|
unsigned long long first_lba, sectors, bam_for = 0;
|
|
range_t r;
|
|
blkio_dev_t dev;
|
|
blkio_params_t params;
|
|
uint32_t start_lbn = 0, count = 0;
|
|
artemis_sig_t sig;
|
|
int i, format;
|
|
|
|
if ((argc != 7 && argc != 9) || strcmp(argv[3], "--first-lba") != 0 || strcmp(argv[5], "--sectors") != 0)
|
|
return usage();
|
|
mode = argv[1];
|
|
img = argv[2];
|
|
if (strcmp(mode, "--format") == 0) format = 1;
|
|
else if (strcmp(mode, "--check") == 0) format = 0;
|
|
else return usage();
|
|
if (parse_count(argv[4], &first_lba) != 0 || parse_count(argv[6], §ors) != 0) {
|
|
fprintf(stderr, "mkartemis: --first-lba and --sectors take a number of sectors\n");
|
|
return 1;
|
|
}
|
|
if (argc == 9) {
|
|
if (!format || strcmp(argv[7], "--bam-for") != 0 || parse_count(argv[8], &bam_for) != 0 || bam_for < sectors) {
|
|
fprintf(stderr, "mkartemis: --bam-for takes a number of sectors, at least --sectors, with --format\n");
|
|
return 1;
|
|
}
|
|
}
|
|
if (first_lba % 2u != 0u || sectors % 2u != 0u || sectors == 0u || sectors / 2u > 0xFFFFFFFFull) {
|
|
fprintf(stderr, "mkartemis: the partition must start and end on a 1 KiB block (even LBA and sector count)\n");
|
|
return 1;
|
|
}
|
|
|
|
r.fd = open(img, format ? O_RDWR : O_RDONLY);
|
|
if (r.fd < 0) { fprintf(stderr, "mkartemis: %s: %s\n", img, strerror(errno)); return 1; }
|
|
r.base = (off_t)first_lba * 512;
|
|
r.blocks = (uint32_t)(sectors / 2u);
|
|
r.read_only = !format;
|
|
params.forth_block_size = BLKIO_FORTH_BLOCK_SIZE;
|
|
params.total_blocks = r.blocks;
|
|
params.opaque = &r;
|
|
if (blkio_open(&dev, &g_range_vt, ¶ms) != BLKIO_OK) { fprintf(stderr, "mkartemis: cannot open the range\n"); return 1; }
|
|
|
|
sf_time_init();
|
|
if (format) {
|
|
blk_subsys_set_format_bam_for(bam_for / 8u); /* sectors -> 4 KiB devblocks */
|
|
if (attach(&dev, &start_lbn, &count) != 0) return 1;
|
|
if (artemis_sig_check(&dev, &sig) != ARTEMIS_SIG_BLANK) {
|
|
fprintf(stderr, "mkartemis: the partition is not blank -- not formatted\n");
|
|
return 1;
|
|
}
|
|
if (blk_subsys_confirm_format(start_lbn) != BLK_OK) { fprintf(stderr, "mkartemis: format failed\n"); return 1; }
|
|
blk_subsys_shutdown();
|
|
if (artemis_sig_genesis_stamp(&dev) != 0) { fprintf(stderr, "mkartemis: the signature stamp failed\n"); return 1; }
|
|
/* blk_commit_format does not report a failed write: read the volume back, read-only */
|
|
close(r.fd);
|
|
r.fd = open(img, O_RDONLY);
|
|
r.read_only = 1;
|
|
if (r.fd < 0 || check_volume(&dev, &count, &sig) != 0) {
|
|
fprintf(stderr, "mkartemis: the format did not read back -- the partition is not usable\n");
|
|
return 1;
|
|
}
|
|
printf("mkartemis: formatted %u blocks, STFR v2, signature stamped, read back ok\n", r.blocks);
|
|
} else {
|
|
if (check_volume(&dev, &count, &sig) != 0) return 1;
|
|
printf("mkartemis: STFR v2, %u user blocks, bam %u devblocks, signature ok, disk_uuid ",
|
|
count, g_bam_devblocks);
|
|
for (i = 0; i < 16; i++) printf("%02x", sig.disk_uuid[i]);
|
|
printf("\n");
|
|
}
|
|
close(r.fd);
|
|
return 0;
|
|
}
|