/* 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 #include #include #include #include #include #include #include #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; } 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(); first_lba = strtoull(argv[4], NULL, 10); sectors = strtoull(argv[6], NULL, 10); 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 (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; } if (format) { 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; } printf("mkartemis: formatted %u blocks, STFR v2, signature stamped\n", r.blocks); } else { blk_volume_meta_t meta; artemis_sig_result_t src; if (blk_get_volume_meta(&meta) != BLK_OK || meta.magic != 0x53544652u || meta.version != 2u) { fprintf(stderr, "mkartemis: no STFR v2 volume header\n"); return 1; } src = artemis_sig_check(&dev, &sig); if (src != ARTEMIS_SIG_OK) { fprintf(stderr, "mkartemis: the Artemis signature does not check out (%d)\n", (int)src); 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"); blk_subsys_shutdown(); } close(r.fd); return 0; }