Open Work
July 9, 2026 · View on GitHub
The single source of truth for what's left to do on Rusty Backup. Each
section carries enough design detail to be actionable without external
references. The MiSTer plan
(mister_filesystem_implementation_plan.md)
is the one other living plan and is treated as a single line item here;
everything else has been consolidated into this file.
Last reconciled against the code: 2026-06-03 (later — AtariST MiSTer prereqs).
When an item lands, remove its block. When new work surfaces, add a block here.
1. Filesystem engine — Unix priority track
Closed 2026-06-02. ReiserFS (v3.5 + v3.6), UFS (UFS1 + UFS2), and JFS
(JFS2) all read end-to-end through the trait: inspect, layout-preserving
backup compactor, browse, file read, symlinks, recursive directory
descent. UFS U.4 (fsck + edit + repair) shipped 2026-06-03; see §10
for the per-FS one-liners. JFS J.4a (fsck read-only verifier),
multi-IAG dispatch covering >4k inodes, and the multi-page external
dtree walker (>127-entry directories) all shipped 2026-06-03 late. The
JFS fixture was regenerated in WSL Ubuntu-24.04 to include
/bigdir/file_001..200 for the dtree-walker regression. J.4b
(edit-side write primitives) is in §9 explicitly excluded —
repair()-driven OrphanInode adoption is the only thing it would
unlock, real-world JFS orphans are rare, and Linux fsck.jfs is the
fallback when one surfaces. The multi-level dmapctl walker (>32 GiB
JFS aggregates) is also in §9 — vintage hardware doesn't address that
scale. ReiserFS is intentionally read-only forever (filesystem leaving
the kernel; investment caps at "read what exists").
2. Filesystem engine — multi-session existing tracks
2.1 XFS — close the v4-edit holes and add v5 write
XFS read + edit + fsck shipped on the xfs-efs-fsck track (~10k lines).
Oracle: xfs_repair -n clean on every write. Two oracle Docker images
already documented elsewhere: rusty-xfs-oracle (xfsprogs 4.9.0, v4, v2
inodes) and rusty-xfs-oracle-v1 (xfsprogs 3.1.9, V1-inode IRIX disks).
For v5 you need a third image with recent xfsprogs (e.g. ubuntu:22.04).
Tools: scripts/xfs-oracle.sh; examples/xfs_check.rs (--repair),
xfs_mkdir.rs, xfs_mkfile.rs; and rb-cli mkdir|put|rm|ls|get IMG[@N].
Real disk to test against: ~/Documents/scsi2.raw (SGI, V1 inodes, XFS at
byte 2097152 / @1, partition slot 7). Always operate on a copy.
Open holes in v4 edit (suggested order — easiest/most reusable first):
-
(A) new-chunk inode allocation — shipped 2026-06-02 (single-leaf
- multi-level grow; see §10).
-
(B) block → short-form dir re-compaction — shipped 2026-06-02 (see §10).
-
(C) leaf/node (multi-block) directories — block→leaf conversion shipped 2026-06-02 (v1: 2-data-block leaf form on overflow; see §10). Follow-ups still open: leaf-form INSERT (grow beyond 2 data blocks), leaf-form REMOVE, leaf→block recompaction, node form for very large dirs. v1 is enough to take a single-block dir past 127 entries; further inserts return
Unsupportedwith a clear message. -
(D) bmap-btree file forks — shipped 2026-06-02 (single-leaf bmbt — multi-leaf parked for follow-up; see §10). The R2 abort-on-btree gate is also relaxed: R2/R5 now account bmbt blocks via
collect_bmbt_blocks. -
(E) v5/CRC editing — shipped 2026-06-03 across nine slices (E.1 / E.2 / E.3 / E.4 / E.5a / E.5b / E.5c / E.5d). End-to-end v5 file creation works on the modern
mkfs.xfsfixture,run_fsckstays clean across alloc + dir-grow + bmbt + repair paths. See §10 for per-slice audit. R2 (freespace rebuild) + R3 (inobt rebuild) remain v4-only — both can write a structurally correct CRC tree, but v5 layouts may carryfinobt/rmapbt/refcountbtro-compat metadata that R2's block-completeness map and R3's AGI-summary recompute don't model. Parked in §8 behind those side-trees actually being needed; the existing read path is already finobt-tolerant.
2.2 XFS shrink via clone-into-fresh
Edit shipped, so the prerequisites for a shrink path are in tree. Same
shape as the HFS+ defrag-on-backup pipeline (hfsplus_defrag.rs /
hfsplus_clone.rs) and the proposed btrfs scratch-recreate.
source XFS ──► read every file + xattr via XfsFilesystem
──► create_blank_xfs(target_size, label) → fresh mountable v4 image
──► replay dirs/files into it via edit.rs
──► stream the target image into the compressor
──► metadata.json records shrunk=true so restore knows to grow back
Restore: write cloned bytes into the (larger) target partition, then run an
in-tree xfs_growfs-equivalent (or document the manual xfs_growfs
post-restore step the way today's "Add free space" flow already does).
Each slice oracle-validated against xfs_repair -n:
| # | Slice |
|---|---|
| XR.0 | Decision: v4-only-first vs gate behind hole (E) v5 editing. v4 is enough to land the feature end-to-end on IRIX disks and old Linux; v5 follows when (E) does. |
| XR.1 | create_blank_xfs(size_bytes, label, sector_size, ag_count) -> Vec<u8> — emit a minimum-sized fresh v4 XFS that mounts clean and passes xfs_repair -n. Mirrors create_blank_hfsplus / create_blank_efs. |
| XR.2 | clone_xfs_volume(source, target_size) -> Result<Vec<u8>> — BFS-replay dirs + files (xattrs once that edit primitive lands; ignore for now or refuse with a clear error). Reuses edit.rs write primitives. |
| XR.3 | stream_xfs_shrunk_to_writer — peer of stream_defragmented_hfsplus: clone into a temp/in-memory target, then stream into the compressor with no double-disk-pass. |
| XR.4 | Pre-flight refusal: v5 sources (until XR.0+E), realtime volumes, multi-device XFS, images with xattrs we can't emit. |
| XR.5 | Backup-engine shrink_to_minimum flag wiring for XFS partitions, GUI checkbox + tooltip. Mirrors HFS+ Step 22f-i. |
| XR.6 | End-to-end backup → restore round-trip; restore-side post-write xfs_growfs-equivalent or document the manual step. |
Until XR.0 is decided, XFS backups continue through the existing layout-preserving compactor (output size is fine — zeros compress trivially; only wall-clock CPU is hurt).
2.3 HFS+ journal — Step 27 transactional writes
Journal parse + replay + dirty-volume recovery shipped (Steps 24-26 of the
HFS+ enhancements track). The Step 4 "refuse journaled" relaxation also
landed — prepare_for_edit now accepts a journaled volume when its journal
is clean (empty, start == end) and still refuses dirty ones.
What's still open: route every do_sync_metadata block write through
TransactionBuilder.record_* so edit-mode mutations land inside the
journal rather than writing directly to disk, plus the
replay_dirty_journal-on-open hook for open_editable_filesystem. The
recorder (TransactionBuilder in src/fs/hfsplus_journal.rs:847) and
replay_journal are in tree from steps 25/26; the plumbing below hasn't
been picked up.
Touch sites in src/fs/hfsplus.rs:
do_sync_metadatabecomes the natural transaction boundary. Every block-level write inside it (write_fork_data, B-tree node writes, bitmap writes, VH + alt-VH) goes throughpending_tx.record_*instead of writing to disk directly.- After in-memory mutations succeed,
pending_tx.commit(...)runs once at the end ofdo_sync_metadata. A failure rolls back via the Step 5 snapshot AND skips the commit, so the journal stays untouched. - Touch-date stamping (Step 8) and counter updates ride inside the transaction.
open_editable_filesystemfor HFS+: whenvh.attributes & kHFSVolumeJournaledBit != 0:- Clean (
kHFSVolumeUnmountedBitset) — proceed; transactional writes preserve journaling. - Dirty +
replay_dirty_journalon — callreplay_journalfirst, then proceed. - Dirty + not authorized — keep refusing with the Step 4 message.
- Clean (
- Multi-megabyte mutations (defrag-clone in place) stay routed through
clone_hfsplus_volumewriting to a fresh target with no journal; only edit-mode mutations get journaled.
Tests:
- End-to-end edit on a clean journaled HFS+ image: create a file,
do_sync_metadata, reopen with macOS-style replay, assert the file is present and the journal is empty. - End-to-end edit on a dirty journaled image with
replay_dirty_journal= true: replay applies pending transaction, then our own transaction lands on top, both visible after a second replay-and-reopen. - Snapshot rollback during a journaled mutation: forced
DiskFull→ assert the journal is byte-identical to before the mutation (no partial transaction written).
2.4 btrfs — scratch-recreate (gated on writer)
The earlier "trim at last chunk end" idea was abandoned: real-world
layouts have metadata/system chunks placed mid-device, so last-chunk-end
is often close to total_bytes even on mostly-free volumes; trimming
past the 64 MiB / 256 GiB superblock mirrors drops redundancy; and
restore-side resize_btrfs_in_place would corrupt the volume if any
chunk lies past the trim point (chunk tree still references original
physical offsets).
Recommended path: clone the live source into a freshly formatted, sized-to-fit target btrfs during backup. Same pattern as the HFS+ defrag-on-backup pipeline.
Hard prerequisite: a pure-Rust btrfs writer. We have none today —
src/fs/btrfs.rs is read-only: superblock + chunk tree + root tree + FS
tree + extents → Filesystem reads. No EditableFilesystem, no
allocator, no B-tree insert, no chunk tree builder, no CRC stamping for
tree blocks.
Building one is genuinely large (pessimistically larger than the HFS+
edit-mode work) because btrfs is COW with cross-linked B-trees:
chunk tree, extent tree, FS tree, root tree, free-space tree (post-v4.5
default), plus crc32c on every metadata block, plus subvolume layout at
minimum. Multi-month. No alternative ships it faster — shelling out
to mkfs.btrfs + btrfs send/receive is Linux-only and requires root,
losing the Windows/macOS coverage the project's "pure Rust, no platform
cfg" rule (CLAUDE.md) protects.
| # | Slice |
|---|---|
| B.0 | Decision — do we want to invest in a btrfs writer at all, or close as "wontfix — back up btrfs verbatim and let users shrink in-OS"? Needs user input before any code work. |
| B.1 | create_blank_btrfs(size, label) -> Vec<u8> — emit a fresh mountable btrfs. Bootstrap chunk array, system chunk, single data + metadata chunks, empty root/extent/fs trees, free-space tree. Validate with btrfs check. |
| B.2 | Single-file write: given a blank target and a (path, bytes), allocate a data extent, write the file, insert INODE_ITEM/DIR_ITEM/DIR_INDEX/EXTENT_DATA, update extent tree back-refs, CRC everything. |
| B.3 | Recursive directory replay: walk source via BtrfsFilesystem::list_directory → emit into target. Handle nested dirs + many-file directories (B-tree split). |
| B.4 | clone_into_blank_btrfs(source, target_size) -> Vec<u8> + stream_btrfs_to_target for streaming into the compressor with no multi-GB temp file. |
| B.5 | Pre-flight refusal for subvolumes / snapshots / compressed extents we can't yet emit, with a clear error. |
| B.6 | Backup engine shrink_to_minimum wiring for btrfs partitions (parallel to HFS+ flag); GUI checkbox + tooltip + log polish. |
| B.7 | Restore-side: post-resize resize_btrfs_in_place already shipped; verify end-to-end. |
Until B.0 is decided, btrfs backups continue through the existing layout-preserving compactor.
3. Filesystem engine — small to medium
3.1 Compressed superfloppy output (restore-path fix)
Superfloppy (partition-less) volumes now go through compaction — a lightly-used
raw ext4 .img backs up to a smaller packed .img and restores/grows back
e2fsck-clean (backup sizes.rs, restore already handles it). But the output is
still forced to raw (CompressionType::None in backup/mod.rs): honoring an
explicit --format zstd|gzip|lz4 for a superfloppy produced a tiny backup
(64 MiB → ~1.7 KB) that failed to restore ("Bad magic number in super-block",
wrong size) — the per-partition restore path mishandles a compressed superfloppy
member. Fix the compressed-superfloppy restore, then re-enable honoring the
compression format for superfloppy (the codec-gating match was reverted; see the
comment on effective_compression). Big win: KB-scale backups of mostly-empty
raw fs images.
4. Clonezilla import — remaining gaps
GPT support shipped. The remaining gaps are LVM and RAID. Both are real but rare in the project's use case (vintage hardware retro restores); the motivating workflows are CF/SD cards and single-disk vintage drives, not modern Linux servers. Open because a future user might hit them; they sit behind everything in the MiSTer plan and §1-§3 above.
4.1 LVM (Logical Volume Manager)
Clonezilla preserves LVM2 metadata in lvm_*.conf / lvm_logv.list /
lvm_vg_dev.list sidecar files plus a <disk>-<part>.<fs>-ptcl-img.*
partclone image of the physical volume (the partition that backs
the PV header). Today we ignore the LVM sidecars entirely; the
partclone image is treated as raw bytes — restore writes the PV back
verbatim. The LV layout inside the PV is invisible to inspect/browse,
and restore-time resize leaves the VG metadata stale.
Work:
- Parse
lvm_*.conf(text format — UUIDs, PV/VG/LV map, extent counts). - Map each
lv_*payload partclone image to its parent VG/LV. - Surface LVs as logical partitions in inspect (one row per LV inside the PV partition).
- On restore, rewrite PV header
pe_countand the VG metadata text to match the new partition size when the user resizes.
Reference: Clonezilla ocs-functions LVM block + lvmdump output
format.
4.2 RAID (mdadm / Linux software RAID)
Clonezilla images of Linux software RAID arrays write the array members
as separate partitions with type byte 0xFD (Linux RAID Autodetect) and
a metadata sidecar describing the array shape. Today we detect the
0xFD partition type byte in src/partition/mbr.rs:107 for display
("Linux RAID") but do nothing with the RAID metadata. Restore writes
each member partition back verbatim — fine for RAID-1, meaningless for
RAID-0/5/6 without reassembling the array first.
Work:
- Parse the RAID sidecar (level, chunk size, member UUIDs, member order).
- Refuse single-member restore for RAID-0/5/6 with a clear "this is part of an array; restore needs all N members at once" message.
- Optional: synthesize an mdadm superblock on restore so the OS re-assembles the array on boot.
Reference: Linux md/raid*.c superblock format; Clonezilla
ocs-functions mdadm dump block.
5. MiSTer computer-core filesystems
The MiSTer FS plan lives in its own file because of its size (Wave 1 +
Wave 2 + Wave 3, ~100-150 sessions estimated):
mister_filesystem_implementation_plan.md.
Companion status survey:
full_MiSTer_support_status.md.
Treat as a single tracked initiative here; progress is on that doc's own per-format spine.
Gaps to reconcile back into the plan when it gets picked up:
- BBCMicro / AcornElectron — Acorn DFS — DONE (2026-06-13).
fs::dfsreads + writes the flat-catalogue DFS on single-sided.ssd(40-track 100K / 80-track 200K), bidirectionally cross-validated byte-exact against an independent clean-room reader/writer. Remaining: double-sided.dsd(track-interleaved) and ADFS-on-floppy. - AcornAtom (Atom DOS) — niche, omitted from §1 + §5 of the plan.
- SAM-Coupe (SAM DOS / MasterDOS) — omitted.
- ZX-Spectrum native FS (TR-DOS / G+DOS) — only FAT + +3DOS are covered. The native floppy FS isn't in scope yet.
- PET2001 D80/D82 — DONE (2026-06-11).
fs::cbmreads + writes the 8050.d80/ 8250.d82geometries from scratch (nocbmcrate), bidirectionally validated against the Pythond64reference. - §0 vs §4 reconciliation — §0 says "Add/Delete is the target for every format" but §4 marks ~9 floppy cores as "write deferred". Decide which is right (only ColecoAdam EOS has a legitimate read-only reason — the core itself is RO).
6. CLI / GUI
6.1 Commander Mode — two-pane file explorer
Full design + runnable mock: commander_mode.md; pick-up
notes: commander_mode_handoff.md. A
full-page, Midnight-Commander-style overlay with two panes (each a disk-image
partition or a host folder), staged copy/delete between them, multi-select,
right-click menu, and export/archive-out. Locked decisions and the
editable-metadata + export-crate inventories live in the plan doc.
Build order follows the CONTRIBUTING playbook (engine -> model -> thin view) and reuses existing layers rather than reinventing:
- model/ — reuse
BrowseSession(open / list /open_editable/ commit),EditQueue+StagedEdit+edit_queue::apply_edit,model::file_types. New:model::dir_listing::DirListing(cwd + sorted listing + multi-selection; pure, unit-tested) andmodel::commander_source(partition probe + per-paneBrowseSessionbuilder). - gui/ — thin view in
src/gui/commander/. Holds the model objects + small UI scratch only; no orchestration, no background spawns (route through a model runner /BrowseSession::spawn_open). Extract shared widgets the browse view already has (file_detailhex/metadata, type/creator editor rows, a source picker) instead of duplicating them.
Status (2026-06-16): plan + mock + shell + a working two-pane file manager.
Models (unit-tested): dir_listing (image + host listing sources),
commander_source, commander_ops (apply / stage_copy / stage_host_to_image /
spawn_host_copy). Each pane browses a disk image (partition picker) or a
host folder (sortable grid + multi-select + ../double-click nav). Image
panes stage delete + copy-in onto per-pane EditQueues (Apply/Discard, virtual
overlay, unsaved guards); host panes write immediately (delete behind a confirm).
The middle column copies a selection across the panes in all four combos
(image↔image and host→image staged; image→host and host→host immediate/threaded).
Next: the M6 right-click / view batch (plan §15) — Export to hard drive
(§15.3, reuses spawn_host_copy), Calculate Checksums (§15.2, model::checksum
- add the
sha1crate), Rename (§15.1, newEditableFilesystem::rename), and a per-pane Tree view (§15.4, the R4 browse-view share); wildcard Find is deferred (M7, §15.5). Then M1 widget extraction + the M4 File Info window, the browsable-partition gate, and drag-to-load. Milestones in the plan doc §13.
7. User-side verification (not coding)
7.NH — Need help (user research blocking, surfaced 2026-06-05 MiSTer session)
Two MiSTer Wave-2 manual-verify attempts each hit a wall that isn't a rusty-backup issue but needs the user to source / research the right real-hardware-side software before the engine work we shipped can be demonstrated in-emulator. Both blockers are well-scoped; both have proven engine paths host-side that can substitute as evidence for the spine rows. Captured here so the next session knows what's pending the user's research.
X68000 — need a Human68k system with the SCSI driver loaded
- The MiSTer X68000 core boots from
boot3.vhd(16 KB stub on the SD; valid SASI signature\x82w68000Wbut no real Human68k install). With FDD0 =BLANK_disk_X68000.D88the floppy boots Human68k v3.0 fine — but the floppy's CONFIG.SYS doesn't include a SCSI device driver line (typicallyDEVICE = \SYS\SCSIROM.SYSor similar), so even with a valid SCSI HDF mounted in the SASI slot, Human68k can't see it as a drive letter. - Tried
hd0.hds(the real 100 MB Sharp SCSI image fromC:\Temp\hd0.zip): byte-0 hasX68SCSI1signature so the SASI BIOS should recognise it, but without a SCSI driver in CONFIG.SYS there's still no drive letter. - Tried
Bomberman.HDF(real 10 MB Sharp self-bootable game disc): IPL menu auto-boots straight into the game; no shell stop. - What the user needs to source: either (a) a Human68k system
floppy whose CONFIG.SYS already loads the SCSI driver — the
Human 68k v3.02 (1993)(Sharp - Hudson)[a].diminC:\Temp\hd0.zipis a strong candidate (matched-pair release withhd0.hds, would need.dim → .D88conversion); or (b) a known-good HDF with Human68k + SCSI driver pre-installed and editable from outside. - What rusty-backup already validates without this:
tests/cli_x68000.rsround-trip on the synthetic D88 fixture +tests/x68000_resize.rsreconstruct → resize → re-open + the Wave-2 spine close-out commit. Engine surface is complete. - Separate but related: the OPEN-WORK §8 entry "X68000 SASI / SCSI HDD format gaps" captures the code-side follow-ups (signature detection, sector-size derivation, IPL menu builder). Closing those would land a self-bootable HDF that doesn't need a working CONFIG.SYS workflow — a separate path to the same end.
QL — need a QXL-driver-capable system ROM
- The MiSTer QL setup ships
boot.rom(stock Sinclair JS) andminerva+qlsd_ql.rom(Minerva + Marcel Kilgus's QLSD SD-card interface). QLSD exposessdc1_for SD-card-image format, NOTwin1_for QXL.WIN. - Our
rb-cli put-emitted file is a canonical QXL.WIN container — byte 0 =QLWA, header layout matchesql-filer'sWinHeader.java(DISC_IDENTIFIER = {'Q','L','W','A'},SECTOR_LENGTH = 512,HEADER_LENGTH = 64) and oursrc/fs/qdos.rsdocumentation byte-exact. Three independent references confirm: sQLux byte-truth oracle (passing host-side),ql-filerWinHeader.java, our own engine. Format is canonical. - For the MiSTer QL core to surface our file as
win1_it needs a ROM with a QXL / Gold Card / Trump Card / Super GoldCard driver — not whatminerva+qlsd_ql.romprovides. With QLSD loaded, the user typesDIR win1_and gets "not found" because no driver bound that device prefix; undersdc1_it might attach but as an SD-card-image format (not QLWA). - What the user needs to source: a QXL.WIN-aware QL system ROM
for MiSTer. Candidates from QL community archives:
Trump Card + Minerva, Super GoldCard ROM, or kilgus's
qlmister.romvariant that bundles the QXL driver (referenced indocs/mister_filesystem_implementation_plan.mdWave-2 session log but not committed in tree). - What rusty-backup already validates without this:
sQLux byte-truth oracle (the kilgus headless harness has
rb-cli put → SuperBASIC COPY → host file diff, byte-exact, per the Wave-2 close-out commit). Engine + container format are complete.
Common shape: both gaps are "MiSTer real-hardware setup needs
software rusty-backup doesn't and shouldn't ship" — the
ROMs/drivers in question are proprietary or community-distributed
and live outside our scope. The user-side ref / write-verified parks
on the Wave-2 spine rows stay [!] until the user runs these tests
with the right setup; the engine-level surface is independently
proven via the host-side oracles.
Move these to the right §X8 sub-bucket once the user has gathered research or sourced the needed ROMs/disks.
7.0 — Original user-side verification list (pre-2026-06-05)
- HFV in BasiliskII / MAME — boot/mount our blank + cloned HFVs
against the bootable samples (
Mac OS 8.1.HFV,Starterdisk.hfv). Our blank/cloned volumes fsck clean and round-trip byte-identically, but in-emulator mount is unverified. - HFV restore filename default — optional nicety: default the
restore output filename to
.hfvwhen the backup was aNone-table HFS. Round-trip works without it. - CD CHD ISO9660 browse — open a CD CHD (game ISO ripped to CHD) in
Browse, navigate the ISO9660 tree, extract a file, SHA256-match
against the same file extracted via mounting the source ISO.
Implementation is in tree (
CdCookedReaderinsrc/rbformats/chd.rs,ImageFormat::ChdCdCookedrouting insrc/rbformats/mod.rs); just a user-side sanity check. - MacPlus MFS in BasiliskII — boot a real System 1.0 / 2.0 MFS
floppy from a public archive (macintoshrepository.org has many)
using BasiliskII + a Mac Plus ROM, then round-trip via
rb-cli getrb-cli puton the same image and confirm BasiliskII still mounts it cleanly with our changes. No Linux apt tool produces MFS, so this is the canonical reference cross-check. Engine + write-path are shipped (14 unit + 4 cli tests, all green).
- Apple-II DOS 3.3 cross-check via a2kit —
cargo install a2kit(or vendor as a workspace dev-dep), produce a known disk via a2kit's CLI, round-trip files through ourrb-cli get/putand confirm byte-for-byte identity with a2kit's view. Also useful: open one of ourrb-cli-written images in CiderPress2 (Windows) to confirm the Apple-II-era tools accept our output. Engine + write-path are shipped (18 unit + 5 e2e + 4 cli tests, all green). - MiSTer X68000 core boot test — workflow A+C+B per
docs/mister-deployment-testing-plan.md§3.4. Build a 1.2 MB Human68k floppy viarb-cli putagainst the synthetic disk (engine ships full Add/Delete),scpto/media/fat/games/X68000/, mount in the core,dir A:lists the written file,TYPE foo.txtreads back the seeded bytes. Reverse (workflow B) —savefrom Human68k, power down, pull SD,rb-cli ls / getagrees. Engine surface is shipped (10 unit- 1 e2e dispatch test); only the real-hardware mount + boot is outstanding.
- MiSTer Archie / ADFS core boot test — workflow A+C per
docs/mister-deployment-testing-plan.md§3.5. Build a 800 KB E-format ADFS disc with the synthetic-fixture pattern, mount as:0,*CAT :0lists the file,*Type HELLOreads back the seed. Engine surface shipped (5 unit + 1 e2e + 3 cli); only real-hardware boot outstanding. ADFS read-side FSM walker landed 2026-06-04 — the formula was the Linux kernel's publishedadfs_map_layoutmacro applied to the correctly parsed disc record. Previous-session puzzles unwound:- dr.root split was wrong. The kernel's
__adfs_block_map(fs/adfs/adfs.h) splits indaddr asfrag_id = indaddr >> 8,block += ((indaddr & 0xFF) - 1) << log2sharesize. CROS42'sdr.root = 0x243is therefore(frag_id = 2, indaddr_lo = 0x43)—ADFS_ROOT_FRAG = 2, the same constant E-format uses. Not "frag 579 zone 2". log2bpmbwas misnamed. Disc-record byte 0x05 is log2(bytes per map bit) (kernellog2bpmb), notlog2_map_bits.map2blk = log2bpmb - log2_secsizeis the shift that converts a fragment's map-bit count to a sector count. For CROS42 log2bpmb=12, sector=9, map2blk=+3 (1 map bit = 8 sectors = 4096 B).- FSM is at middle of disc (kernel formula was right).
map_addr_sec = signed_asl((nzones>>1) * zone_size_bits - ((nzones>1) ? 480 : 0), map2blk). For CROS42 (33 zones, zone_size_bits=3986, map2blk=+3): map_addr_sec = 506368 = byte 0xF740000. Matches. - Resolved end-to-end:
frag 2, zone 16, start_bit 40 → result = 40-32+63296 = 63304 → sector 506432 → byte 0xF748000 → +secoff (2) → byte 0xF748400, where the Hugo magic sits exactly. Same formula resolves ICEBIRD's root and arc-04's E-format root (dr.root=0x203→(frag_id=2, indaddr_lo=3)→ bit 528, length 32 → sector 2 → byte 0x800 Nick). Cross-verified byexamples/adfs_fsm_probe.rs. - Walker shipped (
src/fs/adfs.rs::AdfsFsm): full DR parser (60 bytes, includinglog2bpmb,log2sharesize,nzones_high,disc_size_high,format_version,root_size),map_lookup(indaddr, block)mirroring__adfs_block_map+adfs_map_lookup,free_bytes()mirroringadfs_map_statfs. Wired intolist_directory(root + subdirs) +read_filefor E/F/HD; D-format keeps direct-byte addressing (old-map). Hugo + Nick directory magics treated interchangeably (the layout is identical for 26-byte-entry F-format dirs on this read path). - End-to-end byte-truth confirmed (read side):
rb-cli ls 'C:\Temp\CROS42.hdf'returns the full root (!Boot / Apps / Comms / Develop / Documents / Emulator / Emulators / Files / Games / Media / Printing / ReadMe (FILE 607 B) / Swap / Utilities / Utils — exact match to the 2026-06-04 RPCEmu-mount ground truth). Subdirs descend too:rb-cli ls 'C:\Temp\CROS42.hdf' Apps/!Clocklists !Help / !Run / !RunImage / !Sprites / !Sprites22 / !Sprites23 / Messages. File extraction byte-exact:rb-cli get 'C:\Temp\CROS42.hdf' Apps/!Clock/!Helppulls the 363-byte help text verbatim. ICEBIRD lists ClassicROS / Demos / Diskmags / Tools; arc-04 lists 1_DataComm. - End-to-end byte-truth confirmed (write side):
rb-cli put cros42.hdf foo.txt MyTestadds the file viaEditableFilesystem;rb-cli getround-trips byte-exact;rb-cli rmremoves it cleanly; existing files (Apps/!Clock/!Help) stay byte-identical across the put+rm cycle. Implementation honours the kernelscan_free_mapchain: bit-8 freelink delta, predecessor pointer rewriting on carve. - Still open:
- HDD resize. Same-nzones grow is bounded by FSM
geometry — CROS42 can grow 512 MB → 537 MB without
crossing nzones (33 zones × ~15.6 MB each = 514 MB
theoretical, with some clamp slack). Anything past
that requires FSM relocation:
map_addrlives at(nzones>>1) * zone_size_bitsshifted bymap2blk, so growing nzones moves the physical FSM forward by up to several MB. The bytes at the new map_addr are part of the existing data area, so resize would need to walk every fragment, identify allocations that overlap the new map_addr region, and relocate them (updating dir entries' indaddrs). No byte-truth oracle exists (Linux'sADFS_FS_RWflags itself DANGEROUS / experimental, and RISC OS's resize tool runs only inside the OS). Deferred until a maintainer with an RPCEmu / real-Archie boot loop is willing to validate. Workaround for grow-CF-card: restore byte-for-byte, then*AddDriveinside RISC OS. - Variable-size F+ directories (
format_version != 0,root_sizefrom DR). Walker handles size, but the big-dir entry format (adfs_bigdirentry, 8-byte indaddr, longer names) needs its own parser. Not observed yet on our samples. - Old-map (D-format) FSM still uses the "indaddr = byte offset" fallback. No D-format real sample to validate against. Reopen if one surfaces.
- Multi-deep freelink chain. Today
find_chain_headonly carves from the chain root. CROS42 / ICEBIRD zones we've inspected carry a single chain entry per zone, but a heavily-used disc could grow longer chains; allocating from inner entries would need the walker to thread the predecessor pointer through the chain. Not blocking for the current MiSTer distribution workflow.
- HDD resize. Same-nzones grow is bounded by FSM
geometry — CROS42 can grow 512 MB → 537 MB without
crossing nzones (33 zones × ~15.6 MB each = 514 MB
theoretical, with some clamp slack). Anything past
that requires FSM relocation:
- Samples on disc (all confirmed in the 2026-06-04 probe):
C:\Temp\CROS42.hdf(512 MB populated HD-format, TOSEC Acorn) — dr.root=0x243, frag 2 zone 16 bit 40 → byte 0xF748400 (Hugo). Verified via probe.C:\Temp\ICEBIRD.hdf(512 MB populated HD-format) — identical DR shape to CROS42; same address resolves.C:\Temp\adfs_arc04_e_orig.adf(800 K E-format populated, from 8bs.com/pool/arc/arc-04.zip) — frag 2 zone 0 → byte 0x800 (Nick). Verified via probe.C:\Temp\adfs_blank256E.hdf(256 MB blank E-format HD).C:\Temp\adfs_blank1024Eplus.hdf(1 GB blank E+ format HD).
- dr.root split was wrong. The kernel's
- MiSTer QL core boot test — workflow A+C per
docs/mister-deployment-testing-plan.md§3.6. Build a QXL.WIN hard-disk image, place at/media/fat/games/QL/win1_, boot the QL core,DIR win1_,LOAD win1_HELLOreads the seed. Engine surface shipped (20 unit + 2 e2e + 5 cli — including write); the write path passes a headless sQLux byte-truth oracle (rb-cli put → sQLux SuperBASIC COPY → host file round-trips byte-exact, per-file 64-byte QDOS header convention honoured). Only the real-hardware mount + boot is outstanding..mdvmicrodrive container (per-sector 60-byte records) deferred. - MiSTer BK0011M ANDOS core boot test — workflow A only per
docs/mister-deployment-testing-plan.md§3.8. Mount one of our ANDOS-scaffolded volumes and visually confirm the core boots (full read path is parked behind the boot-test oracle). .d88container decoder —tests/fixtures/anchor_mister_ BLANK_disk_X68000.D88.zstis a real Sharp.d88from the MiSTer X68000 distribution waiting for a consumer. The format is a 32-byte header + per-track LE-offset table + per-sector blocks. Once landed insrc/rbformats/containers/d88.rs, the existing Human68k engine (read + EditableFilesystem write, all shipped) validates against the BLANK byte-for-byte. Probably a 1-commit slice. Same decoder also unlocks the X68000 ZIP (1.5 GB of real disks already on the MiSTer SD card, not committed)..mdvQDOS microdrive reader —tests/fixtures/anchor_mister_ {GamesCart,crazy}.mdv.zstare real QL microdrive cartridges from the MiSTer QL distribution. The format is per-sector record headers (visible in the first sector asMD/Test8-byte cart names) — completely separate from the QXL.WIN HDD formatsrc/fs/qdos.rscurrently implements. Lives in QDOS Reference Manual ch. 12; ~300 LOC engine + tests against the two anchored fixtures.- AcornAtom — already MiSTer-ready via existing FAT
(reconciles the §5 plan gap noted as "Atom DOS niche / omitted").
The MiSTer AcornAtom core uses a 100 MB FAT-formatted .vhd as the
SD-card backing, not native Atom DOS. We already fully support
that:
scripts/build_atom_vhd.shpacks the fullhoglet67/AtomSoftwareArchiveV13.00 (5311.atmfiles, 16 MB raw) into a fresh FAT16 VHD viamkfs.fat+mtools, and our existing FAT engine reads any.atmfrom it byte-for-byte identical to the source. The packed VHD is on the user's MiSTer at/media/fat/games/AcornAtom/AtomSoftwareArchive_V13.vhd. No Atom DOS engine work is required for MiSTer scope. A futuresrc/fs/atom_file.rsfor.atmmetadata decoding (load/exec addresses, AGD detection) would be a content-aware nicety but is not on the spine.
Items that have a real shape but no schedule. Surface them here so they aren't lost.
WSL toolchain — verified 2026-06-03 on Ubuntu-24.04: to avoid
re-litigating setup, the following is confirmed working end-to-end
(appliance smoke-tested, not just dpkg -l presence checked):
mkfs.jfs (jfsutils 1.1.15), guestfish (libguestfs 1.52.0),
linux-image-virtual + linux-modules-extra-6.8.0-124-generic,
/boot/vmlinuz-* 0644 via dpkg-statoverride, zstd, mkfs.xfs /
xfs_repair / xfs_db (xfsprogs 6.6.0). The minimal-image appliance
boots and round-trips reads/writes; xfs_repair -n runs clean on the
bundled v5 fixture. Implication for the entries below: any park
reason that names "tooling unavailable" or "no oracle" is wrong —
both fixtures and oracles are accessible from this host.
X68000 SASI / SCSI HDD format gaps (scoped 2026-06-05 via real-disc probes + IPL ROM disasm)
Today's MiSTer manual-verify session surfaced concrete gaps between
our partition/x68k.rs + fs/human68k.rs and real Sharp X68000 HDDs.
All five anchored against probed bytes and the disassembled IPL ROM —
no hand-waving.
Reference material in tree (sibling repos, already cloned):
../x68kd11s/iplrom/iplrom30.s— disassembled X68030 IPL ROM. Two key citations:- Line 4761
Lff09e8: SASI HDD descriptor template starting\x82w68000W\x00followed by 31 bytes of geometry / partition pointers. Exact field layout we'd map. - Line 16420
Lff9488: SCSI signature check —cmpi.l #\$53435349,(-\$0014,a0)(='SCSI'at offset -0x14 from boot-device pointer).X68SCSI1at byte 0 ofhd0.hdsmatches.
- Line 4761
../dis68k/lib/libfat-human68k/ff.c— Human68k-port of ChaN FatFs:- Line 2160:
0xAA55boot signature check commented out. - Lines 421–432: standard BPB field offsets (still 8-byte OEM — Hudson's 18-byte OEM is achieved by stuffing the extra 10 bytes into the JMP/code area before the BPB fields).
_MAX_SS/_MIN_SS+SS(fs)macro: variable sector size when_MAX_SS != _MIN_SS.
- Line 2160:
../X68000_MiSTer/Doc/diskemu.txt+rtl/diskemu/sasidev.vhd— describes the core's emulation architecture; the signature-checking firmware runs on a Nios II soft-CPU compiled intodiskemu_mainmem.hex(binary, not source).
Real-image anchors (locally available, byte-probed today):
/c/Temp/Bomberman/games/X68000/media/Bomberman/Bomberman.hdf(10,441,728 B = 40788 × 256). Byte 0:\x60\x00\x00\xca(68000 BSR.W boot-IPL opcode) +\x1b[6;32H X68000 HARD DISK IPL MENUASCII at byte 0x40. X68K partition table at byte 0x400 with one Human68k entry at start_lba 33. Sector size 256 B. Custom Hudson Soft game-data format inside the partition (NOT readable FAT)./c/Users/spam/AppData/Local/Temp/mister-tests/hd0.hds(104,857,600 B = 102400 × 1024). Byte 0:X68SCSI1\x02\x00\x00\x03\x1f\xff\x01\x00- ASCII
Human68K SCSI-DISK by Keisoku Giken. IPL boot code at byte 0x400, X68K partition table at byte 0x800 (= sector 2 in 1024-B units). Sector size 1024 B. Real Human68k FAT12 partition at byte 0x8000, BPB starts with\x60\x24SHARP/KG 1.(18-byte OEM).
- ASCII
/media/fat/games/X68000/boot3.vhdoriginal stub (16,384 B). Byte 0:\x82w68000W\x00\xc0\x00\x00\x00\xfe\x4f\xfc. Real SASI signature + 8 bytes of geometry / capacity descriptor.
Concrete code gaps (in dependency order):
-
[RESOLVED]
partition/detect_superfloppynow accepts the X680000x60(68000 BRA.S) jump byte in addition to the FAT VBR0xEB/0xE9(seesrc/partition/mod.rs:196-202). Raw X68000.D88decodes that surface a Human68k BPB at byte 0 (either the standard little-endian floppy layout or the Sharp/KG big-endian one at offset 0x12) dispatch as"human68k"end-to-end. Resolved pre-2026-06-10; the doc here was stale. -
[RESOLVED for SCSI read]
partition/x68k.rshardcoded 512-byte sectors when computingpartition_offset = start_lba * SECTOR_SIZE. Reality: SASI HDDs use 256-B sectors, SCSI HDDs use 1024-B sectors. With the old math: probed offsets are off by 2× (Bomberman) or 0.5× (hd0.hds) from where the FAT BPB actually lives, causingbytes_per_sector 0 not in {256,512,1024,2048}onrb-cli ls hd0.hds@1. Fixed:x68k::detect_sector_sizereads the boot signature andPartitionTable::X68know carriessector_size;to_partition_infonormalizesstart_lba/size_bytesinto 512-byte LBA units so everystart_lba * 512consumer lands on the real partition. Verified read/browse/extract end-to-end against the BlueSCSI SxSI v3.02HD10/20/30/40_512.hdaset (1024-B SCSI). Resolved (slice 4 + follow-up, 2026-06-07). Partition-table side:patch_x68k_entriestakes the disk's logicalsector_size, and therbformats/mod.rsX68k reconstruct branch derives it from metadata to fixnew_sectors, thedisk_size_field, and the table offset (0x400 SASI / 0x800 SCSI). Partition-data side:PartitionMetadatanow persistsstart_byte(the true non-512-aligned byte offset), the backup engine reads atPartitionInfo::byte_offset(), and the reconstruct loop + restore sites write/resize atPartitionMetadata::byte_offset(). Verified on real 256-B SASI game disks (Populous / Lemmings / SSF2 / Votoms) — backup→restore now lands the partition region byte-identical at byte 8448, and in-placerb-cli resizeis byte-exact on SASI too. -
[RESOLVED for read]
partition/x68k.rsgeometry detection.X68kPartitionTable::detect_with_geometrynow probes both table offsets and derives the sector size:X68SCSI1-> (0x800, 1024);\x82w68000W-> (0x400, 256); otherwise it tries (0x800, 512) then (0x400, 256). The table location is the reliable SASI marker — real SASI game disks (Bomberman) carry a custom IPL with no Sharp signature but put theX68Ktable at 0x400 with 256-B sectors. The non-512-aligned partition offset that 256-B sectors produce is carried throughPartitionInfo::start_byte/byte_offset(). Verified read/browse/extract againstBomberman.hdfand the full~/Downloads/X68000-fixturesset (BlueSCSI / ZuluSCSI / HDS / Henkan Bancho / SCSI2SD.ima). Still open: we don't reject non-Sharp HDDs that coincidentally carry theX68Kmagic. (The SASI 256-B partition-data offset across backup / restore / reconstruct is now handled via the persistedPartitionMetadata::start_byte— resolved 2026-06-07.) -
[RESOLVED]
fs/human68k.rsFAT BPB parser now handles the Sharp / Keisoku Giken SCSI-HDD BPB: a 2-byte BRA.S + 16-byte OEM ("SHARP/KG 1.00") with big-endian fields at offset 0x12. Layout verified byte-for-byte against HD10/HD20 (seeHuman68kBpb::parse_sharp_kg). The standard little-endian FAT BPB path is preserved for floppies. Notedis68k/lib/libfat-human68ksidesteps this entirely by replacing sector 0 with a synthetic standard boot sector (FS_REPLACE_SECTOR0/fake_bootsect); we parse the real on-disk BPB instead. -
[CLOSED 2026-06-10] Self-bootable IPL builder shipped as the five-phase A-E plan (see commits 9fafd99, 70c7cd8, 3d978ac, eb5cf4c on
main).src/partition/x68k_hdd_builder.rsis the shared engine, andrb-cli new-x68k-hddis the user-facing CLI verb at parity with theexamples/build_x68k_hddscaffold. Coverage:- Phase A (SASI IPL stub) — byte-0
BRA.S selfhalt-loop stub satisfies the Sharp IPL ROM's "byte 0 == $60" check; MAMEx68000 -sasichains into it cleanly. - Phase B (printed banner) — clean-room 100-byte 68k stub
calls IOCS
B_PRINT(function $21, TRAP #15) to clear screen + print "Rusty Backup X68000 HDD — Boot Human68k from FDD0 to mount as C:", then halts. - Phase C (SCSI variant) — byte-0
X68SCSI1+ 8-byte geometry descriptor + 40-byte Keisoku Giken string, IPL at byte 0x400, table at byte 0x800, 1024-B sectors. MAMEx68030 -hardboots it; byte-identical to theSCSI_NetBSD.hdsreference for bytes 0..0x40. - Phase D (
--system-disk) — recursive clone of an entire Human68k donor floppy (flat or.dim/.D88/.xdf/.hdmvia the floppy-container layer) into the output partition: 100 files- 6 dirs from
Human 68k v3.02 (Sharp - Hudson).dimland byte-exact, includingHUMAN.SYS,COMMAND.X,BIN/SWITCH.X,BIN/FORMAT.X.
- 6 dirs from
- Phase E (CLI verb) —
rb-cli new-x68k-hdd IMG --size 16M [--variant sasi|scsi] [--stub print|halt] [--system-disk PATH.DIM]at GUI/CLI parity (per CLAUDE.md). Both surfaces share the samebuild_x68k_hddentry point.
Phase D.2 —
--boot-sector-donor(shipped 2026-06-10, BPB-patch fix shipped same day): for users with a real Sharp X68000 SCSI HDD donor (typically the well-knownhd0.hds, 100 MB, file size 104,857,600 bytes, Sharp/Keisoku Giken), theextract_partition_boot_sectorhelper reads the donor's Human68k partition boot sector (Sharp IPL Copyright 1990 SHARP at byte0x8000) and overlays it onto the generated partition's first sector at build time. The Sharp boot CODE survives intact; the embedded Sharp/KG BPB region (offsets0x12..0x22) is rewritten bypatch_sharp_kg_bpb_from_pc_bpbwith the output partition's actual FAT geometry. Result: HDD self-boots straight toC:>on every power-on; noSWITCH.X /HDstep needed; no size constraint — any--sizefrom 1 MiB up to the ~512 MiB cap of the Sharp/KG u8sectors_per_fatfield. Output partitions are formatted with 1024-byte FAT sectors (via the newcreate_blank_fat_with_sector_size) to match the SCSI logical sector size the donor's boot code reads at. License footprint: the boot-sector bytes flow user → user (your donor file → your output file) — they never live in the rusty-backup repo or shipping binaries. Same legal pattern as--system-diskor as runningSWITCH.Xmanually. SCSI only today; SASI donors are rare and unvalidated.First-cut bug + fix. The initial Phase D.2 commit (
f7e3fe6) was likely non-functional: it overlaid the donor sector verbatim while leaving our partition withcreate_blank_fat's default 512-byte-sector FAT. The donor's BPB then steered its boot code at the wrong byte offsets (its BPB said "1024-B sectors at byte 0x400" but our actual FAT was at byte 0x200 with 512-B sectors). The BPB-patch fix this commit ships parameterizes the FAT creation sector size and patches the donor's BPB with our partition's geometry, so the boot code reads the correct sectors regardless of output--size. MAME-byte-level evidence: the Drv3 activity overlay onmame x68030 -hard rb_phaseD2_fixed.hdfmatches the originalhd0.hdsdonor's boot pattern exactly, distinct from the stalled single-Drv0 pattern of the broken first-cut HDD.Honesty note on verification: MAME headless (
mame x68030 -hard out.hdf) doesn't render the emulated X68000 text screen to its AVI capture (the originalhd0.hdsexhibits the same headless-render artifact). End-to-end "boots toC:>with visible prompt" verification needs an interactive MAME session — not available in our headless test harness today. Strongest signals shipped: BPB patcher is unit-tested for byte-correct big-endian Sharp/KG layout; MAME accepts the HDD at ~230% real-time without crashing; SCSI drive activity overlay (Drv3 ●●) matches the donor's exact boot I/O pattern, distinct from a hung boot.The original Phase D workaround still ships: without
--boot-sector-donor(e.g. users without a real Sharp HDD donor),--system-diskalone produces an HDD where the user boots the same donor floppy from FDD0 once and runsA:\BIN\SWITCH.X /HD. One manual step writes the same boot sector from inside Human68k, and the HDD self-boots thereafter.Still open for §7.NH closure: the MiSTer X68000 manual-verify on real hardware. Today MAME
x68000 -sasi/x68030 -hardboth chain into our IPL cleanly; verification on the actual MiSTer X68000 core requires the user to source a Human68k system disk with SCSI driver inCONFIG.SYS(or use--system-disk+ the one-step SWITCH.X workflow above). - Phase A (SASI IPL stub) — byte-0
8a. Full read/write support — progress + remaining plan (2026-06-07)
Driven by the real ~/Downloads/X68000-fixtures set (BlueSCSI / ZuluSCSI
/ HDS / Henkan Bancho .hda/.hds, Bomberman.hdf SASI, SCSI2SD .ima).
Goal stated by the user: create / delete / extract files, change partition
sizes, and clone the filesystem — full support.
DONE (uncommitted on main):
- Read — every fixture inspects / browses / extracts. SHARP-KG
big-endian BPB parser (
Human68kBpb::parse_sharp_kg), geometry detection (X68kPartitionTable::detect_with_geometry), and thePartitionInfo::start_byte/byte_offset()accessor for the non-512-aligned SASI offset. - 🐛 Big-endian FAT fix — the FAT table is big-endian on real X68000
disks (dir entries stay LE).
Human68kBpb::fat_big_endiangatesfat_lookup/fat_set(FAT16). Was silently truncating every file1 cluster on both SCSI and SASI. Regression test
reads_big_endian_fat_chain_across_clusters. - Slices 1–3 (file management) —
create_file/create_directory/delete_entryare subdir-aware (helpersdir_slot_offsets,find_free_slot_in_dir,grow_dir_chain,find_entry_slot_in_dir,dir_is_empty); recursive delete uses the trait default over the new subdir-awaredelete_entry. Verified via CLImkdir/put/get/rmon a Bomberman copy + multi-cluster byte-exact round-trip. - Picker:
.hds+.imaadded toDISK_IMAGE_EXTS. Docs (README formats + per-core tables,full_MiSTer_support_status.md) updated. - Unrelated build-blocker fixed:
completions::bin_stem(Windows-path basename failed on Unix).
Slice 4 — resize — DONE (2026-06-07): resize_human68k_in_place
(src/fs/human68k.rs) is the Human68k peer of fs::resize_fat_in_place:
it accepts the 0x60 BRA.S jump, reads the big-endian SHARP/KG BPB at
0x12 via Human68kBpb::parse, and reads/writes the FAT big-endian
(FAT16). Grow extends the FAT (shifting root+data forward) and bumps
total_sectors; shrink keeps the FAT size so every cluster keeps its byte
offset (existing files stay byte-exact) and refuses to drop below the FAT16
floor. Wired into fs::resize_filesystem_for (so rb-cli resize + GUI +
the reconstruct-time call all route there) and the backup-folder restore
site in src/restore/mod.rs. patch_x68k_entries now takes the disk's
logical sector_size (1024 SCSI / 256 SASI / 512 synthetic), and the
rbformats/mod.rs X68k reconstruct branch derives that sector size from
metadata (original_size_bytes / length_sectors) to fix the new_sectors
length math, the disk_size_field, and the table offset (0x400 SASI vs
0x800 SCSI). Verified byte-exact on the BlueSCSI HD10 fixture across a
grow→shrink round-trip (multi-cluster COMMAND.X with HU header +
HUMAN.SYS + Japanese filenames all survive).
256-byte-SASI partition-data offset — DONE (follow-up, 2026-06-07).
Once real 256-byte SASI game disks with readable Human68k FAT volumes
landed in the fixture set (Populous / Lemmings / SSF2 / Votoms — partition
at sector 33 = byte 8448, non-512-aligned, with a Sharp/KG big-endian BPB
declaring 1024-byte FS sectors), the engine paths were converted off the
floored start_lba * 512: PartitionMetadata gained a persisted
start_byte, the backup engine reads at PartitionInfo::byte_offset(),
and the reconstruct loop + restore sites write/resize at
PartitionMetadata::byte_offset() (falling back to start_lba * 512
when start_byte is absent, so old backups round-trip unchanged).
Verified: in-place rb-cli resize grow+shrink byte-exact on SASI, and
backup→restore lands the partition region byte-identical at byte 8448
(tests/x68000_resize.rs::x68k_sasi_256byte_partition_round_trips_at_true_byte_offset).
Slice 5 — defragmenting clone — DONE (2026-06-07):
create_blank_human68k + Human68kFormatTemplate + format_template()
in src/fs/human68k.rs format a fresh FAT16 volume matching a source's
conventions (sector size, FAT count, root entries, endianness, OEM/boot
stub), re-patching only total-sectors + sectors-per-FAT. The new
src/fs/human68k_clone.rs hosts clone_human68k_volume (DFS walk +
contiguous replay, peer of clone_pfs3_volume) and
stream_defragmented_human68k (tempfile-backed streaming wrapper).
FAT16-only (FAT12 floppies use the floppy-container converter);
read-only attr / mtimes aren't carried over (same as the interactive
edit path) and surface as a warning. Wired into the CLI as rb-cli repack IMG[@N] [--size] (in-place defragment; defaults to the FS's own
declared size, not the partition-table length, so 256-byte-SASI disks
work) and into the GUI as a per-partition "Defragment…" button (Inspect
tab, image-file sources only) backed by model::repack_runner. The
in-place resizer can only trim trailing free space (it keeps cluster
byte-offsets); the clone reclaims holes left by deleted/relocated files.
Validated end-to-end on the real Populous.hdf 256-byte-SASI fixture
(tree identical, POP.X / LOAD.DAT / HUMAN.SYS byte-exact, X68k table
intact). This closes the X68000 full read/write/resize/clone goal.
- GUI/CLI parity: the write slices flow through
cli/resolve.rs(byte_offset()) andgui/inspect_tab.rs(converted) — both work for SASI today. Confirm the GUI browse-edit toolbar exposes mkdir / add / delete for Human68k partitions.
Reference fixtures live at ~/Downloads/X68000-fixtures; reference C
sources at ~/repos/dis68k/lib/libfat-human68k (FatFs port; fakes
sector 0), ~/repos/x68kd11s (Human68k 3.02 source). See memory note
x68000_scsi_hdd.md.
9. Explicitly excluded
Out, not parked. Listed so the question doesn't get re-litigated.
- StuffIt X /
.sitx— licensing. - SIT methods 6 / 8 / 14 — XADMaster itself flags as "interesting / partial".
- SIT method 13 compressor (writer) — stored / RLE90 covers preservation; no need for the compressor side.
- Resume support for partial backups/restores — explicitly deferred.
- Partition-scoped device restore (
/dev/disk3s2style) — explicitly deferred. - Disk-expansion v1 out-of-scope: shrinking via partition-table edits, shifting non-trailing partitions, RDB expand, per-partition free space.
- In-place XFS / btrfs shrink —
xfs_repair-scale risk; the clone-into-fresh paths replace them. - 23 N/A MiSTer cores — tape / cartridge / ROM-only; no filesystem
to support. See
full_MiSTer_support_status.md. a2kit/cbm/fluxfoxas dependencies — see the MiSTer plan §2 for the per-crate rationale; all are reference ports, not crates.- In-place btrfs balance / shrink — replaced by §2.4 scratch-recreate (gated on B.0 decision).
- JFS J.4b — edit-side write primitives (multi-month surface:
xtree-write + dtree-write + dmap-write + IAG alloc/free). J.4a
fsck shipped, so the read-side audit + OrphanInode reporting are
in tree. The only thing J.4b would unlock is
repair()-driven adoption of OrphanInode findings into/lost+found/. Real-world JFS orphans are rare (single-user vintage volumes don't accumulate the dangling-inode patterns big-multi-user systems do), and on the rare hit the user can route through Linuxfsck.jfsto resolve. Investment / payoff ratio doesn't justify the multi- month surface. Closed indefinitely — reopen only if a real user workflow surfaces meaningful orphan volumes that fsck.jfs can't handle. - XFS rmapbt rebuild / refcountbt rebuild (R2 / R3 fully safe
on rmapbt-enabled v5 images). Slices 1–6 of the v5 unlock shipped
(see §10): finobt + rmapbt + refcountbt parsers/walkers, R2 v5
gate lift, R3 finobt resync. R2 / R3 silently skip on
has_rmapbt() || has_reflink()to avoid leaving side-tree records stale. Making rmapbt-rebuild itself work is a separate ~500–800 LOC slice: walk rmapbt to find records over old tree blocks, emit new records pointing at the new ones, update AGF rmap counters. Same shape for refcountbt when reflinks are actually in use. Closed indefinitely — the vintage / single-disk retro-restore target rarely hits--repairon modern v5 images with these features, and when it does the user can route throughxfs_repairin WSL. Reopen only if a real workflow needs in-app v5+rmapbt repair coverage that the WSL fallback can't provide. - JFS multi-level dmapctl walker (read-side, BMAP at
MAX_BMAP_DEPTH_1_BLOCKS= 32 GiB cap). Vintage hardware doesn't address a multi-TiB JFS aggregate; the rusty-backup target is CF / SD card / single-disk retro-restore workflows where 32 GiB is already an outlier.walk_bmapreturnsOk(None)past the cap and falls back to the conservative full-partition extent — backups are still correct, only compaction efficiency is hurt. Lift would require implementing the L0/L1/L2 dmapctl hierarchy walk (separate from the xtree walker that already covers BMAP's internal-xtree pointers). Closed-by-design rather than parked. - External
chdman— replaced by the in-tree MAME CHD core vialibchdman-rs. The Windows-host elevation rework removed the separatechdman.exedependency. - BK0011M ANDOS write / extract / resize — kept as detect-only
forever. The Soviet-era ANDOS filesystem on the BK0011M MiSTer
core has no English-language layout spec, no community-maintained
reader / writer to validate byte-truth against, and a vanishingly
small target audience among retro enthusiasts using rusty-backup.
Our scaffold detects an ANDOS-shaped volume (the four canonical
sector-0 signatures landed during CLI parity work — see
detect_superfloppy) and routes it to the engine, which surfaces a cleanUnsupportedfor read / write / resize. That's enough forrb-cli inspectto report "ANDOS detected" without crashing. Spine row 5 (BK0011M) is therefore complete at the detect floor. Reopen only if (a) a credible spec turns up AND (b) someone steps forward as the maintainer for the format.
10. Recently closed (2026-06-02 sweep + follow-ups)
Audit trail. Each was either shipped, closed-by-design, or moved into the structure above before its source plan doc was deleted in the docs-consolidation pass.
-
X68000 floppy-container any-to-any conversion (XDF / HDM / DIM / D88 — Wave 2 prereq, 2026-06-06) — closes the third-party-tool gap for the X68000 row's floppy workflow.
src/rbformats/containers/floppy_geom.rshosts the four-element media set (1.2 MB / 1.44 MB 2HD, 720 KB / 640 KB 2DD) with size-unique geometries so size-based inference is unambiguous;xdf.rs/hdm.rsare raw-flat passthroughs delegating to the shared helper;dim.rshandles DIFC (256-byte signed header + track-existence bitmap, zero-fills missing tracks on decode, all-tracks-present on encode) and a generic "256-byte header + flat payload" fallback for non-DIFC.dimvariants (e.g. IBM XDF DIM); DIFC has no 640 KB media byte so DIM-encode rejects that geometry with a clear pointer to the other three formats. The pre-existingd88.rsencode_d88_bytes/decode_d88_bytesalready round-trip-tested; the newconvert_floppy_containerengine incontainers/mod.rsis the shared any-to-any entry point used by every surface. Surfaces:rb-cli floppy convert <in> <out>(extension-driven), `rb-cli floppy convert--to [--recursive]` (bulk), `rb-cli floppy info ` (geometry probe), `FloppyConvertDialog` in the Inspect tab (single-file GUI), and the existing GUI Bulk Convert dialog gets `Xdf` / `Hdm` / `Dim` / `D88` entries via four new `ExportFormat` variants intercepted in `bulk_convert_runner` and routed through the same engine. The inspect-tab materializer (`gui::prepare_disk_image_path`) now decodes `.xdf` / `.hdm` / `.dim` to tempfile so the inner FAT / Human68k filesystem is browsable directly. Tests: 65 container unit + 3 integration cells (`tests/floppy_container_roundtrip.rs` covers the 16-cell matrix, asserts DIM-DD640 rejection, and feeds a 3-file mixed-format batch through `start_bulk_convert` to confirm runner routing). Drops the `xdf2d88` Linux-tooling note from `docs/mister-deployment-testing-plan.md` §3.4. Unblocks the X68000 Wave-2 spine row's floppy column. -
AtariST MiSTer-core prereqs (§5 MiSTer plan → §10) — first slice of the MiSTer-core spine.
src/partition/atari.rsparses AHDI root sectors (4 big-endian 12-byte slots at 0x1C6, GEM / BGM / XGM / RAW IDs, optional disk-size + bad-sector + 0x1234 word-sum checksum) and walks XGM extended chains MBR-EBR-style — slot 0 (logical) is relative to the current extended root sector, slot 1 (XGM link) relative to the outermost container; cycle + hop + total-partition guards. Wires through a newPartitionTable::Ahdivariant gated by the magic-lesslooks_like_ahdi_rootprobe (rejects MBR signatures, requires at least one in-bounds entry with a recognized ID), inserted between RDB and superfloppy probes.partitions()emits synthetic MBR type bytes (GEM→0x01, BGM→0x06, XGM→0x05) so existing FAT12/16 dispatch routes the partitions directly; backup emitsahdi.jsonsidecar; editor / single-file CHD assemble bail with clear "not yet implemented" messages.src/rbformats/containers/{mod,msa}.rsadds the container-decode layer (per §3.2 of the MiSTer plan): pure-RustContainerKindenum +detect_container_kindmagic-first sniffer +open_container_bytes/open_container_readerreturning aBox<dyn ReadSeek>view; MSA decoder handles the 10-byte$0E0Fheader + per-track payloads (uncompressed or$E5-RLE-compressed, including the$E5 $E5 \$00 \$01literal-marker encoding); strict spt/sides/track-range bounds + 4 MiB decoded-size cap. Wired intomodel::source_reader::open_read(in-memory Cursor) and GUIprepare_disk_image_path(.msa→.sttempfile) so both the streaming + path-based open paths see flat sectors. End-to-end test inmodel/source_reader.rsround-trips a FAT12-shaped 720K MSA throughopen_read→PartitionTable::detectand asserts the detector lands onPartitionTable::None { fs_hint: "FAT" }. Tests: 11 AHDI unit + 2 partition::tests integration + 10 container/MSA + 2 source_reader (is_msa_path + end-to-end MSA → FAT). Closes the AtariST prereqs row indocs/mister_filesystem_implementation_plan.mdWave 1; AtariST is now[~]in progress (every applicable stage except reference cross-check + CLI parity flips on the FAT inheritance). v4 only. -
XFS R3 finobt resync (slice 5) (§8 parked → §10) —
repair_inobt_ leafnow usessb.sblock_hdr_len()(was hardcoded 16-byte v4 header) and re-stamps sblock-CRC on v5, returns per-recordInobtRecordChangeso the caller can propagate into finobt. Newsync_finobt_for_changeswalks the AG's finobt with the generalisedwalk_short_btree_blocks, finds each change's start_agino key, rewrites the matching record's mask + freecount in place, re-stamps the CRC. Pure-update transitions only; 0-boundary transitions refuse cleanly withUnsupportedso the caller surfaces a per-AG failure entry. R3 driver'shas_finobt()early skip comes down (rmapbt still gates). Tests added (2): direct call ofsync_finobt_for_changeson the v5 fixture's AG 0 finobt verifying mask propagation + CRC re-stamp + on-disk bytes; refusal-path test asserting the zero-boundaryUnsupportederror. -
XFS v5 side-tree walks (slices 1+2+3+4+6) (§8 parked → §10) — Modern xfsprogs 6.6.0's bundled v5 fixture has
features_ro_compat = 0xF(FINOBT + RMAPBT + REFLINK + INOBTCNT). All three side-trees now parsed and walked into the block-completeness map. (1) finobt:XFS_FIBT_MAGIC+XFS_FIBT_CRC_MAGICconstants,XfsAgireadsagi_free_root/agi_free_levelat offsets 328/332,XfsSuperblock::has_finobt()/finobt_magic()helpers,walk_short_btree_blocksgeneralised fromwalk_inobtso the same descent shape drives finobt walks too. (2) refcountbt: parsesagf_refcount_root/_levelat offsets 88/92. On-disk magic isR3FC(0x52334643), notR3C(0x52334320) as older kernel headers list — verified via probe of the bundled fixture. (3) rmapbt: parsesagf_rmap_root/_levelat offsets 24/36 (=agf_roots[2]/agf_levels[2]). (4)check_allocationsin fsck.rs now walks each side-tree per AG when its corresponding ro_compat bit is on; theif !sb.is_v5()gate aroundUnaccountedBlockscomes down. (6) R2 silently skips onhas_rmapbt() || has_reflink()and R3 silently skips onhas_rmapbt()— preserves the "no-op on clean volume" promise without claiming to safely rebuild trees whose side-tree records would go stale. Tests added (2):opens_modern_xfs_v5_fixtureextended to assert all three feature bits AND that AG 0's three side-tree roots are populated inXfsAgf/XfsAgi;repair_r6_drops_dangling_shortform_ entry_on_v5_fixturetolerates the now-activeUnaccountedBlocksfinding (R6 zeroes an inode without freeing its blocks; the completeness check correctly surfaces those blocks as leaked). -
JFS multi-page dtree walker (§8 parked → §10) —
parse_inline_dtree(name kept for API compat; now handles external dtrees too) descends BT_INTERNAL dtroots into off-disk dtpages. CRITICAL finding: kernel jfs_dtree.h declares dtpage'snextindex/freecnt/freelistas__le16, but on-disk truth (confirmed via probe of/bigdirin the regenerated fixture) is u8 — matching dtroot's layout. Walker uses on-disk layout: header is 32 bytes (one slot), u8 fields at offsets 17–21, self_pxd at 24. Recursive descent withDTREE_MAX_DEPTH = 16+DTREE_MAX_PAGES_VISITED = 65536cycle/budget guards. Sharedparse_dtree_ldtentrydecoder + continuation-slot closure works for both inline-dtroot (continuation in dtroot bytes) and external-dtpage (continuation in same page) slot pools. Fixture regenerated via WSL Ubuntu-24.04'sscripts/generate-jfs-fixtures.shto add/bigdir/file_001..200past the inline 8-slot cap, forcing JFS to convert /bigdir to an external dtree (1 internal dtroot + 2 leaf dtpages chained via sibling next pointers). Tests added (2 + 6 re-pinned): full walk emits all 200 unique names;read_fileround-trips file_001's "line 001" content after walker enumerated it; bmap / pmap / root- inode / used-size assertions re-pinned for new fixture geometry (pmap[0]=0xFFFFFFFF, mapsize=3788 nfree=3514 alloc=274, root.size=48 nlink=4); orphan-detection test moved to fino 34 (extent 1) since fino 8 is now a real bigdir/file_001 entry. J.4a fsckExternalDtreeNotWalkedwarning path remains as defence-in-depth for future fixtures with even-deeper dtrees but is dormant on the current fixture. -
JFS J.4a — fsck (read-only verifier) (§8 parked → §10) — new
src/fs/jfs_fsck.rs(~370 LOC) closes the read-side half of the §8 J.4 entry. Mirrorsufs_fsck.rs/efs_fsck.rsin shape (Builder- ordered passes) but adapts to JFS's AIT + IAG + BMAP layout.
Passes: (1) geometry re-validation (version / bsize / pbsize); (2)
AIT walk via
read_ait_inodefor AGGREGATE_I / BMAP_I / LOG_I / BADBLOCK_I / FILESYSTEM_I —dinode.di_number == inumplus size-non-zero on BMAP_I + FILESYSTEM_I (LOG_I points to SB logpxd, BADBLOCK_I empty on clean volume); (3) IAG walk viaread_fileset_iag_atover(FILESYSTEM_I.di_size / PSIZE) - 1IAGs, with the CORRECTED pmap interpretation (pmap[i]is the u32 word covering 32 inodes inside extenti, MSB-first → bit31 - in_extent) — an extent with length=0 + non-zero pmap word firesIagPmapInoextSkew; per-bit walk builds the allocated-fileset-inum set withFIRST_USER_FILESET_INO = 4skip (fino 0 dummy / 1 badblock / 2 root / 3 root-ACL kernel-reserved); (4) BMAP walk viarequire_bmap_walksurfaces stats + cross-checks dbmap.nfree against pmap-walk-derived nfree; (5) connectivity BFS from FILESET_ROOT_INO through inline dtrees, external dtrees emitExternalDtreeNotWalkedwarning + skip. Orphans = pmap-allocated ∖ reachable;PmapAllocatedInodeIsZerofires for the pmap/dinode skew variant. Wired intoFilesystem::fsckso the trait method returnsSome(Ok(result))for JFS volumes.OrphanInodeis the only repairable code today — a repair driver needs the J.4b edit-side write primitives. Constants promoted to pub(crate) for the sibling module. Tests (3 added; JFS suite 57 → 60):fixture_fsck_cleanis_clean() round-trip on the real fixture,fsck_flags_pmap_inoext_skewsets the MSB ofpmap[10](an unallocated-extent slot) and asserts the code surfaces,fsck_flags_orphan_inodeforges an orphan at fino 8 by setting pmap bit 23 ofpmap[0]+ stamping mode=0o100644 + di_number=8 at fino 8's dinode byte offset, asserts OrphanInode + orphaned_entries repairableflag.
- ordered passes) but adapts to JFS's AIT + IAG + BMAP layout.
Passes: (1) geometry re-validation (version / bsize / pbsize); (2)
AIT walk via
-
UFS U.4 —
repair()(§8 parked → §10) — closes the §8 parked driver.repair()lives on theEditableFilesystemimpl atsrc/fs/ufs.rsand delegates torepair_ufs, mirroringrepair_efsin shape: re-runsfsck_ufs, switches on the repairable issue codes, returns aRepairReport. Three branches: (1)ReplicaSb*Mismatch— reads the primary SB byte image once via the newread_primary_sb_bytes, parses the CG out of every replica issue's message, then rewrites each affected CG's replica slot via the newwrite_replica_sb_bytes(cg, &bytes)(refuses cg=0 and length != SB_READ_SIZE); (2)BitmapMissingAllocation— groups fragments by CG so each CG's free-bitmap reads + writes once, parses fragment number from each issue's message, clears the stray free bit (UFS polarity: set=FREE so clearing = mark allocated), bumps cg_cs.nffree down by the cleared count viaupdate_cg_cs; (3)OrphanInode— via the newadopt_orphans_into_lost_found_ufshelper: looks up or creates/lost+foundunder root inum 2 through the traitcreate_directory, then for each orphan re-reads the inode (to source the actual d_type viamode_to_dirent_type, not assume), generatesino_<inum>with_<n>suffix retry on collision (capped at 1000 retries before failed-fix bookkeeping), callsdir_insert+ writes the lost+found inode back. Unrepairable codes (geometry damage, double-allocation, out-of-range pointers, indirect-read failures) flow intounrepairable_count. Tests (src/fs/ufs.rs::tests, 3 added; UFS suite 62 → 65):repair_clears_bitmap_missing_allocation _and_refscks_cleanflips a root-claimed fragment's bitmap bit on the real UFS1 fixture and assertsis_clean()post-repair;repair_adopts_orphan_inode_into_lost_found_and_refscks_cleanforges an orphan viaalloc_inode+write_inodewith no parent link, repairs, asserts/lost+found/ino_<inum>exists with matching inum and the volume re-fscks clean;repair_rewrites_replica_sb_from_primarycorrupts CG 1's replica SB magic + bsize on a synthetic 2-CG UFS2 image and asserts everyReplicaSb*Mismatchis gone post-repair (geometry warnings from the hand-rolled image tolerated). -
JFS multi-IAG dispatch (§8 parked → §10) — lifts the §8 refusal in
read_fileset_iagfor FILESYSTEM_I sized past 2 × PSIZE. The old cap topped out at EXTSPERIAG * INOSPEREXT = 4096 fileset inodes; the new dispatch routes lookups to whichever IAG holds the requested fino via a cached HashMap. Surface (src/fs/jfs.rs):read_fileset_iag_at(iag_no)is the new primitive (logical page1 + iag_noof FILESYSTEM_I — page 0 is the dinomap header),iag_for_fino(fino)is the cached lookup (returnsCloned FilesetIag so callers don't carry a&mut selfborrow across the next reader seek),read_fileset_inode_global(fino)is the trait- layer entry point. The oldread_fileset_inode(fino, &iag)validates that the supplied iag's iagnum matchesiag_no_for_fino(fino)— silent mismatches now surface as a clearParseerror instead of garbage. Thedi_size > 2 * PSIZEgate is replaced byiag_no >= (di_size / PSIZE) - 1so any IAG covered by the on-disk fileset inode is now reachable. JfsFilesystem gains aniag_cache: HashMap<u32, FilesetIag>field (lazy, never cleared — read-only mode, xtree stable). All three trait callers (root / list_directory / read_file) moved toread_fileset_inode_global; the list_directory per-child loop benefits most because a directory whose children straddle IAGs now pays one logical-page read per IAG, not per inum. Tests (src/fs/jfs.rs::tests, 4 added; JFS suite 53 → 57):iag_no_for_fino_splits_every_4096_inodes— pure arithmetic;iag_cache_short_circuits_second_lookup_for_same_ iag— first hit populates, second hit reuses, third hit on a different inum in the same IAG stays at len=1;read_fileset_inode_global_matches_iag_supplied_form— dispatch result is byte-identical to the explicit(fino, &iag)form on the single-IAG fixture;read_fileset_inode_rejects_iag_mismatch— passing IAG 0 for inum 4096 returns the new "belongs in IAG 1" Parse error. -
UFS U.4 — fsck + write + EditableFilesystem (§8) — shipped across three slices on 2026-06-03. fsck (
src/fs/ufs_fsck.rs, ~500 LOC) mirrorsefs_fsck.rs: 5 ordered passes covering geometry re-validation, per-CG header + replica SB cross-check (every CG > 0, comparing magic/bsize/fsize/frag/ncg/fpg/ipg/sblkno/cblkno/iblkno/ total_frags), inode-table walk with full direct + single/double/ triple indirect descent (walk_inode_blockshelper returns both data + indirect-block fragments so a thorough bitmap check accounts for both), bitmap consistency (every inode-claimed fragment's bit must be CLEAR — UFS bitmap is set=FREE, opposite of every Linux FS), connectivity BFS from root inum 2. Repairable codes:ReplicaSb*Mismatch(11 fields),BitmapMissingAllocation,OrphanInode. Write primitives:write_frag_run,write_inode(version-aware UFS1 128 B / UFS2 256 B encoding),read_cg_iused_bitmap/write_cg_iused_bitmap/write_cg_free_bitmap(named by effect —alloc_*/free_*— so callers don't have to remember which bitmap uses which polarity),read_cg_cs/update_cg_csfor the 4-field cylinder-group summary,alloc_inode/free_inode/alloc_frag_run/free_frag_run. EditableFilesystem:create_file$ (\text{direct} \text{extents} \text{only}, ≤ 12 \times \text{bsize}), $create_ directory(single-block initial layout with./..stamped via DIRENT2 reclen + d_type, parent nlink bumped + CG ndir counter incremented),delete_entry(empty-dir scan, unlink- first-then-free crash-survivability, parent nlink + ndir decremented on dir delete),sync_metadata(no-op — every primitive flushes synchronously),free_space(per-CG nffree sum × fsize). Tests: 62 UFS tests total — fixture round-trips (create + list_directory + read_file + fsck-clean), edit failure modes (delete non-empty dir, oversize file rejection), bitmap + inode primitive smoke tests, replica-SB-magic-mismatch detection on a hand-rolled 2-CG synthetic UFS2 image.repair()itself — applying the repairable codes — is the only piece left of the §8 UFS line, parked there with a ~200-300 LOC scope estimate. -
JFS multi-level xtpage walker (§8) —
parse_xtpage_buf/read_xtpage_at/read_xtree_logical_page/walk_xtree_leavesprimitives onJfsFilesystem. The header layout is identical for inline xtroot (288 B) and external xtpage (4 KiB); the parser validatesnextindexagainst the buffer's actual capacity so corruption surfaces asParserather than slice OOB. Descent is iterative with a 16-level cycle guard and a 1M-node-visit budget. Refusals lifted:read_file_datanow walks every leaf XAD (sorted by logical offset, sparse gaps zero-filled);walk_bmapaccepts BMAP control inode's internal xtree (read_bmap_logical_pagedescends viaread_xtree_logical_page);read_fileset_iagaccepts FILESYSTEM_I internal xtree (with a separate multi-IAG gate ondi_size > 2 * PSIZE). Tests: 4 new on a synthetic 2-level xtree built on aCursor<Vec<u8>>— parse round-trip, DFS leaf walk, descent to correct physical block via sentinel bytes, page-uncovered error. 53 JFS tests pass total. Multi-IAG dispatch + dtree walker + dmapctl walker remain parked in §8. -
HFS — extending a raw partition image (§3.1) —
emit_apm_disk_with_hfsinsrc/fs/hfs_clone.rsnow takesApm::parseas optional: a non-APM source (a raw single-partition HFS image likepartition-0.img, no DDR at sector 0) is a valid input. Drivers come up empty, no Apple_HFS self-entry is mimicked, and the output is a fresh APM-wrapped disk with oneApple_HFSpartition at block 64. The fallback DDR mirrors whatbuild_minimal_apmproduces. GUI defaultoutput_hfvnow keys onpartition_offset == 0(covers both.hfvsuperfloppies and any raw single-partition HFS image), and the "Flat HFV" radio + Save As dialog broaden to "Bare HFS image (.hfv / .img)" so the cap-at-2047-MB framing reads as a classic-HFS limit rather than BasiliskII-specific. CLIexpandabout-string updated to advertise raw-source support. Tests: unitemit_apm_from_non_apm_sourceinsrc/fs/hfs_clone.rs(asserts 0 drivers copied, HFS at block 64, output parses + fsck-clean) and integrationtest_expand_runner_wraps_non_apm_source_in_apmintests/filesystem_e2e.rs(end-to-end runner frompartition-0.img-style source to APM-wrapped output with file round-trip). -
XFS hole (E.1) — v5/CRC primitives — new
src/fs/xfs/v5_crc.rswithcrc32c(Castagnoli, init=0xFFFFFFFF, xorout=0xFFFFFFFF, matched toxfs_start_cksum_safe),stamp_crc(buf, crc_off)that zeroes the field before computing, per-block-type stampers (stamp_inode_v3/stamp_dir3_blk_hdr/stamp_da3_blkinfo/stamp_sblock_crc_header/stamp_lblock_crc_header/stamp_agf/stamp_agi/stamp_agfl/stamp_superblock), and the field-offset constants every later slice keys off. Newsb_uuid/sb_meta_uuidfields onXfsSuperblockplus ameta_uuid()accessor that routes through the META_UUID incompat bit. A fixture cross-check (v5_crc_primitive_matches_on_disk_root_inode_and_ag_headers) verifies our CRC of mkfs.xfs's root inode + AGF + AGI + SB matches the on-disk values — if the crc32c crate's params ever drifted from whatxfs_repairexpects, that test fires. -
XFS hole (E.2) — v3 inode core stamping —
write_inode_region(the read-modify-write helper that every edit / repair path funnels through) callsstamp_inode_v3over the freshly-copied inode bytes on v5, just before the sector span commits.init_free_inode_chunkstamps each of the 64 fresh slots in a new chunk with its owndi_ino. Upstream alloc/repair gates still reject v5 because the AGI / SB / sblock write paths haven't landed yet — when E.5 lifts them, inode writes Just Work. Verified by a v5-fixture round-trip test that mutatesdi_atime, writes back throughwrite_inode_region, and re-validatesdi_crc. -
XFS hole (E.3) — dir3 block / data / leaf1 builders — three dir builders parameterized on
is_v5:build_block_diremits XDB3 with 64-byte hdr + bestfree at offset 48;build_leaf_data_blockemits XDD3 (same layout);build_leaf1_block_v4→build_leaf1_blockemits XD3F with 56-bytexfs_da3_blkinfo+ magic 0x3DF1 at byte 8. CRC tuple is left zero in the builder; call sites (create_block_dir_from_shortform,block_insert_entry,convert_block_dir_to_leaf_formover 3 blocks,dir_remove_entry) stamp viastamp_dir3_blk_hdr/stamp_da3_blkinfoonce the destination fsblock is known. The bests-extraction in the conversion path adapts (v4: bytes 6..8, v5: bytes 50..52). Newv5_crc::fsblock_to_daddrhelper translates a filesystem block to the 512-byte-basic-block disk address (fsbno << (blocklog - 9)) everyblknostamp needs. Three unit tests inedit.rs::testsround-trip each builder through the existing dir3-aware reader. -
XFS hole (E.4) — sblock-crc btree builder —
btree_build'scapacities/blocks_needed[_for]thread anis_v5flag (v5 shrinksmax_leaf/max_internby the 40-byte CRC overhead), andbuild_sblock_btree/build_alloc_btreetakeOption<&XfsSuperblock>. WhenSome(sb), every produced block (leaf + internal node) carries the standard 16-byte v4 short header followed by the CRC-header tuple at bytes 16..56 (bb_blkno/bb_lsn/bb_uuid/bb_owner/bb_crc) via the newstamp_v5_sblockhelper.freespace_rebuildpicks v5 magics (XFS_ABTB_CRC_MAGIC/XFS_ABTC_CRC_MAGIC) and passesSome(sb)throughbuild_alloc_btreeon v5 volumes; inobt rebuild (R3) andswap_inobt_blocks_in_agstay v4-only because their upstream gates still reject v5. Two builder-level tests verify the round-trip and assert every emitted block's CRC validates viacrc_valid. -
XFS hole (E.5a) — AGF/AGI/AGFL/SB stampers — new per-sector stamp+write helpers (
write_agi_sector/write_agf_sector/write_agfl_sector/write_sb_primary) onXfsFilesystem. Each branches onsb.is_v5(), stamps the v5 CRC tuple (uuid/lsn/crc), and writes through. Every existing AGI / AGF / SB write site funnels through these —alloc_inode_slot,alloc_new_inode_chunk,grow_inobt_with_new_record,free_inode, R3resync_sb_inode_counts, R4 secondary-SB fixup, R4b summary rewrites, R2write_agf_fields+resync_sb_fdblocks,bump_sb_ifree/bump_sb_inode_counters. AGFL helper stays in tree (#[allow(dead_code)]) for future allocation refills. -
XFS hole (E.5b) — splice / grow inobt v5 + lift inode alloc —
walk_inobtreader accepts both v4 (IABT) and v5 (IAB3) magics + the 56-byte CRC header.splice_inobt_single_leaf_recordand the single-leaf splice path intry_claim_slot_from_existing_ chunksusesblock_hdr_len(sb.is_v5())to pack records and re-stamp the leaf's sblock-crc before write.grow_inobt_with_new_ recordselectsXFS_IBT_CRC_MAGIC+Some(sb)on v5 so every emitted node carries a stamped CRC.free_inodemirrors the splice-write stamp. Top-level v5 gates onalloc_inode_slot/alloc_new_inode_chunkcome down. Integration testalloc_new_inode_chunk_on_v5_stamps_every_block_and_stays_cleandrives a fresh-chunk allocation on the modern fixture and asserts: AGI/AGF/SB CRCs re-verify, the inobt leaf's sblock-crc re-verifies, AGI/SB counters bump by +64, every fresh dinode carries di_version=3- di_ino + di_uuid + valid di_crc, and
run_fsckstays clean.
- di_ino + di_uuid + valid di_crc, and
-
XFS hole (E.5c) — bmbt v5 + fork_offset sweep + create_file —
build_bmbt_leaf_v4→build_bmbt_leafparameterized onis_v5(72-byte CRC header,XFS_BMAP_CRC_MAGIC).do_create_filestamps the leaf'sxfs_btree_block_lhdrCRC viav5_crc::stamp_lblock_crc_header(owner = new file inode) before the write.fork_offset(false)hardcoded at 10 sites inedit.rsis swept tofork_offset(sb.is_v5())so the literal area starts at byte 176 on v5.init_file_inodezeroes bytes 100..176 on v5 so a recycled slot doesn't inherit stale v3 fields. Integration testcreate_file_on_v5_fixture_round_trips_through_fsckcreates a new file on the modern fixture, re-opens read-only, lists / reads the file back, and assertsrun_fsckstays clean — the verifier walks every metadata block, so a stale CRC anywhere along the alloc / init / insert path would surface as a failure. -
XFS hole (E.5d) — repair gates + verify — R5 (
run_inode_core_ repair), R6 (run_dir_repair), R7 nlink (run_nlink_repair), and R7 orphan reconnect (run_orphan_reconnect) all lift their v5 early-returns now thatwrite_inode_region,dir_insert_entry,init_empty_shortform_dir, and the inobt-walk traversal are v5- aware. R5/R6 inobt-leaf iteration in their own helpers picks upXFS_BTREE_SBLOCK_CRC_LEN(56 B) when v5. R2 (freespace rebuild) and R3 (inobt repair) stay v4-only: both could write a CRC- correct tree, but v5 layouts may carryfinobt/rmapbt/refcountbtro-compat metadata that R2's block-completeness map and R3's AGI-summary recompute don't model — parked in §8. Stale"v5 not supported"comment insrc/fs/mod.rsupdated to reflect full v5 read+edit+fsck support. Integration testrepair_on_clean_v5_fixture_runs_passes_and_stays_cleanruns the full repair pass on a clean v5 volume: no fixes applied, no failures, andrun_fsckstays clean post-repair. 114 xfs tests pass. -
Windows install + self-update — Phases 1-9 + elevation A-H all done (Setup.exe via Inno, in-app
self_update+self_replace, ARPDisplayVersionrefresh, per-user file-association registrar, asInvoker elevation rework, Windows-VM runtime validated). -
BasiliskII HFV support — Phases 1-8 done; 4.1/4.2 standalone "New blank HFV" dialog closed-by-design (folded into Expand / Export-to-HFV).
-
HFS+ editable surface —
impl EditableFilesystem for HfsPlusFilesystematsrc/fs/hfsplus.rs:3703with full create/delete/sync_metadata + Finder info + resource fork + blessed folder + xattr B-tree + hardlinks + HFSX case-sensitivity + journal parse + replay + history view + defrag-on-backup. -
Superfloppy → partitioned-disk export —
src/restore/ superfloppy_wrap.rsships MBR + GPT synthesis with alignment variants and VBRhidden_sectorspatching. -
Clonezilla GPT parsing —
<disk>-gpt-1st+<disk>-gpt-2ndraw-sector readers wired throughsrc/restore/mod.rs:2048-2080via the same CRC-validatedGpt::parsepath native GPT restores use. -
Earlier btrfs "trim at last chunk end" plan — abandoned (real- world layouts don't have a useful last-chunk-end; superblock mirrors matter; restore-side resize would corrupt). Replaced by §2.4.
-
XFS gap-fill — v5 read shipped, fsck shipped (R1-R8 pipeline + multi-level inobt rebuild + freespace btree rebuild), edit shipped (v4 + snapshot/rollback + wired into
open_editable_filesystem), AGF walk shipped viafreespace_rebuild.rs. Open: holes A-E in §2.1 and shrink in §2.2. -
JFS + UFS — promoted from parked (
additional_nix_filesystems.md"JFS / UFS parked, noted for completeness") to scheduled in §1. -
PLAN-ext-btrfs.mdTask 16 —tests/filesystem_e2e.rsships 89 ext2/ext4/btrfs browse + read + symlink + permissions + nested-dir + compaction round-trip + resize + detection-routing tests, with committed fixtures. -
Disk expansion — Phases 0-5 all shipped (PartitionBar widget, XFS recognizer, Mode A free-space, partition-table writers for MBR/GPT/APM/SGI, Mode B last-partition extend, Add-Partition flow).
-
Amiga Phase 10 —
resize_affs_in_place/resize_sfs_in_place/resize_pfs3_in_place+ their validators + RDB PART-entry fixup all shipped. Standard "restore an Amiga RDB image to a larger CF card and grow to fill" works end-to-end. -
HFS+ defrag-on-backup Steps 22g + 23 —
shrink_to_minimumwired via the "Resize partitions to minimum size" GUI checkbox and the per-partition Defrag toggle;"Defrag emit: ..."log line insrc/backup/mod.rs:1109-1121. -
HFS+ journal Steps 24-26 + 28-29 — parse + replay + transaction recorder + history viewer + fsck journal phase. §2.3 covers what's left.
-
SEA detection —
find_sea_archive+ extract.rs routing classifies.seaas SIT-over-Mac-app data fork. -
Native Mac archives reader/writer — BinHex 4.0 + StuffIt classic
- SIT5 + SEA, CLI verbs, Archives tab in GUI, all shipped. §6.1 covers the GUI import / auto-unwrap / folder-emit / file-picker remainder.
-
docs/codecleanup.md— every actionable item closed; survey retired. -
docs/virtualization-formats.md— Phases 1-5 closed; modern-VM containers (VHD dynamic / QCOW2 / VMDK flat + sparse) and legacy imports (GHO + IMZ with password decryption + file-aware FAT+NTFS reconstruction) all shipped. -
docs/efs_resize_and_edit.md— EFS read + edit + fsck + in-place grow + conservative shrink + aggressive shrink all shipped. -
docs/chdman_replacement.md— Stages 1-10 shipped; no externalchdmanbinary required. The user-side CD CHD browse verify in §7 is the only loose end. -
Unix-FS read track (§1 ReiserFS / UFS / JFS) — three filesystems shipped end-to-end. ReiserFS (v3.5
ReIsErFs+ v3.6ReIsEr2Fs, reiser4 rejected): superblock at byte 65536 + bitmap-driven layout-preservingCompactReiserFsReader(set bit = allocated) + S+tree walker (collect_leaf_block_numbersDFS with cycle / level / child=0 guards) +read_statdata/read_symlink_target/list_directory(DIR_ENTRY decode, hidden-filter) /read_file(SD/IND/DRCT stitching with sparse + tail-pad handling). Fixturetests/fixtures/test_reiserfs_v3_6.img.zst. UFS (UFS1 4.2BSD/FFS + UFS2 — SunOS / Solaris / *BSD): superblock probe at 65536 then 8192 with both endians, SU+J dirty refused, cylinder-group bitmap walker (set = FREE — opposite polarity of every other Linux FS, matching primitiveBitmapReader::highest_clear_bit),CompactUfsReader,UfsInode(UFS1 128 B / UFS2 256 B),resolve_logical_block(direct → single → double → triple indirect with sparse handling), DIRENT2 decode withd_reclenalignment validation, inline-symlink decode at the version-specific cutoffs. JFS (JFS2 only — AIX JFS1 rejected via the"JFS1"ASCII magic gate paired with version1..=2): Aggregate Superblock at byte 32768, AIT bootstrap atAIMAP_OFF + 2 * SIZE_OF_MAP_PAGE = 0xB000, BMAP B+tree walker that tightenslast_data_byteto the pmap-derived highest allocated block (still maxed againstlogpxd/fsckpxdso the inline log rides out verbatim),CompactJfsReader, IAG-based dinode locator with pmap sanity check, inline-dtree walker (stbl iteration through slots 1..8 + dtslot continuation chain for names > 11 chars), inline-xtree XAD walker with sparse gap-fill. Parked Tier B+ follow-ups (UFS U.4 / JFS J.4 fsck+edit, JFS J.3 multi-page B+tree walkers) live in §8. -
ProDOS access-bit setter —
EditableFilesystem::set_prodos_access(entry, access)patches the access byte at directory-entry offset 30 in place. Lock ($21 = read- backup) / Unlock ($C3 = read + write + destroy + rename +
backup) buttons on the browse-view edit toolbar;
StagedEdit:: SetProdosAccess { entry, access }queues through the same replace-prior + pending-query helpers as the rest of the staged-edit pipeline.
- backup) / Unlock ($C3 = read + write + destroy + rename +
backup) buttons on the browse-view edit toolbar;
-
NTFS file-aware GHO — compressed path —
NtfsCompressedStateinsrc/rbformats/gho.rscarries per-block zlib metadata + a lazy scan cursor;open_ntfs_compressed_mft_onlydecompresses just the $MFT run to bootstrap the index, thenextend_ntfs_compressed_indexdecompresses the rest on demand asGhoReaderreads progress. The scan result rides throughntfs_scan_cache_storeso re-opening the same.GHOskips the multi-second decompress pass. Compressed + uncompressed file-aware NTFS GHO backups now round-trip identically. -
rb-cli fsck --format json|yaml—FsckResult/FsckIssue/FsckStats/OrphanedEntry/RepairReportall carry#[derive(Serialize)]. The verb mirrors the structured-output flag every other read-only verb already has, with aclean: booltop-level envelope for scripted branching and arepair: { applied / failed / unrepairable }sub-object when--repairruns. Unsupported filesystems emitstatus.error: true, status.code: GENERIC_FAILURE, result: null. Process exit code stays issue-driven so$?branching works in either text or structured mode. -
rb-cli getglobbing — accepts the same glob /--exclude/--ignore-case/--case-sensitivesyntax asls/rm. Adds-r/--recursive(literal-directory source without it errors with a clear pointer to the flag; glob-matched directories without it skip with a warning),--force(overwrite existing host files),--skip-existing(skip silently). Destination semantics follow cp/rsync: literal-file source treats DST as the target path (orDST/<basename>when DST exists as a directory or has a trailing separator); literal-directory +-rmirrors the tree underDST/<source-basename>/; glob lays matches out relative to the longest non-glob prefix of the pattern. Symlinks ride out as plain text files containing the target (lossy but cross-platform safe); specials skip with a one-line note. -
XFS hole (C) — block→leaf dir conversion (v1: 2 data blocks) —
block_insert_entryfalls through toconvert_block_dir_to_leaf_formwhenbuild_block_dirreportsDiskFullafter appending the new entry. The conversion sorts every entry by hash (matching the reader'sdir_hashname), splits at the byte midpoint between block 0 (with synthetic./..) and block 1, allocates two new dir blocks (data block 1 + the XD2F leaf1 index block at file offsetXFS_DIR2_LEAF_OFFSET = $2^{32}$ / blocksize), builds and writes all three blocks viabuild_leaf_data_block(XD2D — header+bestfree+tag entries packed against the head, free record at the tail recorded inbestfree[0]) andbuild_leaf1_block_v4(xfs_da_blkinfo+xfs_dir2_leaf_hdr+ sorted entries[hashval(4) + address(4)] + bests array at the tail), then rewrites the inode viawrite_dir_inode_multi_extent(di_format=Extents,di_size=2*dirblksize,di_nblocks=3*blocks_per_dir,di_nextents=3, 3-record inline data fork — well within the 9-extent inline cap). Address encoding:(file_block * dirblksize + byte_off) / 8— block 0 entries get dataptrs < 512, block 1 entries 512..1024 (at 4 KiB dirblksize).dir_can_insertpre-flights the split fit and refuses leaf-form sources before do_create_file allocates an inode (avoids orphans).block_remove_entryrejects leaf-form sources with a clear error — remove + leaf→block recompaction is a follow-up slice. Reader path unchanged (stillwalk_dir2_data_blocks). Testdir_overflow_converts_block_to_leaf_formadds files until the inserter rejects with the leaf-form-not-implemented message, verifies100 adds absorbed by the conversion,
di_format=Extents,nextents = 3, every original + every successful add lists back, andrun_fsckstays clean. v4 only. -
XFS hole (D) — bmap-btree file forks (single-leaf) —
do_create_fileno longer caps atMAX_SINGLE_EXTENT_BLOCKSblocks ormax_inlineextents:alloc_extentsreturns arbitrary runs (capped atbmbt_leaf_max = (bs - hdr) / 16),runs_to_extent_recordssplits any run whose count exceeds the 21-bit on-diskblockcountfield into multiple consecutive records, andinit_file_inodedispatches between inline-extents and btree formats. When the record count exceedsmax_inline, do_create_file allocates one block for the bmbt leaf, writes the leaf viabuild_bmbt_leaf_v4(XFS_BMAP_MAGIC, level 0, sibling pointers NULLFSBLOCK, extents packed after the long-form header), andinit_file_inodewrites the in-inode root viawrite_bmbt_root_to_leaf(level 1, numrecs 1, key = first extent's startoff, ptr = leaf fsblock, remaining (key, ptr) slots zeroed).di_format = 3,di_nblocksincludes the leaf block. Multi-leaf bmbt (>= bmbt_leaf_max records) is refused with a clear error after rolling back the data allocation. R2 freespace rebuild'smark_inode_blocksnow walks bmbt-format inodes via the newcollect_bmbt_blockshelper (mirrorswalk_bmbt's descent + rightsib chain, returns block numbers) and marks the bmbt blocks as owned alongside the extent blocks — the previous abort ondi_format==3is gone. R5 inode-nblocks repair also picks up bmbt blocks. Reader path unchanged (stillwalk_bmbt). Testcreate_file_writes_bmbt_format_when_extents_exceed_inline_capforges both AGs' bnobt/cntbt into 12 free fragments of 100 blocks (gaps of 100) so the fast-path single-extent alloc fails, then creates a 1100-block file whose 11 carved runs blow past the 9-extent inline cap and force btree format. Assertions:DiFormat::Btree,nextents > 9,nblocks = data + 1(one leaf), byte-for-byte data round-trip via the standardread_file. v4 only. -
XFS hole (B) — block → short-form dir re-compaction —
dir_remove_entrycallstry_recompact_block_dir_to_shortformafterblock_remove_entryrewrites the data block. The helper bails (no-op, leaves dir in block form) when the dir is multi-extent / not at file offset 0 / any inode >u32::MAX(8-byte short-form not implemented) / the surviving entries don't fit the inode literal area. Otherwise: build the short-form fork bytes (count + i8count=0 + parent + per-entrynamelen(1) + offset(2) + name + [ftype] + ino(4), with offset cookies matching the notional dir2 data-block positions starting after./..),free_blocksthe directory's data block(s), and rewrite the inode:di_format=Local,di_size=sf_len,di_nblocks=0,di_nextents=0, literal area zeroed before the fork bytes are written so stale block-fork bytes can't leak through. Mirrorsxfs_dir2_block_to_sf. v4 only. Testblock_dir_recompacts_to_shortform_on_shrinkadds 20 files (forces short-form → block conversion), deletes all 20 (last delete trips the re-compaction), then verifies the root is back toDiFormat::Localwithnblocks=0/nextents=0, originals intact, free space fully reclaimed, and the volume fsck-clean. -
XFS hole (A) — new-chunk inode allocation (single-leaf + multi-level grow) —
alloc_inode_slotlifted intotry_claim_slot_from_existing_chunks; the newalloc_new_inode_chunkreadsdi_versionfrom the root inode, carvesblocks_per_chunkcontiguous fsblocks aligned tosb_inoalignmt(orblocks_per_chunkwhen ALIGN is off) via the newalloc_blocks_aligned+carve_aligned_from_largestprimitives, writes 64 free dinodes (magic- version +
di_mode = 0+di_next_unlinked = NULLAGINO), then dispatches between two splice strategies. Single-leaf splice (splice_inobt_single_leaf_record) — when the AGI root is a one-block level-1 leaf with at least one open slot, sort + rewrite the leaf in place; AGI root/level unchanged. Multi-level grow (grow_inobt_with_new_record) — when the existing tree is multi-level or its only leaf is full, gather every existing record from every leaf, add the new all-free chunk, key-sort, allocateblocks_needed_forcontiguous AG-local blocks for a fresh tree AND reclaim the old tree blocks in one freespace rebuild via the newswap_inobt_blocks_in_aghelper (alloc + free fused; the old blocks coalesce into the AG's current-full-free set, thennblocks carve from the largest extent, andrebuild_ag_freespacewrites one set of new bno/cnt trees), re-build the inobt via R3'sbuild_sblock_btree, write every new tree block, then relocate AGI root + level. AGIcount/freecount+ sbsb_icount/sb_ifreebump by 64 once after dispatch — both paths share the same counter update. Slot search re-runs and claims slot 0 of the new chunk via the existing -1 accounting. v4 only (v5/CRC is hole E). New helpers:collect_inobt_all_blocksininobt_repair.rs(sharedwalk_inobttraversal returns leaves + all blocks). Tests: 5 alignment-carve unit tests +alloc_new_inode_chunk_extends_inobt_and_keeps_volume_cleanend-to-end against the v4 fixture (counter deltas, sorted record + all-free shape, every dinode initialized, our verifier stays clean) +alloc_new_inode_chunk_promotes_full_leaf_to_multi_level(forges 254 phantom records to fill the root leaf, then verifies the new tree is level >= 2, AGI root moved, all max_leaf+1 records survive in per-leaf-sorted form, sb counters bumped). Oracle helper:examples/xfs_fill_chunks.rs.
- version +
-
Native Mac archives GUI polish (Workflows A / B / C / D / E) — full sweep. Detection is now magic-driven via
macarchive::detect::detect_mac_archive(sixMacArchiveKindvariants — BinHexSingleFile, BinHexOverSit, BinHexOverSea, Sit, Sit5, Sea); filename extension is hint-only.MacArchiveKind::label()keeps user-visible strings ASCII-only (CLAUDE.md egui-font rule).detect_mountable_imagesniffs DiskCopy 4.2 / raw HFS / raw HFS+ for the auto-unwrap "Mount in new Inspect tab" affordance. D.4: inspect / restore / backup tab pickers gained a "Mac archives" filter group alongside Disk Images. D.1:extract::open_bytesroutes throughdetect_mac_archiveand synthesizes a one-entry archive (with propercrc16_arcstamping) for loose BinHex single-file inputs, so the Archives tab and every downstream extract path handle.hqxend-to-end without any special case. D.2: Archives tab caches aMountablePayloadon load when the archive has exactly one entry whose data fork sniffs as a disk image; "Mount in new Inspect tab ()" button writes the bytes to a tempfile, hands path + tempdir guard to the Inspect tab via the new InspectTab::load_image_with_tempdir, and the RustyBackupApp update loop drainstake_pending_inspect_open+ switchesactive_tab. D.3: per-entry checkbox column on the Archives tab grid; newextract::extract_filtered(keep: impl FnMut(usize) -> bool)(folder markers always extract); "Extract Selected... (N)" button alongside Extract All. A: browse-viewstage_host_fileintercepts any picked file that sniffs as a Mac archive when the target is HFS / HFS+; queues aPendingArchiveImport; modal pops with the per-kind three-action set ("Convert" / "Expand" / "Convert and expand" + "Convert only" for HQX-over / "Add as-is" + Cancel). Extract path dumps to an AppleDouble tree under a BrowseView-ownedarchive_import_tempdir, then re-stages the resulting children through the existingstage_host_directory/stage_host_fileflow so resource forks come back throughdetect_resource_fork. Tempdir cleaned up on Apply / Discard / close. B: three new "Export Mac archive" buttons under the existing Extract row — BinHex (single file only), StuffIt (any selection — folders walk recursively), and StuffIt-over-BinHex (any selection).walk_fs_to_input_nodesreads data + resource fork + type/creator from the FileEntry / Filesystem trait;build_archive_tree+ optionalbinhex::build_binhexwrap produces the output bytes. C: a "Save as Mac archive..." button on selected files inside the disk image sniffs the bytes and pops a Save-as-is / Decode-and-save modal with an inline ForkFormat dropdown (BinHex / MacBinary / AppleDouble / Raw) for the decode path. E: a "Browse archive..." button opens a floating read-only viewer window with the same shape as the standalone Archives tab — entry list with per-entry checkboxes, fork-format dropdown, Extract All / Extract Selected. Edit operations inside the archive (would need repacking) are deferred. Modal wording lives indocs/mac-archives-gui-wording.md(kept as the spec source of truth). Tests: 17 detect-tests (6 kinds + 3 mountable + edge cases) + 4 extract-tests (open_bytes synth, extract_filtered subset).