# Pre10 Completion and Targeted Smoke Report

Status: **PRE10 IMPLEMENTATION COMPLETE; TARGETED SMOKE PASS; FULL FEATURE TEST NOT RUN**

Date: 2026-08-13

## Scope and goals

Pre10 is a controlled maintenance-alignment release based on the final Pre9
tree. Its purpose is to make the FreeBSD EROFS implementation easier to compare
and maintain alongside the independent Linux EROFS repository without copying
Linux-only lifecycle or memory-management models.

The planned work was limited to three source batches:

1. Remove unused Linux-shaped compatibility declarations and improve source
   ordering.
2. Extract bounded helpers where Linux and FreeBSD responsibilities are
   comparable, while retaining FreeBSD ownership and errno behavior.
3. Introduce a BSD-native synchronous decompressor request and descriptor
   boundary with Linux-comparable codec names.

Full feature validation, CI execution, performance redesign, Linux page/folio/
bio/workqueue integration, and new format support were outside Pre10. This
report does not claim that every EROFS feature was tested.

## Repository history and identities

The relevant pushed commits are:

| Commit | Purpose |
| --- | --- |
| `cdfcd79640f252830c4e507f65aa7387eaa98ae0` | Snapshot `repo-pre-10` from the current `repo-pre-9`. |
| `b5d6f5ce0696407f9f1cb3f0a9f8cbab643eea49` | Add the Pre10 execution plan under `planning/pre10/`. |
| `3c7c774b296a2ee90578a78f372aad5fd2edbec6` | Batch A compatibility cleanup and ordering. |
| `8c246ea3c4dbf94b92d1f27a9d1b6b6eda582c31` | Batch B bounded metadata helper extraction. |
| `d3c1cb90b21ae8a97f549bc256d120268e5d09e9` | Record the discovered `insmntque()` vnode lifecycle issue. |
| `5c8a47e916b1521cb8a35ec4f546e5e16fb8c2dc` | Batch C decompressor request and descriptor alignment. |
| `9fbf0d7e24084d2342ecd61eff21592b1a83d225` | Fix `insmntque()` failure ownership. |
| `ff6586c2b24e155a3cbdf55b2ed80edb36d3e406` | Clarify the issue status after the source fix. This is the commit tested by the final smoke run. |

The snapshot report records the Pre9 source tree inherited by Pre10 as
`e657e8a63b097b061670f75ca01947a568ef33d5`. The final smoke runner independently
recorded these tested identities:

```text
commit=ff6586c2b24e155a3cbdf55b2ed80edb36d3e406
repo_pre_10_tree=3387e32efbcb0bec7e0bb57ecfc42daf9b962d28
src_tree=ec684d8dbd7662070234da1fe947aba48a293db8
```

Evidence: `/work/tests-dev/temp/pre10-smoke-final-20260813T102543Z/dut-identities.txt`.

## Batch A: compatibility cleanup and ordering

Batch A changed `src/internal.h`, `src/data.c`, and `src/zmap.c`.

Repository-wide consumer searches proved that the following declarations did
not represent implemented FreeBSD behavior and had no source, Makefile, ABI,
initializer, or field-access consumer:

- `EROFS_SYNC_DECOMPRESS_*`;
- `EROFS_ZIP_CACHE_*`;
- `struct erofs_buf` and `__EROFS_BUF_INITIALIZER`;
- the unused `erofs_map_blocks.buf` member; and
- `erofs_is_fileio_mode()`.

Removing these declarations improves maintainability because the FreeBSD tree
no longer advertises Linux mechanisms that it does not implement. The real
FreeBSD synchronous contiguous-buffer path and the Pre9 mount-owned decoded
LZMA extent cache remain explicit and unchanged.

Declarations and existing function definitions were grouped by actual
responsibility, closer to the Linux source's readable organization where the
responsibilities match. Static call topology was preserved, and moved function
bodies were compared for equivalence. No return value, allocation flag, lock,
mapping flag, `bread`/`brelse` ownership rule, or runtime structure field was
changed.

Detailed evidence: `repo-pre-10/docs/pre10-batch-a.md`.

## Batch B: bounded FreeBSD helper boundaries

Batch B changed `src/super.c`, `src/data.c`, and `src/inode.c`.

