CRT rewind: bounded real FILE implementation

2026-10-18 source checkpoint after the original Mad Bomber packed zlib loader resolved _fdopen and stopped at missing rewind. This adds a real void cdecl handler with actual descriptor seek and stream state reset. Independent source review and the authorized normal build plus compiled rewind, CRT stdio and fdopen tests all passed.

Contract and source audit

Microsoft's rewind documentation describes seeking to the beginning and clearing both EOF and error indicators; the function has no return value. Its console-input and modern invalid-parameter handling are separate behavior, not asserted as fully implemented Win98 CRTDLL semantics here. Wine's implementation performs a seek followed by clearerr, including when the seek fails.

Existing handle_fseek resets EOF, update-stream direction and the buffer pointers, but preserves the error flag and exposes a return value. Existing clearerr removes EOF/error only. The new helper reuses FILE lookup, flag and buffer helpers, and the existing synchronous fs_seek_result import. It does not call public handlers with their stack epilogues, alter fseek, or duplicate the underlying VFS seek implementation.

Supported streams are this CRT's canonical owned unbuffered disk FILEs, whether created by fopen, fdopen or a disk freopen. Writes already reach the VFS on each call, so there are no pending output bytes to flush. Before touching the cursor or flags, rewind requires the owned record, its matching descriptor, valid in-use/unbuffered flags, empty count, canonical _ptr/_base, and buffer size two. Copied/unknown/NULL/closed FILE pointers, console streams, custom or pending buffers and tampered descriptor fields fail fast without mutation. This avoids treating an unimplemented flush or keyboard discard as success.

The supported path calls the real host seek with offset zero and SEEK_SET. It clears EOF/error, resets update direction and the empty buffer state even if that seek fails. A prematurely closed descriptor therefore yields EBADF through the existing errno cell and clears the stream indicators; the FILE still owns the same closed tombstone until fclose releases it. Other seek errors map to EINVAL. Success leaves errno unchanged. The inherited dynamic, instance-local errno allocation and its whole-heap-OOM limitations are unchanged.

The host result dword uses scratch below the caller frame, leaving EAX intact. The cdecl handler pops only the return address. No stream-list lock spans host RPC, and a sibling instance uses the same process FILE ownership. This does not add general concurrent FILE-operation synchronization.

Validation

test/test-crt-rewind.js uses real WAT handlers and the real VFS. It covers:

Source checks passed: JS syntax, WAT fragment balance and logical operands, append-only API table, both API generators, region-literal ratchet, callback ownership census and diff whitespace. The first census check encountered the intentionally omitted host.js in the sparse checkout; restoring that exact tracked file made the unchanged census pass. No baseline was regenerated.

The normal gated build and all three compiled fixtures passed against source c6f955b1fa5e80e0eb7f27f8436150d6ad584bfc. Candidate module SHA-256: c3ae99a84199766991055f1d68196af03a9af10eeb118e97c6794f67d3ae7d1a. The canonical module remained aafa600aabbf48b03ba1166ae7bf9c69dc77af499e8e4cb813e60c5bd3b924b1. Minimum observed free disk was 2509422592 bytes; no disk, time or log guard fired, and each child process group was absent on completion. Logs, command receipts, build artifact hashes, the supervisor recovery recipe and retained previous modules are in /tmp/wa-crt-descriptor-oct18/scratch/runs/20261018-crt-rewind-validation/.

The sibling broker test exercises synchronous routing, not actual OS Worker scheduling. Concurrent operations on one FILE, buffered flushing, console keyboard discard and whole-heap errno allocation failure remain outside this slice. No original-game retry was run.