# repo22 compression P0 WIP integration manual report

Date: 2026-08-08 23:06 UTC
Baseline: `78182686e968c659932458cbe7a1e0889397f20b`
Guest: FreeBSD 15.0-RELEASE-p8 amd64, QEMU TCG
Build reference: `/work/dev-freebsd-releng` (`REVISION=15.0`, source branch
`RELEASE-p9`)
Linux behavior reference: `/work/dev-src-linux/fs/erofs`
Host production tool: erofs-utils 1.8.6

## Result

**PASS**, with the extent-record guest case accurately recorded as
**MKFS-UNAVAILABLE** rather than PASS.

The retained ten-file WIP was reviewed and integrated without reset, checkout,
stash, or revert. Both module configurations build and load. DEFLATE, ZSTD,
and MicroLZMA full and partial references read correctly; targeted corruption
returns `EIO`. A fragment-backed compressed metabox now works after packed
inode initialization, while loop, recursive-NID, and range mutations fail
closed. HEAD2 and interlaced images pass real FreeBSD kernel reads. LZ4,
xattr/metabox, chunk, external compressed multi-device, and NFS regressions
also pass.

No CI or test runner was added. Only source, Markdown manual tests, and this
report are intended for the commit.

## Instruction and baseline audit

- `find /work -name AGENTS.md -type f -print` returned no paths. There were no
  applicable `AGENTS.md` files.
- Initial local `HEAD` and `xdm/main` both resolved to
  `78182686e968c659932458cbe7a1e0889397f20b`.
- The exact ten tracked WIP files were present:
  `build.sh`, `src/Makefile`, `src/decompressor.c`, `src/deflate.c`,
  `src/internal.h`, `src/lzma.c`, `src/super.c`, `src/zdata.c`, `src/zmap.c`,
  and `src/zstd.c`.
- Untracked build, fixture, overlay, artifact, and other-repository paths were
  not staged.

## Initial guest module cleanup

Before any build under test was loaded, the guest reported:

```text
Id Refs Address                Size Name
 5    1 0xffffffff82822000     a690 erofs-nozstd.ko
```

`kldstat -v -i 5` proved the path was `./erofs-nozstd.ko` and the contained
module name was `erofs`. The cleanup used the observed KLD ID, not a guessed
filename:

```sh
kldunload -i 5
kldstat
kldstat | grep -i erofs
```

The second `kldstat` contained no EROFS entry and the final grep printed
`none`.

## Source review

### Packed inode and metabox order

- `packed_nid` and `metabox_nid` are decoded and checked before carrier loads.
- A packed NID with the metabox selector bit is rejected before any metadata
  recursion can begin.
- The packed inode is loaded before the metabox inode, matching the Linux
  dependency order and allowing a compressed metabox to terminate in a packed
  fragment.
- The packed inode must be a regular, non-fragment inode. This rejects a packed
  carrier that would recurse back through itself.
- A fragment-backed metabox must have a distinct loaded packed inode, non-zero
  size, a real fragment tail mapping, and a range wholly inside the packed
  inode.
- Fragment recursion checks compare inode NIDs rather than object pointers,
  which also catches separately allocated `erofs_node` objects for the same
  on-disk inode.

### DEFLATE, ZSTD, and MicroLZMA

- DEFLATE uses `inflate(..., Z_SYNC_FLUSH)` until the requested output is full,
  detects no-progress loops, accepts `Z_OK` for partial output, and requires
  `Z_STREAM_END` plus complete input consumption for full output.
- ZSTD uses FreeBSD's formal `<contrib/zstd/lib/zstd.h>` API with
  `ZSTD_createDCtx_advanced`, `ZSTD_DCtx_setParameter`,
  `ZSTD_decompressStream`, `ZSTD_isError`, and `ZSTD_freeDCtx`.
- ZSTD partial decoding stops after the requested output is produced. Full
  decoding requires frame completion and complete input consumption.
- MicroLZMA partial decoding accepts `XZ_OK` or `XZ_STREAM_END` after exact
  requested output. Full decoding requires `XZ_STREAM_END` and
  `buffer.in_pos == srclen`.