It extracted three `static` helpers with one direct caller each:

- `erofs_read_superblock()` isolates superblock read, fixed-size copy,
  validation, field decode, checksum, generation seed, and compression
  configuration parsing. `erofs_mountfs()` still owns mount allocation, GEOM
  transfer, device scanning, internal inode setup, publication, and the single
  mount cleanup path.
- `erofs_fill_map_dev()` performs only the final `m_em`, `m_dif`, and `m_pa`
  assignments. Device selection, range checks, overflow checks, flat-device
  semantics, and `ENODEV`/`EINTEGRITY` decisions remain in `erofs_map_dev()`.
- `erofs_fill_vnode()` performs only inode-derived vnode field setup. FreeBSD
  vnode allocation, locking, mount association, hash insertion, race handling,
  construction state, downgrade, publication, and cleanup remain in
  `erofs_vget()`.

These boundaries improve Linux/FreeBSD visual comparability at the function
responsibility level while deliberately retaining the FreeBSD GEOM, vnode,
hash, lock, and cleanup contracts. Linux lifecycle names such as `iget` or
`fill_super` were not adopted where their semantics would be misleading.

Detailed evidence: `repo-pre-10/docs/pre10-batch-b.md`.

## Batch C: BSD decompressor request dispatch

Batch C changed `src/internal.h`, `src/decompressor.c`, `src/lz4.c`,
`src/decompressor_lzma.c`, `src/decompressor_deflate.c`, and
`src/decompressor_zstd.c`.

It introduced a stack-owned, synchronous, contiguous-buffer
`z_erofs_decompress_req` and a descriptor table covering LZ4, LZMA, DEFLATE,
ZSTD, SHIFTED, and INTERLACED. Codec entry points now use the Linux-comparable
names `z_erofs_lz4_decompress`, `z_erofs_lzma_decompress`,
`z_erofs_deflate_decompress`, and `z_erofs_zstd_decompress` because their
functional roles are equivalent.

The interface remains BSD-native:

- no page, folio, bio, workqueue, XArray, shrinker, asynchronous request, or
  Linux ownership wrapper was introduced;
- request fields borrow the existing compressed and decoded buffers only for
  the synchronous callback;
- `zdata.c` still owns buffer release and LZMA decoded-cache publication;
- plain SHIFTED/INTERLACED transforms remain in the existing dispatch model;
- LZ4 padding behavior, partial-decoding source, LZMA dictionary validation,
  ZSTD conditional availability, and codec resource release remain unchanged;
- configuration I/O errors preserve priority and backend failures remain
  normalized to the existing `EIO` result.

This gives third-party maintainers a recognizable request/descriptor and codec
naming shape without importing Linux kernel runtime assumptions.

Detailed evidence: `repo-pre-10/docs/pre10-batch-c.md`.

## P1 vnode lifecycle finding and fix

Independent Batch B review found a pre-existing P1 ownership violation in
`erofs_vget()`: after `insmntque()` returned an error, the old branch wrote
through `vp->v_data` even though FreeBSD may already have executed `vgone()`
and `vput()` and ended caller ownership of that vnode.

Commit `9fbf0d7e24084d2342ecd61eff21592b1a83d225` applied the minimal ownership
fix. The error branch now frees only the independently allocated `en`, clears
`*vpp`, returns the original error, and performs no further access, unlock,
reclaim, or release operation on `vp`. The successful path, vnode hash race,
lock state, errno, and later reclaim path were not changed.

Static ownership review confirms that the kernel failure path clears
`v_data`, installs `dead_vnodeops`, and owns vnode cleanup; dead vnode reclaim
does not release the filesystem's separately allocated `en`, so exactly one
caller-side `free(en, M_EROFS)` remains necessary.

The source defect is fixed, but runtime closure is still pending. Closing the
issue requires a dedicated mount/unmount race test that repeatedly creates or
looks up uncached vnodes while normal and forced unmount are attempted, with
kernel diagnostics capable of detecting stale vnode access, memory corruption,
lock misuse, and double release. The ordinary smoke test below does not trigger
or prove this teardown race.

Issue record:
`repo-pre-10/issues/erofs-vget-insmntque-failure-use-after-release.md`.

## Static review result

