Fix silent disk overwrite of unrecognized Artemis disks
The generic block subsystem (blk_format_or_load_disk) auto-reformatted any disk lacking its own low-level 'STFR' header at attach time, before Artemis's Forth-level BLANK/LithosAnanke/Unrecognized classification ever ran -- so ART-HALT-UNRECOG's "Disk preserved" message was false. Split detection from commit: an unrecognized/blank disk is now left PROVISIONAL (geometry computed in memory only, all writes refused) until explicitly confirmed via the new blk_subsys_confirm_format() / BLK-CONFIRM-FORMAT primitive. Artemis calls it from ART-FORMAT and ART-RESUME, never from ART-HALT-UNRECOG. Verified on amd64/aarch64/riscv64: parity intact (identical dict_hash), normal recognized-disk resume + persist-read unaffected, and a regenerated disk/artemis-unrecognized-test.img (the old copy had itself been silently corrupted by this exact bug) now stays byte-for-byte identical across a halted boot on amd64 and riscv64. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
cc6c8c43f3
commit
148c4aa12c
+84
-33
@@ -132,6 +132,7 @@ struct blk_dev_slot {
|
||||
blk_volume_meta_t vol_meta;
|
||||
uint8_t vol_meta_dirty;
|
||||
uint8_t bam_dirty; /* any bam entry dirty → needs BAM flush to disk */
|
||||
uint8_t format_state; /* BLK_FMT_FORMATTED or BLK_FMT_PROVISIONAL */
|
||||
cache_slot_t cache[DISK_CACHE_SLOTS];
|
||||
|
||||
/* BAM: one entry per user block, heap-allocated at attach time */
|
||||
@@ -397,32 +398,11 @@ static int bam_flush_to_disk(blk_dev_slot_t *slot) {
|
||||
|
||||
/* ===== volume format / load (per disk slot) ===== */
|
||||
|
||||
static int blk_format_or_load_disk(blk_dev_slot_t *slot) {
|
||||
uint8_t hdr[BLK_DEVICE_SECTOR] = {0};
|
||||
(void) read_header_4k(slot, hdr);
|
||||
volmeta_from_buf(&slot->vol_meta, hdr);
|
||||
|
||||
if (slot->vol_meta.magic == 0x53544652u && slot->vol_meta.version == 2) {
|
||||
slot->vol_meta.total_devblocks = (uint64_t) udiv_floor(slot->total_blkio_blocks_1k, 4);
|
||||
if (slot->vol_meta.bam_devblocks == 0) goto fresh_format;
|
||||
|
||||
uint64_t disk_user = (slot->vol_meta.total_blocks > BLK_DISK_SYS_RESERVED)
|
||||
? (slot->vol_meta.total_blocks - BLK_DISK_SYS_RESERVED) : 0;
|
||||
slot->user_blocks = (disk_user > 0xFFFFFFFFu) ? 0xFFFFFFFFu : (uint32_t) disk_user;
|
||||
|
||||
slot->bam = (blk_bam_entry_t *) calloc(slot->user_blocks, sizeof(blk_bam_entry_t));
|
||||
if (!slot->bam) return BLK_ENOMEM;
|
||||
|
||||
slot->devblock_base_4k = slot->vol_meta.devblock_base;
|
||||
return bam_sync_from_disk(slot);
|
||||
}
|
||||
|
||||
if (slot->vol_meta.magic != 0 || slot->vol_meta.version != 0) {
|
||||
log_message(LOG_WARN, "blk: unrecognised disk header (magic=0x%08x ver=%u) — fresh format",
|
||||
slot->vol_meta.magic, slot->vol_meta.version);
|
||||
}
|
||||
|
||||
fresh_format:
|
||||
/* Compute fresh volume geometry in memory only. Pure function of device
|
||||
* size — never touches the disk. Used both for the provisional in-memory
|
||||
* state (detection) and, unchanged, as the values ultimately committed by
|
||||
* blk_commit_format(). */
|
||||
static void blk_compute_fresh_geometry(blk_dev_slot_t *slot) {
|
||||
memset(&slot->vol_meta, 0, sizeof(slot->vol_meta));
|
||||
slot->vol_meta.magic = 0x53544652u;
|
||||
slot->vol_meta.version = 2;
|
||||
@@ -444,24 +424,73 @@ fresh_format:
|
||||
slot->user_blocks = (disk_user > 0xFFFFFFFFu) ? 0xFFFFFFFFu : (uint32_t) disk_user;
|
||||
slot->devblock_base_4k = slot->vol_meta.devblock_base;
|
||||
|
||||
slot->bam = (blk_bam_entry_t *) calloc(slot->user_blocks, sizeof(blk_bam_entry_t));
|
||||
if (!slot->bam) return BLK_ENOMEM;
|
||||
|
||||
slot->vol_meta.free_blocks = slot->vol_meta.total_blocks > BLK_DISK_SYS_RESERVED
|
||||
? slot->vol_meta.total_blocks - BLK_DISK_SYS_RESERVED : 0;
|
||||
}
|
||||
|
||||
/* Actually write the fresh format to disk (zero BAM pages, write header).
|
||||
* Only reachable via blk_subsys_confirm_format() — never automatically. */
|
||||
static int blk_commit_format(blk_dev_slot_t *slot) {
|
||||
if (!slot->dev) return BLK_EINVAL;
|
||||
if (slot->format_state == BLK_FMT_FORMATTED) return BLK_OK; /* already committed */
|
||||
|
||||
/* zero BAM pages on disk, then write header */
|
||||
uint8_t z[1024] = {0};
|
||||
for (uint32_t i = 0; i < slot->vol_meta.bam_devblocks; i++) {
|
||||
uint32_t base1k = (slot->vol_meta.bam_start + i) * 4u;
|
||||
for (uint32_t k = 0; k < 4; k++) (void) blkio_write(slot->dev, base1k + k, z);
|
||||
}
|
||||
uint8_t hdr[BLK_DEVICE_SECTOR];
|
||||
volmeta_to_buf(&slot->vol_meta, hdr);
|
||||
(void) write_header_4k(slot, hdr);
|
||||
/* flush with reserved bits marked */
|
||||
slot->bam_dirty = 1;
|
||||
(void) bam_flush_to_disk(slot);
|
||||
blkio_flush(slot->dev);
|
||||
|
||||
slot->format_state = BLK_FMT_FORMATTED;
|
||||
return BLK_OK;
|
||||
}
|
||||
|
||||
/* Detect the disk's low-level container state. NEVER writes to disk here —
|
||||
* on anything other than a recognized STFR/v2 header, the slot is left
|
||||
* PROVISIONAL with geometry computed in memory only, and all writes to it
|
||||
* are refused (see blk_get_buffer/blk_update) until the disk's owner
|
||||
* explicitly calls blk_subsys_confirm_format(). This is what lets a
|
||||
* higher-level "unrecognized disk, halt" decision actually mean the disk
|
||||
* was left untouched. */
|
||||
static int blk_format_or_load_disk(blk_dev_slot_t *slot) {
|
||||
uint8_t hdr[BLK_DEVICE_SECTOR] = {0};
|
||||
(void) read_header_4k(slot, hdr);
|
||||
volmeta_from_buf(&slot->vol_meta, hdr);
|
||||
|
||||
if (slot->vol_meta.magic == 0x53544652u && slot->vol_meta.version == 2 &&
|
||||
slot->vol_meta.bam_devblocks != 0) {
|
||||
slot->vol_meta.total_devblocks = (uint64_t) udiv_floor(slot->total_blkio_blocks_1k, 4);
|
||||
|
||||
uint64_t disk_user = (slot->vol_meta.total_blocks > BLK_DISK_SYS_RESERVED)
|
||||
? (slot->vol_meta.total_blocks - BLK_DISK_SYS_RESERVED) : 0;
|
||||
slot->user_blocks = (disk_user > 0xFFFFFFFFu) ? 0xFFFFFFFFu : (uint32_t) disk_user;
|
||||
|
||||
slot->bam = (blk_bam_entry_t *) calloc(slot->user_blocks, sizeof(blk_bam_entry_t));
|
||||
if (!slot->bam) return BLK_ENOMEM;
|
||||
|
||||
slot->devblock_base_4k = slot->vol_meta.devblock_base;
|
||||
slot->format_state = BLK_FMT_FORMATTED;
|
||||
return bam_sync_from_disk(slot);
|
||||
}
|
||||
|
||||
if (slot->vol_meta.magic != 0 || slot->vol_meta.version != 0) {
|
||||
log_message(LOG_WARN, "blk: unrecognised disk header (magic=0x%08x ver=%u) — "
|
||||
"deferring format decision to disk owner, disk untouched",
|
||||
slot->vol_meta.magic, slot->vol_meta.version);
|
||||
}
|
||||
|
||||
blk_compute_fresh_geometry(slot);
|
||||
|
||||
slot->bam = (blk_bam_entry_t *) calloc(slot->user_blocks, sizeof(blk_bam_entry_t));
|
||||
if (!slot->bam) return BLK_ENOMEM;
|
||||
|
||||
slot->format_state = BLK_FMT_PROVISIONAL;
|
||||
return BLK_OK;
|
||||
}
|
||||
|
||||
@@ -527,7 +556,9 @@ int blk_subsys_attach_device(struct blkio_dev *dev) {
|
||||
if (rc != BLK_OK) { if (slot->bam) free(slot->bam); free(slot); return rc; }
|
||||
|
||||
slot->vol_meta.mounted_time = blk_get_timestamp();
|
||||
slot->vol_meta_dirty = 1;
|
||||
/* Don't dirty a PROVISIONAL slot's header — nothing may be written to
|
||||
* disk until the owner explicitly confirms the format. */
|
||||
if (slot->format_state == BLK_FMT_FORMATTED) slot->vol_meta_dirty = 1;
|
||||
|
||||
chain_append(slot);
|
||||
g.total_user_lbn += slot->user_blocks;
|
||||
@@ -553,7 +584,10 @@ int blk_subsys_shutdown(void) {
|
||||
blk_dev_slot_t *s = g.head;
|
||||
while (s) {
|
||||
if (s->dev) {
|
||||
if (s->vol_meta_dirty) {
|
||||
/* Never persist anything for a slot still awaiting an explicit
|
||||
* format decision from its owner — defensive; should already
|
||||
* be unreachable since nothing can dirty a PROVISIONAL slot. */
|
||||
if (s->vol_meta_dirty && s->format_state == BLK_FMT_FORMATTED) {
|
||||
uint8_t hdr[BLK_DEVICE_SECTOR];
|
||||
volmeta_to_buf(&s->vol_meta, hdr);
|
||||
(void) write_header_4k(s, hdr);
|
||||
@@ -571,6 +605,18 @@ int blk_subsys_shutdown(void) {
|
||||
return BLK_OK;
|
||||
}
|
||||
|
||||
/* Commit the low-level format for the slot owning lbn. Called by the
|
||||
* disk's higher-level owner once it has classified the disk content and
|
||||
* decided it is safe to touch (e.g. Artemis's ART-BOOT-DETECT resolving
|
||||
* to BLANK or a recognized marker). Must NOT be called on the path that
|
||||
* halts for unrecognized content — that is what keeps the disk untouched. */
|
||||
int blk_subsys_confirm_format(uint32_t lbn) {
|
||||
if (!g.initialized) return BLK_ENODEV;
|
||||
blk_dev_slot_t *slot = lbn_to_slot(lbn);
|
||||
if (!slot || !slot->dev) return BLK_ERANGE;
|
||||
return blk_commit_format(slot);
|
||||
}
|
||||
|
||||
/* ===== block buffer access ===== */
|
||||
|
||||
uint8_t *blk_get_buffer(uint32_t block_num, int writable) {
|
||||
@@ -599,6 +645,7 @@ uint8_t *blk_get_buffer(uint32_t block_num, int writable) {
|
||||
}
|
||||
|
||||
/* disk */
|
||||
if (writable && slot->format_state == BLK_FMT_PROVISIONAL) return NULL;
|
||||
uint32_t rel_pbn = lbn_to_slot_pbn(slot, block_num);
|
||||
uint32_t dev4k = slot_pbn_to_devblock(rel_pbn);
|
||||
uint32_t pack = slot_pbn_pack_offset(rel_pbn);
|
||||
@@ -627,6 +674,7 @@ int blk_update(uint32_t block_num) {
|
||||
|
||||
blk_dev_slot_t *slot = lbn_to_slot(block_num);
|
||||
if (!slot) return BLK_ERANGE;
|
||||
if (slot->format_state == BLK_FMT_PROVISIONAL) return BLK_ERESERVED;
|
||||
|
||||
uint32_t offset = block_num - slot->start_lbn;
|
||||
slot->bam[offset].allocated = 1;
|
||||
@@ -728,6 +776,7 @@ int blk_mark_allocated(uint32_t block_num) {
|
||||
if (block_num < g.ram_user) return BLK_EINVAL;
|
||||
blk_dev_slot_t *slot = lbn_to_slot(block_num);
|
||||
if (!slot) return BLK_ERANGE;
|
||||
if (slot->format_state == BLK_FMT_PROVISIONAL) return BLK_ERESERVED;
|
||||
uint32_t offset = block_num - slot->start_lbn;
|
||||
if (!slot->bam[offset].allocated) {
|
||||
slot->bam[offset].allocated = 1;
|
||||
@@ -745,6 +794,7 @@ int blk_mark_free(uint32_t block_num) {
|
||||
if (block_num < g.ram_user) return BLK_EINVAL;
|
||||
blk_dev_slot_t *slot = lbn_to_slot(block_num);
|
||||
if (!slot) return BLK_ERANGE;
|
||||
if (slot->format_state == BLK_FMT_PROVISIONAL) return BLK_ERESERVED;
|
||||
uint32_t offset = block_num - slot->start_lbn;
|
||||
if (slot->bam[offset].allocated) {
|
||||
slot->bam[offset].allocated = 0;
|
||||
@@ -765,7 +815,8 @@ int blk_allocate(uint32_t *block_num) {
|
||||
|
||||
blk_dev_slot_t *s = g.head;
|
||||
while (s) {
|
||||
if (s->raw_base || !s->dev || s->vol_meta.free_blocks == 0) { s = s->next; continue; }
|
||||
if (s->raw_base || !s->dev || s->format_state == BLK_FMT_PROVISIONAL ||
|
||||
s->vol_meta.free_blocks == 0) { s = s->next; continue; }
|
||||
|
||||
uint32_t limit = s->user_blocks;
|
||||
uint32_t hint = (s->vol_meta.first_free > s->start_lbn)
|
||||
|
||||
Reference in New Issue
Block a user