Files
LithosAnanake/tools/mkartemis.c
T
rajamesandClaude Opus 5.5 195b7feb07 fix(build): image checks cannot pass on a stale stick; mkartemis refuses bad numbers and reads its format back
Review of the stick's increment 3 (a4221d8b..c416c805), on Captain Bob's word:

- check_images.sh deletes both images before building and makes each build a
  check: a failed build left last run's lithos-pc.img in place, and every stick
  check passed against it.
- mkartemis takes --first-lba and --sectors as plain decimal numbers only:
  "abc", "-2", "2048x" and the like formatted at LBA 0, over the GPT.
- mkartemis --format reopens the image read-only and reads the volume back:
  blk_commit_format ignores its own write errors, so a header that never
  landed was reported formatted. The header is read from the disk and judged
  as attach judges it (magic, version 2, BAM size); blk_get_volume_meta gives
  the in-memory geometry of a blank disk, magic included, so --check no longer
  relies on it either.

check_images.sh keeps each case as a check (40, PASS). stick_boot and datastick
on amd64/aarch64/riscv64 PASS on the rebuilt tools.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 19:02:54 -04:00

231 lines
8.6 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
* mkartemis --check IMG --first-lba L --sectors N
*
* L and N count 512-byte sectors; both must be even (1 KiB blocks).
*/
#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\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 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;
blk_subsys_shutdown();
return rc;
}
int main(int argc, char **argv)
{
const char *mode, *img;
unsigned long long first_lba, sectors;
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 || 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], &sectors) != 0) {
fprintf(stderr, "mkartemis: --first-lba and --sectors take a number of sectors\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, &params) != BLKIO_OK) { fprintf(stderr, "mkartemis: cannot open the range\n"); return 1; }
sf_time_init();
if (format) {
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, signature ok, disk_uuid ", count);
for (i = 0; i < 16; i++) printf("%02x", sig.disk_uuid[i]);
printf("\n");
}
close(r.fd);
return 0;
}