update
This commit is contained in:
@@ -0,0 +1,358 @@
|
||||
# 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:
|
||||
|
||||
```sh
|
||||
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:
|
||||
|
||||
```text
|
||||
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:
|
||||
|
||||
```text
|
||||
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:
|
||||
|
||||
```text
|
||||
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:
|
||||
|
||||
```text
|
||||
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:
|
||||
|
||||
```text
|
||||
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:
|
||||
|
||||
```text
|
||||
inline size=31 st_blocks=8
|
||||
plain size=21211 st_blocks=48
|
||||
chunk size=90017 st_blocks=176
|
||||
```
|
||||
|
||||
Representative compression image hashes were:
|
||||
|
||||
```text
|
||||
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:
|
||||
|
||||
```text
|
||||
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:
|
||||
|
||||
```text
|
||||
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:
|
||||
|
||||
```text
|
||||
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:
|
||||
|
||||
```text
|
||||
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:
|
||||
|
||||
```text
|
||||
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.
|
||||
Reference in New Issue
Block a user