- All decoder failures are translated to the filesystem read error `EIO` and
  all allocated decoder/output buffers are released.

### ZSTDIO build gate

- `src/Makefile` consumes `opt_zstdio.h` and adds FreeBSD's zstd compatibility
  include directory for `zstd.c`.
- `build.sh` accepts only `EROFS_ZSTDIO=0` or `1` and creates the corresponding
  option header.
- The disabled translation unit contains only the availability result and a
  local stub; it references no `ZSTD_*` symbol.
- A filesystem advertising ZSTD is rejected during compression-config parsing
  when the module lacks ZSTDIO, before any file read can reach the stub.

### HEAD2, interlaced, and extent records

- HEAD2 selects `z_algorithmtype[1]` and respects the HEAD2 big-pcluster bit.
- Plain records with interlaced advise use the interlaced byte rotation before
  returning data.
- Extent records retain Linux ordering and responsibilities for 4-, 8-, 16-,
  and 32-byte records, partial references, explicit algorithm format, shifted
  and interlaced plain data, and final fragments.
- Explicit extent tables with a non-empty file and zero extent count fail
  closed.
- Final fragment mappings are checked against the loaded packed inode before
  reads.

## Build matrix

Host commands:

```sh
EROFS_ZSTDIO=0 ./build.sh
cp build/erofs.ko /tmp/repo22-erofs-nozstd-probe.ko
nm -u /tmp/repo22-erofs-nozstd-probe.ko

EROFS_ZSTDIO=1 ./build.sh
cp build/erofs.ko /tmp/repo22-erofs-zstd-probe.ko
nm -u /tmp/repo22-erofs-zstd-probe.ko
```

| Build | Size | SHA256 | `ZSTD_*` undefined | `bcmp` undefined | Guest KLD |
|---|---:|---|---|---|---|
| no ZSTDIO | 68744 | `c96f4e1620f005a19d7ed581f98f1894969b38a34d7021f54dbce99919888ef9` | none | none | load/unload PASS |
| ZSTDIO | 69984 | `7f8cb03afb9c76b535709af1a06bf993cbab74bb5899b2f4646a6fd20b00d15c` | five formal API names | none | load/unload PASS |

The enabled module's unresolved ZSTD set was exactly:

```text
ZSTD_DCtx_setParameter
ZSTD_createDCtx_advanced
ZSTD_decompressStream
ZSTD_freeDCtx
ZSTD_isError
```

Both were loaded by path, identified with `kldstat -v -i ID`, and unloaded by
that exact ID.

## ZSTDIO behavior

With the disabled module, a normal LZ4 image still read successfully:

```text
nozstd_lz4_sha256=370eb0a8df86868c4842ca535ed64670f0277ea2ed47a703f089bbb13ee4ac52
nozstd_lz4_dmesg_delta=empty
```

The same module rejected the ZSTD image:

```text
nozstd_mount_rc=1
mount: /dev/md0: erofs: ZSTD compression requires ZSTDIO support: Operation not supported
```

The ZSTDIO module mounted the image and produced the expected complete SHA256
shown below.

## Fixture construction and proof

Fixtures and build outputs remained outside the repository commit.
Authentication used the existing private askpass workflow; its contents and
credentials were not printed or recorded.

### Partial-reference images

`dump.erofs -s` and `dump.erofs --path=PATH -e` established compressor,
layout, logical lengths, physical starts, and pcluster lengths. Byte-level
patching asserted the original inode NID, map-header offset, HEAD record, and
pblk before setting `Z_EROFS_LI_PARTIAL_REF` or redirecting a reused pblk.
CRC32C was recomputed for every superblock mutation.

