Found by handing a volume we wrote to fsck.minix.
$ fsck.minix -f ours.img
Inode 7 is marked as unused, but file '/OTHER.BIN' uses it
Inode 7 is used, but marked as unused in the bitmap.
Every MinixFs image this project has ever produced said that, about its last inode.
What the convention actually is
Read off mkfs.minix rather than off a document. A fresh volume, all three versions:
v1: firstdatazone=10 imap bits=[0, 1] zmap bits=[0, 1]
v2: firstdatazone=15 imap bits=[0, 1] zmap bits=[0, 1]
v3: firstdatazone=15 imap bits=[0, 1] zmap bits=[0, 1]
Bit 0 of the inode bitmap is reserved for the inode number that means "none", and inode N is bit N — hence bits 0 and 1 on a fresh volume: the reserved one and the root. The zone bitmap works the same way and covers the data zones only, counting from the first of them: absolute zone Z is bit Z - firstdatazone + 1. The metadata zones below firstdatazone are not in the map at all.
MinixV1 and MinixV2 do both correctly. MinixFs did neither.
|
ours |
correct |
| inode bitmap |
bit N-1 |
bit N |
| zone bitmap |
bit Z (absolute) |
bit Z - firstdatazone + 1 |
| zones below firstdatazone |
marked used |
not in the map |
Why only the inode half was reported
The inode half is off by one, so the last inode's bit falls off the end — one complaint per volume, every volume.
The zone half never showed. Our writer sizes a volume to fit exactly, so every zone is in use, and "every bit set" is right by accident whichever way the bits are counted. It only appears once something is free — after an in-place remove, say. With the fix in place a volume at 29 of 30 zones checks clean; before it, the free zone lands under the wrong bit.
Why nothing caught it
The writer, the in-place modifier and the block mover all used the same wrong convention, so they agreed with each other perfectly — and so did the test fixtures, which built their own images with disk[imapOff] = 0x01 and a comment asserting "per spec, bit 0 of the zone bitmap covers firstDataZone". Every part of the picture was consistent, and none of it was minix. The same shape as the UBIFS name offset, where the hand-built fixtures carried the bug they were meant to catch.
Fixed across MinixFsWriter, MinixFsInPlaceModifier and MinixFsBlockMover, with the fixtures corrected against mkfs.minix and a check that makes free space before asking fsck.minix, since that is the state the zone half needs to be visible in.
Also in the block mover
MinixFsBlockMover stopped walking an inode's zone pointers at the first zero (if (ptr == 0) break;). A zero pointer is a hole, not the end of the file, so every pointer behind a hole kept naming a zone that had just been moved.
Found by handing a volume we wrote to
fsck.minix.Every MinixFs image this project has ever produced said that, about its last inode.
What the convention actually is
Read off
mkfs.minixrather than off a document. A fresh volume, all three versions:Bit 0 of the inode bitmap is reserved for the inode number that means "none", and inode N is bit N — hence bits 0 and 1 on a fresh volume: the reserved one and the root. The zone bitmap works the same way and covers the data zones only, counting from the first of them: absolute zone Z is bit
Z - firstdatazone + 1. The metadata zones belowfirstdatazoneare not in the map at all.MinixV1 and MinixV2 do both correctly. MinixFs did neither.
N-1NZ(absolute)Z - firstdatazone + 1Why only the inode half was reported
The inode half is off by one, so the last inode's bit falls off the end — one complaint per volume, every volume.
The zone half never showed. Our writer sizes a volume to fit exactly, so every zone is in use, and "every bit set" is right by accident whichever way the bits are counted. It only appears once something is free — after an in-place remove, say. With the fix in place a volume at 29 of 30 zones checks clean; before it, the free zone lands under the wrong bit.
Why nothing caught it
The writer, the in-place modifier and the block mover all used the same wrong convention, so they agreed with each other perfectly — and so did the test fixtures, which built their own images with
disk[imapOff] = 0x01and a comment asserting "per spec, bit 0 of the zone bitmap covers firstDataZone". Every part of the picture was consistent, and none of it was minix. The same shape as the UBIFS name offset, where the hand-built fixtures carried the bug they were meant to catch.Fixed across
MinixFsWriter,MinixFsInPlaceModifierandMinixFsBlockMover, with the fixtures corrected againstmkfs.minixand a check that makes free space before askingfsck.minix, since that is the state the zone half needs to be visible in.Also in the block mover
MinixFsBlockMoverstopped walking an inode's zone pointers at the first zero (if (ptr == 0) break;). A zero pointer is a hole, not the end of the file, so every pointer behind a hole kept naming a zone that had just been moved.