# QuickFIX C++ file open and checkpoint read failures

The two C++ QuickFIX file profiles now create a file only after the initial
read/write open reports ENOENT. Other errors refuse the store without retrying
in truncating mode. The C++ text profile also refuses a failed checkpoint read
before recovering its index, instead of treating that error as an empty fresh
checkpoint. These startup corrections add no send-path lock or allocation.

## Reproduced data loss

Baseline source is 3ccf2f1cf, with new test fixtures only. All three reproduction
commands failed their assertions against that unchanged runtime.

Both profiles retried a transient EIO from the existing body's read/write open
using w+b. The retry succeeded and physically truncated the committed one-byte
body. Recovery then erased the now-dangling index: six bytes became zero in the
C++ text profile and 16 bytes became zero in the QuickFIX/J profile. Both opens
reported success. The error is in the fallback operation, before index recovery
can distinguish valid bytes from missing ones.

In the checkpoint reproduction, stdio itself returned EOF with ferror set and
feof clear. The fixture temporarily closes and restores that stream's descriptor
around vfscanf to produce a real failed read within the unit test; this is not
a claimed physical disk incident. LoadSequences accepted it as fresh state.
Open then changed the seven-byte header to six bytes and rewrote the existing
checkpoint instead of preserving an unreadable store.

## Correction and regression coverage

OpenReadWriteCreate and OpenFile now reserve w+b for a missing file. They do not
retry EIO, EMFILE or EACCES in a mode that can destroy existing content. Creating
an actually absent store still succeeds through the existing normal tests.
The correction does not add detection of an arbitrary unfenced external writer.

LoadSequences checks ferror after scanning. EOF is a fresh default only when
the stream really reached its end without an error. The check runs before
LoadIndex, so even a genuinely torn suffix is untouched when the checkpoint
could not be read. A later successful retry still repairs that suffix while
retaining the recovered sender and target counters.

The persistent regressions inject three open errors into each of four C++
profile files and each of five QuickFIX/J files: 27 distinct cases. Every failed
open preserves all physical data files, makes zero truncating-create attempts,
and releases both writer fences; retries recover sender 17 and target 73.
The checkpoint-read regression also preserves its checkpoint and index on
refusal, then proves that an ordinary retry can recover and repair them.

Fault injection is confined to the two test translation units through a shared
fixture. The production headers contain no fault switches. Dependencies are
included before the narrow fopen/fscanf substitutions, which are removed before
including other components or upstream QuickFIX. The failed-read fixture is
POSIX-only; no Windows execution is claimed.

## Verification and evidence

Both physical-store suites, protocol resend/session tests and real-socket tests
pass with GCC 11.5 and Clang 21.1 in both open and development include layouts:
16 successful suite executions. Both physical-store suites also pass with
AddressSanitizer, UndefinedBehaviorSanitizer and leak detection enabled.
The real pinned QuickFIX C++ and QuickFIX/J handoff/crash-recovery smokes pass
without skips. Export scrub reports five changed engine files and zero findings.
This is affected-path verification, not a full correctness or NIC sweep.

Baseline headers, fixture and test patch, byte-loss diagnostics, compiler
versions, verification patch, five source hashes and complete logs are retained
in `~/archive/libhft/quickfix-open-checkpoint-review-20261003/`.
The verification pin and patch identify the tested source independently of
publication rebases. Java, Rust and .NET code and installed NIC binaries are
unchanged by this session.

## Independently verified .NET corrections

The structure owner corrected the separately reported QuickFIX/J range defect
in b447b084f and the throttle accounting in bb07c5bf4. This review independently
ran the affected .NET compatibility/rate-limiter tests and native-required check:
33 passed in development and 33 in the open edition, with zero failures or
reported skips. Both native libraries were rebuilt through their normal build,
not moved into place by an ad-hoc copy.

A separate probe links the actual production store, supplies only its interface
contracts, and requires invalid operations to preserve both checkpoints, body
and index. The pinned real QuickFIX/J 2.3.1 reader accepts all five resulting
stores: refused terminal/overflow frames, refused outbound/inbound checkpoint
overflow, and the valid last-frame/next-sequence boundary. The original three
unreadable checkpoints are no longer produced. These two runtime gaps are closed;
the direct probe is not a full managed-session integration run.

The positive suite also exposed a reporting weakness worth driving separately.
ManagedStoreReadsWhatTheCppImplementationWrote returns Assert.True(true, why)
when its C++ fixture cannot be generated, so its pass list is not proof that the
fixture ran. A negative probe placed an intentionally failing g++ first on PATH,
verified that compiler invocation through a marker, and ran that exact test
without rebuilding. It still reported one passed, zero failed and zero skipped.
That misleading pass is a confirmed test defect, not a successful interoperability
check. The file remains with the structure owner; its correction is requested in
WIP_AGENT_CLAIMS. The unconditional real Java reader probe above does not depend
on that optional C++ fixture. All negative-probe logs and TRX counters are retained
with the other evidence.

One further test-strength observation from the throttle owner is retained here
when closing the old coordination request. The C++ and Java Configure-reset
tests refill to capacity before reconfiguration, already clearing their saved
fraction. The new .NET Configure_drops_a_partial_token test reconfigures with
0.6 of a token still present and is the stronger regression to port. This is
a source-reviewed coverage gap, not an observed Configure defect.
