233 lines
9.9 KiB
Markdown
233 lines
9.9 KiB
Markdown
# 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:
|
|
|
|
```sh
|
|
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.ko` SHA256:
|
|
`aa6708256246b303f4be2a8d516bf503f8a232df1ad3f466f5685573f221c180`.
|
|
- Final `kldload` and `kldunload`: PASS.
|
|
|
|
## Fixture commands
|
|
|
|
The legal baseline was generated by erofs-utils 1.8.6, not by synthesizing an
|
|
EROFS image from scratch:
|
|
|
|
```sh
|
|
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:
|
|
|
|
1. `dump.erofs --path=PATH -e base.primary` supplied each NID and file size.
|
|
2. The patch asserted compact chunk layout, zero xattr size, index format, old
|
|
device ID 1, and every old block address.
|
|
3. A complete physical chunk, rounded to 4096 bytes for the final partial
|
|
chunk, was copied into a deterministic round-robin blob.
|
|
4. Each index was rewritten as little-endian
|
|
`(startblk_hi, device_id, startblk_lo)`.
|
|
5. One zeroed primary block was appended for the enlarged device table;
|
|
`blocks_lo`, `extra_devices`, and `devt_slotoff` were updated. This avoids
|
|
overwriting inode metadata merely to add slots.
|
|
6. Each 128-byte slot was written at offsets 64/68/72/74 with `blocks_lo`,
|
|
`uniaddr_lo`, `blocks_hi`, and `uniaddr_hi`.
|
|
7. The EROFS CRC32C was recomputed over bytes 1024 through 4095 with polynomial
|
|
`0x82f63b78` and initial value `0xffffffff`.
|
|
|
|
The asserted patch manifest was:
|
|
|
|
```text
|
|
/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:
|
|
|
|
```sh
|
|
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:
|
|
|
|
```sh
|
|
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:
|
|
|
|
```sh
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
before_lines=2
|
|
after_lines=2
|
|
no-new-lines
|
|
```
|
|
|
|
The two pre-existing lines were unchanged historical messages:
|
|
|
|
```text
|
|
interface erofs.1 already present in the KLD 'erofs-repo22-xattrfix.ko'!
|
|
md0: truncating fractional last sector by 14 bytes
|
|
```
|
|
|
|
Final state:
|
|
|
|
```text
|
|
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.
|