Files
erofs-freebsd-out-tree/tests/results/manual/2026-08-08T2337Z-metadata-vfs/manual-test-report.md
T
2026-08-13 10:44:59 +02:00

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:

  1. 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.erofs and dump.erofs, while the Linux kernel mount is ENVIRONMENT-UNAVAILABLE.
  2. A real TC010 48-bit statfs mount 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

  • HEAD and FETCH_HEAD both resolved to the required baseline.
  • find /work -name AGENTS.md -type f -print returned no paths.
  • Only repo-community/repo22 was 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.
  • wide enumeration: 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 returned EINTEGRITY.
  • Repeated corrupted lookup remained EINTEGRITY, not cached ENOENT.

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-a remount on the same md unit produced byte-identical complete handles.
  • va_gen equaled handle generation.
  • Replacing nfs-a with nfs-b on the same md unit made the old handle return ESTALE through both fhstat and fhopen.
  • Bad length/pad returned EINVAL; bad generation/NID returned ESTALE.
  • 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_blocks all matched.
  • Inline trusted/security/long-prefix/user xattrs matched exact values.
  • Metabox shared xattrs returned answer=forty-two; the fragment carrier file and repo22.item-000=value-000 matched.
  • 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 ESTALE after image replacement, synchronized va_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.