The final static review accepted Batches A, B, and C and the P1 fix. Checks
included changed-path allowlists, zero-consumer proof, prototype and backend
symbol uniqueness, descriptor coverage and bounds, normalized moved-function
body comparison, cleanup/ownership matrices, errno preservation, mount and
vnode lifecycle ordering, cache ownership, forbidden Linux mechanism searches,
conflict-marker searches, clean-worktree checks, `git diff --check`, and local
`HEAD`/`xdm/main` agreement at each pushed boundary.

No new P0 or P1 defect was found in the three planned batches. Static review is
not runtime proof. In particular, it does not close the dedicated vnode
teardown race test or the older explicit-extent positive-fixture gap recorded
under `repo-pre-10/issues/`.

## Initial inconclusive smoke attempts

Two isolated attempts preceded the final PASS. Neither is treated as a DUT
failure or a PASS.

### Attempt 1

Evidence directory:
`/work/tests-dev/temp/pre10-smoke-20260813T100326Z`.

The readiness probe began before the QEMU process had actually started, then
performed repeated SSH handshakes while the TCG guest was still becoming
responsive. Sources and fixtures were eventually transferred and the build log
reached the final module link/strip stage, but the long SSH build session ended
with transport status `255`. No reliable KLD SHA, `kldload`, or `kldstat`
evidence was produced.

The first runner incorrectly continued into the plain case after the failed
build/load stage. The guest reported `Invalid fstype`, while `results.txt`
incorrectly recorded `mount_status=0` because the exit status was not captured
immediately after `mount`. Since module loading was never proven and the
runner's status recording was unsound, the mount result is not attributable to
Pre10 and the attempt is **INCONCLUSIVE**, not FAIL or PASS.

### Attempt 2

Evidence directory:
`/work/tests-dev/temp/pre10-smoke-retry-20260813T101136Z`.

The fresh guest booted and returned a valid FreeBSD identity after intermittent
connection refusals, banner timeouts, and resets. The orchestration then exited
almost immediately. The claimed SCP failure was not backed by an actual
`scp-images.log`; only a failed attempt to tail that missing file remained.
No build, module load, mount, or hash result exists. This attempt is also
**INCONCLUSIVE** and provides no DUT verdict.

Both attempts stopped only their owned QEMU process, released ports `10040`
and `10041`, deleted their disposable overlays, preserved the immutable base,
and left guard PID `26318` and port `9222` unchanged.

The infrastructure diagnosis was recorded at
`/work/tests-dev/fix-todo/pre10-smoke-ssh-banner-timeout.md`. Its original
`PENDING_FIX` verdict describes those two attempts; the corrected final runner
below subsequently completed the required targeted smoke.

## Corrected fail-fast runner

The successful run used a new directory and port `10042`, a fresh disposable
overlay, 4096 MiB and two TCG CPUs, and a strictly ordered runner:

1. Verify exact Git, base-image, fixture, guard, and port identities before
   starting QEMU.
2. Start QEMU and record its owned PID before beginning readiness checks.
3. Wait for TCP, require two consecutive successful FreeBSD SSH identity
   checks, and establish one persistent SSH ControlMaster connection.
4. Transfer the source archive, guest scripts, and all fixtures in one batch,
   then verify fixture hashes in the guest.
5. Run the guest build as a background job that writes `build.rc` and a
   completion sentinel; poll the sentinel rather than depending on one long
   SSH session.
6. Stop immediately unless the build return is zero, `erofs.ko` exists, its
   hash is recorded, `kldload` succeeds, and `kldstat -n erofs` proves the
   module is loaded.
7. Run each filesystem case separately and immediately record attach, mount,
   mount-proof, hash, unmount, and detach return codes. A missing proof or
   nonzero result stops the runner and cannot be converted to PASS.
8. Review guest cleanup and `dmesg`, unload the module, and prove it is absent.
9. Use the cleanup trap to stop only the recorded QEMU PID, release only the
   run-owned port and overlay, and revalidate the immutable base and guard VM.

Runner evidence:
`/work/tests-dev/temp/pre10-smoke-final-20260813T102543Z/runner.sh` and
`runner.log`.

## Final targeted smoke result

Final evidence directory:
`/work/tests-dev/temp/pre10-smoke-final-20260813T102543Z`.

