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
+17
-3
@@ -156,9 +156,23 @@ Acceptance criteria status, verified 2026-08-02:
|
||||
silently stuck failing this exact test since before this repo's git
|
||||
history begins (bad *data* from some pre-history run, not bad code)
|
||||
until reformatted this same day — see `disk/README.md`.
|
||||
6. ⬜ `ART-HALT-UNRECOG` exists and looks correct by inspection, but has
|
||||
not actually been exercised against genuinely unrecognized disk
|
||||
content — untested, not confirmed.
|
||||
6. ✅ `ART-HALT-UNRECOG` exercised against genuinely unrecognized disk
|
||||
content and confirmed to leave the disk byte-for-byte untouched
|
||||
(sha256 identical before/after), on amd64 and riscv64 — see
|
||||
`disk/README.md`. This closes a real gap found and fixed 2026-08-02:
|
||||
the *generic* block subsystem (`src/block_subsystem.c`) used to
|
||||
auto-reformat any disk it didn't recognize by its own low-level
|
||||
header magic, at attach time, before Artemis's Forth-level
|
||||
BLANK/LithosAnanke/Unrecognized check ever ran — so a halted boot's
|
||||
"Disk preserved" message was previously false. Fixed by splitting
|
||||
the block subsystem's disk detection from disk commit: an
|
||||
unrecognized/blank disk is now left `PROVISIONAL` (geometry computed
|
||||
in memory only, all writes refused) until the disk's owner explicitly
|
||||
calls the new `BLK-CONFIRM-FORMAT ( lbn -- )` primitive. Artemis calls
|
||||
it from `ART-FORMAT` and `ART-RESUME` (`capsules/artemis/init.4th`
|
||||
block 4126) — never from `ART-HALT-UNRECOG`. See
|
||||
`src/block_subsystem.c`'s `blk_format_or_load_disk` /
|
||||
`blk_commit_format` / `blk_subsys_confirm_format` for the mechanism.
|
||||
|
||||
Not done: everything under "FUTURE MATERIAL" below (zones, USB hot-plug,
|
||||
ACL records, PKI) — that line remains accurate and unchanged.
|
||||
|
||||
+28
-28
@@ -1,5 +1,5 @@
|
||||
# Capsule Block Manifest — Auto-generated
|
||||
<!-- Generated by mkcapsule --manifest 2026-08-02T10:46:45Z -->
|
||||
<!-- Generated by mkcapsule --manifest 2026-08-02T15:36:37Z -->
|
||||
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
|
||||
<!-- Hand-written justifications and immutability notes live -->
|
||||
<!-- in MANIFEST.md alongside this auto-generated index. -->
|
||||
@@ -9,7 +9,7 @@
|
||||
| Capsule | Blocks claimed | xxHash64 |
|
||||
|---------|----------------|----------|
|
||||
| `ACL.4th` | 4000, 4001, 4002, 4003, 4004, 4005, 4006, 4007, 4015 | `0xd781d22148ff171d` |
|
||||
| `artemis:init.4th` | 4110, 4111, 4112, 4113, 4122, 4123, 4124, 4125, 4126, 4127, 4128, 4129, 4130, 4131, 4132, 4133, 4134, 4135, 4136, 4137, 4138, 4139, 4140, 4141, 4851, 4852 | `0x02b065d1cec2ebe2` |
|
||||
| `artemis:init.4th` | 4110, 4111, 4112, 4113, 4122, 4123, 4124, 4125, 4126, 4127, 4128, 4129, 4130, 4131, 4132, 4133, 4134, 4135, 4136, 4137, 4138, 4139, 4140, 4141, 4851, 4852 | `0xd622eb8ccb75f29a` |
|
||||
| `common:msg.4th` | 4055 | `0xa99c5bcd3877f80e` |
|
||||
| `doe-campaign.4th` | 4060, 4061, 4062, 4063, 4064, 4065 | `0x3d4549142d91ec20` |
|
||||
| `doe.4th` | 2100, 2101, 2102, 2103, 2104, 2105, 2106, 2107 | `0xb6ecf5374e8ee77c` |
|
||||
@@ -127,10 +127,10 @@
|
||||
| 4107 | `hermes:init.4th` | `0xbb77f3ac463f507f` | ok |
|
||||
| 4108 | `hermes:init.4th` | `0xbb77f3ac463f507f` | ok |
|
||||
| 4109 | `hermes:init.4th` | `0xbb77f3ac463f507f` | ok |
|
||||
| 4110 | `artemis:init.4th` | `0x02b065d1cec2ebe2` | ok |
|
||||
| 4111 | `artemis:init.4th` | `0x02b065d1cec2ebe2` | ok |
|
||||
| 4112 | `artemis:init.4th` | `0x02b065d1cec2ebe2` | ok |
|
||||
| 4113 | `artemis:init.4th` | `0x02b065d1cec2ebe2` | ok |
|
||||
| 4110 | `artemis:init.4th` | `0xd622eb8ccb75f29a` | ok |
|
||||
| 4111 | `artemis:init.4th` | `0xd622eb8ccb75f29a` | ok |
|
||||
| 4112 | `artemis:init.4th` | `0xd622eb8ccb75f29a` | ok |
|
||||
| 4113 | `artemis:init.4th` | `0xd622eb8ccb75f29a` | ok |
|
||||
| 4114 | `hermes:init.4th` | `0xbb77f3ac463f507f` | ok |
|
||||
| 4115 | `hermes:init.4th` | `0xbb77f3ac463f507f` | ok |
|
||||
| 4116 | `hermes:init.4th` | `0xbb77f3ac463f507f` | ok |
|
||||
@@ -139,26 +139,26 @@
|
||||
| 4119 | `hermes:init.4th` | `0xbb77f3ac463f507f` | ok |
|
||||
| 4120 | `hermes:init.4th` | `0xbb77f3ac463f507f` | ok |
|
||||
| 4121 | `hermes:init.4th` | `0xbb77f3ac463f507f` | ok |
|
||||
| 4122 | `artemis:init.4th` | `0x02b065d1cec2ebe2` | ok |
|
||||
| 4123 | `artemis:init.4th` | `0x02b065d1cec2ebe2` | ok |
|
||||
| 4124 | `artemis:init.4th` | `0x02b065d1cec2ebe2` | ok |
|
||||
| 4125 | `artemis:init.4th` | `0x02b065d1cec2ebe2` | ok |
|
||||
| 4126 | `artemis:init.4th` | `0x02b065d1cec2ebe2` | ok |
|
||||
| 4127 | `artemis:init.4th` | `0x02b065d1cec2ebe2` | ok |
|
||||
| 4128 | `artemis:init.4th` | `0x02b065d1cec2ebe2` | ok |
|
||||
| 4129 | `artemis:init.4th` | `0x02b065d1cec2ebe2` | ok |
|
||||
| 4130 | `artemis:init.4th` | `0x02b065d1cec2ebe2` | ok |
|
||||
| 4131 | `artemis:init.4th` | `0x02b065d1cec2ebe2` | ok |
|
||||
| 4132 | `artemis:init.4th` | `0x02b065d1cec2ebe2` | ok |
|
||||
| 4133 | `artemis:init.4th` | `0x02b065d1cec2ebe2` | ok |
|
||||
| 4134 | `artemis:init.4th` | `0x02b065d1cec2ebe2` | ok |
|
||||
| 4135 | `artemis:init.4th` | `0x02b065d1cec2ebe2` | ok |
|
||||
| 4136 | `artemis:init.4th` | `0x02b065d1cec2ebe2` | ok |
|
||||
| 4137 | `artemis:init.4th` | `0x02b065d1cec2ebe2` | ok |
|
||||
| 4138 | `artemis:init.4th` | `0x02b065d1cec2ebe2` | ok |
|
||||
| 4139 | `artemis:init.4th` | `0x02b065d1cec2ebe2` | ok |
|
||||
| 4140 | `artemis:init.4th` | `0x02b065d1cec2ebe2` | ok |
|
||||
| 4141 | `artemis:init.4th` | `0x02b065d1cec2ebe2` | ok |
|
||||
| 4122 | `artemis:init.4th` | `0xd622eb8ccb75f29a` | ok |
|
||||
| 4123 | `artemis:init.4th` | `0xd622eb8ccb75f29a` | ok |
|
||||
| 4124 | `artemis:init.4th` | `0xd622eb8ccb75f29a` | ok |
|
||||
| 4125 | `artemis:init.4th` | `0xd622eb8ccb75f29a` | ok |
|
||||
| 4126 | `artemis:init.4th` | `0xd622eb8ccb75f29a` | ok |
|
||||
| 4127 | `artemis:init.4th` | `0xd622eb8ccb75f29a` | ok |
|
||||
| 4128 | `artemis:init.4th` | `0xd622eb8ccb75f29a` | ok |
|
||||
| 4129 | `artemis:init.4th` | `0xd622eb8ccb75f29a` | ok |
|
||||
| 4130 | `artemis:init.4th` | `0xd622eb8ccb75f29a` | ok |
|
||||
| 4131 | `artemis:init.4th` | `0xd622eb8ccb75f29a` | ok |
|
||||
| 4132 | `artemis:init.4th` | `0xd622eb8ccb75f29a` | ok |
|
||||
| 4133 | `artemis:init.4th` | `0xd622eb8ccb75f29a` | ok |
|
||||
| 4134 | `artemis:init.4th` | `0xd622eb8ccb75f29a` | ok |
|
||||
| 4135 | `artemis:init.4th` | `0xd622eb8ccb75f29a` | ok |
|
||||
| 4136 | `artemis:init.4th` | `0xd622eb8ccb75f29a` | ok |
|
||||
| 4137 | `artemis:init.4th` | `0xd622eb8ccb75f29a` | ok |
|
||||
| 4138 | `artemis:init.4th` | `0xd622eb8ccb75f29a` | ok |
|
||||
| 4139 | `artemis:init.4th` | `0xd622eb8ccb75f29a` | ok |
|
||||
| 4140 | `artemis:init.4th` | `0xd622eb8ccb75f29a` | ok |
|
||||
| 4141 | `artemis:init.4th` | `0xd622eb8ccb75f29a` | ok |
|
||||
| 4142 | `hermes:init.4th` | `0xbb77f3ac463f507f` | ok |
|
||||
| 4143 | `hermes:init.4th` | `0xbb77f3ac463f507f` | ok |
|
||||
| 4144 | `hermes:init.4th` | `0xbb77f3ac463f507f` | ok |
|
||||
@@ -207,8 +207,8 @@
|
||||
| 4840 | `init-l8-transition.4th` | `0xbcc1a81976f0a4c9` | ok |
|
||||
| 4841 | `init-l8-transition.4th` | `0xbcc1a81976f0a4c9` | ok |
|
||||
| 4842 | `init-l8-transition.4th` | `0xbcc1a81976f0a4c9` | ok |
|
||||
| 4851 | `artemis:init.4th` | `0x02b065d1cec2ebe2` | ok |
|
||||
| 4852 | `artemis:init.4th` | `0x02b065d1cec2ebe2` | ok |
|
||||
| 4851 | `artemis:init.4th` | `0xd622eb8ccb75f29a` | ok |
|
||||
| 4852 | `artemis:init.4th` | `0xd622eb8ccb75f29a` | ok |
|
||||
|
||||
## Conflicts
|
||||
|
||||
|
||||
@@ -142,11 +142,15 @@ Block 4125
|
||||
DROP UPDATE ;
|
||||
Block 4126
|
||||
( Boot sequence -- format/resume/halt words )
|
||||
( BLK-CONFIRM-FORMAT commits the disk container -- only on )
|
||||
( paths that don't halt, so unrecognized disks stay untouched )
|
||||
: ART-FORMAT ( -- )
|
||||
." Artemis: blank disk -- formatting" CR
|
||||
ART-HDR-LBN BLK-CONFIRM-FORMAT
|
||||
ART-HDR-WRITE FLUSH ;
|
||||
: ART-RESUME ( -- )
|
||||
." Artemis: LithosAnanke disk -- resuming" CR ;
|
||||
." Artemis: LithosAnanke disk -- resuming" CR
|
||||
ART-HDR-LBN BLK-CONFIRM-FORMAT ;
|
||||
: ART-HALT-UNRECOG ( -- )
|
||||
." ARTEMIS HALT: unrecognized disk content" CR
|
||||
." Disk preserved. Manual intervention required." CR
|
||||
|
||||
@@ -31,6 +31,30 @@ VM's `--disk-img=` flag instead.
|
||||
- `artemis-poison.img` — a separate test image (exact scenario not
|
||||
documented elsewhere in the repo as of this writing; name suggests an
|
||||
adversarial/corruption test, not confirmed).
|
||||
- `artemis-unrecognized-test.img` — exercises `ART-HALT-UNRECOG`
|
||||
(`.claude/ARTEMIS.md` acceptance criterion #6). **Regenerated
|
||||
2026-08-02**: the previous copy of this file had itself been silently
|
||||
reformatted by a since-fixed bug in the *generic* block subsystem
|
||||
(`src/block_subsystem.c`) — it carried a valid low-level `'STFR'`/v2
|
||||
header despite being meant to represent foreign disk content, direct
|
||||
forensic evidence of the bug described in `.claude/ARTEMIS.md`'s
|
||||
Build Status item 6. Regenerated as 30MB of a repeating
|
||||
`POISON-UNRECOGNIZED-DISK-TEST-FIXTURE--NOT-BLANK-NOT-STFR-NOT-ARTEMIS--`
|
||||
ASCII pattern — deliberately neither blank, nor the block subsystem's
|
||||
own `'STFR'` magic, nor Artemis's `"ARTEMIS\0"` marker. Verified on
|
||||
amd64 and riscv64 post-fix: boot correctly halts
|
||||
(`ARTEMIS HALT: unrecognized disk content`) and the file's sha256 is
|
||||
now byte-for-byte identical before and after boot. Keep this fixture
|
||||
in this poisoned state — if a future change makes its sha256 change
|
||||
across a boot, that is exactly the regression this fixture exists to
|
||||
catch.
|
||||
|
||||
Note on incidental header churn: `artemis.img` picks up a few changed
|
||||
header bytes on every ordinary boot even though no user data changes —
|
||||
`blk_subsys_attach_device()` always records a fresh `mounted_time` on a
|
||||
successfully recognized disk, which gets flushed at shutdown. This is
|
||||
expected bookkeeping, not a bug; revert it before committing rather than
|
||||
carrying timestamp noise in git history.
|
||||
|
||||
These are regenerable QEMU raw disk images, not source — see
|
||||
`.claude/ARTEMIS.md` for the storage model they exercise.
|
||||
|
||||
Binary file not shown.
+1519
-1519
File diff suppressed because it is too large
Load Diff
+1523
-1523
File diff suppressed because it is too large
Load Diff
+1515
-1515
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -210,6 +210,16 @@ typedef struct {
|
||||
uint8_t dirty; /* 1=UPDATE called; content needs flush */
|
||||
} blk_bam_entry_t;
|
||||
|
||||
/* Low-level disk container format state. This is distinct from — and known
|
||||
* nothing about — any higher-level content classification (e.g. Artemis's
|
||||
* own BLANK/LithosAnanke/Unrecognized marker check). A slot stays
|
||||
* PROVISIONAL, and all writes to it are refused, until its owner explicitly
|
||||
* calls blk_subsys_confirm_format() after deciding the disk is safe to
|
||||
* touch. This is what makes "halt, disk preserved" actually true instead
|
||||
* of being overwritten by this layer's own opportunistic reformat before
|
||||
* the owner ever gets a look. */
|
||||
enum { BLK_FMT_FORMATTED = 0, BLK_FMT_PROVISIONAL = 1 };
|
||||
|
||||
/* ===== Public API ===== */
|
||||
int blk_subsys_init(VM *vm, uint8_t *ram_base, size_t ram_size);
|
||||
|
||||
@@ -247,6 +257,11 @@ int blk_allocate(uint32_t * block_num);
|
||||
|
||||
int blk_subsys_add_raw_device(uint8_t *buf, uint32_t nblocks);
|
||||
|
||||
/* Commit the low-level format (write header + BAM) for the disk slot that
|
||||
* owns lbn. No-op (returns BLK_OK) if already FORMATTED. Must be called
|
||||
* by the disk's owner before any write to that slot will succeed. */
|
||||
int blk_subsys_confirm_format(uint32_t lbn);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+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)
|
||||
|
||||
@@ -279,6 +279,17 @@ void block_word_update(VM *vm) {
|
||||
blk_update((uint32_t) blk);
|
||||
}
|
||||
|
||||
/* BLK-CONFIRM-FORMAT ( lbn -- ) : commit the low-level disk container
|
||||
* format for the slot owning lbn. Must be called by the disk's owner
|
||||
* (e.g. Artemis) only after classifying disk content as safe to touch —
|
||||
* never on the path that halts for unrecognized content. Until this is
|
||||
* called, all writes to that disk slot are refused by the block layer. */
|
||||
void block_word_confirm_format(VM *vm) {
|
||||
if (vm->dsp < 0) { vm->error = 1; return; }
|
||||
cell_t blk = vm_pop(vm);
|
||||
if (blk_subsys_confirm_format((uint32_t) blk) != BLK_OK) { vm->error = 1; return; }
|
||||
}
|
||||
|
||||
/* SAVE-BUFFERS ( -- ) : sync and write all dirty blocks */
|
||||
void block_word_save_buffers(VM *vm) {
|
||||
blk_vm_flush_all(vm);
|
||||
@@ -457,6 +468,7 @@ void register_block_words(VM *vm) {
|
||||
register_word(vm, "BLOCK", block_word_block);
|
||||
register_word(vm, "BUFFER", block_word_buffer);
|
||||
register_word(vm, "UPDATE", block_word_update);
|
||||
register_word(vm, "BLK-CONFIRM-FORMAT", block_word_confirm_format);
|
||||
register_word(vm, "SAVE-BUFFERS", block_word_save_buffers);
|
||||
register_word(vm, "EMPTY-BUFFERS", block_word_empty_buffers);
|
||||
register_word(vm, "FLUSH", block_word_flush);
|
||||
|
||||
Reference in New Issue
Block a user