9.9 KiB
repo22 real multi-device manual test report
Date: 2026-08-08 19:37 UTC
Baseline: 96e22cc713cc57180ce3ecb2b98852f090e3868b plus this multi-device batch
Guest: FreeBSD 15.0-RELEASE-p8 amd64, QEMU TCG
Host tools: erofs-utils 1.8.6
Result
PASS. Real external providers, explicit slot mapping, flatdev, unified addresses, 48-bit fields, failure rollback, forced GEOM orphaning, and the single-device regressions all passed. There were no new dmesg lines, traps, or panics. Final EROFS mount, md-provider, and loaded-module counts were zero.
Build and module
Commands:
git diff --check
./build.sh
nm -u build/erofs.ko | awk '$NF == "bcmp" {bad=1} END {exit bad}'
sha256sum build/erofs.ko
Actual:
git diff --check: PASS.- Cross-build against
/work/dev-freebsd-releng: PASS. - Unresolved
bcmp: none, PASS. - Final
build/erofs.koSHA256:aa6708256246b303f4be2a8d516bf503f8a232df1ad3f466f5685573f221c180. - Final
kldloadandkldunload: PASS.
Fixture commands
The legal baseline was generated by erofs-utils 1.8.6, not by synthesizing an EROFS image from scratch:
WORK=/work/build/repo22-multidev-fixtures-20260808
mkdir -p "$WORK/src/cross"
python3 - <<'PY'
from pathlib import Path
import hashlib
root = Path('/work/build/repo22-multidev-fixtures-20260808/src')
def content(label, blocks, tail=0):
out = bytearray()
for block in range(blocks):
seed = f'{label}:block:{block}'.encode()
chunk = bytearray()
counter = 0
while len(chunk) < 4096:
chunk += hashlib.sha256(
seed + counter.to_bytes(4, 'little')).digest()
counter += 1
out += chunk[:4096]
if tail:
out += hashlib.sha256(f'{label}:tail'.encode()).digest()[:tail]
return bytes(out)
root.joinpath('cross/striped.bin').write_bytes(content('striped', 24, 173))
root.joinpath('cross/second.bin').write_bytes(content('second', 17, 29))
root.joinpath('control.txt').write_text(
'repo22 multidev deterministic control\n', encoding='ascii')
PY
truncate -s 0 "$WORK/base.blob"
mkfs.erofs -T0 --all-root --chunksize=4096 \
--blobdev="$WORK/base.blob" "$WORK/base.primary" "$WORK/src"
erofs-utils 1.8.6 accepts one --blobdev; direct generation of two or more
external slots is therefore MKFS-UNAVAILABLE. Multi-slot fixtures were made
by a structured, assertion-driven patch of the legal baseline:
dump.erofs --path=PATH -e base.primarysupplied each NID and file size.- The patch asserted compact chunk layout, zero xattr size, index format, old device ID 1, and every old block address.
- A complete physical chunk, rounded to 4096 bytes for the final partial chunk, was copied into a deterministic round-robin blob.
- Each index was rewritten as little-endian
(startblk_hi, device_id, startblk_lo). - One zeroed primary block was appended for the enlarged device table;
blocks_lo,extra_devices, anddevt_slotoffwere updated. This avoids overwriting inode metadata merely to add slots. - Each 128-byte slot was written at offsets 64/68/72/74 with
blocks_lo,uniaddr_lo,blocks_hi, anduniaddr_hi. - The EROFS CRC32C was recomputed over bytes 1024 through 4095 with polynomial
0x82f63b78and initial value0xffffffff.
The asserted patch manifest was:
/control.txt nid=44 size=38 chunk=4096 indexes=1 base=1440
/cross/second.bin nid=50 size=69661 chunk=4096 indexes=18 base=1632
/cross/striped.bin nid=56 size=98336 chunk=32768 indexes=4 base=1824
Pre-kernel qualification:
fsck.erofs --device=two.blob1 --device=two.blob2 \
--extract=two.extract two.primary
fsck.erofs --device=three.blob1 --device=three.blob2 \
--device=three.blob3 --extract=three.extract three.primary
fsck.erofs --device=four.blob1 --device=four.blob2 \
--device=four.blob3 --device=four.blob4 \
--extract=four.extract four.primary
All extracted source SHA256 values matched. The 48-bit feature and patched
device_id=0 + uniaddr extraction are not understood by erofs-utils 1.8.6;
those layouts were qualified by the FreeBSD kernel SHA256 tests below and the
Linux 7.1 erofs_map_dev semantics.
FreeBSD attachment and mount commands used one md provider per image/blob:
mdconfig -a -t vnode -f primary -u 90
mdconfig -a -t vnode -f blob1 -u 91
mdconfig -a -t vnode -f blob2 -u 92
mount -t erofs -o ro -o device.2=/dev/md92 \
-o device.1=/dev/md91 /dev/md90 /mnt/repo22-multidev
The reversed option order was intentional. Flatdev used only:
mdconfig -a -t vnode -f two.flat -u 90
mount -t erofs -o ro /dev/md90 /mnt/repo22-multidev
Fixture SHA256
| Fixture | SHA256 |
|---|---|
| one-blob primary | fa2f35f59f63cc4a56d230ba960a6c1d27748506fccce081447cab0dc0d540fd |
| one-blob slot 1 | 3aeb19c519636b61dc3ebd47c6286098deaa0990ff618ce3facbff4fd65907cd |
| two-slot primary | 46d76d5cc311f97f263ea4c20a2510338f5b11da9d71e0c73f69ae3751afd696 |
| two-slot blob 1 | 53853d033adf154a956192d9813726888c3f9575ecd13fa45a26aea5c4f07eaf |
| two-slot blob 2 | 9ee3e3ad420024ff1945d92469d3d337817977f28c5b745c4ca7b1664f4d6584 |
| two-slot flatdev | 4b561db1eb0048989256f3c51504b4611881069bf283fbfe2051847e8162301e |
| three-slot primary | 4f2dc4ee6385f31139151e1b04279a8fb90fcd2cbba928ee6949df62f1bb9585 |
| three-slot blobs 1/2/3 | 12c5d73cb12e1bb5fd1a9d9715ec88d2132dbaa8df62e2b5aad3cfb454116a3a, 3d1574254a97e572fca323f153bb2a46ebdfb060a0765298700768e39a7f91d8, ba42a2a3724771975c27a43ac8d64a2568515fb4e8ad14b29cc09a7a865ffba0 |
| four-slot primary | d3366dfb5f4c98db2db669ead3209a0abbba5df989d9aea7e1217aff46f0154b |
| four-slot blobs 1/2/3/4 | ad24e257baf97f855f756cc917878a9616858860ddbfab4ffe3cc362e9233662, eb55bd05f873dd8732bcbe35a0818ef232a9eff6fedec618f5f949253d7cd2d1, 59f9a3aed34cc8598e6a96381b55a18f6a0e6674e3e26921834948ad333cb3c5, d5080743a85e47752cd33e86e26690b8978d314e685b27d449d5394c44e54244 |
| device ID 0 + uniaddr | e4cf71f76f2a2e690e905e4135c21ce29d647be5dd2b6ce567ac19f82d1875d2 |
| 48-bit high uniaddr | 00d49beea1237125886f13912bf062153b992582c1cf3a2494b44679285b192e |
| overlapping-slot negative | 6ecfc77b688ac1931f3e904f48fa43a81c05a6047c21ccfc4636e9e07c17d5cf |
| mapped-ID-3 negative | dd6ddc6943b7edd2009a48999d03a2d5b69a9ce7d820357527dbcdb52adb2afc |
Source SHA256 values for the cross-device fixture:
6a38f93ca44de4affcef23ad5979505225f21e5b6912b5c886866170c007b16c ./control.txt
45b7df16a5833013042694c2e8171d782102bf20abbc6c1ec2789518d5a65125 ./cross/second.bin
7ab83ca35cc4bb361bf60b5619d3f55b717fb8e2a7a81a2f069dce859efe8de9 ./cross/striped.bin
Test matrix
| Test | Expected | Actual | Status |
|---|---|---|---|
| TC093 plain single device | full SHA256 match | both files matched | PASS |
| TC093 chunk single device | device ID 0 reads primary | both files matched | PASS |
| compression regression | LZ4 data unchanged | both files matched | PASS |
| TC098 fragments | packed-inode file matches | 65b9a59e...e7cd |
PASS |
| metabox regression | cold metabox-backed files match | f1 and f10 matched known hashes |
PASS |
| TC006/TC099 primary + one blob | all external chunks use slot 1 | three files matched | PASS |
| TC094 two external slots | reversed option order, cross-slot SHA256 | all three files matched | PASS |
| TC100 four providers total | slots 1-3, order 3/1/2 | all three files matched | PASS |
| additional four external slots | file crosses four blobs | SHA256 matched | PASS |
| statfs combined blocks | sum primary and external blocks | 184 KiB reported for 46 blocks | PASS |
| TC101 flatdev | non-zero IDs add uniaddr on primary |
SHA256 matched | PASS |
| TC101 device ID 0 + uniaddr | unified range selects external provider | SHA256 matched | PASS |
| 48-bit root/index/uniaddr | high fields are not truncated | source SHA256 matched | PASS |
Error and rollback matrix
| Case | Expected | Actual | Status |
|---|---|---|---|
| split primary, no external options | ENXIO flatdev media bound |
Device not configured |
PASS |
| missing slot 2 | ENXIO |
external devices don't match ... Device not configured |
PASS |
| short slot 2 | ENXIO |
Device not configured |
PASS |
| swapped short/long providers | fail before root | Device not configured |
PASS |
| same provider in two slots | EINVAL |
Invalid argument |
PASS |
| overlapping unified ranges | EINTEGRITY |
Integrity check failed |
PASS |
| explicit writable request | EROFS |
Read-only file system |
PASS |
| mapped device ID 3 with two slots | read-time ENODEV |
Operation not supported by device |
PASS |
| second concurrent reuse mount | EBUSY |
Device busy; first mount remained readable |
PASS |
| normal detach while mounted | EBUSY |
mdconfig ... Device busy |
PASS |
| forced orphan then cold slot-2 read | ENXIO |
Device not configured |
PASS |
| unmount after forced orphan | clean close of detached consumer | succeeded | PASS |
Every mount failure was followed immediately by successful md detach. Opened external devices were therefore rolled back, including failures after one or more earlier slots had opened.
dmesg and cleanup
The final matrix captured dmesg before and after all tests:
before_lines=2
after_lines=2
no-new-lines
The two pre-existing lines were unchanged historical messages:
interface erofs.1 already present in the KLD 'erofs-repo22-xattrfix.ko'!
md0: truncating fractional last sector by 14 bytes
Final state:
kldstat -n erofs: empty
mdconfig -l: empty
mount -p | grep erofs: empty
Tool limitations
- MKFS-UNAVAILABLE: erofs-utils 1.8.6 cannot directly emit more than one external blob option in this environment. Multi-slot images were derived from a legal mkfs image with deterministic, asserted structural patches.
- MKFS-UNAVAILABLE: erofs-utils 1.8.6 rejects the experimental 48-bit incompat feature and does not correctly extract the patched device-ID-0 unified-address control. Both were verified by complete FreeBSD kernel SHA256 reads.
- KERNEL-FAIL: none.