Overall result: **PASS** for the targeted Pre10 smoke scope.

### Isolation

- Immutable base image mode before and after: `0444`.
- Immutable base image SHA-256 before and after:
  `67f359621f23a1d745f0889370cbb99a096cee3e99a0b2f3bb18fc7a91bf6fef`.
- The `.bp` was not booted or modified directly; the run used a disposable
  qcow2 overlay.
- Guard PID: `26318`; guard SSH port: `9222`.
- Guard command identity and `/proc` start value `1102996924` matched before
  and after, and port `9222` remained reachable.

Evidence: `base-before.txt`, `cleanup-status.txt`, `guard-before.txt`,
`guard-command-before.txt`, `guard-command-after.txt`,
`guard-start-before.txt`, and `guard-start-after.txt` in the final evidence
directory.

### KLD build, load, and unload

- FreeBSD guest build return: `0`.
- Built KLD SHA-256:
  `4ce2d44a0902502a40f07805606485c99869371b3cbab1c70ee19a8ac29d90f1`.
- The loaded module hash matched the built module hash.
- `kldstat` showed `erofs.ko` loaded as module ID `5`.
- `kldunload` succeeded, and the final `kldstat -n erofs` correctly reported
  that no such loaded module remained.

Evidence: `build-result.out`, `build-result.rc`, `module-load.out`,
`module-load.rc`, and `guest-evidence.tar.gz`.

### Filesystem cases

| Case | Mounted file SHA-256 | Time | Result |
| --- | --- | ---: | --- |
| Plain `/data.txt` | `056f8f7585667dc695e2edf936deaf84cfee0f88671ba6cd5be22ce890763433` | `0s` | PASS |
| LZ4 `/compressed.bin` | `3ff012b76087c4da65ce0b69813a76f47ea367e95782edf8b8cd6cd3ec4d1880` | `5s` | PASS |
| LZMA full `/level.dat` | `ddda39737f0f6093e828a032ec161511fefbb1fa361bc6cbffdbc91e48e4c461` | `2s` | PASS |

Each case recorded zero returns for md attach, EROFS mount, mount proof,
full-file hash, unmount, and md detach. The exact fixture image hashes were:

```text
plain image  3785ca07e7bd16f6c611191596ae0314253c0ae9b7217a4b22de25799b0f08ac
LZ4 image    967cc1b625546f9f9f881472e71cc3d849c65feb4e98e67fbfdc9b90a4be6dda
LZMA image   32107a084b27362a093768b88746c37c2e99a74b9f1301a9d4046988479defe9
```

Evidence: `fixture-host-sha256.txt`, `results.txt`, and the per-case files in
`guest-evidence.tar.gz`.

### Kernel messages and cleanup

The post-test `dmesg` delta and suspect scan were empty: no EROFS mount,
integrity, decompression, panic, or assertion error was found. After all cases,
only the guest root filesystem and `devfs` remained mounted, no test md device
remained, and the EROFS module was unloaded.

Host cleanup also passed:

- the run-owned QEMU process stopped;
- port `10042` was released;
- the disposable overlay was deleted;
- the base image mode and hash were unchanged; and
- the guard VM remained unchanged and reachable.

Evidence: `dmesg-delta.txt`, `dmesg-suspect.txt`, `final-guest-state.out`,
`cleanup-status.txt`, and `guest-evidence.tar.gz`.

## Validation limits and conclusion

Pre10's planned source changes are complete, independently statically reviewed,
committed, pushed, built in a FreeBSD guest, and covered by the targeted plain,
LZ4, and LZMA smoke run. That targeted scope passed at tested commit
`ff6586c2b24e155a3cbdf55b2ed80edb36d3e406`.

No full feature test and no `/work/tests-dev/test_all.py` run was performed.
This report therefore does not claim all features, codecs, mapping layouts,
multi-device behavior, partial references, tailpacking, memory pressure, or
concurrent teardown behavior passed.

A feature-specific test is still required to close the fixed `insmntque()`
issue's runtime-validation status: the dedicated normal/forced mount-unmount
race with uncached vnode creation and kernel diagnostics described above. It
is desirable and required for issue closure, but it was not run because the
instruction for Pre10 was smoke testing only and explicitly excluded feature
testing.
