Skip to content

# a SysV or Xenix file past its tenth block read back wrong from the eleventh on #80

Description

@Hawkynt

The reader disagreed with itself about how wide a pointer is inside an indirect block.

// SysVReader.EnumerateIndirectPointers — the layout walk
public const int IndirectPointerBytes = 4;
var pointer = BinaryPrimitives.ReadUInt32LittleEndian(...);

// SysVReader.ReadIndirect — the extraction path
var ptrsPerBlock = this.BlockSize / 3; // 24-bit ptrs
var ptr = Read24(this._data.AsSpan((int)offset + i * 3));

An s5fs inode squeezes thirteen block numbers into thirty-nine bytes, three each. The blocks below it hold plain four-byte little-endian words. The extraction path read those three at a time, so everything past the first pointer of an indirect block resolved to the wrong block, and a file came back correct through its tenth block and wrong from the eleventh on.

What settled it

Not a document — the kernel. A volume written with four-byte indirect pointers:

$ sudo mount -t sysv -o loop,ro v.img m
$ md5sum m/*
4b4e0da58aa21d56260eb08d9cabb4ef  m/A.BIN   <- matches the source
f4b761342e24d7e167bfb85906ed1ca4  m/B.BIN   <- matches
374e535fb9f335816b1c744a586b32ba  m/C.BIN   <- matches

Our own reader gave zeros from offset 11264 on for the same volume. The driver read it perfectly.

The hole spans beside those calls had the same mistake — an absent indirect root stood for BlockSize / 3 blocks of hole rather than BlockSize / 4.

Why it was never seen

Our writers refused to produce a file large enough to need an indirect block at all: SysV and Xenix capped at 10 240 bytes, MinixFs at 7 168. So the whole indirect path was reachable only by a volume from a real System V or Xenix machine, and nothing here had one. Two halves of one reader disagreed for as long as neither was exercised.

Fixed in SysVReader and XenixReader. CoherentReader is deliberately left alone: its writer emits three-byte pointers in indirect blocks and its reader and in-place modifier agree with it, there is no kernel driver or fsck for Coherent on this machine, and changing it on the strength of a SysV result would be a guess dressed as a fix.

Related, in the same place

SysVBlockMover and XenixBlockMover found the run to repoint by its starting offset alone:

if (owner == null || namedAt < 0 || runOffset != oldOffset) continue;

Once a file can span more than its inode's ten pointers, a defragmentation can put another file where this one had been, and two files of one length are then told apart by nothing — the pointers of whichever turned up first got rewritten. Xenix after ConsolidateAtEnd: 'SAME1.BIN' came back holding 'SAME5.BIN's bytes. The run is now matched by owner as well as offset.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions