NTFS undelete corpus
tests/generated/make_ntfs_undelete_corpus.py builds
tests/fixtures/ntfs/undelete.img.gz with mkntfs and ntfs-3g and records the
ground truth in undelete.manifest.json. Each entry carries the original
path, size, SHA-256, MFT record number and expectations.
| scenario | content | expectation |
|---|---|---|
| A | resident files (20 B, ~100 B JSON, 400 B binary, Unicode name, empty file) | exact, Excellent |
| B | contiguous 64 KiB and 1 MiB | exact, ≥ Very good, one extent |
| C | 2- and 10-extent fragmented files | exact, ≥ Very good, fragmentation reported |
| D | 1/10/25/50/100 % of clusters marked allocated in $Bitmap and overwritten |
allocation counts match, likelihood declines monotonically, wording never says "overwritten" |
| E | file whose directory records were reused by new directories | path uncertain (\?\document.txt), data still exact |
| F | deleted records with corrupted USA, attribute length, runlist, name length | typed diagnostics, scan continues; a lost runlist is Unrecoverable |
| G | JPEG and raw binary whose clusters were zeroed after deletion (TRIM simulation) | $Bitmap still free; the JPEG contradicts its format (≤ Very poor), the raw file is ambiguous (likelihood kept, confidence ≤ 65) |
| Z | file that legitimately consists of zeros | exact, ambiguous zero assessment, confidence ≤ 65 |
| H | file inside a deleted (not reused) directory | exact, path recovered through the deleted directory |
| V | real JPEG, PNG, PDF, DOCX | exact, structure validates, Excellent |
The integration test tests/integration/tests/ntfs_undelete.rs asserts every
row and additionally checks that the image is never written and that a
recovery destination equal to the image is refused.
FAT and exFAT corpora
tests/generated/make_fat_undelete_corpus.py builds FAT12, FAT16 and FAT32
images with mtools (no mounting), and
tests/generated/make_exfat_undelete_corpus.sh builds an exFAT image with
mkfs.exfat and exfat-fuse over a loop device. Each has a manifest with paths,
sizes, SHA-256 digests and expectations:
| scenario | content | expectation |
|---|---|---|
| A | small and 200 KB contiguous files | exact under the contiguous assumption |
| C | file written into holes between fillers | FAT: heuristic reconstruction, exact but capped; exFAT: FAT chain intact, ≥ 2 extents |
| D | file whose clusters were reused by a new file | reallocated clusters reported, ≤ Very poor, not exact |
| E | empty file | Excellent, validation not applicable |
| H | file inside a deleted directory | exact, path through the deleted directory |
| L | long name with spaces and Unicode | name reconstructed from deleted LFN / name entries |
| V | JPEG, PDF, DOCX | validators pass, exact |
W (fat32w only) |
JPEG, PDF, DOCX, TXT deleted the way Windows does on a large FAT32 volume: FAT chain cleared and first-cluster high word zeroed, low word pointing into 36 MiB of older files | start inferred, exact; Good for validated types, Poor with confidence ≤ 60 for the unknown type |
The fat32w image (40 MiB, 512-byte clusters, about 80 000 clusters) is the
regression fixture for the 0 % / 0-byte results first seen on a 16 GB USB
stick.
tests/integration/tests/fat_exfat_undelete.rs asserts every row for all
five images and runs 240 corruption rounds without panics.
ext2/3/4 corpora
tests/generated/make_ext_undelete_corpus.sh builds an ext4 image (4 KiB
blocks, extents, checksummed 64-bit journal tags), an ext3 image (4 KiB
blocks, block maps, legacy journal tags) and an ext2 image (1 KiB blocks,
six block groups, no journal) with mke2fs and the kernel drivers over loop
devices. The manifests record each file's inode number, size, SHA-256 and
extent count (filefrag) before deletion, so the tests assert exactly
what the journal still yields:
| scenario | content | ext3/ext4 expectation | ext2 expectation |
|---|---|---|---|
| A, B | 700 B and 200 KB contiguous files | exact, ≥ Very good, layout from the journal | found by inode, no name, no size, Unrecoverable |
| E | empty file | Excellent, validation not applicable (the journal shows it was empty) | as above |
| L | long name with spaces and Unicode | name and path from directory slack | — |
| V | JPEG | validator passes, exact | as above |
| H | file inside a directory removed with rm -r |
exact; the directory's layout comes from the journal, its entries from its (still readable) block | as above |
| D | 1 MiB file whose blocks and inode were reused by a new file | reallocated blocks reported, ≤ Very poor, not exact; the name is chosen by the transaction in which it was live | absent (the inode is alive again) |
| C | file written into holes between fillers | exact, extent count as recorded | as above |
| S | sparse file (head, 290 KB hole, tail) | exact, sparse reported, holes read as zeros | as above |
| J | file grown after its first commit | exact from the newest journal copy | as above |
| absent | file renamed while alive | never a candidate | — |
The ext2 rows document what the kernel's ext2 driver leaves behind: it
clears the size, the block map and even the directory entries on deletion,
so without a journal PhoinixDR reports deleted inodes with their deletion
time only and leaves the content to carving (scan --deep).
tests/integration/tests/ext_undelete.rs asserts every row for the three
images, checks journal tag checksums, the allocation view against the
superblock's free count, and runs 120 corruption rounds without panics.