14 KiB
repo22 Metadata and VFS Manual Test Report
Started: 2026-08-08 23:37 UTC
Completed: 2026-08-09 UTC
Baseline: c208bf1f4b8d7a85777f7fe45e8c6e8d3a9f2d1a
Guest: FreeBSD 15.0-RELEASE-p8 amd64, QEMU TCG
FreeBSD source reference: /work/dev-freebsd-releng, releng/15.0
Linux source reference: /work/dev-src-linux/fs/erofs
Host production tool: erofs-utils 1.8.6
Result
PASS for the implemented metadata/VFS changes and every runnable required FreeBSD 15 regression.
Two limitations are recorded rather than misreported as PASS:
- The Linux host had no loop provider/EROFS kernel mount path, so the
deterministic nonzero-padding fixture passed Linux erofs-utils 1.8.6
fsck.erofsanddump.erofs, while the Linux kernel mount is ENVIRONMENT-UNAVAILABLE. - A real TC010 48-bit
statfsmount needs a provider as large as the declared multi-terabyte image. The corrected test records this as a provider/tool requirement; no small-media mount is called a positive PASS.
An initial NFS stress run completed all data and metadata assertions but failed
cleanup because service nfsd onerestart inherited the deliberately open NFS
client descriptor. The procedure was corrected to run the service command with
3<&-; the complete stress/background/cleanup sequence then passed. The first
cleanup failure is retained here as evidence for the TC132 ordering fix.
Baseline and Scope Audit
HEADandFETCH_HEADboth resolved to the required baseline.find /work -name AGENTS.md -type f -printreturned no paths.- Only
repo-community/repo22was modified. - Existing untracked
build/objects and historical manual-test artifacts were preserved and excluded from staging. - No CI implementation, binary fixture, overlay, image, or VM artifact is part of the intended commit.
Build and ABI Validation
Commands:
EROFS_ZSTDIO=0 ./build.sh
EROFS_ZSTDIO=1 ./build.sh
nm -u module.ko | awk '$NF == "bcmp" { n++ } END { print n + 0 }'
git diff --check -- repo-community/repo22
Results:
| Build | SHA256 | bcmp count |
Result |
|---|---|---|---|
| ZSTDIO disabled | 031038ef195497dc6a1d840d55b293292e051fb888c679c89c8cfbd19b56d525 |
0 | PASS |
| ZSTDIO enabled | 16166b9ffc96a532bda9925513e10df11f4cf8a06b3c15afbc09b1a4e4b9d2df |
0 | PASS |
Both modules loaded and unloaded on FreeBSD 15. The final unload used the observed KLD ID so arbitrary copied filenames did not affect cleanup.
FreeBSD 15 source inspection confirmed the exact current ABI:
vnode_pager_local_getpages(struct vop_getpages_args *)
vnode_pager_local_getpages_async(struct vop_getpages_async_args *)
The local ext2 vnode vector registers both functions directly. EROFS now does the same.
Deterministic Fixture Evidence
The checked-in prepare-fixtures.sh was syntax-checked and rerun into separate
untracked repro3-* directories. It reproduced the expected byte-identical
inline, special, pager, NFS, and nlink images and generated the current namei
variants.
Key structural evidence:
inline_nid=39 inode_off=1248 inode_blockoff=1248 xattr_icount=0->695
inline_data_blockoff=4068 inline_size=31
wide_nid=42 block=6 dirents=80 last_nameoff=4043 padding_patch=4084:4092
nlink1_nid=43 i_format_bit4=1 i_nb=0x1234
special compact rdev raw_u=0x543abc21
special extended rdev raw_u=0x543abc21
Generated image hashes:
| Image | SHA256 |
|---|---|
inline.erofs |
0435b3ea748a88cccbdc6390dec4285a3706bec3dc09de58aa1808544ebc63d0 |
inline-cross-block.erofs |
63ebb7632687b564beb4c9dd8036eb4ac63c4495f63061ad7eec1c134c656932 |
special-compact.erofs |
fd78256dd83d9d6d957e5f843c7a8e8a175a4b3243d528bebd299b0226853237 |
special-extended.erofs |
e73e9b84d9ceb8c2b07e9c2732733b0fd607736c68c09522a2402fbeef6ba8d5 |
namei-base.erofs |
d1730ff23836797c6c09e1b39b1cf23efc16e27f85ab07fdcab577bb82871659 |
namei-padding-nonzero.erofs |
9a94e9af2cab264b9c11975a20d78e615d6fe1e6f86267173cb5ac86aecb2b17 |
namei-corrupt-short.erofs |
fb89f74795a5569ed3a85d63836dd75a06e1f17823d0508048c37da710ea6e75 |
namei-corrupt-nameoff.erofs |
b0b70ee615f163430f04edb91ab76c27ee8cc9a75b8ebb2008834f5b550e3933 |
namei-corrupt-name.erofs |
6a980ad3e241603eda2ef71a470c82975291c614366a401e4e89c17c9adf9b91 |
pager-plain.erofs |
36596edea5bfbaa1157f6c142095a7ee9949b5df1b1a35620c0d22ec853f3c09 |
pager-lz4.erofs |
b06daee6b02a6ebe967655be760b496c8a9d922cf47acb5c840e82a48a34c51d |
nfs-a.erofs |
6d86dcf620b007d069895e3e94a21a74dadcf35e84c92dc4f7c21a2ec23bd901 |
nfs-b.erofs |
18913fd319daca20b3e4d30a89c05c416b4d3ca396394509e5112d548674f4c2 |
nlink.erofs |
798eb81b3ba7270ee653b00adeba47a6e03c982adef1982bf5b4e0935669ae83 |
nlink1-patched.erofs |
19fd85f32ed89117d8e02bc19ca09655dd3bae9152cf693a399eda2135233042 |
The padding patch was followed by a rigorous CRC32C recomputation over the superblock block. Linux erofs-utils produced the exact payload:
wide entry 079
The Linux kernel comparison command failed before mount with “failed to setup loop device”; it is therefore not labeled PASS.
TC147: FLAT_INLINE Bounds
Commands included fresh md attach/mount for the base and cross-block images,
then cold cat/stat access.
Observed on the final module:
positive hash=0347f272ba395aff6df5fd824a7c552fa26d7f017283f0544136385abef31b01 stat=31 8
corrupt stat: Integrity check failed
corrupt stat: Integrity check failed
corrupt cat: Integrity check failed
dmesg_before=122 dmesg_after=122 mounts=0 mds= module_rc=1
The positive file read exactly. The checksum-valid corrupt inode failed with
EINTEGRITY before its 31-byte inline range could cross the metadata block.
The mapping path also uses checked additions, and primary/metabox declared
bounds are validated at inode decode.
Self-review first rejected the old i_xattr_icount=1020 mutation because it
merely moved the inline data into the next block without crossing that block.
The corrected value 695 places the tail at block offset 4068. Its first
genuine rerun exposed a stale constructing vnode: the second stat returned
EBADF. The failure path now calls vgone() before vput(), and the complete
final rerun above returned EINTEGRITY for every repeated access.
TC022/TC056: Special st_rdev
The source fixture used real Linux char/block nodes with major 2748, minor
344865, plus a FIFO. stat_special.c checked st_rdev directly because
FreeBSD stat -f %Lr truncates before minor() for large values.
Compact and extended results were identical:
PASS char rdev=0xa430005bc21 major=2748 minor=344865
PASS block rdev=0xa430005bc21 major=2748 minor=344865
PASS fifo rdev=0xffffffffffffffff
This proves Linux new_decode_dev(0x543abc21) followed by FreeBSD makedev();
a little-endian integer cast would not produce this FreeBSD dev_t.
TC055: Real Compressed Allocation
For every row, full FreeBSD kernel reads matched the expected SHA256 and
st_blocks * 512 matched the inode's real on-disk compressed size:
| Shape | File | Size | Allocated bytes | SHA256/result |
|---|---|---|---|---|
| LZ4 full | shape.dat |
1048576 | 8192 | 370eb0a8... PASS |
| LZ4 compact | shape.dat |
1048576 | 8192 | 370eb0a8... PASS |
| LZ4 fragment | fragment.dat |
1048699 | 0 | a3a83e5c... PASS |
| LZ4 ztailpacking | inline.dat |
65536 | 0 | e2aa4a0a... PASS |
| MicroLZMA partial A | a.dat |
1048576 | 4096 | 370eb0a8... PASS |
| MicroLZMA partial B | b.dat |
700000 | 4096 | 5a840803... PASS |
| DEFLATE compact/partial | a.dat |
1048576 | 36864 | 370eb0a8... PASS |
| DEFLATE full/partial | b.dat |
1050624 | 8192 | 61b17076... PASS |
| ZSTD partial A | a.dat |
1048576 | 4096 | 370eb0a8... PASS |
| ZSTD partial B | b.dat |
700000 | 4096 | 5a840803... PASS |
Uncompressed regressions:
inline size=31 st_blocks=8
plain size=21211 st_blocks=48
chunk size=90017 st_blocks=176
Representative compression image hashes were:
LZ4 full d784f8dc... LZ4 compact 37942ef1...
LZ4 fragment a4e5d40e... LZ4 ztail 035069eb...
MicroLZMA 2f4bd89d... DEFLATE 8bc720a1... ZSTD 7f5ee4f8...
TC141/TC148: Directory Compatibility and Strictness
FreeBSD results:
- Cold
/alpha/bravo/charlie/payload.txt: PASS without parent warming. - Repeated cold lookup and post-negative-cache existing lookup: PASS.
- Patched nonzero tail bytes: accepted.
wideenumeration: 320 files plus.and..; 322 dirents through a 128-byte buffer and restart cookies.- Short block, non-monotonic
nameoff, and/in an on-disk name: lookup and readdir both returnedEINTEGRITY. - Repeated corrupted lookup remained
EINTEGRITY, not cachedENOENT.
The duplicate validators were removed; lookup and readdir use the shared
helper in dir.c.
TC132/TC133: NFS Generation and Stress
Direct handle validation after the final superblock-hash implementation:
nfs-a: fsid=00000034:000000e0 nid=0x2e gen=849213208
nfs-b: fsid=00000034:000000e0 nid=0x2e gen=4011239099
nfs-aremount on the same md unit produced byte-identical complete handles.va_genequaled handle generation.- Replacing
nfs-awithnfs-bon the same md unit made the old handle returnESTALEthrough bothfhstatandfhopen. - Bad length/pad returned
EINVAL; bad generation/NID returnedESTALE. - Handle generation mutation used
gen_xor 1, not a hard-coded value.
NFSv3/TCP READDIRPLUS stress:
- Four clients mounted; requested 512/1024/4096 readdir sizes were clamped by FreeBSD to 8192, while default was 65536.
- All four 12,050-name sorted listings had SHA256
4fbf1b6103e8884223629c3fd03f4ec36061e65cd2a1becfc96670daa96f9d71. - Twelve traversal workers and eight cat/stat controller jobs were waited by PID; every exit status was zero.
- READDIRPLUS client/server count was 1304 in the recorded full run.
- RPC timeouts, invalid replies, retries, and server write RPCs were zero.
- The 21,757,952-byte throughput file hash was
0841effed82d1adf394b6834ce30d4d9eb5fc9427527e854ec1cb6bb4b86c119. - nfsd restart with an open descriptor passed after the service command closed inherited fd 3.
- Corrected full rerun ended
NFS-STRESS-CLEAN-PASS.
TC149: Real Pager Faults
tests/mmap_fault.c was compiled natively on FreeBSD 15 and run against both
plain and LZ4 images.
Both runs printed:
PASS size=21211 pages=6 fnv1a64=a1890a1c216724be random-faults=6 \
eof-zero=PASS sigbus=PASS private-cow=PASS
The helper verified deterministic random faults after MADV_DONTNEED, full
mapping equality with pread, partial EOF-page zeroes, child SIGBUS on the
next full page, MAP_SHARED write denial, FreeBSD private COW semantics, and
O_RDWR -> EROFS.
dmesg added exactly the two expected child exits on signal 10 (SIGBUS). No
parent crash, VM assertion, trap, panic, or dirty writeback appeared.
Additional Regressions
| Area | Evidence | Result |
|---|---|---|
| Inline/system xattr | trusted, security, long-prefix, user values exact | PASS |
| Shared xattr | shared and per-inode values exact | PASS |
| Metabox xattr | dirA/nested.txt and hello.txt enumerated/read |
PASS |
| Fragment-backed metabox | positive file/xattrs exact; self-loop and range images rejected | PASS |
| Single-device chunk | plain.bin, deep/payload.bin hashes exact |
PASS |
| Chunk multidevice | block-map and indexed files hashes exact | PASS |
| External compressed multidevice | complete 1 MiB LZ4 SHA256 370eb0a8... |
PASS |
| Compact nlink rules | explicit nlink 1/2 plus flagged nlink-one image | PASS |
| ZSTD enabled final load/read | final module and zstd-partial-ref read |
PASS |
Supporting fixture hashes:
system-inline-four 6f521b62...
shared xattr 208b61ca...
metabox xattr ef8d619c...
chunk single 7aa8db22...
chunk multidev 73343b70... + a36c2b9b...
external LZ4 56256124... + 0d71102c...
NFS stress image 6928f05b...
The final-module fragment-backed metabox rerun used the previously qualified,
checksum-valid image with SHA256
8a9a62bd203994711b8272192915d811e6c3de23e07ad9607dd63e66cc109bcd.
/tree/d00/file000.txt produced SHA256
4536c1d7121f48829475f29179f54baa57154b4ef817cf0776f81782585d29ad;
the shared and per-file xattrs were shared-value and value-000. The
self-loop and out-of-range images returned Integrity check failed. dmesg
remained at 122 lines, and the post-run audit showed zero mounts, md providers,
and loaded EROFS modules.
Final-Binary Closure Rerun
After the vgone() error-path fix, the final ZSTDIO module SHA256
16166b9ffc96a532bda9925513e10df11f4cf8a06b3c15afbc09b1a4e4b9d2df
was used for one continuous closure matrix:
- LZ4 full/compact/fragment/ztailpacking, MicroLZMA partial, DEFLATE
compact/full partial, and ZSTD partial hashes and
st_blocksall matched. - Inline trusted/security/long-prefix/user xattrs matched exact values.
- Metabox shared xattrs returned
answer=forty-two; the fragment carrier file andrepo22.item-000=value-000matched. - Single-device chunk, external chunk provider, and external compressed LZ4
hashes matched; compressed
st_blocks=16. - The final module preserved the same NFS handle across same-md remount, made
the old handle
ESTALEafter image replacement, synchronizedva_gen, and completed a real local NFSv3/TCP client read with matching SHA256.
The first closure script stopped after the system-xattr row because it queried
an obsolete metabox attribute name. Its trap left zero mounts, md providers,
modules, and services. The corrected answer/metaboxshared queries and all
remaining rows passed. The complete final audit was:
dmesg_before=122 dmesg_after=122
erofs_mounts=0 nfs_mounts=0 mds= module_rc=1 services=0/0/0
FINAL-REGRESSION-REST-PASS
Final Environment and Cleanup
Final guest audit:
mounts=0
mds=
modules=0
services=0
final_dmesg_before=122 final_dmesg_after=122
The final dmesg tail contained only the historical pre-test duplicate-module diagnostic and the two intentional pager-child SIGBUS exits. The final compression, xattr, chunk, multidevice, generation, and NFS closure pass added no dmesg lines.
Guest helper hashes:
| Helper | SHA256 |
|---|---|
mmap_fault |
b19c9c28a7abdfebe4243e3f4876b711eebc1c02b9856c15cb8bf15ae2da3a65 |
nfs_fh_tool |
0dcad233cc8768ca84569c8d030939b526d50c64190fc54d5f0de013191f11d6 |
stat_special |
b5c0eabd06541268e799df4f72fa7f37a460617bd8960f918b27710483db3841 |
readdir_probe |
44747ccd8d9a285a37d8049b7d61d00946db88482c08a3570e59b5a2fa1b4ea2 |
No password material was printed into this report or written into tracked files.