17 KiB
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 -printreturned no paths. There were no applicableAGENTS.mdfiles.- Initial local
HEADandxdm/mainboth resolved to78182686e968c659932458cbe7a1e0889397f20b. - 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, andsrc/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:
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:
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_nidandmetabox_nidare 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_nodeobjects 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, acceptsZ_OKfor partial output, and requiresZ_STREAM_ENDplus complete input consumption for full output. - ZSTD uses FreeBSD's formal
<contrib/zstd/lib/zstd.h>API withZSTD_createDCtx_advanced,ZSTD_DCtx_setParameter,ZSTD_decompressStream,ZSTD_isError, andZSTD_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_OKorXZ_STREAM_ENDafter exact requested output. Full decoding requiresXZ_STREAM_ENDandbuffer.in_pos == srclen. - All decoder failures are translated to the filesystem read error
EIOand all allocated decoder/output buffers are released.
ZSTDIO build gate
src/Makefileconsumesopt_zstdio.hand adds FreeBSD's zstd compatibility include directory forzstd.c.build.shaccepts onlyEROFS_ZSTDIO=0or1and 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:
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:
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:
nozstd_lz4_sha256=370eb0a8df86868c4842ca535ed64670f0277ea2ed47a703f089bbb13ee4ac52
nozstd_lz4_dmesg_delta=empty
The same module rejected the ZSTD image:
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:
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:
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:
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:
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:
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:
rpcinfo -t 127.0.0.1 nfs 3
Readiness succeeded on attempt 2:
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:
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:
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.