| Image | SHA256 |
|---|---|
| `deflate-partial-ref.erofs` | `8bc720a1250ef28794d091b6264e76060fbf01d66ea706c59c24c1960fa347ac` |
| `deflate-partial-ref-corrupt.erofs` | `6ff9dff9b3b5aba7da9b4a93f5b068270b4916267d2fe0fd3702c02be4c19fae` |
| `lzma-partial-ref.erofs` | `2f4bd89d2340273dd4052ea73aa9ac43802db431258c118c5dfede7727be9408` |
| `lzma-partial-ref-corrupt.erofs` | `bfbbd304a401c091d63e8b8e42b2b1984d9750760e940aded89acc909551d838` |
| `zstd-partial-ref.erofs` | `7f5ee4f8a20f20d32eaf8780a7081229f7dbf6b896ad23986da36e1a1c580519` |
| `zstd-partial-ref-corrupt.erofs` | `0c6e6b312b879297e2f71f406dcd35236654800e7cd115df0cb9a748297c48c1` |

The LZMA and ZSTD full file was 1048576 bytes. Their partial file was 700000
bytes and reused the complete source pcluster. The DEFLATE fixture contained
multiple real compressed extents, including reused physical extents in the
partial file.

### Fragment-backed compressed metabox

A METABOX-capable inspector proved:

```text
packed_nid=40
metabox_nid=38
metabox inode: regular, compressed-full, size=20480
metabox fragment header=0x800000000004e800
fragmentoff=321536
packed inode: regular, plain, size=342016
321536 + 20480 = 342016
```

Positive image SHA256:
`8a9a62bd203994711b8272192915d811e6c3de23e07ad9607dd63e66cc109bcd`.

The negative images changed one proven field each:

| Negative | Exact mutation | SHA256 |
|---|---|---|
| self-loop | `packed_nid: 40 -> 38` | `e9501ed9d149e2d735d95156669d144e733bf4be620bb69ea8a0c41f996b436c` |
| range | `fragmentoff: 321536 -> 342016` | `3fdcf2a41f50da93a5931edcef0d86ff2f576ecba781833036eaa930d99197d5` |
| metabox recursion | set bit 63 in `metabox_nid` | `b873e0cf4d2892d2d154a5769f494660f47f4429161e7be001b833da6dd0d705` |
| packed recursion | set bit 63 in `packed_nid` | `4fa368d1b1d47bb56b82132e6055d105ed2508b179a8df2a98ec5a728f91c9ac` |

### HEAD2

The HEAD2 fixture patch asserted the original bytes and made only these semantic
changes, plus the resulting CRC32C bytes:

```text
feature_incompat: 0x00000003 -> 0x0000000b
map h_advise:     0x0002 -> 0x0006
first di_advise:  HEAD1 (1) -> HEAD2 (3)
```

Image SHA256:
`fc70cbef0442a86ac2f507aebd7ac7bcfbdfcc3d0b9b3cf2ff5231334583d816`.
The targeted corrupted copy SHA256 was
`f0d9ad645804565c5aca7a92128df3ae9c81717a08ad7e8692489cd2ae606700`.

### Interlaced

This image was generated specifically with installed erofs-utils 1.8.6:

```sh
mkfs.erofs -zlz4 -C4096 -Efragments -T0 \
  interlaced-1.8.6.erofs source
```

`dump.erofs -e` reported 105 real extents, with 4096-byte plain extents
interspersed with compressed extents. Image SHA256:
`d77d86f874361bae86cae9e8f6d05d9b6c68ef04cd5c8779368aef033fa485c3`.

### Extent metadata

Installed `mkfs.erofs -V` reported 1.8.6. Exact source-tree search found no
on-disk `Z_EROFS_ADVISE_EXTENTS`, `z_erofs_extent_recsize`, or
`struct z_erofs_extent {` definition in the 1.8.6 include/lib tree. Its
internal `struct z_erofs_extent_item` is an in-memory compressor item, not the
new on-disk extent-record ABI.

Although a newer-tool extent image existed in the WIP build area, it was not
mounted or scored. The required result is therefore:

```text
extent metadata guest result: MKFS-UNAVAILABLE
```

The independent static review compared `src/erofs_fs.h` and `src/zmap.c`
against `/work/dev-src-linux/fs/erofs/erofs_fs.h` and `zmap.c`, covering record
sizes 4/8/16/32, implicit physical bases, explicit-count binary search,
physical/logical high words, format bits, partial references, interlaced data,
fragments, metabox metadata reads, and malformed explicit zero counts.

