# QuickFIX C++ recovery I/O failures and repeated body seeks

Both C++ QuickFIX file profiles now refuse recovery when the body length cannot
be read, before changing an index or checkpoint. Previously a failed seek or
position query could be treated as evidence that a committed record was a torn
append, deleting its resend-index entry. Recovery also measures the fenced body
once instead of once per record. No send-path lock or allocation is added.

## Reproduced failures

The baseline is 1ebbe70bc. A FIFO body makes the seek failure deterministic in
the checked-in POSIX regressions. With one complete valid entry, the C++ text
profile accepted the store and changed its six-byte header to zero bytes. The
QuickFIX/J profile refused the open only after changing its 16-byte header to
zero bytes. Refusing an open after deleting data is not safe recovery.

A separate Linux interposer confirmed the same failure on ordinary files by
returning EIO from the body end-seek or position query. The probe also exercised
an incomplete-only index and an empty index, checking all physical data files
for changes. Eleven of the twelve baseline cases failed the preservation and
refusal contract. The passing control was QuickFIX/J with an empty header and
a failed end-seek: its final Open seek already refused that case without
mutation. The initial baseline script expected all twelve to fail; its control
expectation was corrected and the full baseline rerun is retained.

## Correction and startup work

LoadIndex obtains the body length before processing any index bytes. An unknown
length returns failure immediately, without inferring a torn record. The
existing writer fence makes that length stable during supported recovery;
this does not make an unfenced external writer safe. Genuine torn suffixes and
final dangling entries remain recoverable when body length is known.

Both profiles reuse the one recovered length. QuickFIX/J also drops a redundant
final body seek: measuring its length has already positioned that stream at the
end, and intervening recovery operations do not access it.

The interposer counted calls while reopening an unchanged 512-record store:

| Profile | Body end-seeks before | Body end-seeks after | Body position queries before | Body position queries after |
|---|---:|---:|---:|---:|
| C++ text | 512 | 1 | 512 | 1 |
| QuickFIX/J binary | 513 | 1 | 512 | 1 |

These are actual library-call counts, not estimated latency or syscall counts.
No quiet-host timing or wire-latency claim is made.

## Verification

All twelve injected ordinary-file error cases pass after the correction, with
zero changed data files. The checked-in FIFO regressions cover complete,
incomplete and empty headers, preserve checkpoints on refusal, and prove a
successful retry after replacing the FIFO with a regular body file.

Four existing suites pass with GCC and Clang, each in both open and development
include layouts: both physical-store suites, protocol resend/session tests and
real-socket file-store tests. This is 16 successful suite executions, not a
full C++ correctness sweep. The two physical-store suites also pass with
AddressSanitizer, UndefinedBehaviorSanitizer and leak detection enabled.

Both pinned upstream QuickFIX C++ and QuickFIX/J handoff/crash-recovery smokes
pass without skips. Export scrub reports no findings in the four changed engine
files. The changed code was compiled as C++11; no Windows execution is claimed,
and the FIFO regression is POSIX-only.

## Evidence and remaining scope

Baseline source snapshots, test patch, ordinary-file probe and interposer,
compiler versions, error-case output, seek counts, full verification logs and
source hashes are retained in
`~/archive/libhft/quickfix-recovery-io-review-20261003/`.
The verified source is the recorded base commit plus verification-source.patch;
the four engine hashes allow checking it after publication rebases.

This correction does not modify Java, Rust, .NET or NIC-installed binaries.
The separately confirmed .NET QuickFIX/J range defect remains reserved by its
structure owner, and the prepared managed NIC bundle still requires staging.