## Core FreeBSD result matrix

The guest test used repeated `mdconfig -a -t vnode -f IMAGE`, read-only EROFS
mounts, `sha256 -q`, byte-exact `dd`/`cmp`, and a small C helper that printed
`errno` on read failure.

| Case | Complete SHA256 / result | Boundary or random proof | Status |
|---|---|---|---|
| DEFLATE full | `370eb0a8df86868c4842ca535ed64670f0277ea2ed47a703f089bbb13ee4ac52` | full stream completion | PASS |
| DEFLATE partial | `61b17076c2dfae88da7912d00df534b894cf6d27178863c6d8e91f8617ebb91e` | offset 122900, 512 bytes: `fa381301af1b62fa259addbe7ae427fd54486abc7604ea7619e7a9c47965606d`; offset 736700, 1024 bytes: `be1d2941b054626376fa58155ce0ef8d6357dd0defcbf9eaa37d19ff6098873c` | PASS |
| DEFLATE corrupt | `read_errno=5`, failure after 65536 output bytes | target extent only | PASS |
| MicroLZMA full | `370eb0a8df86868c4842ca535ed64670f0277ea2ed47a703f089bbb13ee4ac52` | full input consumed | PASS |
| MicroLZMA partial | `5a840803f5372b7be1db70713fed7705bf6e982ca2fd319f722c21f094f6c8dd` | offset 65500, 2048 bytes: `49c231f92dde0b0104d7e5b3a01d918dde818c3a6dca05393e2a424d01c214f2`; offset 524287, 4097 bytes: `b8e80c144eacd1f8863c72eab66272379923f0d738f63576ab8286455e0dde8c` | PASS |
| MicroLZMA corrupt | `read_errno=5`, zero output bytes | target pcluster only | PASS |
| ZSTD full | `370eb0a8df86868c4842ca535ed64670f0277ea2ed47a703f089bbb13ee4ac52` | formal FreeBSD API | PASS |
| ZSTD partial | `5a840803f5372b7be1db70713fed7705bf6e982ca2fd319f722c21f094f6c8dd` | same two boundary/random hashes as MicroLZMA | PASS |
| ZSTD corrupt | `read_errno=5`, zero output bytes | target pcluster only | PASS |
| HEAD2 full | `7e2f40362554f4460e80ec2f8d91f4e6c04a8e58f2980d637b2d383a7aa3b3f8` | offset 65500, 4096 bytes: `f0a80e3217f54897eae271b6e570c862abcffad3a4ffd19ffec0444ee06ae721` | PASS |
| HEAD2 corrupt | `read_errno=5`, zero output bytes | patched HEAD2 pcluster | PASS |
| interlaced full | `7e2f40362554f4460e80ec2f8d91f4e6c04a8e58f2980d637b2d383a7aa3b3f8` | offset 16240, 8192 bytes across first compressed/plain transition: `b1387900e55e5672944f8299fe66ac9542007f2b0cbed81d5974831d6040cdae` | PASS |
| extent records | static format/control-flow review only | erofs-utils 1.8.6 cannot emit | MKFS-UNAVAILABLE |

## Fragment-backed metabox results

| Case | Actual | Status |
|---|---|---|
| positive file | SHA256 `4536c1d7121f48829475f29179f54baa57154b4ef817cf0776f81782585d29ad` | PASS |
| shared-prefix xattr | `shared-value` | PASS |
| per-file xattr | `value-000` | PASS |
| self-loop | mount exit 1; `packed inode nid=38 is not a non-recursive regular file: Integrity check failed` | PASS |
| out-of-range | mount exit 1; `Integrity check failed` | PASS |
| metabox NID bit 63 | mount exit 1; `Integrity check failed` | PASS |
| packed NID bit 63 | mount exit 1; `Integrity check failed` | PASS |

The `vmstat -m` EROFS row after the matrix showed zero active allocations:

```text
erofs    0     0 240695 16,32,64,128,256,384,1024,2048,4096,8192,16384,32768,65536
```

The cumulative allocation count increased as expected; the active allocation
and active-byte columns were both zero.

## Regression matrix

| Regression | Actual | Status |
|---|---|---|
| LZ4 legacy full index | SHA256 `370eb0a8df86868c4842ca535ed64670f0277ea2ed47a703f089bbb13ee4ac52` | PASS |
| LZ4 compact index | same SHA256 | PASS |
| LZ4 64 KiB big pcluster | same SHA256 | PASS |
| LZ4 all-fragments | SHA256 `a3a83e5c524b5ed446a06ce78cf407192a0c80119f15d2bc3489a50515eb49e0` | PASS |
| LZ4 ztailpacking | SHA256 `e2aa4a0a0cbcf422f397c7069a38ae0f073781386958e7db0dfa3ff2ca075513` | PASS |
| xattr/metabox | file SHA256 `4536c1...`; xattr `value-000` | PASS |
| single-device chunk | SHA256 `de29abbd47ecd9136f64f22f73fcb40bce1718a8865e6282e74953aa1df80c44` | PASS |
| external compressed LZ4 extent | complete SHA256 `370eb0...` | PASS |
| external compressed boundary | offset 65500, 4096 bytes: `0d66627577218a39a620e8b28d8c8d64b974b184063c52cb0cceeaf0c297c0db` | PASS |
| local NFSv3/TCP over external compressed extent | complete SHA256 `370eb0...` | PASS |

The external compressed fixture declared slot 1 at unified block 4, stored the
real two-block LZ4 pcluster in the external provider, and zeroed the
corresponding bytes in the primary image. Primary SHA256 was
`5625612426d68624c77dd94b09754da583fc6bd90767e9e9d958e4fc89131a4d`;
blob SHA256 was
`0d71102ce471af0a30d4d61a1fbeef1fc33437c85651d7c7f9c60ebd305ec9d0`.

## NFS startup-race investigation

The first regression run started rpcbind, mountd, and nfsd and immediately
called `mount_nfs`. The client printed one transient
`RPCPROG_NFS: RPC: Program not registered`, then retried successfully and read
the correct complete SHA256. No dmesg line changed.

A separate clean rerun waited for:

```sh
rpcinfo -t 127.0.0.1 nfs 3
```

Readiness succeeded on attempt 2:

```text
program 100003 version 3 ready and waiting
```

The subsequent NFS mount emitted no RPC warning and produced the same complete
SHA256. This proves the first message was a user-space service-registration
race, not an EROFS or NFS data-path failure.

## dmesg

Core, regression, and clean NFS snapshots were each byte-identical before and
after their respective test matrices. All six snapshot files had SHA256:

```text
2aa3500d33a7cfbe0db87854d3427231cda9cf92b7f85e553f957231f7359680
```

There was no new panic, trap, decompression diagnostic, integrity message, GEOM
orphan warning, or NFS kernel message.

## Cleanup

Every test used a trap that unmounted the current EROFS/NFS mount, detached the
specific md unit, stopped NFS services in client-first order, restored or
removed `/etc/exports`, and unloaded the module by the observed KLD ID.

Final audits after the core, regression, disabled-ZSTD LZ4, and clean NFS runs
all reported:

```text
erofs mounts=0
NFS mounts=0
md units=0
erofs modules=0
nfsd/mountd/rpcbind processes=0
```

## Limitations

- **MKFS-UNAVAILABLE**: erofs-utils 1.8.6 cannot generate the new on-disk extent
  record format. Extent coverage is independent static ABI/control-flow review
  only. No newer-tool extent image is counted as a FreeBSD PASS.
- METABOX and the transformed HEAD2/partial-reference fixtures require newer
  format-aware tooling plus byte-level assertions. Their guest reads are real
  FreeBSD kernel tests; fixture creation and mutations are documented and
  checksum-validated.
- No CI, automated runner, benchmark threshold, memory-pressure run, or forced
  OOM scenario was added. The requested functional manual matrix and active
  allocation checks were completed.
- Kernel failures: none.
